例子代码:https://github.com/Simple-Stark/Study

什么是反射?

在我们以往的学习中,都创建过对象,例如

Student student = new Student();
student.say();

此时,我们想要操作一个对象,如果不通过上面的代码new一个对象出来,那我们什么都做不了,并且我们操作的对象已经确定了是Student类。但是**如果此时我们不知道我们将要操作什么类型的对象的情况下,那该怎么办呢?**于是,反射就派上了用场。

假设现在有这样一个场景

public class Animal {
    void say() {
        System.out.println("Animal Say");
    }
}

public class Cat extends Animal{

    @Override
    void say() {
        System.out.println("Cat Say");
    }
}

public class Dog extends Animal{
    @Override
    void say() {
        System.out.println("Dog Say");
    }
}

public void Test1() {
    Animal cat = new Cat();
    cat.say();
    Animal dog = new Dog();
    dog.say();
}
输出:
Cat Say
Dog Say

此时我们在编译的时候就指定了Animal的类型,具体是Cat或者Dog,那么当我们不知道我们要创建什么类型的Animal的时候,如果没有反射,我们将无计可施。

类加载的过程

想要满足上面的需求,在程序运行期间指定要创建的类的类型,就需要用到反射,而为什么反射能够解决这个问题,我们需要先来了解一下类加载的过程。

image.png

RTTI:这个就是我们平时用的创建类的方式,当我们写完Java类之后,编译器会编译,生成一个class文件,而在运行期就是加载这个class文件到内存种进行运作。
反射:与RTTI相反,我们在编译期并没有确定什么,而是在运行期确定我们是要生成Cat还是Dog。

此时又引出一个新的问题,为什么反射可以在运行期进行这种操作呢?

JVM类加载的流程和内存结构

image.png
图中可以看出,当我们编写完一个Java类之后,编译器会生成一个对应的class文件,然后运行时将class文件通过JVM的类加载器ClassLoader加载到内存中。这里我们不关注这个类加载器,主要关注这个class文件,因为反射实现的关键就是这个class文件,class文件中有这个类的所有细信息,包括但不限于属性、方法、实现的接口、继承的父类、类名。
image.png

反射关键类图

image.png
Member就是class文件对应的类

对象初始化的过程

image.png
通过上图可以看出,不管是编译期创建对象还是运行期创建对象,其实都是通过构造函数来生成对象,不同的是编译器是直接用,而运行期是先生成一个构造函数的对象再创建实例对象。
使用反射,第一步就是获得Class对象,只有获取了Clss对象,才能进行后续的操作。

获得Class对象的几种方式
@Test
public void Test2() throws ClassNotFoundException {
    // 方式一 类Class
    Class personClass = Person.class;

    // 方式二 实例.getClass()
    Person person = new Person();
    Class personClass1 = person.getClass();

    // 方式三 Class.forName("类的全路径")
    Class personClass2 = Class.forName("top.simple.stark.reflex.Person");

    System.out.println(personClass == personClass1);
    System.out.println(personClass == personClass2);
}
输出:
true
true

为什么结果都是true呢:因为在我们的项目中,一个类编译出来的只有一个class文件,对应的Class对象也只有一个。也就是说,同一个类只有一个Class对象。

实践:通过反射创建实例对象

// 首先我们有一个Person类
public class Person {

    public String name = "muse";

    protected Integer age = 1;

    private Byte sex = (byte) 1;

    Boolean isMarriage = true;

    public Person() {
    }

