What is the relationship between packages and the class path?
As you may already know, Windows and all Unix-based operating systems (the latter group includes macOS, Linux, etc.—sometimes referred to collectively as *nix.) make extensive use of an environment variable called PATH
, which is simply a list of directory paths, separated from each other by a semicolon (in Windows) or a colon (in *nix). When you issue a command (e.g. by typing it into the search field of the Start menu or in a command prompt in Windows, or in a Terminal window in macOS), the operating system searches through the directories listed in the PATH
environment variable, looking for an executable program with the specified name; if one is found, it is executed.
Java uses a class path to perform a similar operation: When the compiler or the class loader encounters a reference to a class or interface that is not part of the standard library, it searches the class path for the specified class or interface. The details of this operation are closely related to Java packages: though it may sound paradoxical, we need a reasonably good understanding of both to have a solid understanding of either one.
The class path may be specified in three ways:
Command-line arguments
Every tool in the JDK (including the JRE) that makes use of the class path supports its specification via a command-line argument. For example, javac
(the Java compiler) and java
(the Java application launcher) both allow the class path to be set via the -cp
and -classpath
options. jshell
, which is a relatively recent addition to the toolset, and thus follows current conventions more closely in this regard, provides the -c
and --class-path
options.
CLASSPATH
environment variable
If the class path is not set from the command line, the CLASSPATH
environment variable value will be read and—if present—used. Like PATH
, this is a user-account-specific or system-wide setting; thus, it can be used to set a common class path for any or all of the Java tools. (On the other hand, since using the command line options permits more fine-grained specification of the class path, it is the preferred method.)
Default
If the class path is not set using either of the above mechanisms, then the class path consists solely of the current working directory.
When a class or interface that is not in the standard library is referenced at compile-time or runtime, the class path is searched, in order of specification. That is, if the class path contains multiple directory paths, each will be searched, in the order in which they are listed in the classpath, until a match is found; if no match is found, then the operation will fail.
The following command may be used to start the Java application launcher in Windows, instructing it to load a class with the fully-qualified name edu.cnm.deepdive.FizzBuzz
, and launch it as a Java application (i.e. invoke its main
method). The -cp
is used to specify where the application launcher should look for the bytecode of the edu.cnm.deepdive.FizzBuzz
class.
java -cp c:\fb;c:\bootcamp;c:\projects edu.cnm.deepdive.FizzBuzz
On macOS, Linux Ubuntu, etc., we would write the (nearly) equivalent:
java -cp /fb:/bootcamp:/projects edu.cnm.deepdive.FizzBuzz
Note that in both cases, we’ve specified the fully-qualified name of the class to be launched. In fact, the import facilities discussed earlier are simply conveniences that enlist the compiler’s help to simplify our source code; the Java compiler and class loader always search for classes and interfaces using their fully-qualified names, no matter what import
statements are (or were) in the source code. Starting with this fact clear in our minds is essential to understanding how the search proceeds.
In the Windows example above, the Java application launcher, using the class loader, will search for the following, in the order shown:
c:\fb\edu\cnm\deepdive\FizzBuzz.class
c:\bootcamp\edu\cnm\deepdive\FizzBuzz.class
c:\projects\edu\cnm\deepdive\FizzBuzz.class
(Please note again the correspondence between packages and directories.)
If the FizzBuzz.class
file is found in any of the locations listed, the search stops, the FizzBuzz
class is loaded from the bytecode file into memory, and the application launcher attempts to invoke the class main
method. (Note: Bytecode output from the compiler is written to files with a .class
extension.)
If, during execution, the code of the edu.cnm.deepdive.FizzBuzz
references any other class (or interface) that is not in the standard library, the same search process will be repeated, using the fully-qualified name of that class, together with each of the directory paths specified in the class path, to search for the bytecode of the referenced class. However, every time this search is performed, the relationship between packages and directories is enforced: If the bytecode is found in a directory corresponding to the fully-qualified name specified, but the fully-qualified name embedded in the bytecode does not agree with the fully-qualified name being searched for, the search fails.