例子代码: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的时候,如果没有反射,我们将无计可施。
类加载的过程
想要满足上面的需求,在程序运行期间指定要创建的类的类型,就需要用到反射,而为什么反射能够解决这个问题,我们需要先来了解一下类加载的过程。
RTTI:这个就是我们平时用的创建类的方式,当我们写完Java类之后,编译器会编译,生成一个class文件,而在运行期就是加载这个class文件到内存种进行运作。
反射:与RTTI相反,我们在编译期并没有确定什么,而是在运行期确定我们是要生成Cat还是Dog。
此时又引出一个新的问题,为什么反射可以在运行期进行这种操作呢?
JVM类加载的流程和内存结构
图中可以看出,当我们编写完一个Java类之后,编译器会生成一个对应的class文件,然后运行时将class文件通过JVM的类加载器ClassLoader加载到内存中。这里我们不关注这个类加载器,主要关注这个class文件,因为反射实现的关键就是这个class文件,class文件中有这个类的所有细信息,包括但不限于属性、方法、实现的接口、继承的父类、类名。
反射关键类图
Member就是class文件对应的类
对象初始化的过程
通过上图可以看出,不管是编译期创建对象还是运行期创建对象,其实都是通过构造函数来生成对象,不同的是编译器是直接用,而运行期是先生成一个构造函数的对象再创建实例对象。
使用反射,第一步就是获得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}