web-dev-qa-db-fra.com

Est-il possible de créer une instance de classe imbriquée en utilisant Java Reflection?

Exemple de code:

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?
    }        
}

Est-il possible de créer une nouvelle instance de la classe Bar en donnant son nom? J'ai essayé d'utiliser:

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

il trouve la classe, mais lorsque j'utilise c.newInstance (), il lance InstantiationException.

34
kars7e

Vous devez sauter à travers quelques cerceaux pour ce faire. Tout d'abord, vous devez utiliser Class.getConstructor () pour trouver l'objet Constructor que vous souhaitez appeler:

Renvoie un objet Constructor qui reflète le constructeur public spécifié de la classe représentée par cet objet Class. Le paramètre parameterTypes est un tableau d'objets Class qui identifient les types de paramètres formels du constructeur, dans l'ordre déclaré. Si cet objet Class représente une classe interne déclarée dans un contexte non statique, les types de paramètres formels incluent l'instance englobante explicite comme premier paramètre.

Et puis vous utilisez Constructor.newInstance () :

Si la classe déclarante du constructeur est une classe interne dans un contexte non statique, le premier argument du constructeur doit être l'instance englobante

54
skaffman

Les classes internes ne peuvent en effet pas être construites sans avoir d'abord construit la classe parente. Il ne peut pas exister en dehors de la classe parent. Vous devrez passer une instance de la classe parent lors de la réflexion. Les classes imbriquées sont static et peuvent être utilisées indépendamment de la classe parente, donc également lors de la réflexion.

Voici un SSCCE qui montre tout ça.

package mypackage;

import Java.lang.reflect.Modifier;

public class Parent {

    public static class Nested {
        public Nested() {
            System.out.println("Nested constructed");
        }
    }

    public class Inner {
        public Inner() {
            System.out.println("Inner constructed");
        }
    }

    public static void main(String... args) throws Exception {
        // Construct nested class the normal way:
        Nested nested = new Nested();

        // Construct inner class the normal way:
        Inner inner = new Parent().new Inner();

        // Construct nested class by reflection:
        Class.forName("mypackage.Parent$Nested").newInstance();

        // Construct inner class by reflection:
        Object parent = Class.forName("mypackage.Parent").newInstance();
        for (Class<?> cls : parent.getClass().getDeclaredClasses()) {
            if (!Modifier.isStatic(cls.getModifiers())) {
                // This is an inner class. Pass the parent class in.
                cls.getDeclaredConstructor(new Class[] { parent.getClass() }).newInstance(new Object[] { parent });
            } else {
                // This is a nested class. You can also use it here as follows:
                cls.getDeclaredConstructor(new Class[] {}).newInstance(new Object[] {});
            }
        }
    }
}

Cela devrait produire

 Construit imbriqué 
 Construit intérieur 
 Construit imbriqué 
 Construit intérieur 
 Construit imbriqué 
25
BalusC

Code rapide et sale:

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

Explication: Vous devez informer le Bar de son enfermement de Foo.

7
meriton

Oui. N'oubliez pas que vous devez alimenter l'instance externe dans une classe interne. Utilisez javap pour trouver le constructeur. Vous devrez passer par Java.lang.reflect.Constructor plutôt que de compter sur le mal 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 est intéressant sur le constructeur car (en supposant que -target 1.4 ou version ultérieure, désormais implicite) vous obtenez une affectation d'un champ d'instance avant d'appeler le super constructeur (autrefois illégal).

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
2

D'autres réponses ont expliqué comment vous pouvez faire ce que vous voulez faire.

Mais je veux vous suggérer que le fait que vous ayez à faire cela est une indication qu'il y a quelque chose qui ne va pas dans la conception de votre système. Je suggère que soit vous ayez besoin d'une méthode d'usine (non statique) sur la classe englobante, soit vous devez déclarer la classe interne comme statique.

La création d'une instance de classe interne (non statique) de manière réfléchie a une "odeur" d'encapsulation cassée.

1
Stephen C

Ce n'est pas entièrement optimal, mais cela fonctionne pour les profondeurs des classes internes et des classes statiques internes.

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 );
    }
}
0
mrswadge