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.
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
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é
Code rapide et sale:
Foo.Bar.class.getConstructors()[0].newInstance(new Foo());
Explication: Vous devez informer le Bar de son enfermement de Foo.
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
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.
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 );
}
}