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


person kars7e    schedule 19.01.2010    source источник
comment
Nitpick: это не вложенный класс, это внутренний класс-член. Вложенные классы являются статическими, и их легко создать с помощью механизма, который вы только что попробовали.   -  person skaffman    schedule 20.01.2010
comment
Каковы подробности InstantiationException?   -  person Andrew Hare    schedule 20.01.2010
comment
Внутренние классы - это тип вложенного класса (если я правильно помню терминологию JLS).   -  person Tom Hawtin - tackline    schedule 20.01.2010
comment
@Tom: Черт возьми, ты прав... только что проверил JLS (java.sun.com/docs/books/jls/ Third_edition/html/).... Внутренний класс — это вложенный класс, который явно или неявно не объявлен статическим. Виноват.   -  person skaffman    schedule 20.01.2010


Ответы (7)


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

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

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

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

person skaffman    schedule 19.01.2010
comment
Это то, что мне было нужно. Спасибо за полное объяснение! - person kars7e; 20.01.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
person BalusC    schedule 19.01.2010
comment
Отличный и полный пример! - person Jorge; 02.05.2013

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

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

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

person meriton    schedule 19.01.2010
comment
Это ускользает от большей части проблемы, так как Bar может быть не статичным и/или может быть даже не виден... - person Snicolas; 23.09.2013
comment
Эм, мой ответ предполагает, что класс не статичен? И если бы класс был невидимым, OP получил бы IllegalAccessException, а не InstantiationException... - person meriton; 23.09.2013

да. Помните, что вам нужно передать внешний экземпляр внутреннему классу. Используйте 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
person Tom Hawtin - tackline    schedule 19.01.2010
comment
Я никогда раньше не слышал о javap. Спасибо, что показали мне этот хороший инструмент :). - person kars7e; 20.01.2010

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

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

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

person Stephen C    schedule 20.01.2010

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

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 );
    }
}
person mrswadge    schedule 01.11.2013

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

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

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

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

person dermoritz    schedule 14.05.2013