Java 反射相关知识

前言

我们都知道一个普通的类,有一个唯一的全限定类名用以标识这个类,还可以有代码块、构造方法、成员变量和成员方法,其中代码块、成员变量和成员方法又可以被关键字static 修饰,同时一个类还可以继承自一个父类,以及多个接口,甚至还有内部类和注解。不仅如此,一个类中可以添加注解的地方还有很多,比如构造方法、成员变量、成员方法、成员方法中的参数,甚至连局部变量都有可能被添加上注解。你以为这就完了?还有内部类没有考虑呢,内部类又可以分布在多个地方,比如声明在一个类内部,甚至声明在一个成员方法内!简直不要太复杂,妈妈听了都直言太难了。

何为反射

讲了那么多,反射是什么啊?为什么要有反射啊?知乎上有人说反射是一种能够在程序运行时动态访问、修改某个类中任意属性(状态)和方法(行为)的机制(包括private实例和方法);廖雪峰老师说反射就是 Reflection ,Java的反射是指程序在运行期可以拿到一个对象的所有信息,是为了解决在运行期,对某个实例一无所知的情况下,如何调用其方法;接下来该我了? ... 反射是在程序运行时,能够通过全限定类名动态获取指定类的类型信息,进而可以实例化它、执行它的任意方法、直接修改它的任意属性以及获取其他与此类相关的信息,诸如它的父类、它继承的所有接口、它的任意位置的注解信息以及其所声明的内部类等等上面所提及到的与类相关的信息。

那这有什么用啊?用处可大了,能看到这篇文章的人,总是会接触到 Spring 的。我们知道在没有使用任何框架的情况下,要想实例化一个对象,最常用的就是在你的源码里面使用 new 关键字,但这样就必须知道自己实例化的对象属于哪个类,而且只能是在编辑源码的时候作出指示,简单点来说就是只能在程序运行前在编辑源代码时在合适的位置使用 new 关键字将其实例化,这个要求什么呢,那就是源码能够修改。

Spring 作为一种几乎可以应用到所有 Java 应用的框架,它难道会提前预知任何开发者要写的业务类吗?连是什么类都不知道,如何使用 new 关键字将其实例化出来呢?何况 Spring 作为一个已成型的框架,其所有程序文件均已是字节码文件,我们并不能直接修改它的源码 。但是用过 Spring 的小伙伴们都知道,Spring 可以接管几乎所有类的实例化,你只需要指定一个全限定类型名,它马上就可以返回一个对应实例对象给你,那既然源码无法修改,也即意味着无法在源代码中手动 new 出相应的实例对象,那么问题来了,Spring 如何在不修改源代码和不知道具体的业务类时,是如何实例化出你所给定的任意类的实例对象的呢?聪明人都看到了,通过反射可以在程序运行时,通过全限定类名动态获取指定类的类型信息,进而可以实例化它,亦即获得它的实例对象。、

如何反射

获取类型信息

反射第一步,都是获取指定类的类型信息,也就是获取某个类的 Class 对象,主要有四种方法,直接示例:

package cn;

public class TestClass {

    public static void main(String[] args) throws Exception {
        
        // 通过全限定类型名获取
        Class<?> classInfo_1 = Class.forName("cn.TestClass");

        // 直接获取
        Class<?> classInfo_2 = TestClass.class;

        // 先实例化一个对象,在利用 Object 类里的 getClass 方法获取
        TestClass testClass = new TestClass();
        Class<?> classInfo_3 = testClass.getClass();

        // 通过加载任意上下文类的类加载器获取类信息
        class Tmp {
            // 演示用途,模拟任意上下文类
        }
        ClassLoader classLoader = Tmp.class.getClassLoader();
        Class<?> classInfo_4 = classLoader.loadClass("cn.TestClass");
    }
}

只要有了 Class 对象,我们就可以对这个类为所欲为了,这词怪怪的... 接下来将分别开始分类介绍类的构造器、成员变量、成员方法、内部类和注解信息的获取。

获取构造器

想想构造器有什么用呢?构造器对应的就是一个类的构造方法,什么意思呢?意思就是我可以利用构造器实例化一个对象!来呀,直接看实例,分情况讨论,在正式开始之前,先来认识 Class 对象里的几个有关构造器的方法:

public Constructor<?>[] getConstructors();
public Constructor<T> getConstructor(Class<?>... parameterTypes);

public Constructor<?>[] getDeclaredConstructors();
public Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes);

记住这个 Declared 的字面意思,因为后面常出现,它的意思的(本类)已声明的(不管是被什么修饰符所修饰),来咯,上菜!

package cn;

import java.lang.reflect.Constructor;

public class TestClass {

    private int a;

    private int b;

    public TestClass() {
    }

