2012-06-05 11 views
46

Jeśli jeden zapisuje dwie publiczne klasy Java z tą samą nazwą niewrażliwą na wielkość liter w różnych katalogach, wówczas obie klasy nie są użyteczne w środowisku wykonawczym. (Testowałem to w systemach Windows, Mac i Linux z kilkoma wersjami JVM HotSpot. Nie zdziwiłbym się, gdyby inne JVM-y, w których byłyby jednocześnie użyteczne.) Na przykład, jeśli utworzę klasę o nazwie a i jedną o nazwie A, tak jak to :Rozróżnianie wielkości liter w nazwach klas Java

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

Trzy projekty Eclipse zawierające kod powyżej są available from my website.

Jeśli spróbować ja nazywając myCase na obu klasach tak:

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

typechecker powiedzie, ale kiedy uruchomić plik klasy generowania przez kod bezpośrednio powyżej uzyskać:

Exception in thread "main" java.lang.NoClassDefFoundError: testcase/A (wrong name: testcase/a)

W Java, nazwy są w dużym stopniu wrażliwe. Niektóre systemy plików (na przykład Windows) nie są rozróżniane wielkości liter, więc nie jestem zaskoczony powyższym zachowaniem, ale wygląda na to, że jest nieprawidłowy. Niestety specyfikacje Java są dziwnie nieprecyzyjne, a jakie klasy są widoczne. Java Language Specification (JLS), Java SE 7 Edition (rozdział 6.6.1, strona 166) mówi:

If a class or interface type is declared public, then it may be accessed by any code, provided that the compilation unit (§7.3) in which it is declared is observable.

w punkcie 7.3, JLS definiuje obserwowanie jednostce kompilacji pod względem wyjątkowo niejasne:

All the compilation units of the predefined package java and its subpackages lang and io are always observable. For all other packages, the host system determines which compilation units are observable.

Java Virtual Machine Specification jest podobnie niejasne (sekcja 5.3.1):

The following steps are used to load and thereby create the nonarray class or interface C denoted by [binary name] N using the bootstrap class loader [...] Otherwise, the Java virtual machine passes the argument N to an invocation of a method on the bootstrap class loader to search for a purported representation of C in a platform-dependent manner.

Wszystko to prowadzi do czterech pytań w porządku malejącym znaczeniu:

  1. Czy są jakieś gwarancje dotyczące klas ładowanych przez domyślny moduł ładujący klasy w każdej maszynie JVM? Innymi słowy, czy mogę zaimplementować poprawną, ale zdegenerowaną maszynę JVM, która nie załaduje żadnych klas, z wyjątkiem tych w java.lang i java.io?
  2. Jeśli są jakieś gwarancje, czy zachowanie w powyższym przykładzie narusza gwarancję (tj. Czy zachowanie jest błędem)?
  3. Czy istnieje sposób na jednoczesne ładowanie HotSpot a i A? Czy pisanie niestandardowego programu ładującego klasy?
+3

Więc niech mi to wprost .. Masz dwie klas o podobnych nazwach 'testcase.a' i' testcase.A ', w dwóch * różnych * katalogach na ścieżce klas (ponieważ nie możesz mieć ich w tym samym katalogu w systemie plików niewrażliwym na wielkość liter) - i zastanawiasz się, dlaczego JVM nie może znaleźć odpowiedniego pliku klasy do załadowania? –

+0

@GregHewgill Opisałeś scenariusz na początku mojego pytania poprawnie, ale moje pytania są szersze. –

+0

Cóż, cały pomysł "ścieżki klasy" jest zależny od platformy z punktu widzenia JLS i JVMS. Żaden z tych dokumentów nie pomoże ci rozwiązać tego problemu. –

Odpowiedz

18
  • Are there any guarantees about which classes are loadable by the bootstrap class loader in every JVM?

bity rdzeniowe i kawałki języka plus wspierające klasy implementacji. Nie ma gwarancji, że uwzględnisz jakąś klasę, którą napiszesz. (Normalna maszyna JVM ładuje klasy w oddzielnym programie ładującym klasy od modułu ładowania początkowego, a w rzeczywistości zwykły moduł ładowania początkowego ładuje zwykle klasy z pliku JAR, ponieważ powoduje to bardziej wydajne wdrażanie niż wielka stara struktura katalogów pełna klas).

  • If there are any guarantees, does the behavior in the example above violate the guarantee (i.e. is the behavior a bug)?
  • Is there any way to make "standard" JVMs load a and A simultaneously? Would writing a custom class loader work?

