Perché una class interna anonima non contiene nulla generato da questo codice?

package com.test; public class OuterClass { public class InnerClass { public class InnerInnerClass { } } public class InnerClass2 { } //this class should not exist in OuterClass after dummifying private class PrivateInnerClass { private String getString() { return "hello PrivateInnerClass"; } } public String getStringFromPrivateInner() { return new PrivateInnerClass().getString(); } } 

Quando si esegue javac sulla riga di comando con Sun JVM 1.6.0_20 , questo codice produce 6 file .class:

OuterClass.class
OuterClass $ 1.class
OuterClass $ InnerClass.class
OuterClass $ InnerClass2.class
OuterClass $ class interna $ InnerInnerClass.class
OuterClass $ PrivateInnerClass.class

Quando viene eseguito in JDT in eclipse, produce solo 5 classi.

OuterClass.class
OuterClass $ 1.class
OuterClass $ InnerClass.class
OuterClass $ InnerClass2.class
OuterClass $ class interna $ InnerInnerClass.class
OuterClass $ PrivateInnerClass.class

Quando decompilato, OuterClass$1.class non contiene nulla. Da dove viene questa class extra e perché viene creata?

Non ho la risposta, ma sono in grado di confermarlo e ridurre lo snippet al seguente:

 public class OuterClass { private class PrivateInnerClass { } public void instantiate() { new PrivateInnerClass(); } } 

Questo crea OuterClass$1.class

 Compiled from "OuterClass.java" class OuterClass$1 extends java.lang.Object{ } 

Ed ecco javap -c per OuterClass.class :

 Compiled from "OuterClass.java" public class OuterClass extends java.lang.Object{ public OuterClass(); Code: 0: aload_0 1: invokespecial #1; //Method java/lang/Object."":()V 4: return public void instantiate(); Code: 0: new #2; //class OuterClass$PrivateInnerClass 3: dup 4: aload_0 5: aconst_null 6: invokespecial #3; //Method OuterClass$PrivateInnerClass."": //(LOuterClass;LOuterClass$1;)V 9: pop 10: return } 

E per OuterClass$PrivateInnerClass :

 Compiled from "OuterClass.java" class OuterClass$PrivateInnerClass extends java.lang.Object{ final OuterClass this$0; OuterClass$PrivateInnerClass(OuterClass, OuterClass$1); Code: 0: aload_0 1: aload_1 2: invokespecial #1; //Method "":(LOuterClass;)V 5: return } 

Come puoi vedere, il costruttore sintetizzato accetta un OuterClass$1 .

Quindi javac crea il costruttore predefinito per prendere un argomento extra, di tipo $1 , e il valore di tale argomento predefinito è 5: aconst_null .


Ho trovato che $1 non viene creato se una delle seguenti condizioni è vera:

  • Tu rendi public class PrivateInnerClass
  • Si dichiara un costruttore PrivateInnerClass per PrivateInnerClass
  • O non si chiama il new su di esso
  • Probabilmente altre cose (es. Nidificate static , ecc.).

Forse correlato

  • ID bug: 4295934 : la compilazione di una class interna privata crea un file di class anonimo nella directory errata

Crea la seguente fonte in una directory chiamata test:

 package test; public class testClass { private class Inner { } public testClass() { Inner in = new Inner(); } } 

Compilare il file dalla directory padre javac test/testClass.java

Si noti che il file testClass$1.class viene creato nella directory corrente. Non sono sicuro del motivo per cui questo file è stato creato anche in quanto è stato creato anche un test/testClass$Inner.class .

VALUTAZIONE

Il file testClass$1.class è per una class fittizia richiesta da un “costruttore di accessi” per il costruttore privato della class interna privata testClass$Inner . Il disassemblaggio mostra che il nome completo di questa class è correttamente annotato, quindi non è chiaro il motivo per cui il file di class finisce nella directory sbagliata.

Sto usando lo snippet più piccolo dei polygenelubrificanti.

Ricorda che non esiste un concetto di classi nidificate nel bytecode; il bytecode è, tuttavia, a conoscenza dei modificatori di accesso. Il problema che il compilatore sta cercando di aggirare qui è che il metodo instantiate() deve creare una nuova istanza di PrivateInnerClass . Tuttavia, OuterClass non ha accesso al costruttore di OuterClass$PrivateInnerClass ( OuterClass$PrivateInnerClass verrà generato come una class protetta da pacchetto senza un costruttore pubblico).

Quindi cosa può fare il compilatore? La soluzione ovvia è di cambiare PrivateInnerClass per avere un costruttore protetto da pacchetti. Il problema qui è che questo permetterà a qualsiasi altro codice che si interfaccia con la class per creare una nuova istanza di PrivateInnerClass , anche se è dichiarata esplicitamente come privata!

Per cercare di impedirlo, il compilatore javac sta facendo un piccolo trucco: invece di rendere il costruttore normale di PrivateInnerClass visibile da altre classi, lo lascia come nascosto (in realtà non lo definisce affatto, ma è la stessa cosa da fuori ). Invece, crea un nuovo costruttore che riceve un parametro aggiuntivo del tipo speciale OuterClass$1 .

Ora, se guardi instantiate() , chiama quel nuovo costruttore. In realtà invia null come secondo parametro (del tipo OuterClass$1 ) – quel parametro è usato solo per specificare che questo costruttore è quello che dovrebbe essere chiamato.

Quindi, perché creare un nuovo tipo per il 2 ° parametro? Perché non usare, ad esempio, Object ? È usato solo per differenziarlo dal costruttore normale e null viene comunque passato! E la risposta è che, dato che OuterClass$1 è privato di OuterClass, un compilatore legale non consentirà mai all’utente di invocare il OuterClass$PrivateInnerClass speciale OuterClass$PrivateInnerClass , poiché uno dei tipi di parametri richiesti, OuterClass$1 , è nascosto.

Immagino che il compilatore di JDT usi un’altra tecnica per risolvere lo stesso problema.

Basandomi sulla risposta dei polygenelubrificanti, direi che questa misteriosa class impedisce a chiunque altro (ad esempio, fuori da OuterClass ) di OuterClass un’istanza di un OuterClass$PrivateInnerClass , perché non hanno accesso a OuterClass$1 .

Dopo la ricerca ho trovato questo link. http://bugs.java.com/bugdatabase/view_bug.do?bug_id=6378717

Il commento si riferisce al codice sorgente disponibile nel link specificato.

Questo non è un bug.

Il compilatore sta cercando di risolvere un problema di accesso. Poiché la class interna Test.Request è privata, il suo costruttore è privato. Questo può essere visto se si utilizza -private a javap:

$ javap -private Test \ $ Richiesta compilata da “Test.java” class finale Test $ Richiesta estende java.lang.Object {final Prova questo $ 0; Test privato $ Richiesta (Test); Test $ Request (Test, Test $ 1); }

Tuttavia, la JVM non consentirà la sottoclass anonima dell’accesso a Coucou (Test $ 1) a questo costruttore privato. Questa è una differenza fondamentale tra JVM e il linguaggio di programmazione Java quando si tratta di classi nidificate. La lingua consente alle classi nidificate di accedere ai membri privati ​​della class che li include.

Originariamente, quando le classi nidificate venivano aggiunte al linguaggio, la soluzione a questo problema era di rendere privato il pacchetto del costruttore e apparirebbe così:

$ javap -private Test \ $ Richiesta compilata da “Test.java” class finale Test $ Richiesta estende java.lang.Object {final Prova questo $ 0; Test di $ Request (Test); }

Tuttavia, questo può facilmente portare a problemi in cui è ansible ottenere l’accesso al costruttore quando non si dovrebbe. Per risolvere questo problema, è stata inventata la soluzione attuale. Il costruttore “reale” rimarrà privato:

 private Test$Request(Test); 

Tuttavia, altre classi nidificate devono poter chiamare questo costruttore. Quindi deve essere fornito un costruttore di accesso. Tuttavia, questo costruttore di accesso deve essere diverso dal costruttore “reale”. Per risolvere questo problema, il compilatore aggiunge un parametro aggiuntivo al costruttore di accesso. Il tipo di questo parametro aggiuntivo deve essere qualcosa di unico che non sia in conflitto con ciò che l’utente potrebbe aver scritto. Quindi una soluzione ovvia è aggiungere una class anonima e usarla come tipo del secondo parametro:

 Test$Request(Test, Test$1); 

Tuttavia, il compilatore è intelligente e riutilizza qualsiasi class anonima se ne esiste una. Se cambi l’esempio per non includere una class anonima, vedrai che il compilatore ne creerà una:

public abstract class Test {private final class Richiesta {} private final class OtherRequest {Request test () {return new Request (); }}}

Se non c’è accesso al costruttore privato, il compilatore non ha bisogno di generare alcun costruttore di accesso che spieghi il comportamento di questo esempio:

Prova di class astratta pubblica {class finale privata Richiesta {}}

Un altro punto: se OuterClass$1 è già dichiarato dall’utente, OuterClass$PrivateInnerClass lo avrà comunque come argomento del costruttore:

 public class OuterClass { ... public String getStringFromPrivateInner() { PrivateInnerClass c = new PrivateInnerClass(); Object o = new Object() {}; return null; } } 

  public java.lang.String getStringFromPrivateInner ();
   Codice:
    0: nuovo # 2;  // class OuterClass $ PrivateInnerClass
    3: dup
    4: aload_0
    5: aconst_null
    6: invokespecial # 3;  // Metodo OuterClass $ PrivateInnerClass. "":
 (LOuterClass; L OuterClass $ 1 ;) V
    9: astore_1
    10: nuovo # 4;  // class OuterClass $ 1
    13: dup
    14: aload_0
    15: invokespecial # 5;  // Metodo OuterClass $ 1. "" :( LOuterClass;) V
    18: astore_2
    19: aconst_null
    20: areturn