    public TestClass(int a) {
        this.a = a;
    }

    private TestClass(int a, int b) {
        this.a = a;
        this.b = b;
    }

    @Override
    public String toString() {
        return "a = " + a + "; b = " + b;
    }

    public static void main(String[] args) throws Exception {

        // 通过全限定类型名获取类信息
        Class<?> classInfo_1 = Class.forName("cn.TestClass");

        // 获取类的所有公开的构造方法
        for (Constructor<?> constructor : classInfo_1.getConstructors()) {
            System.out.println(constructor);
        }
        System.out.println();

        // 获取指定参数类型的公开构造方法
        System.out.println(classInfo_1.getConstructor(int.class));

        try {
            // 试图使用 getConstructor 方法获取私有的构造方法:
            System.out.println(classInfo_1.getConstructor(int.class, int.class));
        } catch (NoSuchMethodException e) {
            System.out.println("试图使用 getConstructor 方法获取私有的构造方法失败:" + e.getMessage());
        } catch (SecurityException e) {
            System.out.println("试图使用 getConstructor 方法获取私有的构造方法失败:" + e.getMessage());
        }

        // 获取类的所有构造方法,不管是公开的还是私有的
        for (Constructor<?> constructor : classInfo_1.getDeclaredConstructors()) {
            System.out.println(constructor);
        }
        System.out.println();

        // 获取指定参数类型的任意构造方法
        System.out.println(classInfo_1.getDeclaredConstructor());
        System.out.println(classInfo_1.getDeclaredConstructor(int.class));

        // 试图使用 getDeclaredConstructor 方法获取私有的构造方法
        System.out.println(classInfo_1.getDeclaredConstructor(int.class, int.class));
    }
}

运行结果:
public cn.TestClass(int)
public cn.TestClass()

public cn.TestClass(int)
试图使用 getConstructor 方法获取私有的构造方法失败:cn.TestClass.<init>(int, int)
private cn.TestClass(int,int)
public cn.TestClass(int)
public cn.TestClass()

public cn.TestClass()
public cn.TestClass(int)
private cn.TestClass(int,int)

总结一下:

// 获取指定类的所有 public 修饰(即公开)的构造方法
public Constructor<?>[] getConstructors();

// 获取指定类的指定 public 修饰(即公开)的构造方法,以参数辨别
public Constructor<T> getConstructor(Class<?>... parameterTypes);

// 获取指定类所声明的所有任意修饰符修饰(即无论 public / private / protected / default)的构造方法
public Constructor<?>[] getDeclaredConstructors();

// 获取指定类所声明的指定任意修饰符修饰(即无论 public / private / protected / default)的构造方法,以参数辨别
public Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes);

实例化对象

获取了构造器对象后,我们就可以进行对象的实例化,示例,来个有难度的,以私有构造方法实例化一个对象(在上述代码不变的情况下只修改 main 函数如下所示):

    public static void main(String[] args) throws Exception {

        // 通过全限定类型名获取类信息
        Class<?> classInfo_1 = Class.forName("cn.TestClass");

        // 获取该类的私有构造器
        Constructor<?> constructor = classInfo_1.getDeclaredConstructor(int.class, int.class);

        // 开始实例化
        // 由于其修饰符并不是公开的,所以我们在实例化之前,必须先设置其可访问,否则实例化失败
        constructor.setAccessible(true);
        // 传入相应的参数开始实例化
        Object obj = constructor.newInstance(1, 2);
        // 打印辨别
        System.out.println(obj.toString());
        System.out.println(obj instanceof TestClass);
    }

    运行结果:
    a = 1; b = 2
    true

除此之外,如果某个类有公开的无参构造函数,则有一个更简洁的实例化方法,如下所示:

    public static void main(String[] args) throws Exception {

        // 通过全限定类型名获取类信息
        Class<?> classInfo_1 = Class.forName("cn.TestClass");

        // 直接通过类对象实例化,前提是该类有公开的无参构造函数
        Object obj = classInfo_1.newInstance();

        // 打印辨别
        System.out.println(obj.toString());
        System.out.println(obj instanceof TestClass);
    }

    运行结果:
    a = 1; b = 2
    true

执行指定方法

首先,通过 Class 对象获取对应的方法实例对象(我们能简称其为方法器吧),同样的,获取方法器有几个类似获取构造器的方法:

public Method[] getMethods();
public Method getMethod(String name, Class<?>... parameterTypes);

public Method[] getDeclaredMethods();
public Method getDeclaredMethod(String name, Class<?>... parameterTypes);

