Oggetto nullo quando passato a una classe interna anonima
-
08-07-2019 - |
Domanda
Quando si passa un oggetto finale (una stringa nel codice seguente) viene visualizzato come nullo quando viene stampato dalla classe interna anonima. Tuttavia, quando viene passato un tipo di valore finale o una stringa finale diritta, il suo valore viene visualizzato correttamente. Cosa significa veramente final
nel contesto della classe interna anonima e perché l'oggetto viene passato null?
public class WeirdInners
{
public class InnerThing
{
public InnerThing()
{
print();
}
public void print(){
}
}
public WeirdInners()
{
final String aString = "argh!".toString();
final String bString = "argh!";
System.out.println(aString);
System.out.println(bString);
InnerThing inner =new InnerThing(){
public void print()
{
System.out.println("inner"+aString); // When called from constructor, this value is null.
System.out.println("inner"+bString); // This value is correctly printed.
}
};
inner.print();
}
public static void main(String[] args)
{
WeirdInners test1 = new WeirdInners();
}
}
Questo è un comportamento molto strano per me perché l'aspettativa è che la stringa sia un oggetto, perché chiamare toString ()
cambia le cose?
Altre informazioni: questo comportamento si osserva solo utilizzando Java 1.4, non in Java 5. Qualche suggerimento su una soluzione alternativa? Non chiamare toString ()
su una stringa esistente è abbastanza equo, ma poiché questo è solo un esempio, ha implicazioni nel mondo reale se lo eseguo su un oggetto non String.
Soluzione
Se controlli la sezione su costanti del tempo di compilazione
in JLS, vedrai che chiamare .toString ()
fa la differenza. Come le sciocchezze come il prefisso con false? Null + " " ;:
.
Ciò che è importante qui è l'ordinamento relativo dell'impostazione dei campi chiusi e del costruttore. Se si utilizza -target 1.4
o versione successiva (che non è l'impostazione predefinita in 1.4!), I campi verranno copiati prima di chiamare il super. Con le specifiche precedenti alla 1.3 questo era un bytecode illegale.
Come spesso accade in questi casi, javap -c
è utile per vedere cosa sta facendo il compilatore javac. Le specifiche sono utili per capire perché (dovresti avere sufficiente pazienza).
Altri suggerimenti
La mia ipotesi sarebbe quella di innescare comportamenti indefiniti quando il costruttore di InnerThing () passa (implicitamente) il suo this
al metodo di stampa di una sottoclasse anonima di InnerThing mentre l'oggetto non è completamente costruito. Questo this
si basa a sua volta su un riferimento implicito al this
di WierdInners.
La chiamata di .toString ()
sposta l'inizializzazione di aString dal tempo di compilazione al runtime. Perché il comportamento indefinito differisca tra Java 1.4 e 1.5 è probabilmente un dettaglio dell'implementazione di JVM.
È pericoloso invocare metodi sovrascritti da un costruttore di superclassi, poiché verranno chiamati prima che la parte della sottoclasse dell'oggetto sia inizializzata.
Inoltre, una classe interna che accede alle variabili finali di un ambito che racchiude effettivamente accederà alle copie di tali variabili (ecco perché devono essere definitive) e questi campi copiati risiedono nella sottoclasse anonima.
Sospetto che il motivo per cui bString
sia trattato in modo diverso è che il suo valore è noto al momento della compilazione, il che consente al compilatore di incorporare l'accesso al campo nella sottoclasse, rendendo irrilevante il tempo di inizializzazione del campo.