inner-classes istanziare - È possibile creare un'istanza di classe nidificata utilizzando Java Reflection?




significato che (7)

Esempio di codice:

public class Foo
{
    public class Bar
    {
         public void printMesg(String body)
         {
             System.out.println(body);
         }
    }
    public static void main(String[] args)
    {
         // Creating new instance of 'Bar' using Class.forname - how?
    }        
}

È possibile creare una nuova istanza di Class Bar dando il suo nome? Ho provato a usare:

Class c = Class.forName("Foo$Bar")

trova la classe, ma quando uso c.newInstance () lancia InstantiationException.


Answers

Ecco una risposta per la classe annidata (static inner): Nel mio caso ho bisogno di acquisire il tipo con il suo nome completo

Class.forName(somePackage.innerClass$outerClass).getConstructor().newInstance();

il ' $ ' è fondamentale!

con un punto otterrai ClassNotFoundException per la classe "package.innerClass.outerClass". L'eccezione è fuorviante :-(.


Altre risposte hanno spiegato come è possibile ciò che si vuole fare.

Ma voglio suggerirvi che il fatto che sia necessario farlo è un'indicazione che c'è qualcosa di sbagliato nella progettazione del sistema. Suggerirei di aver bisogno di un metodo factory (non statico) sulla classe che lo include, o di dichiarare la classe interna come statica.

La creazione di un'istanza di classe interna (non statica) riflette in modo riflessivo un "odore" di incapsulamento rotto.


Codice veloce e sporco:

Foo.Bar.class.getConstructors()[0].newInstance(new Foo());

Spiegazione: Devi dire al Bar della sua chiusura Foo.


Questo non è del tutto ottimale, ma funziona per profondità di classi interne e classi statiche interne.

public <T> T instantiateClass( final Class<T> cls ) throws CustomClassLoadException {
    try {
        List<Class<?>> toInstantiate = new ArrayList<Class<?>>();
        Class<?> parent = cls;
        while ( ! Modifier.isStatic( parent.getModifiers() ) && parent.isMemberClass() ) {
            toInstantiate.add( parent );
            parent = parent.getDeclaringClass();
        }
        toInstantiate.add( parent );
        Collections.reverse( toInstantiate );
        List<Object> instantiated = new ArrayList<Object>();
        for ( Class<?> current : toInstantiate ) {
            if ( instantiated.isEmpty() ) {
                instantiated.add( current.newInstance() );
            } else {
                Constructor<?> c = current.getConstructor( instantiated.get( instantiated.size() - 1 ).getClass() );
                instantiated.add( c.newInstance( instantiated.get( instantiated.size() - 1 ) ) );
            }
        }
        return (T) instantiated.get( instantiated.size() - 1 );
    } catch ( InstantiationException e ) {
        throw new CustomClassLoadException( "Failed to load class.", e );
    } catch ( IllegalAccessException e ) {
        throw new CustomClassLoadException( "Failed to load class.", e );
    } catch ( SecurityException e ) {
        throw new CustomClassLoadException( "Failed to load class.", e );
    } catch ( NoSuchMethodException e ) {
        throw new CustomClassLoadException( "Failed to load class.", e );
    } catch ( IllegalArgumentException e ) {
        throw new CustomClassLoadException( "Failed to load class.", e );
    } catch ( InvocationTargetException e ) {
        throw new CustomClassLoadException( "Failed to load class.", e );
    }
}

Sì. Ricorda che devi alimentare l'istanza esterna a una classe interiore. Usa javap per trovare il costruttore. Dovrai passare attraverso java.lang.reflect.Constructor piuttosto che fare affidamento sul malvagio Class.newInstance .

Compiled from "Foo.java"
public class Foo$Bar extends java.lang.Object{
    final Foo this$0;
    public Foo$Bar(Foo);
    public void printMesg(java.lang.String);
}

javap -c è interessante sul costruttore perché (assumendo -target 1.4 o successivo, ora implicito) si ottiene un'assegnazione di un campo di istanza prima di chiamare il super costruttore (usato per essere illegale).

public Foo$Bar(Foo);
  Code:
   0:   aload_0
   1:   aload_1
   2:   putfield        #1; //Field this$0:LFoo;
   5:   aload_0
   6:   invokespecial   #2; //Method java/lang/Object."<init>":()V
   9:   return

È necessario saltare attraverso alcuni cerchi per farlo. Innanzitutto, è necessario utilizzare Class.getConstructor() per trovare l'oggetto Constructor che si desidera richiamare:

Restituisce un oggetto Constructor che riflette il costruttore pubblico specificato della classe rappresentata da questo oggetto Class. Il parametro parameterTypes è un array di oggetti Class che identificano i tipi di parametri formali del costruttore, nell'ordine dichiarato. Se questo oggetto di classe rappresenta una classe interna dichiarata in un contesto non statico, i tipi di parametri formali includono l'istanza di inclusione esplicita come primo parametro.

E poi usi Constructor.newInstance() :

Se la classe dichiarante del costruttore è una classe interna in un contesto non statico, il primo argomento del costruttore deve essere l'istanza che lo include


Puoi utilizzare una variabile temporanea:

boolean outerBreak = false;
for (Type type : types) {
   if(outerBreak) break;
    for (Type t : types2) {
         if (some condition) {
             // Do something and break...
             outerBreak = true;
             break; // Breaks out of the inner loop
         }
    }
}

A seconda della funzione, puoi anche uscire / tornare dal ciclo interno:

for (Type type : types) {
    for (Type t : types2) {
         if (some condition) {
             // Do something and break...
             return;
         }
    }
}






java reflection inner-classes