Можно ли создать экземпляр вложенного класса с помощью Java Reflection?
Пример кода:
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?
}
}
Можно ли создать новый экземпляр класса Bar с указанием его имени? Я пытался использовать:
Class c = Class.forName("Foo$Bar")
он находит класс, но когда я использую c.newInstance(), он генерирует исключение InstantiationException.
7 ответов
Вам нужно прыгнуть через несколько обручей, чтобы сделать это. Во-первых, вам нужно использовать Class.getConstructor(), чтобы найти Constructor
объект, который вы хотите вызвать:
Возвращает объект Constructor, который отражает указанный открытый конструктор класса, представленного этим объектом Class. Параметр parameterTypes - это массив объектов Class, которые идентифицируют формальные типы параметров конструктора в объявленном порядке. Если этот объект Class представляет внутренний класс, объявленный в нестатическом контексте, типы формальных параметров включают в себя явный включающий экземпляр в качестве первого параметра.
И тогда вы используете Constructor.newInstance ():
Если декларирующий класс конструктора является внутренним классом в нестатическом контексте, первым аргументом конструктора должен быть включающий экземпляр
Внутренние классы действительно не могут быть созданы без конструирования родительского класса. Он не может существовать вне родительского класса. Вам нужно будет передать экземпляр родительского класса при отражении. Вложенные классы static
и они могут использоваться независимо от родительского класса, таким образом, также при выполнении рефлексии.
Вот SSCCE, который демонстрирует все вещи.
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[] {});
}
}
}
}
Это должно произвести
Вложенный построенный Внутренний построен Вложенный построенный Внутренний построен Вложенный построенный
Быстрый и грязный код:
Foo.Bar.class.getConstructors()[0].newInstance(new Foo());
Пояснение: Вы должны сообщить Бару о включенном в него Foo.
Да. Помните, что вам нужно передать внешний экземпляр внутреннему классу. использование javap
найти конструктор. Вам нужно будет пройти java.lang.reflect.Constructor
а не полагаться на зло 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
интересно на конструкторе, потому что (при условии -target 1.4
или позже, теперь неявно) вы получаете назначение поля экземпляра перед вызовом супер-конструктора (раньше это было недопустимо).
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
Другие ответы объяснили, как вы можете делать то, что хотите.
Но я хочу вам сказать, что тот факт, что вам нужно сделать это, является признаком того, что в вашей системе что-то не так. Я бы посоветовал вам либо использовать (нестатический) фабричный метод для включающего класса, либо вам нужно объявить внутренний класс как статический.
Создание (не статичного) экземпляра внутреннего класса отражательно "пахнет" нарушенной инкапсуляцией.
Это не совсем оптимально, но работает для глубин внутренних классов и внутренних статических классов.
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 );
}
}
Вот ответ для вложенного класса (static inner): в моем случае мне нужно получить тип по его полному имени
Class.forName(somePackage.innerClass$outerClass).getConstructor().newInstance();
"$" имеет решающее значение!
с точкой вы получите ClassNotFoundException для класса "package.innerClass.outerClass". Исключение составляет заблуждение:-(.