目录
??学习背景
学习Java的小伙伴,可能听过Java反射机制
,但是熟悉又有点陌生,本文主要是通过思考面试中经常被问到的几个Java反射机制
的问题,再通过理论知识
结合代码实例
及应用场景
进行讲解,加深自己对Java反射机制
的认知和理解,也希望能帮助到有需要的小伙伴~
??一、Java反射机制是什么?
??1.1 反射原理
(1)Java反射机制(Java Reflection
)是Java语言中一种动态(运行时)访问、检测 & 修改它本身
的能力,主要作用是动态(运行时)获取类的完整结构信息 & 调用对象的方法
~
更简单点的说就是Java程序在运行时(动态)通过创建一个类的反射对象,再对类进行相关操作,比如:
- 获取该对象的成员变量 & 赋值
- 调用该对象的方法(含构造方法,有参/无参)
- 判断该对象所属的类
PS:不过说实话,直接看比较官方的定义还是有点难理解,再来更加通俗点的说吧~
(2)一般情况下,我们使用某个类,都会知道这个类,以及要用它来做什么,可以直接通过new
实例化创建对象,然后使用这个对象对类进行操作,这个就属于正射
~
(3)而反射
则是一开始并不知道要初始化的是什么类,无法使用new
来实例化创建对象,主要是通过JDK提供的反射API来实现,在运行时才知道要操作的是什么类,并且可以获取到类的完整构造以及调用对应的方法,这就是反射
~
??1.2 反射例子
代码如下:
package com.justin.java.lang;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
/**
* @program: Jdk1.8 Test
* @description: 正射、反射简单调用示例
* @author: JustinQin
* @create: 2021/8/22 13:23
* @version: v1.0.0
**/
public class Student {
private int id;
public void setId(int id) {
this.id = id;
}
public int getId() {
return id;
}
public static void main(String[] args) throws Exception{
//一、正射调用过程
Student student = new Student();
student.setId(1);
System.out.println("正射调用过程Student id:" + student.getId());
//二、反射调用过程
Class clz = Class.forName("com.justin.java.lang.Student");
Constructor studentConstructor = clz.getConstructor();
Object studentObj = studentConstructor.newInstance();
Method setIdMethod = clz.getMethod("setId", int.class);
setIdMethod.invoke(studentObj, 2);
Method getIdMethod = clz.getMethod("getId");
System.out.println("正射调用过程Student id:" + getIdMethod.invoke(studentObj));
}
}
输出结果:
正射调用过程Student id:1
反射调用过程Student id:2
上述例子反射的调用过程,可以看到获取一个类的反射对象
,主要过程为:
- 获取类的
Class
实例对象 - 根据
Class
实例对象获取Constructor
对象 - 再根据
Constructor
对象的newInstance
方法获取到类的反射对象
获取到类的反射对象
后,就可以对类进行操作了~ 例如,上述示例中对类的方法进行调用过程为:
- 根据
Class
实例对象获取到类的Method
对象 - 再根据
Method
对象的invoke
方法调用到具体类的方法
前面一点也提到了获取到类的Class
实例对象,上面示例反向调用过程
中我们是通过Class.forName("类的全局定名")
这种方式来获取到类的Class
实例对象,除了这种,常用的还有其他两种,往下讲解~
??二、Java反射机制中获取Class的三种方式及区别?
??2.1 Class的几种获取方式
(1)获取类的java.lang.Class
实例对象,常见的三种方式分别为:
- 通过
MyClass.class
获取,这里的MyClass指具体类~~ - 通过
Class.forName("类的全局定名")
获取,全局定名为包名+类名 - 通过
new MyClass().getClass()
获取,这里的MyClass指具体类~
(2)通过MyClass.class
获取,JVM会使用ClassLoader
类加载器将类加载到内存中,但并不会做任何类的初始化工作,返回java.lang.Class
对象
(3)通过Class.forName("类的全局定名")
获取,同样,类会被JVM加载到内存中,并且会进行类的静态初始化工作,返回java.lang.Class
对象
(4)通过new MyClass().getClass()
获取,这种方式使用了new
进行实例化操作,因此静态初始化和非静态初始化工作都会进行,getClass
方法属于顶级Object
类中的方法,任何子类对象都可以调用,哪个子类调用,就返回那个子类的java.lang.Class
对象
PS: 这3种方式,最终在JVM堆区对应类的
java.lang.Class
对象都属于同一个,也就是内存地址相同,进行==
双等号比较结果为true
,原因是JVM类加载过程中使用的是同一个ClassLoader
类加载器加载某个类,不论加载多少次,生成到堆区的java.lang.Class
对象始终只有一个,除非自定义类加载器,破坏JVM的双亲委派机制,使得同一个类被不同类加载器加载,JVM才会把它当做两个不同的java.lang.Class
对象
??2.2 代码演示几种方式的区别
创建一个实体类,分别在实体类中创建类的静态代码块
、动态代码块
、有参构造方法
、无参构造方法
,方便测试几种方式的区别及内存地址是否相同~
(1)实体类:
public class MyClass {
private static final String staticStr = "Hi";
private static i