什么是反射?

在我们学习 Java 之初,我们怎么去写代码?

1 Student student = new Student();

首先我们要创建类的实例化对象,在去调用类的方法、获取值等操作,如果没有这个对象的话,那么我们什么也做不了,例如:

 1 package 反射;
 2 
 3 public class Reflection01 {
 4     public static void main(String[] args) {
 5         Student student = new Student();
 6         student.say();
 7     }
 8 }
 9 
10 class  Student {
11     public void say() {
12         System.out.println("执行say()方法");
13     }
14 }
15 
16 /**
17     输出:
18         执行say()方法
19 */

 student.say();  是调用 Student 类里的 say() 方法,这是我们在刚学 Java 时最熟悉的利用实例化对象去执行方法,让程序去运行的手段。实现上述的前提是,我们要知道操作的是哪一个类,但是如果我们不知道具体要操作哪一个类的时候,该如何做呢?

 1 package 反射;
 2 
 3 public class Reflection02 {
 4     public static void main(String[] args) {
 5 
 6     }
 7 }
 8 
 9 interface Animal {
10     void eat();
11 }
12 
13 class Cat implements Animal {
14     public void eat() {
15         System.out.println("猫吃鱼");
16     }
17 }
18 
19 class Dog implements Animal {
20     public void eat() {
21         System.out.println("狗吃骨头");
22     }
23 }

观察如上代码,CatDog 均为接口 Animal 的实现类,并且都实现了 eat() 方法, Cat 类实现的是  System.out.println("猫吃鱼");  ,Dog 类实现的是  System.out.println("狗吃骨头");  ,这个是我们在编译期间来确定的,比如:

1 Cat cat = new Cat();
2 cat.eat(); // 猫吃鱼
3 Dog dog = new Dog();
4 dog.eat(); // 狗吃骨头

假如我们在编译期间没有办法确定我们是用 Cat 类 还是用 Dog 类,那么该怎么办呢?

这时候就需要用到了反射。

反射的定义

Java反射机制 是在运行状态中,对任意一个类 ,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性;这种动态获取信息以及动态调用对象方法的功能称为 Java 语言的反射机制。

反射 被视为动态语言的关键,反射机制允许程序在执行期间借助于 Reflection API 取得任何类的年内部信息,并能直接操作对象的内部属性和方法。

由此可以做如下总结,Java 反射就是在运行状态中:

  • 获取任意类的名称、package 信息、所有属性、方法、注解、类型、类加载器等

  • 获取任意对象的属性,并能改变对象的属性

  • 调用任意对象的方法

  • 判断任意一个对象所属的类

  • 实例化任意一个类对象

通过反射 ,我们可以动态实现装配 ,降低代码耦合,动态代理等。

但是,反射过度使用也会消耗系统的性能。

Java 运行分为两种状态:

  • 编译时,通过 Javac 命令,生成一个或多个 .class 字节码 文件,(每个 .class 字节码文件对应一个类);

  • 运行时,通过 Java 命令,将一个或多个 .class 字节码文件加载到内存中。(由 JVM 提供的类加载器完成)。

反射的原理

研究反射的原理,首先要知道类加载的过程

类加载过程

在类加载过程中,我们最初的方式是 RTTI,即首先我们编写完 Java 代码,会通过 Javac 编译生成 .class 文件,这是在编译期间的动作,那么在运行期如何执行呢?其实就是将 .class 文件加载到内存中。

那么反射是什么呢?反射是反而为之,在编译期我们并没有确定加载哪个类,而是在运行期来确定。

那么反射为什么能够在运行期执行这样一个反射的操作呢?

接下来,我们从 JVM 类加载角度来分析一下。

JVM 类加载流程和内存结构

如下是 JVM 加载的流程图,

观察上图,首先是看图片左侧,是 Java 的一个源文件,这个源文件在编译的时候会变成一个 .class 文件,如:cafe babe 0000 0031 0022 0a00... 这其实就是该类的所有信息,在我们有所有信息的时候,classLoader(类加载器)才会把这个 .class 文件加载到 JVM 中,在加载的过程中就涉及到了加载、验证、准备、解析、初始化等一系列操作,加载完成之后就是 JVM 的内存管理了。