为了更全面地演示,我们给上面的 TestClass 增加了父类,并添加了各种修饰符修饰的方法,我们实践出真知:

    package cn;

    import java.lang.reflect.Method;

    class Super {
        public void super_public(){
            System.out.println("super_public");
        }

        private void super_private(){
            System.out.println("super_private");
        }

        protected void super_protected(){
            System.out.println("super_protected");
        }

        void super_default(){
            System.out.println("super_default");
        }
    }

    public class TestClass extends Super {

        private int a;

        private int b;

        public TestClass() {
        }

        public TestClass(int a) {
            this.a = a;
        }

        private TestClass(int a, int b) {
            this.a = a;
            this.b = b;
        }

        @Override
        public String toString() {
            return "a = " + a + "; b = " + b;
        }

        public void a_public(){
            System.out.println("a_public");
        }

        private void a_private(){
            System.out.println("a_private");
        }

        protected void a_protected(int i){
            System.out.println("a_protected");
        }

        void a_default(){
            System.out.println("a_default");
        }

        public static void main(String[] args) throws Exception {

            // 通过全限定类型名获取类信息
            Class<?> classInfo_1 = Class.forName("cn.TestClass");

            // 直接通过类对象实例化,前提是该类有公开的无参构造函数
            Object obj = classInfo_1.newInstance();

            // 获取所有公开方法,包括父类的
            for (Method method : classInfo_1.getMethods()) {
                System.out.println(method);
            }
            System.out.println();

            // 获取本类中所声明的所有方法,无论是什么修饰符修饰的
            for (Method method : classInfo_1.getDeclaredMethods()) {
                System.out.println(method);
            }
            System.out.println();

            // 获取指定方法
            System.out.println(classInfo_1.getMethod("super_public"));
            System.out.println(classInfo_1.getDeclaredMethod("a_protected", int.class));
        }
    }


    运行结果:
    public static void cn.TestClass.main(java.lang.String[]) throws java.lang.Exception
    public java.lang.String cn.TestClass.toString()
    public void cn.TestClass.a_public()
    public void cn.TestClass.super_public()
    public final void java.lang.Object.wait() throws java.lang.InterruptedException
    public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException
    public final native void java.lang.Object.wait(long) throws java.lang.InterruptedException
    public boolean java.lang.Object.equals(java.lang.Object)
    public native int java.lang.Object.hashCode()
    public final native java.lang.Class java.lang.Object.getClass()
    public final native void java.lang.Object.notify()
    public final native void java.lang.Object.notifyAll()

    public static void cn.TestClass.main(java.lang.String[]) throws java.lang.Exception
    public java.lang.String cn.TestClass.toString()
    public void cn.TestClass.a_public()
    void cn.TestClass.a_default()
    protected void cn.TestClass.a_protected(int)
    private void cn.TestClass.a_private()
    public void cn.TestClass.super_public()

    public void cn.TestClass.super_public()
    protected void cn.TestClass.a_protected(int)

太多了,估计也看不下去,直接解释一波,这里获取方法器的方法作用可以说是和获取构造器一摸一样,总结如下:

// 获取所有公开方法,包括父类的
public Method[] getMethods();

// 获取指定的公开方法
public Method getMethod(String name, Class<?>... parameterTypes);

// 获取本类中所声明的所有方法,无论是什么修饰符修饰的
public Method[] getDeclaredMethods();

// 获取本类中所声明的任意指定方法
public Method getDeclaredMethod(String name, Class<?>... parameterTypes);

同理,成功获取到方法器后就可以执行方法了,同样,我们执行一个被 private 修饰符修饰的私有方法,示例如下:

public static void main(String[] args) throws Exception {

    // 通过全限定类型名获取类信息
    Class<?> classInfo_1 = Class.forName("cn.TestClass");

    // 直接通过类对象实例化,前提是该类有公开的无参构造函数
    Object obj = classInfo_1.newInstance();

    // 获取私有方法的方法器,第一个参数是方法名,第二个可变参数是方法参数类型,有多少写多少,没有不写
    Method method = classInfo_1.getDeclaredMethod("a_private");

    // 开始执行方法
    // 同样要设置任何非公开方法为可访问,否则出错
    method.setAccessible(true);
    // 效果等同于 obj.a_private() ,第一个参数是该类的一个实例对象,第二个可变参数是方法实参
    method.invoke(obj);
}

运行结果:a_private

操作成员变量

讲完方法执行,就到了直接修改成员变量值的环节了。和实例化对象和执行方法的步骤极为类似,首先,先了解 Class 类提供了哪些方法给我们去操纵任意成员变量:

public Field [] getFields();
public Field getField(String name);

public Field [] getDeclaredFields();
public Field getDeclaredField(String name);

同理,为了测试,我们将原来的代码略作修改,添加了各种修饰符所修饰的成员变量,直接看代码!

package cn;

import java.lang.reflect.Field;

class Super {

    public int super_var_public;

