java 反射的定义:主要是指程序可以访问、检测和修改它本身状态或行为的一种能力。简单来说,就是一面镜子,让你清楚的看到自己的所有细节。

1. 为什么要使用反射?

  • 问题来了,反射破坏了封装性,甚至让私有变量都可以被外部访问到,使得类变得不那么安全了。为什么还要有反射呢?
  • 1、 反射让开发人员可以通过外部类的全路径名创建对象,并使用这些类,实现一些扩展的功能。如Spring Aop功能。
  • 2、反射让开发人员可以枚举出类的全部成员,包括构造函数、属性、方法。以帮助开发者写出正确的代码。
  • 3、测试时可以利用反射 API 访问类的私有成员,以保证测试代码覆盖率。
  • 总的来说,就是程序员可以利用反射拿到类的信息,进行正常情况下无法进行的扩展与测试。

2. 怎么使用 反射?

  • 在使用Java反射机制时,主要步骤包括:
  • 1.获取 目标类型的Class对象
  • 2.通过 Class 对象分别获取Constructor类对象、Method类对象 & Field 类对象
  • 3.通过步骤2获取的对象分别获取类的构造函数、方法&属性的具体信息,并进行后续操作

2.1 获取 Class 对象的三种方式

  • 三种方式获取的 Class 都是同一个
public class Person {
    private int privateNum = 0;
    int num = 0;
    protected int protectedNum = 0;
    public int publicNum = 0;

    private int privateNum() {
        return privateNum;
    }
    int num() {
        return num;
    }
    protected int protectedNum() {
        return protectedNum;
    }
    public int publicNum() {
        return publicNum;
    }

    public Person() {
    }

    public Person(int privateNum) {
        this.privateNum = privateNum;
    }

    private Person(int privateNum, int num) {
        this.privateNum = privateNum;
        this.num = num;
    }

    public Person(int privateNum, int num, int protectedNum) {
        this.privateNum = privateNum;
        this.num = num;
        this.protectedNum = protectedNum;
    }

    private Person(int privateNum, int num, int protectedNum, int publicNum) {
        this.privateNum = privateNum;
        this.num = num;
        this.protectedNum = protectedNum;
        this.publicNum = publicNum;
    }
}

java反射的三种方式面试题 java反射原理面试_System

2.2 根据 class 获取Constructor、Method、Field 对象

  • Person类
public class Person {
    private int privateNum = 0;
    int num = 0;
    protected int protectedNum = 0;
    public int publicNum = 0;

}

2.2.1 获取 Field 对象

import java.lang.reflect.Field;

public class GetFieldTest {
    public static void main(String[] args) {
        /* 1. 获取 Person 的 class 对象 */
        Person person = new Person();
        Class aClass = person.getClass();

        /* 获取公有字段: 只有public字段可以获取到 */
        // 获取单个公有字段
        try {
            Field publicNum = aClass.getField("publicNum");
            System.out.println("按 name 获取的 Field 对象 --> " + publicNum);
            /* 找不到报错 NoSuchFieldException */
            Field privateNum = aClass.getField("privateNum");
            System.out.println("按 name 获取的 Field 对象 --> " + privateNum);
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        }

        // 获取所有公有字段
        Field[] fields = aClass.getFields();
        System.out.println("======= 输出全部公有字段 ======");
        for (Field field : fields) {
            System.out.println(field);
        }


        System.out.println("============= 华丽分割线 ===========");
        // 获取单个声明字段
        try {
            Field publicNum = aClass.getDeclaredField("privateNum");
            System.out.println("按 name 获取的 DeclaredField 对象 --> " + publicNum);
            /* 找不到报错 NoSuchFieldException */
            Field privateNum = aClass.getDeclaredField("privateNum01");
            System.out.println("按 name 获取的 DeclaredField 对象 --> " + privateNum);
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        }

        /* 获取所有声明字段:获取所有声明的字段 */
        System.out.println("======= 输出所有字段 ======");
        Field[] declaredFields = aClass.getDeclaredFields();
        for (Field declaredField : declaredFields) {
            System.out.println(declaredField);
        }

    }
}

java反射的三种方式面试题 java反射原理面试_反射_02

