反射的概念与应用 --框架核心基础

反射

前言

反射是一个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);

总结

这篇文章主要讲述了反射的概念与具体的应用,相信你仔细阅读这篇文章以后,会对反射有一个清晰的了解。再重述一遍,反射主要应用在框架中,对反射的理解能够助你更好的理解框架的原理。

编写不易,如果觉得这篇文章不错的话,点赞、收藏、关注我吧

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值