    private int super_var_private;

    protected int super_var_protect;

    int super_var_default;

    public void super_public(){
        System.out.println("super_public");
    }

    private void super_private(){
        System.out.println("super_private");
    }

    protected void super_protected(){
        System.out.println("super_protected");
    }

    void super_default(){
        System.out.println("super_default");
    }
}

public class TestClass extends Super {

    private int a;

    private int b;

    public int var_public;

    private int var_private;

    protected int var_protect;

    int var_default;

    public TestClass() {
    }

    public TestClass(int a) {
        this.a = a;
    }

    private TestClass(int a, int b) {
        this.a = a;
        this.b = b;
    }

    @Override
    public String toString() {
        return "a = " + a + "; b = " + b;
    }

    public void a_public(){
        System.out.println("a_public");
    }

    private void a_private(){
        System.out.println("a_private");
    }

    protected void a_protected(int i){
        System.out.println("a_protected");
    }

    void a_default(){
        System.out.println("a_default");
    }

    public static void main(String[] args) throws Exception {

        // 通过全限定类型名获取类信息
        Class<?> classInfo_1 = Class.forName("cn.TestClass");

        // 直接通过类对象实例化,前提是该类有公开的无参构造函数
        Object obj = classInfo_1.newInstance();

        // 获取所有公开成员变量,包括父类的
        for (Field field : classInfo_1.getFields()) {
            System.out.println(field);
        }
        System.out.println();

        // 获取本类中所声明的所有成员变量,无论是什么修饰符修饰的
        for (Field field : classInfo_1.getDeclaredFields()) {
            System.out.println(field);
        }
        System.out.println();

        // 获取指定成员变量
        System.out.println(classInfo_1.getField("super_var_public"));
        System.out.println(classInfo_1.getDeclaredField("var_private"));
    }
}

运行结果:
public int cn.TestClass.var_public
public int cn.Super.super_var_public

private int cn.TestClass.a
private int cn.TestClass.b
public int cn.TestClass.var_public
private int cn.TestClass.var_private
protected int cn.TestClass.var_protect
int cn.TestClass.var_default

public int cn.Super.super_var_public
private int cn.TestClass.var_private

相信大尬都已经熟悉了它的套路了,不过按例还是总结一波吧:

// 获取所有公开的成员变量,包括父类的
public Field [] getFields();

// 获取指定名称的公开成员变量
public Field getField(String name);

// 获取本类中声明的所有成员变量,不包括父类的也无论修饰符
public Field [] getDeclaredFields();

// 获取本类中声明的指定名称的成员变量
public Field getDeclaredField(String name);

老规矩,接下来演示直接修改私有成员变量 var_private 的值:

public static void main(String[] args) throws Exception {

    // 通过全限定类型名获取类信息
    Class<?> classInfo_1 = Class.forName("cn.TestClass");

    // 直接通过类对象实例化,前提是该类有公开的无参构造函数
    Object obj = classInfo_1.newInstance();

    // 为了形成对比,先看看这个 obj 的 var_private 变量的值
    System.out.println(obj);

    // 开始修改
    // 第一步,先获取指定的成员变量的领域器(我们随便叫一下,看它敢不敢答应)
    Field var_private = classInfo_1.getDeclaredField("var_private");

    // 第二步,如果指定的成员变量不是公有的,必须设置其可访问,否则出错
    var_private.setAccessible(true);

    // 第三步,设置值,效果等同于 obj.setVar_Private(100);
    var_private.set(obj, 100);

    // 第四步,测试 var_private 是否已被修改
    System.out.println(obj);

    // var_private.get(obj) 等同于 obj.getVar_Private()
    Object o = var_private.get(obj);
    System.out.println(o);
}

运行结果:
TestClass{a=0, b=0, var_public=0, var_private=0, var_protect=0, var_default=0}
TestClass{a=0, b=0, var_public=0, var_private=100, var_protect=0, var_default=0}
100

内部类

概念

内部类是什么?

class A {
    class B {
       ... 
    }
    ...
}

这就是内部类的一种形式,既然有内部类,一定便会有外部类的存在,那外部类又是什么?外部类是相对内部类而存在的,以上述代码为例,B 就是一个内部类,而 A 就是相对 B 而言的一个外部类,对于没有内部类的普通类来说,它不算是外部类。

严格来讲,主要分为包装类(Enclosing class)或者外部类和嵌套类(Nested class),嵌套类包括静态(static nested class)和非静态(non-static nested class)两种,外部类相对于嵌套类而存在。而我们常说的内部类(Inner class)实际上是非静态嵌套类(non-static nested class)。

记住这个,后面会用到:

