5

In Sierra/Bates SCJP book page 797:

"java and javac [...] first look in the directories that contain the classes that come standard with Java SE. Then they look in the directories defined by classpaths"

Oracle documentation is stating the same order.

(I know I shouldn't do that but...) To test this behaviour I implemented HashSet.java and Lol.java in directory: C:\dev\cmdline\TestProject\sources\java\util

package java.util;
public class HashSet {}

and

package java.util;
import java.util.HashSet;
public class Lol {
    public static void main(String... x) {
        HashSet a = new HashSet();
        a.add("lol");
    }
}

I get a compilation error when executing: C:\dev\cmdline\TestProject\sources>javac java/util/Lol.java

java\util\Lol.java:6: error: cannot find symbol a.add("lol"); ^ symbol: method add(String) location: variable a of type HashSet

...which means that the default classpath (current directory) is first used.

So, is the Oracle documentation wrong? How would you test the classpaths order?

uraimo
  • 19,081
  • 8
  • 48
  • 55
adrien v
  • 88
  • 1
  • 5

2 Answers2

3

Referring to the Oracle Documentation, the statement from the SCJP book may be oversimplified. The Oracle Documentation explicitly differentiates between the "Java Launcher" (java) and the Java Compiler javac. And in fact, the processes are somewhat different.

I'll try to extract the relevant parts that explain the behavior that you are observing:

(From How Classes are Found : How Javac and JavaDoc Find Classes:)

If a referenced class is defined in both a class file and source file, [...] javac uses class files, but automatically recompiles any class files it determines to be out of date. The rules for automatic recompilation are documented in the javac document for Windows or Solaris.

These linked docments contain the corresponding subsection (which is the same in both cases), from which I'll quote here again:

(From javac - Java programming language compiler : SEARCHING FOR TYPES:)

When compiling a source file, the compiler often needs information about a type whose definition did not appear in the source files given on the command line. [...]

When the compiler needs type information, it looks for a source file or class file which defines the type. [...]

A successful type search may produce a class file, a source file, or both. If both are found, you can use the -Xprefer option to instruct the compiler which to use. If newer is given, the compiler will use the newer of the two files. If source is given, it will use the source file. The default is newer.

If a type search finds a source file for a required type, either by itself, or as a result of the setting for -Xprefer, the compiler will read the source file to get the information it needs. In addition, it will by default compile the source file as well. You can use the -implicit option to specify the behavior. If none is given, no class files will be generated for the source file. If class is given, class files will be generated for the source file.

So to summarize: The javac compiler will find your source file for java.util.HashSet, as well as the class file from the bootstrap classes. But by default, it will compile the source file.

(And interestingly, there seems to be no easy way to convince him not to use the source as input: The -implicit option only determines whether a .class file is generated, but even if -implicit:none is set, it will still use the class that was created from the source...)

You can also use the -verbose option to watch this process in more detail:

javac -verbose java/util/Lol.java

produces the following output:

[parsing started RegularFileObject[java\util\Lol.java]]
[parsing completed 100ms]
[search path for source files: .]
[search path for class files: (A long list with rt.jar and related JARs)]
[loading RegularFileObject[.\java\util\HashSet.java]]
[parsing started RegularFileObject[.\java\util\HashSet.java]]
[parsing completed 0ms]
[loading ZipFileIndexFileObject[c:\jdk1.8.0\lib\ct.sym(META-INF/sym/rt.jar/java/lang/Object.class)]]
[loading ZipFileIndexFileObject[c:\jdk1.8.0\lib\ct.sym(META-INF/sym/rt.jar/java/lang/String.class)]]
[checking java.util.Lol]
[loading ZipFileIndexFileObject[c:\jdk1.8.0\lib\ct.sym(META-INF/sym/rt.jar/java/lang/AutoCloseable.class)]]
[loading ZipFileIndexFileObject[c:\jdk1.8.0\lib\ct.sym(META-INF/sym/rt.jar/java/lang/Byte.class)]]
[loading ZipFileIndexFileObject[c:\jdk1.8.0\lib\ct.sym(META-INF/sym/rt.jar/java/lang/Character.class)]]
[loading ZipFileIndexFileObject[c:\jdk1.8.0\lib\ct.sym(META-INF/sym/rt.jar/java/lang/Short.class)]]
[loading ZipFileIndexFileObject[c:\jdk1.8.0\lib\ct.sym(META-INF/sym/rt.jar/java/lang/Long.class)]]
[loading ZipFileIndexFileObject[c:\jdk1.8.0\lib\ct.sym(META-INF/sym/rt.jar/java/lang/Float.class)]]
[loading ZipFileIndexFileObject[c:\jdk1.8.0\lib\ct.sym(META-INF/sym/rt.jar/java/lang/Integer.class)]]
[loading ZipFileIndexFileObject[c:\jdk1.8.0\lib\ct.sym(META-INF/sym/rt.jar/java/lang/Double.class)]]
[loading ZipFileIndexFileObject[c:\jdk1.8.0\lib\ct.sym(META-INF/sym/rt.jar/java/lang/Boolean.class)]]
[loading ZipFileIndexFileObject[c:\jdk1.8.0\lib\ct.sym(META-INF/sym/rt.jar/java/lang/Void.class)]]
java\util\Lol.java:6: error: cannot find symbol
        a.add("lol");
         ^
  symbol:   method add(String)
  location: variable a of type HashSet
[checking java.util.HashSet]
[total 1072ms]
1 error

It does not even try to load the HashSet` class from the bootstrap JARs, but instead, directly refers to your source file:

[loading RegularFileObject[.\java\util\HashSet.java]]

In contrast, when you omit your own HashSet class, you'll see the expected output:

[parsing started RegularFileObject[java\util\Lol.java]]
[parsing completed 100ms]
[search path for source files: .]
[search path for class files: (A long list with rt.jar and related JARs) ]
[loading ZipFileIndexFileObject[c:\jdk1.8.0\lib\ct.sym(META-INF/sym/rt.jar/java/util/HashSet.class)]]
[loading ZipFileIndexFileObject[c:\jdk1.8.0\lib\ct.sym(META-INF/sym/rt.jar/java/lang/Object.class)]]
...

where it obtains the HashSet class from the rt.jar.

Community
  • 1
  • 1
Marco13
  • 53,703
  • 9
  • 80
  • 159
  • OK thanks. Source files come first. Then "the compiler searches for class files first in the bootstrap and extension classes, then in the user class path" (as **correctly** stated in Sierra/Bates and Oracle doc) – adrien v Mar 15 '15 at 15:05
1

The JVM and the Java compiler are two very different things, while the behaviour described by SCJP is the one expected from a JVM, i'm not so sure that javac follows the same rules, in the Sun technote you've linked it's not clearly stated that the two follow the same approach.

The only understandable line states that:

By default, javac and javadoc search the user class path for both class files and source code files.

But it doesn't specify any ordering in regard to class searching in other paths.

They are more explicit here:

The JDK, the JVM and other JDK tools find classes by searching the Java platform (bootstrap) classes, any extension classes, and the class path, in that order. (For details on the search strategy, see How Classes Are Found.)

The only document that matters when in doubt is the JVM Specification, but that doesn't contain details about the java tooling.

And as a side note, notice that you will not be able to invoke user defined classes created in the java.util package, because that and other standard packages (i.e. java.*) are restricted packages, you'll get an exception when trying to invoke a user class defined in those packages (javac will compile your code anyway, no check is performed, but the jvm will complain).

Edit:

Compile your example adding the -verbose option:

javac -verbose java/util/Lol.java

[parsing started RegularFileObject[java/util/Lol.java]]
[parsing completed 37ms]
***[search path for source files: .]
[search path for class files: /Library/Java/JavaVirtualMachines/jdk1.8.0_25.jdk/Contents/Home/jre/lib/resources.jar,/Library/Java/JavaVirtualMachines/jdk1.8.0_25.jdk/Contents/Home/jre/lib/rt.jar,[...],.]
[loading ZipFileIndexFileObject[/Library/Java/JavaVirtualMachines/jdk1.8.0_25.jdk/Contents/Home/lib/ct.sym(META-INF/sym/rt.jar/java/lang/Object.class)]]
[loading ZipFileIndexFileObject[/Library/Java/JavaVirtualMachines/jdk1.8.0_25.jdk/Contents/Home/lib/ct.sym(META-INF/sym/rt.jar/java/lang/String.class)]]
***[checking java.util.Lol]
[loading ZipFileIndexFileObject[/Library/Java/JavaVirtualMachines/jdk1.8.0_25.jdk/Contents/Home/lib/ct.sym(META-INF/sym/rt.jar/java/io/Serializable.class)]]
[loading ZipFileIndexFileObject[/Library/Java/JavaVirtualMachines/jdk1.8.0_25.jdk/Contents/Home/lib/ct.sym(META-INF/sym/rt.jar/java/lang/AutoCloseable.class)]]
***[loading RegularFileObject[./java/util/HashSet.java]]
***[parsing started RegularFileObject[./java/util/HashSet.java]]
[parsing completed 0ms]
[loading ZipFileIndexFileObject[/Library/Java/JavaVirtualMachines/jdk1.8.0_25.jdk/Contents/Home/lib/ct.sym(META-INF/sym/rt.jar/java/lang/Byte.class)]]
[loading ZipFileIndexFileObject[/Library/Java/JavaVirtualMachines/jdk1.8.0_25.jdk/Contents/Home/lib/ct.sym(META-INF/sym/rt.jar/java/lang/Character.class)]]
[loading ZipFileIndexFileObject[/Library/Java/JavaVirtualMachines/jdk1.8.0_25.jdk/Contents/Home/lib/ct.sym(META-INF/sym/rt.jar/java/lang/Short.class)]]
[loading ZipFileIndexFileObject[/Library/Java/JavaVirtualMachines/jdk1.8.0_25.jdk/Contents/Home/lib/ct.sym(META-INF/sym/rt.jar/java/lang/Long.class)]]
[loading ZipFileIndexFileObject[/Library/Java/JavaVirtualMachines/jdk1.8.0_25.jdk/Contents/Home/lib/ct.sym(META-INF/sym/rt.jar/java/lang/Float.class)]]
[loading ZipFileIndexFileObject[/Library/Java/JavaVirtualMachines/jdk1.8.0_25.jdk/Contents/Home/lib/ct.sym(META-INF/sym/rt.jar/java/lang/Integer.class)]]
[loading ZipFileIndexFileObject[/Library/Java/JavaVirtualMachines/jdk1.8.0_25.jdk/Contents/Home/lib/ct.sym(META-INF/sym/rt.jar/java/lang/Double.class)]]
[loading ZipFileIndexFileObject[/Library/Java/JavaVirtualMachines/jdk1.8.0_25.jdk/Contents/Home/lib/ct.sym(META-INF/sym/rt.jar/java/lang/Boolean.class)]]
[loading ZipFileIndexFileObject[/Library/Java/JavaVirtualMachines/jdk1.8.0_25.jdk/Contents/Home/lib/ct.sym(META-INF/sym/rt.jar/java/lang/Void.class)]]
java/util/Lol.java:8: error: cannot find symbol
        a.add("lol");
         ^
symbol:   method add(String)
location: variable a of type HashSet
[checking java.util.HashSet]
[total 449ms]
1 error

Look at the lines with *** , as you can see, javac keeps two different path lists, one for sources and one for class files. While the behaviour for class files is the one described in the documents, javac checks first if a .java source file for a specific object exists and if any javac compile/loads that user defined class. So that line is actually correct regarding class loading, they just don't specify what the global order (sources+classes from the classpath) is during compilation. Sources come always first.

uraimo
  • 19,081
  • 8
  • 48
  • 55
  • Hi. According to Oracle documentation about javac, it behaves the same way as java. "The compiler searches for class files first in the bootstrap and extension classes, then in the user class path". Stated here: http://docs.oracle.com/javase/7/docs/technotes/tools/windows/javac.html – adrien v Mar 15 '15 at 12:01
  • 1
    Wait, i'm updating my answer with some findings :) – uraimo Mar 15 '15 at 12:03
  • 1
    And read @Marco13 answer, he didn't just skim through the docs like me :) – uraimo Mar 15 '15 at 12:18