Подтвердить что ты не робот

Можно ли создать экземпляр вложенного класса с помощью 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(), он выдает ExceptionExceptionException.

4b9b3361

Ответ 1

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

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

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

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

Ответ 2

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

Ответ 3

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

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

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

Ответ 4

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

Ответ 5

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

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

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

Ответ 6

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

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

Ответ 7

Здесь ответ для вложенного класса (static inner): В моем случае мне нужно получить тип по его полному имени

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

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

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