成员嵌套类:成员嵌套类 作为 enclosing class 的成员定义的,成员嵌套类有 enclosing class 属性
局部嵌套类:局部嵌套类定义在 enclosing class 的方法里面,局部嵌套类有 enclosing class 属性和 enclosing method 属性

分类

内部类分为几类:

  • 成员内部类(menber inner class) :形如

        class A {
            class B {
               ... 
            }
            ...
        }
    

的,即是作为外部类 A 的成员的类 B 就是成员内部类。

  • 静态内部类(static inner class),又叫静态嵌套类(static nested class),:是增加了 static 修饰符的成员内部类。

  • 局部内部类(local inner class):是声明在方法内部的类,形如

        class A {
            public void a_method {
               class B {
                  ...
               }
               ... 
            }
            ...
        }
    

其中的类 B 就是一个局部内部类,类 A 则为相对于类 B 的外部类。

  • 匿名内部类(anonymous inner class) :没有名字的局部内部类,不使用关键字 class, extends, implements, 也没有构造方法。匿名内部类隐式地继承了一个类或者实现了一个接口,形如

        class A {
            public void a_method {
                Date date = new Date() {
                    ...
                };
    
                Runnable runnable = new Runnable() {
                    public void run() {
    
                    }
                };
               ... 
            }
            ...
        }
    

上面的两种形式都属于匿名内部类,匿名内部类常作为方法参数传入;

性质

  • 成员内部类: 依赖于外部类实例而存在,即成员内部类持有外部类实例的引用,因此它可以直接访问外部类的任何成员,但是外部类成员不能直接访问成员内部类的成员(只有静态内部类才能使用 static 修饰符,所以成员内部类没有静态成员),但是也是可以访问的,首先实例化它,再访问,示例:

    class AA {
        private int i;
    
        class B {
    
            private int j;
    
            public void mb(){
                System.out.println(i);
            }
        }
    
        public void ma(){
            // 要访问 B.j,因为它是内部类的实例成员,所以必须实例化后再访问
            // 第一步,实例化外部类
            AA aa = new AA();
    
            // 第二步,通过外部类实例 实例化内部类
            B b = aa.new B();
            System.out.println(b.j);
        }
    }
    
  • 静态内部类: 不依赖于外部类实例存在,和外部类属于同一个级别,因此静态内部类不能直接访问外部类的非静态成员,因此它的实例化方法也更为简单:

    class AA {
    private static int k;

      static class C {
          private static int cj;
          
          private int ck;
          
          public int mc(){
              // 可以直接访问外部类的静态成员
              return k;
          }
      }
    
      public void ma(){
          // 实例化静态内部类 C 
          C c = new AA.C();
          
          // 外部类直接访问静态内部类的静态成员
          System.out.println(C.cj);
          
          // 外部类必须先实例化静态内部类 C 后才可以访问其非静态成员
          System.out.println(c.mc());
      }
    

    }

  • 局部内部类:不允许使用 static 修饰,可以直接访问外部类的成员(取决于隐式所继承的类),也可以直接访问局部变量,外部类一般无法访问其成员,如果需要访问的话,它便不应该是一个局部内部类。这里要注意如果是直接访问局部变量,这个局部变量需要被 final 所修饰,也就是说它的值不允许再变化,我们知道局部变量一般是放在线程私有的虚拟机栈中,其生命周期随着方法调用的结束而结束,但是局部内部类作为一个类,其生命周期与外部类是一致的,也就是说局部变量已经没了,但是局部内部类还能访问它,这显然不合逻辑,所以java这样解决,使用 final 修饰,首先禁止大家再改动这个局部变量了,然后匿名内部类会拷贝一份,这样保证了值的统一性,在方法中的变量被释放后还是可以访问。当这个变量是引用变量的时候,也是一样的。引用变量在虚拟机栈中的值是对象在堆的内存地址,这样保证了访问的是同一个对象,但是这个对象还是可以修改自己堆中的值。示例:

    package cn;
    
    class AA {
        private static int k;
    
        private int i;
    
        class B {
    
            private int j;
    
            public void mb(){
                System.out.println(i);
            }
        }
    
        public void ma(){
            final int ma_i = 3;
    
            // 创建一个隐式继承自 B 的匿名内部类
            B b = new B() {
                @Override
                public void mb() {
                    System.out.println("这是匿名内部类重写 B 的方法");
    
                    // 可以直接访问外部类的任意成员
                    System.out.println(i);
    
                    // 访问局部变量 ma_i
                    System.out.println(ma_i);
    
                }
            };
            b.mb();
        }
    }
    

反射玩转内部类

接下来,我们将运用反射获取已声明的类的和内部类相关的各种信息。

获取所有内部类

总共有两个方法:

// 获取所有公开的( public 所修饰的)内部类,包括父类的
public Class<?>[] getClasses();