完成上述操作:

  • 首先是需要一个 Java 源代码,

  • 第二个是 .class 文件,这个文件是需要后续别加载进去的,

  • 第三个是 classloader(类加载器 ),用于加载 .class 文件

那么显然我们就会知道,反射其实用的就是 .class 文件,那么我们如何去使用这个文件呢?

class文件包含了哪些内容?

生成对象的步骤

如上图所示:

1 Person person = new Person();

这是编译期加载的过程。

运行期加载呢?

  • 首先我们也是加载 .class 文件

   Class personClazz = Class.forName("com.muse.Person"); 

  • 其次我们要创建构造函数

   Constructor constructor = personClazz.getConstructor(); 

  • 最后,通过构造函数 Person() 创建对象

   Person person = (Person)contructor.newInstance(); 

 1 package 反射;
 2 
 3 public class Person {
 4     public String name = "laoma";
 5     
 6     protected Integer age = 18;
 7 
 8     private Byte sex = (byte) 1;
 9 
10     Boolean isMarriage = false;
11 
12     public Person() {
13     }
14 
15     public Person(String name, Integer age, Byte sex, Boolean isMarriage) {
16         this.name = name;
17         this.age = age;
18         this.sex = sex;
19         this.isMarriage = isMarriage;
20     }
21 
22     public String getName() {
23         return name;
24     }
25 
26     public void setName(String name) {
27         this.name = name;
28     }
29 
30     public Integer getAge() {
31         return age;
32     }
33 
34     public void setAge(Integer age) {
35         this.age = age;
36     }
37 
38     public Byte getSex() {
39         return sex;
40     }
41 
42     public void setSex(Byte sex) {
43         this.sex = sex;
44     }
45 
46     public Boolean getMarriage() {
47         return isMarriage;
48     }
49 
50     public void setMarriage(Boolean marriage) {
51         isMarriage = marriage;
52     }
53 
54 }
获取 .class 文件的几种方式
 1 package 反射;
 2 
 3 public class Reflection03 {
 4     public static void main(String[] args) throws Throwable {
 5         // 方式一  
 6             // 类.class
 7         Class personClazz = Person.class;
 8         System.out.println(personClazz);
 9 
10         // 方式二  
11             // 实例.getClass()
12         Person person = new Person();
13         Class personClazz1 = person.getClass();
14         System.out.println(personClazz1);
15 
16         // 方式三  
17             // Class.forName("类的全路径")
18         Class personClazz2 = Class.forName("反射.Person");
19         System.out.println(personClazz2);
20     }
21 }
反射实例
 1 package 反射;
 2 
 3 import java.lang.reflect.Constructor;
 4 import java.lang.reflect.Field;
 5 
 6 public class Reflection04 {
 7     public static void main(String[] args) throws Throwable {
 8         // 获取 Person.class 的字节码
 9         Class personClazz = Class.forName("反射.Person");
10         // 获取构造方法 Person()
11         // 获取无参的构造函数
12         Constructor Controctor1 =  personClazz.getConstructor();
13         // 获取有参的构造函数
14         Constructor Controctor2 =  personClazz.getConstructor(String.class, Integer.class, Byte.class, Boolean.class);
15         System.out.println(Controctor1);
16         System.out.println(Controctor2);
17         // 通过构造函数 Person() 创建对象
18         // 调用无参构造方法创建对象
19         Person person1 = (Person)Controctor1.newInstance();
20         person1.setName("你好");
21         System.out.println("person=" + person1);
22         // 调用有参构造方法创建对象
23         Person person2 = (Person)Controctor2.newInstance("loama", 22,(byte)1, true);
24         System.out.println("person=" + person2);
25 
26         // 获取类中的构造方法
27         Constructor[] declarCoonstructors = personClazz.getDeclaredConstructors();
28         System.out.println("获取person的所有构造方法" + declarCoonstructors);
29 
30         // 获取类中的某个对象
31         Field nameField = personClazz.getField("name");
32         // 操作Field,获得属性值
33         String name = String.valueOf(nameField.get(person1));
34         System.out.println("获取类中的name属性" + name);
35 
36         // 获取类中的私有属性
37         /**
38          * 通过反射获得类的public属性值
39          * <p>
40          * getField 只能获取public的,包括从父类继承来的字段。
41          * getDeclaredField 可以获取本类所有的字段,包括private的,但是不能获取继承来的字段。 (注: 这里只能获取到private的字段,但并不能访问该private字段的值,
42          * 除非加上setAccessible(true))
43          */
44         Field nameFieldPrivate = personClazz.getDeclaredField("sex");
45         nameFieldPrivate.setAccessible(true); // 设置为 true 就可以访问私有
46         Byte sex = (Byte) nameFieldPrivate.get(person);
47         System.out.println(sex);
48     }
49 }

