V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
amiwrong123
V2EX  ›  程序员

Java 的内部类反射,使用 newInstance 总是抛 NoSuchMethodException 异常呢?

  •  
  •   amiwrong123 · 2019-07-22 17:59:57 +08:00 · 4750 次点击
    这是一个创建于 1951 天前的主题,其中的信息可能已经有所发展或是发生改变。
    package com.prac;
    
    public class outer {
        int i = 1;
        private static class staticInner{
            int j = 2;
            staticInner(){}
            void print(){
                System.out.println("in staticInner");
            }
        }
        private class normalInner{
            int k =3;
            void print(){
                System.out.println("in normalInner");
            }
        }
    }
    

    外部类,里面有一个静态内部类,非静态内部类,它们都是私有的。

    import java.lang.reflect.Constructor;
    import java.lang.reflect.InvocationTargetException;
    import com.prac.outer;
    public class test2 {
        public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
            outer ooo = new outer();
            Class<?> clazz = null;
            try {
                clazz = Class.forName("com.prac.outer$normalInner");
                System.out.println(clazz.getName());
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            }
            Constructor<?> con = clazz.getDeclaredConstructor();
            Object obj = con.newInstance();
        }
    }
    

    有一个测试类,我想试试用反射来创建私有的内部类实例,因为内部类是权限是私有的,所以获得 Class 引用只能通过 forName (不然就可以 import 这个内部类,然后通过类名.class来获得了)。

    当 31 行,为clazz = Class.forName("com.prac.outer$normalInner")即获得非静态内部类 Class 的引用,然后 newInstance 报错:

    com.prac.outer$normalInner
    Exception in thread "main" java.lang.NoSuchMethodException: com.prac.outer$normalInner.<init>()
    	at java.base/java.lang.Class.getConstructor0(Class.java:3354)
    	at java.base/java.lang.Class.getDeclaredConstructor(Class.java:2558)
    	at test2.main(test2.java:38)
    

    看起来好像是这篇博客讲的原因: https://www.jianshu.com/p/ecda088dcc5f 即非静态内部类构造时,构造函数会隐式地加一个 this 即外部类的引用。但这种情况还有解决办法吗?

    当 31 行为clazz = Class.forName("com.prac.outer$staticInner")即获得静态内部类 Class 的引用,然后 newInstance 报错:

    com.prac.outer$staticInner
    Exception in thread "main" java.lang.IllegalAccessException: class test2 cannot access a member of class com.prac.outer$staticInner with modifiers ""
    	at java.base/jdk.internal.reflect.Reflection.newIllegalAccessException(Reflection.java:376)
    	at java.base/java.lang.reflect.AccessibleObject.checkAccess(AccessibleObject.java:639)
    	at java.base/java.lang.reflect.Constructor.newInstanceWithCaller(Constructor.java:490)
    	at java.base/java.lang.reflect.Constructor.newInstance(Constructor.java:481)
    	at test2.main(test2.java:39)
    

    为什么这个就直接报错,有标识符的成员类就无法访问,因为是静态内部类吗?这种情况还有解决办法吗?

    8 条回复    2019-07-23 14:51:25 +08:00
    shily
        1
    shily  
       2019-07-22 18:18:58 +08:00
    测试一下就知道了啊,错误报在 getDeclaredConstructor(),这个是返回无参数的构造上。你已经通过博客知道,默认的非静态内部类会捕获外部类引用。

    通过 clazz.getDeclaredConstructors() 可以知道它有一个构造,<init>(com.prac.outer),所以很容易修正啊。
    通过 Constructor<?> con = clazz.getDeclaredConstructor(outer.class); 来获取构造方法
    调用时是 Object obj = con.newInstance(ooo);
    amiwrong123
        2
    amiwrong123  
    OP
       2019-07-22 20:43:05 +08:00
    @shily
    好吧,原来是我 getDeclaredConstructor 用的不对,谢谢回答啦。
    但是,现在就报错:
    com.prac.outer$normalInner
    Exception in thread "main" java.lang.IllegalAccessException: class test2 cannot access a member of class com.prac.outer$normalInner with modifiers "private"
    at java.base/jdk.internal.reflect.Reflection.newIllegalAccessException(Reflection.java:376)
    at java.base/java.lang.reflect.AccessibleObject.checkAccess(AccessibleObject.java:639)
    at java.base/java.lang.reflect.Constructor.newInstanceWithCaller(Constructor.java:490)
    at java.base/java.lang.reflect.Constructor.newInstance(Constructor.java:481)
    at test2.main(test2.java:39)
    所以不管是静态内部类还是非静态内部类,只要访问权限是私有,就不可以创建实例了吗
    bringyou
        3
    bringyou  
       2019-07-22 21:25:57 +08:00
    contructor.setAccessible(true)
    codeyung
        4
    codeyung  
       2019-07-22 23:41:04 +08:00
    setAccessible
    amiwrong123
        5
    amiwrong123  
    OP
       2019-07-23 09:54:25 +08:00
    @bringyou
    好吧,现在成功了。谢谢回答啦。
    Constructor<?> con = clazz.getDeclaredConstructor(outer.class);
    con.setAccessible(true);
    Object obj = con.newInstance(ooo);
    但这种情况下,我是不是只能用 Object 引用来指向这个实例,因为没法 import 私有内部类,所以也无法用私有内部类类型来声明变量了?
    palmers
        6
    palmers  
       2019-07-23 10:20:53 +08:00
    这种情况出现的原因,我认为有两方面:
    1. 内部类实例化无参构造是有一个影藏参数就是外部类指针 this, 所以其实不是相比正常的无参构造它并不是无参构造; 非 static 需要先实例化外部类,所以需要这样:
    ```java
    outer ot = new outer();
    Class<?> clazz = Class.forName("com.prac.outer$normalInner");
    Constructor<?> constructor = clazz.getDeclaredConstructor(outer.class);
    Object o = constructor.newInstance(ot);
    ```
    2. 对于私有类或方法外部访问都是拒绝的, 需要解决访问权限问题;

    以上问题解决了, 我想基本不会有问题了,希望能帮到你
    helloSpringBoot
        7
    helloSpringBoot  
       2019-07-23 10:27:35 +08:00
    @amiwrong123 私有的目的不就是这样吗。。。。
    amiwrong123
        8
    amiwrong123  
    OP
       2019-07-23 14:51:25 +08:00
    @palmers
    谢谢回答,现在都解决了。

    @helloSpringBoot
    虽然只能用 Object 引用来指向这个实例,但是有了这个实例我发现就可以做一些“坏事”了,比如获得私有成员。
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   971 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 23ms · UTC 19:52 · PVG 03:52 · LAX 11:52 · JFK 14:52
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.