Java反射是否可以创建内部类的实例?

34

代码示例:

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异常。


5
抠细节:那不是一个嵌套类,而是成员内部类。嵌套类是静态的,并且可以通过你刚尝试的机制轻松实例化。 - skaffman
InstantiationException 的详细信息是什么? - Andrew Hare
4
内部类是嵌套类的一种(如果我记得JLS术语正确的话)。 - Tom Hawtin - tackline
2
@Tom:天啊,你说得对...我刚刚查了一下JLS(http://java.sun.com/docs/books/jls/third_edition/html/classes.html#8.1.3)....“内部类是一个嵌套类,它没有明确或隐式地声明为静态”。我的错。 - skaffman
7个回答

54

要做到这一点,你需要跨越一些麻烦。首先,你需要使用 Class.getConstructor() 查找你想要调用的 Constructor 对象:

返回一个 Constructor 对象,该对象反映了由此 Class 对象表示的类的指定公共构造函数。parameterTypes 参数是一个 Class 对象数组,其中按声明顺序标识构造函数的形式参数类型。如果此 Class 对象表示在非静态上下文中声明的内部类,则形式参数类型包括显式封闭实例作为第一个参数。

然后你需要使用 Constructor.newInstance()

如果构造函数所在的声明类是非静态上下文中的内部类,则构造函数的第一个参数需要是封闭实例。


这正是我所需要的。感谢您提供完整的解释! - kars7e

25

内部类确实不能在未先构建父类的情况下被构建。它无法存在于父类之外。您需要在反射时传递父类的实例。嵌套类是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[] {});
            }
        }
    }
}

这应该会产生以下结果:

嵌套构造
内部构造
嵌套构造
内部构造
嵌套构造

2
优秀而完整的示例! - Jorge

7

快速而简单的代码:

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

说明:您必须告知Bar关于其封闭的Foo。

这使得大多数问题难以解决,因为Bar可能不是静态的,或者甚至可能不可见... - Snicolas
2
嗯,我的答案假设该类不是静态的?如果该类是不可见的,OP 将会得到一个 IllegalAccessException 而不是一个 InstantiationException... - meriton

2

是的。记住你需要将外部实例提供给内部类。使用 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 或更高版本,现在已经默认)在调用 super 构造函数之前会给实例字段赋值(以前是不合法的)。

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

我以前从未听说过javap。谢谢你向我展示这个好工具 :)。 - kars7e

1

其他答案已经解释了如何做你想做的事情。

但我想建议你,你需要这样做的事实表明你的系统设计有些问题。我建议你要么在封闭类上声明一个(非静态)工厂方法,要么将内部类声明为静态。

通过反射创建(非静态)内部类实例会有破坏封装性的“味道”。


0

这并不完全是最优的解决方案,但它适用于内部类和内部静态类的深度。

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

-1

关于嵌套类(静态内部类)的答案如下: 在我的情况下,我需要通过其完全限定名称获取类型

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

$”非常重要!

如果使用点号,你会得到“ClassNotFoundException for class "package.innerClass.outerClass"”异常。这个异常信息是误导人的 :-(。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接