反射机制的相关类

与Java反射相关的类如下:

类名用途
Class类 代表类的实体,在运行的Java应用程序中表示类和接口
Field类 代表类的成员变量(成员变量也称为类的属性)
Method类 代表类的方法
Constructor类 代表类的构造方法

Class类

Class 代表类的实体,在运行的Java应用程序中表示类和接口。在这个类中提供了很多有用的方法,这里对他们简单的分类介绍。

  • 获得类相关的方法

方法用途
asSubclass(Class<U> clazz) 把传递的类的对象转换成代表其子类的对象
Cast 把对象转换成代表类或是接口的对象
getClassLoader() 获得类的加载器
getClasses() 返回一个数组,数组中包含该类中所有公共类和接口类的对象
getDeclaredClasses() 返回一个数组,数组中包含该类中所有类和接口类的对象
forName(String className) 根据类名返回类的对象
getName() 获得类的完整路径名字
newInstance() 创建类的实例
getPackage() 获得类的包
getSimpleName() 获得类的名字
getSuperclass() 获得当前类继承的父类的名字
getInterfaces() 获得当前类实现的类或是接口
  • 获得类中属性相关的方法

方法用途
getField(String name) 获得某个公有的属性对象
getFields() 获得所有公有的属性对象
getDeclaredField(String name) 获得某个属性对象
getDeclaredFields() 获得所有属性对象
  • 获得类中注解相关的方法

方法用途
getAnnotation(Class<A> annotationClass) 返回该类中与参数类型匹配的公有注解对象
getAnnotations() 返回该类所有的公有注解对象
getDeclaredAnnotation(Class<A> annotationClass) 返回该类中与参数类型匹配的所有注解对象
getDeclaredAnnotations() 返回该类所有的注解对象
  • 获得类中构造器相关的方法

方法用途
getConstructor(Class...<?> parameterTypes) 获得该类中与参数类型匹配的公有构造方法
getConstructors() 获得该类的所有公有构造方法
getDeclaredConstructor(Class...<?> parameterTypes) 获得该类中与参数类型匹配的构造方法
getDeclaredConstructors() 获得该类所有构造方法
  • 获得类中方法相关的方法

方法用途
getMethod(String name, Class...<?> parameterTypes) 获得该类某个公有的方法
getMethods() 获得该类所有公有的方法
getDeclaredMethod(String name, Class...<?> parameterTypes) 获得该类某个方法
getDeclaredMethods() 获得该类所有方法
  • 类中其他重要的方法

方法用途
isAnnotation() 如果是注解类型则返回true
isAnnotationPresent(Class<? extends Annotation> annotationClass) 如果是指定类型注解类型则返回true
isAnonymousClass() 如果是匿名类则返回true
isArray() 如果是一个数组类则返回true
isEnum() 如果是枚举类则返回true
isInstance(Object obj) 如果obj是该类的实例则返回true
isInterface() 如果是接口类则返回true
isLocalClass() 如果是局部类则返回true
isMemberClass() 如果是内部类则返回true

Field类

Field 代表类的成员变量(成员变量也称为类的属性)。

方法用途
equals(Object obj) 属性与 obj 相等则返回true
get(Object obj) 获得obj 中对应的属性值
set(Object obj, Object value) 设置 obj 中对应属性值