//  获取所有本类中已声明的内部类,不包括父类的也无论修饰符
public Class<?>[] getDeclaredClasses();

直接看代码示例:

package cn;

class CC {
    public class Super_B {}

    public static class Super_C {}

    class Super_D {}

    public void mc(){
        class D{}

        new B(){};
    }
}

class AA extends CC {
    private static int k;

    private int i;

    // 成员内部类
    public class B {}

    // 静态内部类
    static class C {}

    private class E {}

    public void ma(){

        // 局部内部类
        class C {

        }

        // 隐式继承自 B 的匿名内部类
        new B() {};
    }
}

public class TestClass {

    public static void main(String[] args) throws Exception {
        // 获取一个类类型
        Class<?> aClass = Class.forName("cn.AA");

        // 获取所有公开的内部类( public 所修饰的)的类信息
        for (Class<?> aClassClass : aClass.getClasses()) {
            System.out.println(aClassClass);
        }
        System.out.println("------");

        // 获取所有已声明的内部类的类信息,无论修饰符
        for (Class<?> declaredClass : aClass.getDeclaredClasses()) {
            System.out.println(declaredClass);
        }
        System.out.println("------");

        // 看解释
        System.out.println(aClass.getEnclosingClass());
        System.out.println(aClass.getDeclaringClass());
    }
}

运行结果:
class cn.AA$B
class cn.CC$Super_C
class cn.CC$Super_B
------
class cn.AA$E
class cn.AA$C
class cn.AA$B
------
null
null

相信大家都已经观察出来了,就不多解释了,现在来看看需要看解释的地方,最后面两句话打印出来的结果为什么都是 null 呢?

要搞清楚这个,首先要弄明白 Enclosing Class 和 Declaring Class 是什么意思,看到 Enclosing(译为包装) class ,大概有小伙伴会想起来,前面有解释过,这是包装类或外部类的意思,那为什么 Enclosing class 结果是 null 呢?因为我们获取的 AA 的类对象,AA 有内部类 B、C、E,但是并没有外部类,相反如果获取的是 AA.B 的类对象,那么,这个结果一定是 AA;

再来看 Declaring Class ,这是什么东西呢,它说的是哪个类(因为局部内部类是在成员方法内声明的,所以可以说没有类声明了它)声明的我,比如说 AA ,没有哪个类声明它啊,也就是说它没有外部类,所以它的结果是 null 。反观如果是 A.BB ,则结果是 AA。

我们口说无凭,眼见为实,都让开,把我的 40 米大刀拿上来!

// 原代码不动,只修改主函数
public static void main(String[] args) throws Exception {
    // 获取一个普通类类型信息,普通类是外部类当且仅当普通类有内部类
    Class<?> aa = Class.forName("cn.AA");

    // 获取一个成员内部类,注意写法!用$分隔
    Class<?> b = Class.forName("cn.AA$B");
    System.out.println(b);
    System.out.println(b.getEnclosingClass());
    System.out.println(b.getDeclaringClass());
    System.out.println(b.getEnclosingMethod());

    System.out.println("\n-----\n");

    // 获取一个局部内部类,注意写法!1 表示包装类内第一个方法,顺序与声明顺序一致
    Class<?> c = Class.forName("cn.AA$1C");
    System.out.println(c);
    System.out.println(c.getEnclosingClass());
    System.out.println(c.getDeclaringClass());
    System.out.println(c.getEnclosingMethod());
}

运行结果:
class cn.AA$B
class cn.AA
class cn.AA
null

-----

class cn.AA$1C
class cn.AA
null
public void cn.AA.ma()

可以观察到成员内部类 AA.B 并没有包装方法,但是有包装类 AA 和声明类 AA,而局部内部类 AA.ma.C 有包装类和包装方法,而没有声明类,也就是说局部内部类是没有声明类的,因为它声明在成员方法内,尽管成员方法属于包装类。这验证了上面让大家记住的内容:

成员嵌套类(成员内部类):成员嵌套类 作为 enclosing class 的成员定义的,成员嵌套类有 enclosing class 属性
局部嵌套类(局部内部类):局部嵌套类定义在 enclosing class 的方法里面,局部嵌套类有 enclosing class 属性和 enclosing method 属性

我们再来做个测试,当类开始套娃的时候,它的包装类、声明类和包装方法有何规律:

package cn;

class AA  {

    class C {
        // 开始套娃
        // 测试 1 的类
        class B{}

        public void c(){

            // 测试 2 的类
            class D{};
        }
    }

    public void ma(){

        // 局部内部类
        class C {
            // 开始套娃
            // 测试 3 的类
            class B{}

            public void c(){

                // 测试 4 的类
                class D{};
            }
        }

        // 隐式继承自 B 的匿名内部类
        new B() {};
    }
}

