使用Java反射实例化私有内部类

44

通过Java反射技术,能否从另一个类中实例化一个私有内部类。例如,如果我有以下代码:

public class Main {
    public static void main(String[] args) {}
}

class OtherClass {
    private class Test {}
}

在main方法中实例化并访问Test,这是否可能?


1
http://www.velocityreviews.com/forums/t150488-how-to-access-private-inner-class-with-reflection.html - tckmn
要创建一个嵌套类,你必须拥有一个外部类的实例。 - Peter Lawrey
类被声明为私有的,这意味着你不应该这样做。 - Raedwald
1
更准确地说,要创建一个Inner类(非静态嵌套类),您需要拥有外部类的实例。要创建一个Static Nested类的实例,您不需要外部类的实例。 - Ray
3个回答

46

是的,您可以使用Java反射实例化私有内部类。要做到这一点,您需要先获取外部类的实例,并调用内部类构造函数,该构造函数将在其第一个参数中使用外部类实例。

class OuterClass {
    private class InnerClass {
        {
            //this block is just to confirm that the inner object was created
            //it will be added to every constructor of this class
            System.out.println("inner object created");
        }
    }
}

当我们不知道私有内部类的名称并假设它具有无参构造函数时:

class Main {

    //no comment version
    public static Object giveMeInnerInstance() throws Exception{
        OuterClass outerObject = new OuterClass();
        Class<?> innerClass = OuterClass.class.getDeclaredClasses()[0];
        Constructor<?> constructor = innerClass.getDeclaredConstructors()[0];
        constructor.setAccessible(true);
        return constructor.newInstance(outerObject);
    }

    //commented version
    public static void main(String[] args) throws Exception {
        //we need an outer class object to use the inner object constructor
        //(the inner class object needs to know about its parent object)
        OuterClass outerObject = new OuterClass();

        //let's get the inner class 
        //(we know that the outer class has only one inner class, so we can use index 0)
        Class<?> innerClass = OuterClass.class.getDeclaredClasses()[0];
        //or if we know name of inner class we can use 
        //Class<?> innerClass = Class.forName("full.package.name.OuterClass$InnerClass")

        //since constructor so we could use it to pass instance of outer class and change 
        //its accessibility. We can use this code to get default constructor of InnerClass 
        //since we know that this is the only constructor here
        Constructor<?> constructor = innerClass.getDeclaredConstructors()[0];
        //we could also use 
        //Constructor<?> constructor = innerClass.getDeclaredConstructor(OuterClass.class);

        //the default constructor of the private class has same visibility that class has
        //so it is also private, so to be able to use it we need to make it accessible
        constructor.setAccessible(true);

        //now we are ready to create inner class instance
        Object innerObject = constructor.newInstance(outerObject);
    }
}

如果我们有以下信息:

  • 内部类名称
  • 构造函数参数

我们现在可以使这段代码更加清晰明了。

因此,我们可以通过名称获取所选的内部类,而不是检查内部类列表并选择第一个。

Class<?> inner = Class.forName("our.pack.age.OuterClass$InnerClass")
//                                                     ^^^^^^^^^^^

我们同样可以通过调用getDeclaredConstructor(outerType,rest,of,parameter,types)来选择我们想要使用的构造函数,因此如果我们的内部类看起来像这样

class OuterClass {
    private class InnerClass {

        private int x;

        public InnerClass(int x) {
            this.x = x;
            System.out.println("inner object created");
        }

    }
}

我们的代码可能需要改进

class ReflectionDemo {

    //no comment version
    public static Object giveMeInnerInstance() throws Exception{
        OuterClass outerObject = new OuterClass();
        Class<?> innerClass = Class.forName("com.stackoverflow.q14112166.OuterClass$InnerClass");
        Constructor<?> constructor = innerClass.getDeclaredConstructor(OuterClass.class, int.class);
        constructor.setAccessible(true);
        return constructor.newInstance(outerObject,42);
    }

    public static Object getFieldValue(Object obj, String fieldName) throws Exception{
        Class<?> clazz = obj.getClass();
        Field field = clazz.getDeclaredField(fieldName);
        field.setAccessible(true);
        return field.get(obj);
    }