Method类

Method 代表类的方法。

方法用途
invoke(Object obj, Object... args) 传递object对象及参数调用该对象对应的方法

Constructor类

Constructor 代表类的构造方法。

方法用途
newInstance(Object... initargs) 根据传递的参数创建类的对象

利用反射机制实现 BeanUtils 工具

可以将一个对象的属性相同的值赋值给另一个对象

 1 // 目标对象 Person1
 2 /*
 3  * Copyright (C) 2020 Baidu, Inc. All Rights Reserved.
 4  */
 5 package 反射;
 6 
 7 /**
 8  * @author laoma
 9  */
10 public class Person1 {
11 
12     public String name;
13 
14     protected Integer age;
15 
16     private Byte sex;
17 
18     Boolean isMarriage1;
19 
20     public Person1() {
21     }
22 
23     public Person1(String name, Integer age, Byte sex, Boolean isMarriage) {
24         this.name = name;
25         this.age = age;
26         this.sex = sex;
27         this.isMarriage1 = isMarriage;
28     }
29 }
 1 package 反射;
 2 
 3 import java.lang.reflect.Field;
 4 
 5 // 利用反射实现 BeanUtils 工具
 6 public class BeanUtilsDemo {
 7     /**
 8      * @param originObj 原始对象
 9      * @param targetObj 目标对象
10      * */
11     public static void convertor(Object originObj, Object targetObj) throws Throwable {
12         // 利用反射
13         // 1. 首先获取类
14         Class originClazz = originObj.getClass();
15         Class targetClazz = targetObj.getClass();
16 
17         // 2. 获取属性值
18             // 使用 getDeclaredFields 所有的属性值
19         Field[] originFields = originClazz.getDeclaredFields();
20         Field[] targetFields = targetClazz.getDeclaredFields();
21 
22         // 3. 赋值
23             // 遍历赋值
24             // 通过属性名字来判断,如果属性名相同,那么进行赋值
25         for (Field originItem : originFields) {
26             for (Field targetItem : targetFields) {
27                 // equals() 比较字符串中所包含的内容是否相同。
28                 if (originItem.getName().equals(targetItem.getName())) {
29                     // 这里把两个对象的 setAccessible 都打开,这样就不用担心类型问题
30                     originItem.setAccessible(true);
31                     targetItem.setAccessible(true);
32                     // 使用 set() 设置属性值
33                     targetItem.set(targetObj, originItem.get(originObj));
34                 }
35             }
36         }
37     }
38 
39     public static void main(String[] args) throws Throwable {
40         // 创建一个对象
41         Person person = new Person("laomahh", 10, (byte) 1, false);
42 
43         // 创建一个目标对象
44         Person1 person1 = new Person1();
45 
46         // 调用方法
47         BeanUtilsDemo.convertor(person, person1);
48 
49         System.out.println("person == " + person);
50         System.out.println("person1 == " + person1);
51         System.out.println("person1.isMarriage1 == " + person1.isMarriage1);
52     }
53 }

对于字符串变量来说,使用 “==” 和 “equals()” 方法比较字符串时,其比较方法不同。

  • “==” 比较变量的本事,即 两个对象在内存中的首地址

    (Java 中,对象的首地址是它在内存中存放的起始地址,它后面的地址是用来存放它所包含的各个属性的地址,所以内存中会用多个内存块来存放对象的各个参数,而通过这个首地址就可以找到该对象,进而可以找到该对象的各个属性。)

  • “equals()” 方法比较字符串中所包含的内容是否相同

    例如:

1 String s1,s2,s3 = "abc", s4 ="abc" ;
2 s1 = new String("abc");
3 s2 = new String("abc");
4 
5 s1==s2   // 是 false      两个变量的内存地址不一样,也就是说它们指向的对象不 一样,
6 
7 s1.equals(s2) // 是 true    两个变量的所包含的内容是abc,故相等。

对于非字符串变量来说,"=="和"equals"方法的作用是相同的都是用来比较其,对象在堆内存的首地址,即用来比较两个引用变量是否指向同一个对象。