Чувствительность к регистру имен классов Java

если один пишет два открытых класса Java с одинаковым именем без учета регистра в разных каталогах, то оба класса не могут использоваться во время выполнения. (Я протестировал это на Windows, Mac и Linux с несколькими версиями HotSpot JVM. Я бы не удивился, если бы были другие JVMs, где они могут использоваться одновременно.) Например, если я создам класс с именем a и один по имени A вот так:

// 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";
    }
}

три проекта eclipse, содержащие приведенный выше код доступен с моего сайта.

если попробовать позвонить myCase на обоих классах так:

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

typechecker успешно, но когда я запускаю файл класса, генерируемый кодом непосредственно выше, я получаю:

исключение в потоке" main " java.ленг.NoClassDefFoundError: testcase/A (неверное название: testcase/a)

в Java имена в целом чувствительны к регистру. Некоторые файловые системы (например, Windows) нечувствительны к регистру, поэтому я не удивительно, что такое поведение происходит, но кажется неправильно. К сожалению, спецификации Java странно не связаны с тем, какие классы видны. The спецификация языка Java (JLS), Java SE 7 Edition (раздел 6.6.1, стр. 166) говорит:

если класс или тип интерфейса объявлен открытым, то он может быть доступен любой код, при условии, что единица компиляции (§7.3), в котором он объявлен заметный.

в разделе 7.3 JLS определяет наблюдаемость единицы компиляции в чрезвычайно расплывчатых терминах:

все единицы компиляции предопределенного пакета java и его подпакетов lang и Ио всегда наблюдаемы. Для всех остальных пакетов, хост-система определяет, какие единицы компиляции заметны.

на Спецификация Виртуальной Машины Java аналогично расплывчато (раздел 5.3.1):

выполните следующие действия, чтобы загрузить и тем самым создать класс немассива или интерфейс C обозначается [двоичное имя] N с помощью загрузчика классов bootstrap [...] В противном случае виртуальная машина Java передает аргумент N вызову a способ на загрузчике класса bootstrap для поиска предполагаемого представления C в зависимости от платформы.

все это приводит к четырем вопросам в порядке убывания важность:

  1. существуют ли какие-либо гарантии того, какие классы загружаются загрузчиком(загрузчиками) классов по умолчанию в каждой JVM? Другими словами, могу ли я реализовать допустимый, но вырожденный JVM, который не будет загружать никакие классы, кроме классов в java.Лэнг и Ява.Ио?
  2. если есть какие-либо гарантии, нарушает ли поведение в приведенном выше примере гарантию (т. е. является ли поведение ошибкой)?
  3. есть ли способ сделать загрузку HotSpot a и A одновременно? Будет ли работать пользовательский загрузчик классов?

3 ответов


  • есть ли какие-либо гарантии о том, какие классы загружаются загрузчиком класса bootstrap в каждой JVM?

основные биты и фрагменты языка, а также вспомогательные классы реализации. Не гарантируется включение любого класса, который вы пишете. (Обычный JVM загружает ваши классы в отдельном загрузчике классов из загрузчика, и на самом деле обычный загрузчик загрузчика загружает свои классы из JAR нормально, так как это делает больше эффективное развертывание, чем большая старая структура каталогов, полная классов.)

  • если есть какие-либо гарантии, нарушает ли поведение в приведенном выше примере гарантию (т. е. является ли поведение ошибкой)?
  • есть ли способ заставить "стандартные" JVMs загружать a и A одновременно? Будет ли работать пользовательский загрузчик классов?

Java загружает классы, сопоставляя полное имя класса с именем файла, который затем выполняется поиск на пути к классу. Таким образом testcase.a идет testcase/a.class и testcase.A идет testcase/A.class. Некоторые файловые системы смешивают эти вещи и могут обслуживать другие, когда их просят. Другие понимают это правильно (в частности, вариант формата ZIP, используемый в файлах JAR, полностью чувствителен к регистру и переносим). Нет ничего, что Java может сделать с этим (хотя IDE может справиться с этим для вас, сохраняя .class файлы вдали от родной FS, я не знаю, действительно ли они делают и JDK javac большинство конечно, это не так умно).

однако это не единственный момент, чтобы отметить здесь:файлы классов знают внутренне, о каком классе они говорят. Отсутствие ожидается класс из файла просто означает, что загрузка не выполняется, что приводит к NoClassDefFoundError вы получили. То, что вы получили, было проблемой (неправильное развертывание, по крайней мере, в некотором смысле), которая была обнаружена и решена надежно. Теоретически вы можете создать classloader, который мог бы обрабатывать такие вещи, сохраняя поиск, но зачем? помещение файлов классов в банку исправит вещи гораздо более надежно; они обрабатываются правильно.

в более общем плане, если вы столкнулись с этой проблемой на самом деле много, возьмите на себя выполнение производственных сборок на Unix с чувствительной к регистру файловой системой (рекомендуется система CI, такая как Дженкинс) и найти, какие разработчики именуют классы с только разницей случаев и остановить их, так как это очень путаешь!


прекрасное объяснение Донала мало что оставляет добавить, но позвольте мне кратко поразмышлять над этой фразой:

... Классы Java с тем же именем без учета регистра ...

имена и строки вообще никогда не нечувствительны к регистру сами по себе, это только там толкование что может быть. И, во-вторых, Java не делает такой интерпретации.

Итак, правильная формулировка того, что вы имели в виду, будет:

... Классы Java, файловые представления которых в файловой системе без учета регистра имеют одинаковые имена ...


Не думайте только о папках.

используйте явные различные пространства имен ("пакеты") для ваших классов и, возможно, используйте папки для соответствия вашим классам.

когда я упоминаю "пакеты", я не имею в виду"*.JAR " файлы, но, только понятие:

package com.mycompany.mytool;

// "com.mycompany.mytool.MyClass"

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

когда вы не указываете пакет для своего кода, инструменты java (компилятор, I. D. E., что угодно) предполагают использовать один и тот же глобальный пакет для всех. И, в случае нескольких подобных классов, у них есть список папки, где искать.

пакеты похожи на" виртуальные " папки в вашем коде и применяются ко всем вашим пакетам на вашем пути к классам или установке Java. У вас может быть несколько классов с одинаковым идентификатором, но если они находятся в разных пакетах, и вы укажете, какой пакет искать, у вас не будет никаких проблем.

только мои 2 цента, для вашей чашки кофе Java