    public Person(String name, Integer age, Byte sex, Boolean isMarriage) {
        this.name = name;
        this.age = age;
        this.sex = sex;
        this.isMarriage = isMarriage;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    public Byte getSex() {
        return sex;
    }

    public void setSex(Byte sex) {
        this.sex = sex;
    }

    public Boolean getMarriage() {
        return isMarriage;
    }

    public void setMarriage(Boolean marriage) {
        isMarriage = marriage;
    }

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

@Test
public void Test3() throws Exception {
    // 1.获取Class对象
    Class personClazz = Class.forName("top.simple.stark.reflex.Person");
    // 2.获取构造函数
    // 有参构造
    Constructor constructor1 = personClazz.getConstructor(String.class, Integer.class,Byte.class,Boolean.class);
    // 无参构造
    Constructor constructor = personClazz.getConstructor();
    // 3.创建对象(有参)
    Person person1 = (Person) constructor1.newInstance("张三",18,(byte)11,false);
    System.out.println(person1.toString());
    // 创建对象 无参
    Person person = (Person) constructor.newInstance();
    person.setName("simple");
    System.out.println(person.toString());

    System.out.println(person == person1);
}
输出:
Person{name='张三', age=18, sex=11, isMarriage=false}
Person{name='simple', age=1, sex=1, isMarriage=true}
false

通过上面的代码可以看出,对象被成功的创建出来了,并且我们用了Person对象中的两个构造函数,也就是对应的两个Constructor 对象,那么,如果我们想要对对象的属性进行操作,要怎么办呢?答案是Field对象。

@Test
public void Test4() throws Exception {
    // 1.获取Class对象
    Class personClazz = Class.forName("top.simple.stark.reflex.Person");
    // public属性的获取
    // 2.获取name对应的Field
    Field nameField = personClazz.getField("name");

    // private 属性的获取
    // 2.获取sex对应的Field
    Field sexField = personClazz.getDeclaredField("sex");
    // 将sexField设置为可以访问,如果缺少这个代码,获取时将会出现异常
    sexField.setAccessible(true);

    // protected 属性的获取
    // 2.获取age对应的Field
    Field ageField = personClazz.getDeclaredField("age");
    // 与private同理
    ageField.setAccessible(true);

    // default 属性的获取
    // 2.获取isMarriage对应的Field
    Field isMarriageField = personClazz.getDeclaredField("isMarriage");
    isMarriageField.setAccessible(true);

    // 3.创建一个person对象
    Constructor constructor = personClazz.getConstructor();
    Person person = (Person) constructor.newInstance();

    // 4.获取该对象的name属性的值
    String name = (String) nameField.get(person);
    System.out.println("name:" + name);
    // 4.获取该对象的sex属性的值
    Byte sex = (Byte) sexField.get(person);
    System.out.println("sex:" + sex);
    // 4.获取该对象的age属性的值
    Integer age = (Integer) ageField.get(person);
    System.out.println("age:" + age);
    // 4.获取该对象的isMarriage 属性的值
    Boolean isMarriage = (Boolean) isMarriageField.get(person);
    System.out.println("isMarriage:" + isMarriage);

    // 5.Field的常用方法 参考Field.toString();
     /** Field.toString();
     * --------------------------------------------------------------------
     * public String toString() {
     *         int mod = getModifiers();
     *         return (((mod == 0) ? "" : (Modifier.toString(mod) + " "))
     *             + getType().getTypeName() + " "
     *             + getDeclaringClass().getTypeName() + "."
     *             + getName());
     * }
     */
    System.out.println("Field.toString():" + nameField.toString());
    System.out.println("获取字段的类型:" + nameField.getType());
    System.out.println("获取字段的名字:" + nameField.getName());
    System.out.println("获取字段的访问修饰符:" + Modifier.toString(nameField.getModifiers()));
    System.out.println("获取字段所在类的全路径:" + nameField.getDeclaringClass().getName());
}
}
输出:
name:muse
sex:1
age:1
isMarriage:true
Field.toString():public java.lang.String top.simple.stark.reflex.Person.name
获取字段的类型:class java.lang.String
获取字段的名字:name
获取字段的访问修饰符:public
获取字段所在类的全路径:top.simple.stark.reflex.Person

从上面的代码可以看出,反射不光能够获取public修饰的属性,还能获取private、protected和default修饰的属性,也就是说,反射会破坏程序的封装。
getField()与getDeclaredField()的区别

  • getField()只能获取public修饰的属性。包括从父类那继承来的。
  • getDeclaredField()能获取到一个类全部的属性(无论是什么修饰符),但是不能够获取继承来的属性。(特别注意:这里只能获取到对应的字段,但无法获取对应的值,除非加上setAccessible(true)

反射的实际应用

工具类BeanUtis,实现将一个对象属性相同的值赋值给另一个对象

public class BeanUtils {

    /**
     * 拷贝对象值
     * @param oldObj 被转换的对象
     * @param newObj 目标转换对象
     * @throws IllegalAccessException
     */
    public static void convertor(Object oldObj,Object newObj) throws IllegalAccessException {
        // 1. 获取Class对象
        Class<?> oldObjClass = oldObj.getClass();
        Class<?> newObjClass = newObj.getClass();
        // 2.获取属性值,必须使用getDeclaredFields(),否则非public修饰的字段将无法复制
        Field[] oldObjFields = oldObjClass.getDeclaredFields();
        Field[] newObjFields = newObjClass.getDeclaredFields();

        // 3. 赋值
        for (Field oldObjField : oldObjFields) {
            for (Field newObjField : newObjFields) {
                // 属性相同则赋值
                if (oldObjField.getName().equals(newObjField.getName())) {
                    // 使能够赋值
                    oldObjField.setAccessible(true);
                    newObjField.setAccessible(true);
                    newObjField.set(newObj,oldObjField.get(oldObj));
                }
            }
        }
    }

    public static void main(String[] args) throws Throwable{
        // Service层返回的
        Person person = new Person("muse", 10, (byte)1, true);

        // 需要返回给前段实体对象
        PersonVo personVo = new PersonVo();

        BeanUtils.convertor(person, personVo);

        System.out.println("person" + person);

        System.out.println("personVo" + personVo);

    }

}

输出:
personPerson{name='muse', age=10, sex=1, isMarriage=true}
personVoPersonVo{name='muse', age=10, sex=1, isMarriage1=null}

Q.E.D.


空有烟霞之志,叹无水云之身。