public class TestClass {

    public static void main(String[] args) throws Exception {
        // 这是成员内部类套娃测试
        // 测试 1 的类
        Class<?> test_1 = Class.forName("cn.AA$C$B");
        System.out.println(test_1);
        System.out.println(test_1.getEnclosingClass());
        System.out.println(test_1.getDeclaringClass());
        System.out.println(test_1.getEnclosingMethod());

        System.out.println("\n-----\n");

        // 测试 2 的类
        Class<?> test_2 = Class.forName("cn.AA$C$1D");
        System.out.println(test_2);
        System.out.println(test_2.getEnclosingClass());
        System.out.println(test_2.getDeclaringClass());
        System.out.println(test_2.getEnclosingMethod());

        System.out.println("\n-----\n");

        // 这是局部内部类套娃测试
        // 测试 3 的类
        Class<?> test_3 = Class.forName("cn.AA$1C$B");
        System.out.println(test_3);
        System.out.println(test_3.getEnclosingClass());
        System.out.println(test_3.getDeclaringClass());
        System.out.println(test_3.getEnclosingMethod());

        System.out.println("\n-----\n");

        // 测试 4 的类
        Class<?> test_4 = Class.forName("cn.AA$1C$1D");
        System.out.println(test_4);
        System.out.println(test_4.getEnclosingClass());
        System.out.println(test_4.getDeclaringClass());
        System.out.println(test_4.getEnclosingMethod());
    }
}

运行结果:
class cn.AA$C$B
class cn.AA$C
class cn.AA$C
null

-----

class cn.AA$C$1D
class cn.AA$C
null
public void cn.AA$C.c()

-----

class cn.AA$1C$B
class cn.AA$1C
class cn.AA$1C
null

-----

class cn.AA$1C$1D
class cn.AA$1C
null
public void cn.AA$1C.c()

细细观察,可以发现,尽管是在套娃,但也只向上追溯一层,成功追溯后与未套娃时的规则一致。

内部类实例化

考虑一个问题,经过上面的测试后,我们是可以拿到任意内部类的 Class 对象的,既然如此,我们能不能实例化它,执行它的方法,修改它的属性,想知道答案吗?看看代码先(狗头)!

package cn;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;

class AA  {

    class C {

        public void c(){

            class D{

                private int i;

                // 显式声明 D 的构造函数理应是无参的
                public D() {}

                private void d(){
                    System.out.println("这是套娃层的私有方法!");
                }

                @Override
                public String toString() {
                    return super.toString() + "{" +
                            "i=" + i +
                            '}';
                }
            };
        }
    }
}

public class TestClass {

    public static void main(String[] args) throws Exception {

        // 获取局部内部类类对象
        Class<?> inner = Class.forName("cn.AA$C$1D");
        System.out.println(inner);
        System.out.println("-----\n");

        // 开始实例化
        AA.C c = new AA().new C();
        
        // 尽管已经显式声明了 D 的构造函数是无参的,但如果不添加 AA.C.class 将报错,说没有无参构造函数
        Constructor<?> constructor = inner.getConstructor(AA.C.class);
        Object obj = constructor.newInstance(c);
        System.out.println(obj);
        System.out.println("-----\n");

        // 获取方法 d 并执行
        Method d = inner.getDeclaredMethod("d");
        d.setAccessible(true);
        d.invoke(obj);
        System.out.println("-----\n");

        // 修改成员变量 i
        Field i = inner.getDeclaredField("i");
        i.setAccessible(true);
        i.set(obj, 100);

        // 测试 i 是否变化
        System.out.println(obj);
        System.out.println("-----\n");

        for (Field declaredField : inner.getDeclaredFields()) {
            System.out.println(declaredField);
        }
    }
}

运行结果:
class cn.AA$C$1D
-----

cn.AA$C$1D@481248{i=0}
-----

这是套娃层的私有方法!
-----

cn.AA$C$1D@481248{i=100}
-----

private int cn.AA$C$1D.i
final cn.AA$C cn.AA$C$1D.this$1

事实胜于雄辩,即使是内部类,也一样可以利用反射来为所欲为... 需要注意的是内部类的构造方法,是比较特殊的,首先应该明白成员内部类和局部内部类都是依赖于包装类实例对象而存在,即持有包装类对象的引用,这点可以从测试结果看出,D 的成员变量中有一个 AA.C 类型的引用。从代码中可以看到尽管我在局部内部类中显式声明了

public D() {}

但实际上它的构造方法应该是这种形式的:

public D(AA.C field) {}

什么意思呢?就是说内部类会自动在你所声明的任何构造方法处添加一个包装类的方法参数,以确保内部类始终依赖于一个包装类实例对象。

