反射
文章目录
前言
反射是一个Java中很重要的一点,也是面试官常考的考点;同时在后期如果学习到Spring框架时,知晓反射概念更能助你理解框架的原理和奇妙之处。
那么,今天就让我带你理解反射的概念和使用吧
一、什么是反射?
1.反射的概念
当类被JVM加载阶段结束以后,会在方法区留下类型数据,其中包含了类的字段、方法、接口、版本等描述信息。
当方法区的数据存储完毕后,会在Java堆内存中实例化一个java.lang.Class类的对象,这个对象将作为程序访问类型数据的外部接口。
而通过Class实例对象获取类信息的方法就称为反射,注意这里的Class实例对象,不是我们平时所说的类实例对象(class 实例对象)即 new Object()创建的对象。是Class而不是class。
1.1 补充
关于实例对象的访问方式(new Object()):
句柄访问:Java堆中划分出一块内存用作句柄池,Java栈中reference存储的是对象的句柄地址。句柄中包含对象实例数据与对象类型数据的指针(对象实例数据在Java堆中,对象类型数据在方法区中)。
直接指针访问(常用):referenc中存储的直接就是对象地址,对象头中存储对象类型数据地址。
1.2 小总结
上面所说的对象类型数据其实就是Class实例中的数据,因此可见对象与对象类型数据的关系很为紧密
2. 反射的使用
反射的使用,说简单点就是获取类的Class实例。一共有三种方式,即反射的实现方式有三种:
- 通过一个类的静态变量class获取;
Class cls = Student.class;
- 如果拥有一个实例变量,通过实例变量提供的getClass() 方法获取;
Class cls1 = new Student().getClass();
- 如果你知道类名,通过静态方法Class.forName()获取;
Class cls2 = Class.forName("demo.Student");
3.动态加载
JVM在执行Java程序时,并不是一次性将所有需要的类加载的,而是当需要使用时才进行加载。注意同一个类只会加载一次。例:
public class Main {
public static void main(String[] args) {
if (args.length > 0) {
create(“小明”);
}
}
static void create(String name) {
Student p = new Studant(name);
}
}
如上,程序首先会将Main类加载到内存中,而不会加载Student.class。若执行到create()方法,将Student类加载到内存;若未执行到create() 方法,不会加载Student类。
这就是JVM动态加载Class的特性。
动态加载class的特性对于Java程序非常重要。利用JVM动态加载class的特性,我们就能在运行期根据条件加载不同的实现类。
二、反射的应用
1.继承关系
1.1 获取父类
System.out.println(cls.getSuperclass());
1.2 获取实现接口
System.out.println(cls.getInterfaces());
1.3 判定继承关系
class person{
}
class Student extends person{
}
System.out.println(student instanceof Student ); //true
System.out.println(student instanceof person); //true
System.out.println(student instanceof List); //false
该方法可以判断一个对象,是否由本类或子类 所实例化
2.访问字段
通过Class实例获取字段信息,Class类(Class实例的声明类)提供了一下几个方法来获取字段:
- Field getField(name):根据字段名获取某个public的field(包括父类) Field
- getDeclaredField(name):根据字段名获取当前类的某个field(不包括父类) Field[]
- getFields():获取所有public的field(包括父类) Field[]
- getDeclaredFields():获取当前类的所有field(不包括父类)
示例代码:
public class Main {
public static void main(String[] args) throws Exception {
Class stdClass = Student.class;
// 获取public字段"score":
System.out.println(stdClass.getField("score"));
// 获取继承的public字段"name":
System.out.println(stdClass.getField("name"));
// 获取private字段"grade":
System.out.println(stdClass.getDeclaredField("grade"));
}
}
class Student extends Person {
public int score;
private int grade;
}
class Person {
protected String name;
}
解析:
这是获取到的该字段的信息,格式为:
修饰符 字段类型全限定名 字段名称全限定名
- getName():返回字段名称
- getType():返回字段类型
cls.getDeclaredField("name").getName(); //name
cls.getDeclaredField("name").getType(); //class java.lang.String
- getModifiers():返回代表字段的修饰符的int值,不同的int值代表不同的修饰符类型,使用Modifier.isXXX()进行判定
cls.getDeclaredField("name").getModifiers(); // 4
System.out.println(Modifier.isProtected(4)); //true
Modifier源码:
2.1 获取字段值
public class ReflectDemo {
public static void main(String[] args) throws Exception {
// 反射获取类信息的三种方式。
Class cls = Student.class;
Class cls1 = new Student().getClass();
Class cls2 = Class.forName("demo.Student");
System.out.println(cls1.getDeclaredField("name"));
// 通过反射获取字段值
Student xiaoMing = new Student();
xiaoMing.setAge(12);
xiaoMing.setWeight(100);
Field ageField = cls.getDeclaredField("age");
// 获取该对象的FiledValue
Object o = ageField.get(xiaoMing);
System.out.println(o);
//获取被private修饰的字段值
Field weightField = cls.getDeclaredField("weight");
weightField.setAccessible(true);
Object c = weightField.get(xiaoMing);
System.out.println(c);
}
}
class person{
public boolean sex;
}
class Student extends person{
protected int age;
protected String name;
private int weight;
public int getAge() {
return age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public void setAge(int age) {
this.age = age;
}
public void setWeight(int weight) {
this.weight = weight;
}
public int getWeight() {
return weight;
}
}
提问:
如果我需要获取字段值的字段被private修饰,还可以获取?
答案是不能,会出现异常(如下图)。但是可以通过setAccessible(true)来设置,字段值可以访问,设置字段值同理。但是这个指令并不是万能的,要看虚拟机安全机制的具体实现。
注意:获取字段不需要权限,获取和设置字段值才需要权限(当修饰符是private时)
2.2设置字段值
示例代码:
//通过反射设置该字段值
Student student = new Student();
Field ageField = cls.getDeclaredField("age");
// 表示向student的age字段获取值
ageField.set(student,18);
System.out.println(student.getAge());
当字段修饰符为private时,同上设置即可。
3.访问方法
通过Class实例获取method,有以下几种方式:
- Method getMethod(name, Class…):获取某个public的Method(包括父类)
- Method getDeclaredMethod(name, Class…):获取当前类的某个Method(不包括父类)
- Method[] getMethods():获取所有public的Method(包括父类)
- Method[] getDeclaredMethods():获取当前类的所有Method(不包括父类)
解析:
修饰符 返回类型 方法名称全限定名(参数类型) 这里的括号指的是上图括号中的内容
- getModifiers():返回代表字段的修饰符的int值;
- getReturnType():返回方法返回值类型,也是一个Class实例,例如:String.class;
- getName():返回方法名称,例如:“getScore”;
- getParameterTypes():返回方法的参数类型,是一个Class数组
3.1调用方法
// 获取method,(方法名, 参数类型)
Method method = cls.getDeclaredMethod("setName", String.class);
System.out.println(method);
Student student = new Student();
// 通过method调用方法传入参数
method.invoke(student,"小明");
System.out.println(student.getName());
当遇到方法修饰符为private时,使用之前的方法。
4 调用构造方法
4.1Class实例旧版本实例化对象
使用这种方法实例化对象,只能使用public修饰的无参构造函数,已被废弃,使用时注意。
Student student1 = Student.class.newInstance();
4.2 Constructor实例化对象
通过Class实例获取Constructor的方法如下:
- getConstructor(Class…):获取某个public的Constructor;
- getDeclaredConstructor(Class…):获取某个Constructor;
- getConstructors():获取所有public的Constructor;
- getDeclaredConstructors():获取所有Constructor。
class Student {
protected int age;
protected String name;
private int weight;
public Student(){
}
public Student(int i) {
this.age = i;
}
private int getAge() {
return age;
}
}
Class cls = Student.class;
// 获取constructor
Constructor constructor = cls.getConstructor(int.class);
// 使用constructor实例化对象,并传入参数。
Student student1 = (Student) constructor.newInstance(2);
System.out.println(student1.age);
总结
这篇文章主要讲述了反射的概念与具体的应用,相信你仔细阅读这篇文章以后,会对反射有一个清晰的了解。再重述一遍,反射主要应用在框架中,对反射的理解能够助你更好的理解框架的原理。
编写不易,如果觉得这篇文章不错的话,点赞、收藏、关注我吧