2.2.2 获取 Method对象

import java.lang.reflect.Method;

public class GetMethodTest {
    static class Student{}
    public static void main(String[] args) {
        /* 1. 获取 Person 的 class 对象 */
        Person person = new Person();
        Class aClass = person.getClass();

        /* 获取单个和全部公有方法(public) */
        try {
            Method publicNum = aClass.getMethod("publicNum");
            System.out.println("获取单个公有方法 publicNum : " + publicNum);
            Method privateNum = aClass.getMethod("privateNum");
            System.out.println("获取单个公有方法 privateNum : " + privateNum);
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        }


        Method[] methods = aClass.getMethods();
        /* 全部公有方法,包括父类 */
        for (Method method : methods) {
            System.out.println(method);
        }

        System.out.println("================= 华丽分割线 ================");

        /* 获取单个和全部声明方法 */
        try {
            Method privateNum = aClass.getDeclaredMethod("privateNum");
            System.out.println("获取单个声明方法 publicNum : " + privateNum);
            Method privateNum01 = aClass.getDeclaredMethod("privateNum01");
            System.out.println("获取单个声明方法 privateNum : " + privateNum01);
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        }


        Method[] declaredMethods = aClass.getDeclaredMethods();
        /* 全部声明方法,只有自身方法(不包括父类) */
        for (Method method : declaredMethods) {
            System.out.println(method);
        }

        System.out.println("=============== 再次华丽分割线 ===============");
        /* getEnclosingMethod 方法的返回类型为Method
         * 1. 当此类是本地或匿名类时,它将返回基础类的最近封闭方法。
         * 2. 如果此类不是本地的或匿名的,则返回null。
         */

        /* 模拟一个本地类 和 匿名类 */
        class A{};
        Method local = (new A().getClass()).getEnclosingMethod();
        System.out.println("本地类 --> " + local);

        Method anonymous = new Student(){}.getClass().getEnclosingMethod();
        System.out.println("内部类 --> " + anonymous);
		System.out.println("普通类 -->" + aClass.getEnclosingMethod());
    }
}

java反射的三种方式面试题 java反射原理面试_java_03

2.2.3 获取 Constructor 对象

import java.lang.reflect.Constructor;

public class GetConstructorTest {
    public static class Outter {

        //Outter的无参数构造器
        public Outter() {
            //构造中定义的内部类
            class Inner {

            }

            Class<Inner> innerClass = Inner.class;
            Constructor<?> enclosingConstructor = innerClass.getEnclosingConstructor();
            System.out.println("构造函数中的内部类 -- > " + enclosingConstructor);

            Constructor<?> enclosingConstructor1 = new Object() {}.getClass().getEnclosingConstructor();
            System.out.println("构造函数中的匿名类 -- >" + enclosingConstructor1);
        }
    }
    public static void main(String[] args) {
        /* 1. 获取 Person 的 class 对象 */
        Person person = new Person();
        Class aClass = person.getClass();

        /* 老规矩,公有构造函数 */
        try {
            Constructor constructor = aClass.getConstructor();
            System.out.println("单个 Integer 类型的构造函数 --> " + constructor);
            Constructor constructor01 = aClass.getConstructor(int.class, int.class);
            System.out.println("两个 Integer 类型的构造函数 --> " + constructor01);
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        }
        Constructor[] constructors = aClass.getConstructors();
        for (Constructor constructor : constructors) {
            System.out.println(constructor);
        }

        System.out.println("================ 华丽分割线 =================");
        /* 老规矩,声明构造函数 */
        try {
            Constructor declaredConstructor = aClass.getDeclaredConstructor(int.class,int.class);
            System.out.println("两个 Integer 类型的构造函数 --> " + declaredConstructor);
            Constructor declaredConstructor01 = aClass.getDeclaredConstructor(int.class,String.class);
            System.out.println("Integer,String 类型的构造函数 --> " + declaredConstructor);
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        }
        Constructor[] declaredConstructors = aClass.getDeclaredConstructors();
        for (Constructor constructor : declaredConstructors) {
            System.out.println(constructor);
        }
        System.out.println("============== 我是最美的分割线 ================");
        System.out.println("Outter 构造器中的内部类和匿名类");
        new Outter();
    }
}