Java ładuje klasy przez mapowanie pełnej nazwy klasy na nazwę pliku, która jest następnie wyszukiwana w ścieżce klas. Tak więc testcase.a przechodzi do testcase/a.class i testcase.A przechodzi do testcase/A.class. Niektóre systemy plików mieszają te rzeczy i mogą służyć drugiemu, gdy ktoś o nie poprosi. Inne mają rację (w szczególności wariant formatu ZIP używany w plikach JAR jest w pełni rozróżniany i przenośny). Nie ma niczego, co Java mogłaby zrobić (chociaż IDE może sobie z tym poradzić, przechowując pliki .class z dala od macierzystego FS, nie wiem, czy faktycznie tak jest, a JDK na javac z pewnością nie jest tak inteligentny) .

Jednak nie jest to jedyny punkt do zapamiętania: pliki klas wiedzą wewnętrznie, o jakiej klasie mówią. Brak oczekiwanej klasy z pliku oznacza, że ​​obciążenie się nie udaje, co prowadzi do otrzymania przez ciebie NoClassDefFoundError. To, co otrzymałeś, było problemem (błędnym wdrożeniem przynajmniej w pewnym sensie), który został wykryty i rozwiązany solidnie. Teoretycznie można zbudować moduł ładujący klasy, który mógłby obsłużyć takie rzeczy, utrzymując wyszukiwanie, ale po co się kłopotać? Umieszczenie plików klasy wewnątrz pliku JAR naprawi rzeczy znacznie mocniej; są obsługiwane poprawnie.

Bardziej ogólnie, jeśli naprawdę napotykasz ten problem, weź pod uwagę kompilacje produkcyjne na systemie Unix z systemem plików wrażliwym na wielkość liter (zaleca się system CI taki jak Jenkins) i znajdź programistów, których nazwy są nazywane klasy z różnicami przypadku i powodują ich zatrzymanie, ponieważ jest to bardzo mylące!

+0

Odp: "spraw, aby się zatrzymały". Nie mogę! Piszę kompilator dla innego języka, który kompiluje się do kodu bajtowego Java. Identyfikatory w źródle muszą dopasowywać identyfikatory w celu (kod bajtowy), a kilka ważnych przykładów w drugim języku ma tę samą nazwę niewrażliwą na wielkość liter. –

+0

Cóż, normalnym sposobem radzenia sobie z takimi rzeczami jest normalizacja sprawy w tłumaczeniu. –

+0

To jest to, co zrobiłem, ale sprawia, że ​​interoperacyjność pomiędzy Javą i innym językiem jest znacznie bardziej niezręczna. Kod Java, który odnosi się do klas utworzonych przez skompilowanie kodu napisanego w innym języku, musi używać znormalizowanych nazw zamiast nazw źródłowych. To powiedziawszy, szczegóły mojego projektu są trochę poza tematem, bardziej interesuje mnie zrozumienie opisu specyfikacji klasy JVM dotyczących ładowania klas i szczególnego zachowania maszyny wirtualnej HotSpot JVM. –

-2

Nie myśl tylko o folderach.

Używaj jawnie różnych przestrzeni nazw ("pakietów") dla swoich klas i być może używaj folderów do swoich klas.

Kiedy wspominam „pakiety”, nie mam na myśli „* .JAR” pliki, ale tylko pojęcie:

package com.mycompany.mytool; 

// "com.mycompany.mytool.MyClass" 

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

Jeśli nie zostanie określony pakiet dla kodu Java, narzędzia (kompilator, IDE, cokolwiek), zakładają użycie tego samego globalnego pakietu dla wszystkich. A w przypadku kilku podobnych klas, mają listę folderów, gdzie szukać.

Pakiety są jak "wirtualne" foldery w kodzie i mają zastosowanie do wszystkich pakietów w ścieżce klas lub do instalacji Java. Możesz mieć kilka klas z tym samym ID, ale jeśli są w innej paczce, a Ty określisz, który pakiet będzie szukać, nie będziesz miał problemu.

Tylko moje 2 centy, do filiżanki kawy Java

1

Donal w porządku wyjaśnienie pozostawia trochę do dodania, ale pozwól mi krótko muse w tym zdaniu:

... Java classes with the same case-insensitive name ...

Nazw i smyczki w ogóle nigdy nie są wielkości liter w sobie, to tylko tam interpretacja, które mogą być. Po drugie, Java nie robi takiej interpretacji.

więc poprawne sformułowanie tego, co miał na myśli to:

... Java classes whose file representations in a case-insensitive file-system have identical names ...

+0

Nazwa może być niewrażliwa na wielkość liter (a ja natknąłem się na ciągi niewrażliwe na wielkość liter, dawno temu na dziwnym mainframe, brrr!), Ale nazwy Java są zawsze rozróżniane wielkości liter, a wszystkie łańcuchy były rozróżniane wielkości liter dla ... no, oczywiście przynajmniej przez całą moją karierę. –

Powiązane problemy