Sensibilité à la casse des noms de classes Java

Si l'on écrit deux classes Java publiques avec le même nom insensible à la casse dans des répertoires différents, les deux classes ne sont pas utilisables à l'exécution. (J'ai testé cela sur Windows, Mac et Linux avec plusieurs versions de la JVM HotSpot. Je ne serais pas surpris s'il y a d'autres JVM où elles sont utilisables simultanément. Par exemple, si je crée une classe nommée a et une autre nommée A comme ceci:

// lowercase/src/testcase/a.java
package testcase;
public class a {
    public static String myCase() {
        return "lower";
    }
}

// uppercase/src/testcase/A.java 
package testcase;
public class A {
    public static String myCase() {
        return "upper";
    }
}

Trois projets eclipse contenant le code ci-dessus sont site web.

Si j'essaie d'appeler myCase sur les deux classes comme ceci:

System.out.println(A.myCase());
System.out.println(a.myCase());

Le typechecker réussit, mais quand j'exécute le fichier de classe généré par le code directement ci-dessus, je reçois:

Exception dans le thread" main " java.lang.NoClassDefFoundError: testcase/A (mauvais nom: testcase/a)

En Java, les noms sont généralement sensibles à la casse. Certains systèmes de fichiers (par exemple Windows) sont insensibles à la casse, donc je ne suis pas surpris que le comportement ci-dessus se produise, mais il semble faux . Malheureusement, les spécifications Java sont curieusement non-engageantes sur les classes visibles. La Java Language Specification (JLS), Java SE 7 Edition (Section 6.6.1, page 166) dit:

Si une classe ou un type d'interface est déclaré public, alors il peut être consulté par tout code, à condition que l'Unité de compilation (§7.3) dans laquelle il est déclaré soit observable.

Dans la Section 7.3, le JLS définit l'observabilité d'une unité de compilation dans Termes extrêmement vagues:

Toutes les unités de compilation du paquet prédéfini java et de ses sous-paquets lang et io sont toujours observables. Pour tous les autres paquets, le système hôte détermine quelles unités de compilation sont observables .

La spécification de la machine virtuelle Java est également vague (Section 5.3.1):

Les étapes suivantes sont utilisées pour charger et ainsi créer la classe nonarray ou interface C désignée par [binaire nom] n Utilisation du chargeur de classe bootstrap [...] Sinon, la machine virtuelle Java passe L'argument N à une invocation de a méthode sur le chargeur de classe bootstrap pour rechercher une représentation supposée de C dans une plate-forme de manière dépendante.

Tout cela conduit à quatre questions par ordre décroissant d'importance:

  1. Existe-t-il des garanties sur les classes qui sont chargeables par le(s) chargeur (s) de classe par défaut dans chaque JVM? En d'autres termes, puis-je implémenter un JVM VALIDE, mais dégénérée, qui ne chargera aucune classe sauf celles en java.lang et java.io?
  2. S'il y a des garanties, le comportement dans l'exemple ci-dessus violent la garantie (c'est à dire le comportement d'un bug)?
  3. est - il possible de charger le point D'Accès a et A simultanément? Est-ce que l'écriture d'un chargeur de classe personnalisé fonctionnerait?
46
demandé sur JasonMArcher 2012-06-05 06:26:14

3 réponses

  • Existe-t-il des garanties sur les classes qui sont chargeables par le chargeur de classe bootstrap dans chaque JVM?

Les bits et les morceaux de base du langage, ainsi que les classes d'implémentation de soutien. Pas garanti d'inclure toute classe que vous écrivez. (La JVM normale charge vos classes dans un classloader séparé de celui de bootstrap, et en fait le chargeur de bootstrap normal charge ses classes hors d'un JAR normalement, car cela rend le déploiement plus efficace qu'une grande vieille structure de répertoires pleine de classes.)

  • S'il y a des garanties, le comportement dans l'exemple ci-dessus violent la garantie (c'est à dire le comportement d'un bug)?
  • Existe-t-il un moyen de charger simultanément a et a des JVM "standard"? Est-ce que l'écriture d'un chargeur de classe personnalisé fonctionnerait?

Java charge les classes en mappant le nom complet de la classe dans un nom de fichier qui est ensuite recherché sur le chemin de classe. Donc testcase.a va à testcase/a.class et testcase.A va à testcase/A.class. Certains systèmes de fichiers mélangent ces choses, et peuvent servir l'autre quand on en demande un. D'autres l'obtiennent correctement (en particulier, la variante du format ZIP utilisé dans les fichiers JAR est entièrement sensible à la casse et portable). Il n'y a rien que Java puisse faire à ce sujet (bien qu'un IDE puisse le gérer pour vous en gardant les fichiers .class loin des FS natifs, Je ne sais pas s'ils le font réellement et le JDK javac n'est certainement pas si intelligent).

Cependant ce n'est pas le seul point à noter ici: les fichiers de classe savent en interne de quelle classe ils parlent. L'absence de la classe expected du fichier signifie simplement que la charge échoue, conduisant à la NoClassDefFoundError que vous avez reçue. Ce que vous avez eu était un problème (un mauvais déploiement dans au moins un certain sens) qui a été détecté et traité avec robustesse. Théoriquement, vous pourriez construire un classloader qui pourrait gérer de telles choses en continuant à chercher, mais pourquoi s'embêter? mettre les fichiers de classe dans un JAR corrigera les choses beaucoup plus robustes; ceux-ci sont gérés correctement.

Plus généralement, si vous rencontrez ce problème pour de vrai beaucoup, prenez à faire des builds de production sur un Unix avec un système de fichiers sensible à la casse (un système CI comme Jenkins est recommandé) et trouvez quels développeurs nommez les classes avec juste des différences de casse et faites-les arrêter car c'est très déroutant!

18
répondu Donal Fellows 2012-06-05 18:10:53

La bonne explication de Donal laisse peu à ajouter, mais permettez-moi de réfléchir brièvement sur cette phrase:

... Classes Java avec le même nom insensible à la casse ...

Les noms et les chaînes en général ne sont jamais insensibles à la casse en eux-mêmes , c'est seulement là interprétation qui peut être. Et deuxièmement, Java ne fait pas une telle interprétation.

Donc, une formulation correcte de ce que vous aviez en tête serait:

... Classes Java dont le fichier les représentations dans un système de fichiers insensible à la casse ont des noms identiques ...

1
répondu Tillmann 2012-06-06 07:48:40

Ne pensez pas seulement aux dossiers.

Utilisez différents espaces de noms explicites ("packages") pour vos classes, et peut-être Utilisez des dossiers pour correspondre à vos classes.

Quand je mentionne "paquets", Je ne veux pas dire"*.Jar " fichiers, mais, juste le concept de:

package com.mycompany.mytool;

// "com.mycompany.mytool.MyClass"

public class MyClass
{
   // ...
} // class MyClass

Lorsque vous ne spécifiez pas de package pour votre code, les outils java (compilateur, I. D. E., peu importe), supposent d'utiliser le même package global pour tous. Et, dans le cas de plusieurs autres classes, ils ont une liste de dossiers, où chercher pour.

Les paquets sont comme des dossiers "virtuels" dans votre code, et s'appliquent à tous vos paquets sur votre classpath, ou l'installation de Java. Vous pouvez avoir plusieurs classes, avec le même identifiant, mais, si elles sont dans un paquet différent, et que vous spécifiez quel paquet Rechercher, vous n'aurez aucun problème.

Juste mes 2 cents, pour votre tasse de café Java

-2
répondu umlcat 2012-06-05 17:36:28