Lambda 表达式

Lambda 表达式,也可称为闭包,它是推动 Java 8 发布的最重要新特性,允许把函数作为一个方法的参数(函数作为参数传递进方法中),使用 Lambda 表达式可以使代码变的更加简洁紧凑。

先简单介绍一下 Lambda 表达式的语法:

(parameters) -> expression
或
(parameters) ->{ statements; }

或许看着有点懵,上几个示例:

interface LambdaInterface_1 {
    void test();
}

interface LambdaInterface_2 {
    void test(int i);
}

interface LambdaInterface_3 {
    void test(int i, String s);
}

interface LambdaInterface_4 {
    int test(int i);
}

class LambdaTest {
    public static void test(int i, String s1) {
        System.out.println("只要类的静态方法参数列表和接口方法参数一致就可以有一种神奇的表达式啊");
    }
}

先定义几个接口,注意 Lambda 表达式只能用于只有一个方法的接口,定义好之后,我们就可以这样写 Lambda 表达式:

    LambdaInterface_1 l1 = () -> System.out.println("一个语句就不用大括号啊");
    LambdaInterface_1 l2 = () -> {
        System.out.println("两个及以上就要大括号啊");
        System.out.println("这是第二个括号啊");
    };

    LambdaInterface_2 l3 = i -> System.out.println("一个参数可以不要括号也可以要括号");

    LambdaInterface_3 l4 = (i, s) -> System.out.println("两个参数要括号啊");
    LambdaInterface_3 l5 = (j, haha) -> System.out.println("参数名字不用在意啊,就像重写方法时,只要无重复就可以啊");

    // 原来是要这样写的,但是由于类的静态方法参数列表和接口方法参数一致,所以更简洁了
    // LambdaInterface_3 l6 = (i, s) -> LambdaTest.test(i, s);
    LambdaInterface_3 l6 = LambdaTest::test;

    LambdaInterface_4 l7 = i -> 5;

    // l7 的定义方式和这样定义是一样的
    LambdaInterface_4 l8 = i -> {
        return 5;
    };

如果说为什么它会在内部类目录下,那是因为对于特殊的内部类可以用 Lambda 表达式来表示,比如下面这个例子:

package cn;

interface LambdaInterface {
    void test();
}

class AA  {

    public void c(){

        LambdaInterface lambda = new LambdaInterface() {

            @Override
            public void test() {

            }
        };
        System.out.println(lambda.getClass().getEnclosingMethod());
        System.out.println(lambda.getClass().getEnclosingClass());
    }
}

public class TestClass {

    public static void main(String[] args) throws Exception {

        new AA().c();
    }
}

运行结果:
public void cn.AA.c()
class cn.AA

上面使用匿名内部类的方式实现了 LambdaInterface 接口,也清清楚楚明明白白地指出了这个匿名内部类是有包装类和包装方法的。然后回答一下为什么要将 Lambda 表达式放在这里,是因为上面那种匿名内部类可以使用 Lambda 表达式替换它,就像下面这样:

package cn;

interface LambdaInterface {
    void test();
}

class AA  {

    public void c(){
        
        // 匿名内部类的声明变成这样了
        LambdaInterface lambda = () -> {};
        
        System.out.println(lambda.getClass().getEnclosingMethod());
        System.out.println(lambda.getClass().getEnclosingClass());
    }
}

public class TestClass {

    public static void main(String[] args) throws Exception {
        new AA().c();
    }
}

然后开始搞事情之前,我们运行它之后,运行结果是这样的:

null
null

怎么回事,只是没有使用匿名内部类的声明方式,它连包装类和包装方法都没有了?难道它不是内部类吗?但是它与内部的性质极为相似,对于各种变量的访问权限都是一样的,只是对于局部变量没有强制要求用 final 修饰,但是它规定了即使没有 final 修饰,也不能在 statements 中修改局部变量的值,这就...

既然好奇,那就一定要去了解一下原因,Two Thousand Years Latter...

我带着辣条又回来了!

  • 首先,Lambda 不是匿名内部类的语法糖!
  • 其次,在 Lambad 表达式声明的地方并不会生成一个内部类,而是生成一个 invokedynamic 指令;
  • 再次,Lambad 表达式中的 statements 会生成一个静态方法;
  • 最后,在运行时对 Lambda 表达式中的方法调用会导致 invokedynamic 指令执行,然后生成一个内部类(只在第一次执行时会生成,之后都是复用)来执行由 Lambad 表达式中的 statements 会生成的静态方法。

有兴趣的同学可以看看更详细的说法:08 匿名内部类与 lambda —— lambda 表达式背后的字节码原理

posted @ 2022-03-26 09:14  lizhpn  阅读(67)  评论(0编辑  收藏  举报