Можно ли создать экземпляр вложенного класса с помощью Java Reflection? - PullRequest
34 голосов
/ 20 января 2010

Пример кода:

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 ]

54 голосов
/ 20 января 2010

Вам нужно прыгнуть через несколько обручей, чтобы сделать это.Во-первых, вам нужно использовать Class.getConstructor () , чтобы найти Constructor объект, который вы хотите вызвать:

Возвращает объект Constructor, который отражает указанный открытый конструкторкласс, представленный этим объектом класса.Параметр parameterTypes - это массив объектов Class, которые идентифицируют формальные типы параметров конструктора в объявленном порядке. Если этот объект Class представляет внутренний класс, объявленный в нестатическом контексте, формальные типы параметров включают в себя явный включающий экземпляр в качестве первого параметра.

И затем вы используете Constructor.newInstance () :

Если декларирующий класс конструктора является внутренним классом в нестатическом контексте, первым аргументом конструктора должен быть включающий экземпляр

25 голосов
/ 20 января 2010

Внутренние классы действительно не могут быть созданы без конструирования родительского класса.Он не может существовать вне родительского класса.Вам нужно будет передать экземпляр родительского класса при отражении.Вложенные классы 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[] {});
            }
        }
    }
}

Это должно дать

Nested constructed
Inner constructed
Nested constructed
Inner constructed
Nested constructed
7 голосов
/ 20 января 2010

Быстрый и грязный код:

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

Объяснение: Вы должны сообщить Бару о включенном в него Foo.

2 голосов
/ 20 января 2010

Да.Помните, что вам нужно передать внешний экземпляр внутреннему классу.Используйте 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
1 голос
/ 20 января 2010

В других ответах объясняется, как вы можете делать то, что хотите.

Но я хочу вам сказать, что тот факт, что вам нужно сделать это, является признаком того, что что-то немногонеправильно с вашей системой дизайна.Я бы посоветовал вам либо использовать (нестатический) фабричный метод для включающего класса, либо вам нужно объявить внутренний класс как статический.

Создание (нестатического) экземпляра внутреннего класса отражательно "пахнет" нарушенной инкапсуляцией.

0 голосов
/ 01 ноября 2013

Это не совсем оптимально, но работает для глубин внутренних классов и внутренних статических классов.

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 голосов
/ 14 мая 2013

Вот ответ для вложенного класса (статический внутренний): в моем случае мне нужно получить тип по его полностью определенному имени

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

' $ ' имеет решающее значение!

с точкой вы получите ClassNotFoundException для класса "package.innerClass.outerClass".Исключением является заблуждение: - (.

...