l'ordre javac classpath contredit la documentation Oracle?


Dans Sierra/Bates SCJP livre, page 797:

"java et javac [...] regardez d'abord dans les répertoires qui contiennent les classes standard avec Java SE. Ensuite, ils regardent dans les répertoires définis par classpaths "

Documentation Oracle indique le même ordre.

(Je sais que je ne devrais pas faire ça mais...) Pour tester ce comportement, j'ai implémenté HashSet.java et Lol.java dans répertoire: C:\dev\cmdline\TestProject\sources\java\util

package java.util;
public class HashSet {}

Et

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

J'obtiens une erreur de compilation lors de l'exécution: C:\dev\cmdline\TestProject\sources > javac java / util / Lol.java

Java\util\Lol.java:6: erreur: impossible de trouver le symbole un.add("lol"); ^ symbole: méthode ajouter (Chaîne) emplacement: variable a de type HashSet

...ce qui signifie que le classpath par défaut (répertoire courant) est le premier utiliser.

Alors, la documentation Oracle est-elle incorrecte? Comment testeriez-vous l'ordre classpaths?

Author: Umberto Raimondi, 2015-03-15

2 answers

En se référant à la documentation Oracle, l'énoncé du livre SCJP peut être trop simplifié. La documentation Oracle fait explicitement la différence entre le" Lanceur Java " (java) et le compilateur Java javac. Et en fait, les processus sont quelque peu différents.

Je vais essayer d'extraire les parties pertinentes qui expliquent le comportement que vous observez:

(De Comment les classes sont trouvées: Comment Javac et JavaDoc trouvent les Classes:)

Si une classe référencée est définie à la fois dans un fichier de classe et dans un fichier source, [...] javac utilise des fichiers de classe, mais recompile automatiquement tous les fichiers de classe qu'il détermine obsolètes. Les règles de recompilation automatique sont documentées dans le document javac pour Windows ou Solaris .

Ces documents liés contiennent la sous-section correspondante (qui est la même dans les deux cas), à partir de laquelle je citerai encore ici:

(à Partir de javac-Compilateur de langage de programmation Java: RECHERCHE DE TYPES:)

Lors de la compilation d'un fichier source, le compilateur a souvent besoin d'informations sur un type dont la définition n'apparaissait pas dans les fichiers source donnés sur la ligne de commande. [...]

Lorsque le compilateur a besoin d'informations de type, il recherche un fichier source ou un fichier de classe qui définit le type. [...]

Une recherche de type réussie peut produire un fichier de classe, un fichier source ou les deux. Si les deux sont trouvés, vous pouvez utiliser l'option -Xprefer pour indiquer au compilateur lequel utiliser. Si newer est donné, le compilateur utilisera le plus récent des deux fichiers. Si source est donné, il utilisera le fichier source. La valeur par défaut est nouveaux.

Si une recherche de type trouve un fichier source pour un type requis, soit par lui-même, soit à la suite du paramètre pour -Xprefer, le compilateur lira le fichier source pour obtenir les informations dont il a besoin. En outre, il sera par défaut compilez également le fichier source. Vous pouvez utiliser l'option -implicit pour spécifier le comportement. Si aucun n'est donné, aucun fichier de classe ne sera généré pour le fichier source. Si class est donné, les fichiers de classe seront générés pour le fichier source.

Donc, pour résumer: La javac compilateur trouverez votre source fichier java.util.HashSet, ainsi que les classe fichier à partir du fichier d'amorce classes. Mais par défaut, il compilera le fichier source .

(Et fait intéressant, il ne semble pas y avoir de moyen facile de le convaincre de ne pas utiliser la source comme entrée: L'option -implicit détermine uniquement si un fichier .class est généré, mais même si -implicit:none est défini, il utilisera toujours la classe créée à partir de la source...)

Vous pouvez également utiliser l'option -verbose pour regarder ce processus plus en détail:

javac -verbose java/util/Lol.java

Produit la sortie suivante:

[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

Il n'essaie même pas de charger le HashSet ' classe à partir des pots bootstrap, mais à la place, fait directement référence à votre fichier source:

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

En revanche, lorsque vous omettez votre propre classe HashSet, vous verrez la sortie attendue:

[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)]]
...

Où il obtient la classe HashSet à partir du rt.jar.

 2
Author: Marco13, 2015-03-15 12:12:30

La JVM et le compilateur Java sont deux choses très différentes, alors que le comportement décrit par SCJP est celui attendu d'une JVM, je ne suis pas sûr que javac suive les mêmes règles, dans la technote Sun que vous avez liée, il n'est pas clairement indiqué que les deux suivent la même approche.

La seule ligne compréhensible indique que:

Par défaut, javac et javadoc recherchent le chemin de la classe utilisateur à la fois pour les fichiers de classe et les fichiers de code source.

Mais il ne spécifie pas tout ordre en ce qui concerne la recherche de classe dans d'autres chemins.

Ils sont plus explicites ici:

Le JDK, la JVM et d'autres outils JDK trouvent des classes en recherchant les classes de la plate-forme Java (bootstrap), toutes les classes d'extension et le chemin de classe, dans cet ordre. (Pour plus de détails sur la stratégie de recherche, voir Comment les classes sont trouvées.)

Le seul document qui compte en cas de doute est la spécification JVM, mais qui ne contient pas de détails sur le l'outillage java.

Et en note latérale, notez que vous ne pourrez pas invoquer les classes définies par l'utilisateur créées dans java.paquet util, parce que cela et d'autres paquets standard (c'est-à-dire java.* ) sont des paquets restreints, vous obtiendrez une exception en essayant d'appeler une classe utilisateur définie dans ces paquets (javac compilera votre code de toute façon, aucune vérification n'est effectuée, mais la jvm se plaindra).

Modifier:

Compilez votre exemple en ajoutant le-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

Regardez les lignes avec *** , comme vous pouvez le voir, javac conserve deux listes de chemins différentes, une pour les sources et une pour les fichiers de classe. Alors que le comportement de fichiers de classe est celui décrit dans les documents, javac vérifie d'abord si un .le fichier source java pour un objet spécifique existe et si javac compile / charge cette classe définie par l'utilisateur. Donc, cette ligne est réellement correcte en ce qui concerne le chargement des classes, ils ne spécifient tout simplement pas l'ordre global (sources + classes de la classpath) est en cours de compilation. Les sources viennent toujours en premier.

 1
Author: Umberto Raimondi, 2015-03-15 12:09:52