2014-07-16 31 views
7

Ho trovato uno scenario in cui il programma java si comporta diversamente dopo la ridenominazione di una variabile. Capisco che questo non sia in realtà un codice che chiunque userebbe, ma se qualcuno sa che cosa sta succedendo sarebbe bello avere una spiegazione. Ho provato questo con java 1.6 su Eclipse Kepler.Nome variabile della modifica Java modifica il comportamento del programma

package _test; 

public class TestClass{ 
    public static void main(String...args){ 
     Object testClazz$1 = new Object(){ 
      public String toString() { 
       return "hello"; 
      } 
     }; 
     TestClass$1 test = new TestClass$1(); 
     System.out.println(testClazz$1.toString()); 
     test.doStuff(); 
    } 
} 

class TestClass$1{ 
    public void doStuff(){ 
     System.out.println("hello2"); 
    } 
} 

Questo uscite:

hello

Exception in thread "main" java.lang.NoSuchMethodError: _test.TestClass$1.doStuff()V at _test.TestClass.main(TestClass.java:13)

Per quanto ho capito il compilatore crea un file TestClass $ 1.class per l'oggetto testClazz $ 1 e questo fa sì che una collisione di denominazione.

Ma dopo la ridenominazione dell'oggetto da TestClass $ 1:

package _test; 

public class TestClass{ 
    public static void main(String...args){ 
     Object testClass$1 = new Object(){ 
      public String toString() { 
       return "hello"; 
      } 
     }; 

     TestClass$1 test = new TestClass$1(); 
     System.out.println(testClass$1.toString()); 
     test.doStuff(); 
    } 
} 

class TestClass$1{ 
    public void doStuff(){ 
     System.out.println("hello2"); 
    } 
} 

l'output è:

[email protected]

hello2

Tutte le idee che cosa sta succedendo qui?

+0

Non si conosce esattamente il motivo per cui ciò si sta verificando, ma c'è qualcosa da notare sulla denominazione: è una pratica scorretta denominare le variabili con un segno $ poiché questo è in genere riservato per una variabile generata dal sistema. Sarei disposto a scommettere che questo strano comportamento si sta verificando perché stai inciampando su alcuni TestClass generati dal sistema. – DanK

+0

Il sistema crea una classe anonima TestClass $ 1, è ancora strano che ciò si stia verificando. – user3139461

risposta

0

Quando il classloader incontra la classe anonima Object() {...}, la carica sotto il nome TestClass$1. Ciò crea un conflitto con class TestClass$1 {...} che è stato definito in modo esplicito.

Tuttavia, i conflitti dei nomi di classe vengono gestiti in modo non vergognoso.This bit of documentation ci dice che

If the class c has already been linked, then this method simply returns.

Questo è ciò che accade nel vostro caso. È sempre possibile caricare solo una delle due classi TestClass$1.

I "diversi nomi di variabili" sono non responsabile di qualcosa di diverso da ri-compilazione e ri-linking all'interno del vostro compilatore. A questo punto, il classloader è libero di scegliere quale dei due TestClass$1 piace e lo usa ovunque.


Se stai usando qualcosa come Eclipse (come me) e poi il bytecode otterrà cache, fino a quando una nuova touch operazione sul file sorgente (e l'aggiornamento del timestamp ...). Ecco che cosa ho fatto di riprodurre (in esecuzione OpenJDK 1.7, Eclipse Keplero sotto RedHat):

Mettere questo all'interno di un file sorgente TestClass.java:

package javaclasses.classes; 

public class TestClass{ 
    public static void main(String...args){ 
     Object o = new Object(){ 
      public String toString() { 
       return "hello"; 
      } 
     }; 

     TestClass$1 test = new TestClass$1(); 
     System.out.println(o.toString()); 
     test.doStuff(); 
    } 
} 

class TestClass$1{ 
    public void doStuff(){ 
     System.out.println("hello2"); 
    } 
} 

ctrl + F11 uscite:

[email protected] 
hello2 

Aprire questo in una console e touch TestClass.java

Torna in Eclipse e Ctrl + F11 ora uscite:

hello 
Exception in thread "main" java.lang.NoSuchMethodError: javaclasses.classes.TestClass$1.doStuff()V 
    at javaclasses.classes.TestClass.main(TestClass.java:13) 

Conlusion: Tutto ciò che si può dire in via definitiva è che il ClassLoader di default non è affidabile per risolvere manualmente le classi con lo stesso completo nomi. Cambiare i nomi delle variabili non ha importanza, la data/ora aggiornata sul tuo file sorgente lo fa.

3

Le classi anonime sono denominate automaticamente aggiungendo un numero $ e un numero crescente al nome della classe che le include.

Nel vostro primo esempio della classe Anoymous si chiamerà TestClass$1 che non ha alcun metodo doStuff(), viene sostituita solo toString() è per questo che si ottiene NoSuchMethodError errore.

Nel vostro secondo esempio avete già una variabile locale denominata TestClass$1, quindi il nome generato automaticamente scelto dal compilatore sarà un nome diverso, molto probabilmente TestClass$2. Dal momento che si crea un'istanza TestClass$1 che non è una classe anonima, ma una classe esplicitamente definito dall'utente, che verrà creata un'istanza che ha un metodo doStuff() che stampa correttamente "hello2" e che non esclude Object.toString() così la stampa il valore restituito dal suo processo toString() stamperà il default valore specificato in java.lang.Ojbect (che è il nome della classe aggiunto con un segno @ seguito dal codice hash predefinito in formato esadecimale).

Conclusione: Anche se questo è un esempio interessante, non si dovrebbe mai usare il segno $ nei nomi di classe e nei nomi degli identificatori.

+3

"Il carattere $ deve essere utilizzato solo in codice sorgente generato meccanicamente o, raramente, per accedere a nomi preesistenti su sistemi legacy." citazione dalla [specifica del linguaggio Java SE 7] (http://docs.oracle.com/javase/specs/jls/se7/html/jls-3.html#jls-3.8) – Flo

0

ho modificato il codice per rimuovere il "$" dai nomi di classe e rinominato TestClass $ 1 a t e cambiato il println un po 'come segue:

public class TestClass{ 

    public static void main(String...args){ 
     Object t = new Object(){ 
      public String toString() { 
       return "t.toString()"; 
      } 
     }; 
     TestClass1 tc1 = new TestClass1(); 
     System.out.println(t.toString()); 
     tc1.doStuff(); 
    } 
} 

class TestClass1{ 
    public void doStuff(){ 
     System.out.println("TestClass1.doStuff()"); 
    } 
} 

uscita è ora:

t.toString() 
TestClass1.doStuff() 

È questo che ti aspetti?

+1

Mi interessava solo il modo in cui jvm gestisce nominando le collisioni, non esiste una ragione ragionevole per l'utilizzo di $ registrati in classe e nomi di variabili. – user3139461

Problemi correlati