    //lets test our code
    public static void main(String[] args) throws Exception {
        Object innerClassObject = giveMeInnerInstance();
        System.out.println(getFieldValue(innerClassObject, "x"));           
    }
}

输出:

inner object created
42

1
我正在尝试编写一个方法,该方法将返回一个类,其参数为完全限定名称,并且该方法返回的类可以是任何类型的类(内部类、嵌套类),但不包括匿名内部类。 - Markovian8261
@Pshemo我们可以使用相同的方式来创建私有嵌套静态方法的实例吗? - Kasun Siyambalapitiya
1
@KasunSiyambalapitiya,我不确定你所说的“创建私有嵌套静态方法的实例”是什么意思。实例是类的表示形式,我们可以在其上调用方法,但方法没有它们自己的表示形式(实例)。如果您想问我们是否可以实例化私有静态类,则是的,请记住静态内部类类似于外部类,因此它们不需要创建外部实例,因此您可以在构造函数参数中跳过它们。 - Pshemo
@Pshemo,如果代码中未定义私有嵌套类的构造函数,会怎么样? - Kasun Siyambalapitiya
@KasunSiyambalapitiya 然后编译器会添加默认构造函数(因为类必须至少有一个构造函数)。默认构造函数不带参数(除非它是内部类,那么它将带有外部类实例),并且具有与类相同的可见性,因此对于私有类,它将是私有构造函数,这意味着如果您想使用它,则需要调用 setAccessible(true) - Pshemo
显示剩余15条评论

18

使用反射时,您会发现内部类的构造函数需要一个外部类实例作为附加参数(始终是第一个)。

有关相关信息,请参阅以下问题:

示例:

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;

public class OuterClass {

    private class InnerClass {

    }

    public OuterClass() {
        super();
    }

    public static void main(String[] args) {
        // instantiate outer class
        OuterClass outer = new OuterClass();

        // List all available constructors.
        // We must use the method getDeclaredConstructors() instead
        // of getConstructors() to get also private constructors.
        for (Constructor<?> ctor : OuterClass.InnerClass.class
                .getDeclaredConstructors()) {
            System.out.println(ctor);
        }

        try {
            // Try to get the constructor with the expected signature.
            Constructor<InnerClass> ctor = OuterClass.InnerClass.class
                    .getDeclaredConstructor(OuterClass.class);
            // This forces the security manager to allow a call
            ctor.setAccessible(true);

            // the call
            try {
                OuterClass.InnerClass inner = ctor.newInstance(outer);
                System.out.println(inner);
            } catch (InstantiationException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            } catch (IllegalArgumentException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            } catch (InvocationTargetException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        } catch (NoSuchMethodException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (SecurityException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

}

6
OuterClass.InnerClass.class 只能在 OuterClass 内部使用。由于 InnerClass 是私有的,因此我们无法在外部使用 InnerClass.class 部分,而且很可能我们的代码不会成为 OuterClass 的一部分(如果是的话,就不需要反射)。 - Pshemo
@Pshemo outer.new InnerClass(); 在这里确实可以做到。当从外部使用反射访问内部类时,可能希望它实现一个可访问的接口,以便通过反射调用各个方法时避免痛苦。然而,如果这一切都是在自己的代码中完成的(而不是为了解决第三方库等问题),那么这可能是一个设计缺陷 - 因此,根本不可取。与这个例子类似,如果外部类不是final的话,我们也可以从同一个包或者外部类的子类中“窃取”一个protected成员。 - Sam

1
你可以做以下的事情:
public static void main(String[] args) throws Exception {
    // to get first class in OtherClass
    Class<?> innerClass = OtherClass.class.getDeclaredClasses()[0];
    // getDeclaredConstructors for private constructor
    Constructor<?> constructor = innerClass.getDeclaredConstructors()[0];
    // to enable accessing private constructor
    constructor.setAccessible(true);
    OtherClass outerObject = new OtherClass();
    //// create instance of Test by reflection
    Object o = constructor.newInstance(outerObject);
    System.out.println(o);
}

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