java反射的三种方式面试题 java反射原理面试_System_04

2.3 反射实践

  • Student 类
package cn.cerish.reflection;

public class Student {
    private int age;
    public String name;

    public int getAge() {
        System.out.println("我调用了getAge()");
        return age;
    }

    public void setAge(int age) {
        System.out.println("我调用了setAge()");
        this.age = age;
    }

    public String getName() {
        System.out.println("我调用了getName()");
        return name;
    }

    public void setName(String name) {
        System.out.println("我调用了setName()");
        this.name = name;
    }

    public Student() {
        System.out.println("我是 Student 的无参构造函数");
    }

    public Student(int age, String name) {
        this.age = age;
        this.name = name;
        System.out.println("我是 Student 的有参构造函数");
    }

    @Override
    public String toString() {
        return "Student{" +
                "age=" + age +
                ", name='" + name + '\'' +
                '}';
    }
}

2.3.1 属性赋值

import java.lang.reflect.Field;

public class SetFieldTest {
    public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, InstantiationException {
        /* 1. 获取 Class 对象 */
        Student student = new Student();
        Class<? extends Student> studentClass = student.getClass();
        /* 2. 获取 Student 实例 */
        Student newInstance = studentClass.newInstance();
        System.out.println("初始化后的 Student --> " + newInstance);
        /* 3. 获取 Field */
        Field fieldAge = studentClass.getDeclaredField("age");
        Field fieldName = studentClass.getDeclaredField("name");
        /* 4. 私有属性必须设置,不然不能更改 */
        fieldAge.setAccessible(true);
        /* 5. 给实例newInstance 设置值 */
        fieldAge.set(newInstance, 18);
        fieldName.set(newInstance, "cerish");
        /* 6. 获取实例对象并查看结果 */
        System.out.println("属性赋值后的 Student --> " + newInstance);
    }
}

java反射的三种方式面试题 java反射原理面试_反射_05

  • 若步骤 4 没有进行设置,那么运行报错如下
Exception in thread "main" java.lang.IllegalAccessException: Class cn.cerish.reflection.SetFieldTest can not access a member of class cn.cerish.reflection.Student with modifiers "private"
	at sun.reflect.Reflection.ensureMemberAccess(Reflection.java:102)
	at java.lang.reflect.AccessibleObject.slowCheckMemberAccess(AccessibleObject.java:296)
	at java.lang.reflect.AccessibleObject.checkAccess(AccessibleObject.java:288)
	at java.lang.reflect.Field.set(Field.java:761)
	at cn.cerish.reflection.SetFieldTest.main(SetFieldTest.java:19)

2.3.2 反射调用方法

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class InvokeMethodTest {
    public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        /* 1. 获取 Class 对象 */
        Class<? extends Student> studentClass = Student.class;
        /* 2. 获取 Student 实例*/
        Student newInstance = studentClass.newInstance();

        /* 3. 获取 Method 调用方法 */
        Method setAge = studentClass.getDeclaredMethod("setAge", int.class);
        // 通过 Method对象调用 setAge(): 需传入创建的实例 & 参数
        setAge.invoke(newInstance, 18);
        System.out.println("调用 setAge 方法后的 newInstance --> " + newInstance);

    }
}

java反射的三种方式面试题 java反射原理面试_System_06

2.3.3 反射调用有参构造函数

import java.lang.reflect.InvocationTargetException;

public class InvokeConstructorTest {
    public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        /* 1. 获取 Class 对象 */
        Class<? extends Student> studentClass = Student.class;

        /* 2. 获取 Constructor 调用构造 */
        Student emptyNewInstance = studentClass.getConstructor().newInstance();
        System.out.println("空构造函数的 instance -->" + emptyNewInstance);
        Student fullNewInstance = studentClass.getConstructor(int.class, String.class).newInstance(18, "cerish");
        System.out.println("有参构造函数的 instance -->" + fullNewInstance);
    }
}

java反射的三种方式面试题 java反射原理面试_反射_07

3. 总结

  • 反射其实就是在运行时, 获取 类Class 获取到 属性、方法、构造器等对象信息,然后对获取到的对象信息进行操作,完成扩展的功能。