JAVA-反射

反射

反射

认识一下什么是反射。其实API文档中对反射有详细的说明,我们去了解一下。在java.lang.reflect包中对反射的解释如下图所示

1668575265599

翻译成人话就是:反射技术,指的是加载类的字节码到内存,并以编程的方法解刨出类中的各个成分(成员变量、方法、构造器等)。

反射有啥用呢?其实反射是用来写框架用的,但是现阶段对框架还没有太多感觉。为了方便理解,举个例子:平时我们用IDEA开发程序时,用对象调用方法,IDEA会有代码提示,idea会将这个对象能调用的方法都给你列举出来,供你选择,如果下图所示

1668575796295

问题是IDEA怎么知道这个对象有这些方法可以调用呢? 原因是对象能调用的方法全都来自于类,IDEA通过反射技术就可以获取到类中有哪些方法,并且把方法的名称以提示框的形式显示出来,所以你能看到这些提示了。

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

因为反射获取的是类的信息,那么反射的第一步首先获取到类才行。由于Java的设计原则是万物皆对象,获取到的类其实也是以对象的形式体现的,叫字节码对象,用Class类来表示。获取到字节码对象之后,再通过字节码对象就可以获取到类的组成成分了,这些组成成分其实也是对象,其中每一个成员变量用Field类的对象来表示每一个成员方法用Method类的对象来表示每一个构造器用Constructor类的对象来表示

如下图所示:

1668576426355

1.1 获取类的字节码

反射的第一步:是将字节码加载到内存,我们需要获取到的字节码对象。

1668576691591

比如有一个Student类,获取Student类的字节码代码有三种写法。不管用哪一种方式,获取到的字节码对象其实是同一个。

//获取字节码文件的方式
//第一种,类名.class
Class cls1 = Student.class;
System.out.println(cls1);
//第二种,通过创建好的对象去获取student.getClass()
Student student = new Student();
Class cls2 = student.getClass();
System.out.println(cls2);
//第三种,Class.forName();
Class<?> cls3 = Class.forName("day20230811.reflect.Student");
System.out.println(cls3);
//通过类加载器获取
ClassLoader loader = Student.class.getClassLoader();
Class<?> cls4 = loader.loadClass("day20230811.reflect.Student");
System.out.println(cls4);
public class Test1Class{
    public static void main(String[] args){
        Class c1 = Student.class;//第一种方式
        System.out.println(c1.getName()); //获取全类名
        System.out.println(c1.getSimpleName()); //获取简单类名
         //第二种方式全类名
        Class c2 = Class.forName("com.itheima.d2_reflect.Student");
       
        System.out.println(c1 == c2); //true
        
        Student s = new Student();
        Class c3 = s.getClass();//第三种方式
        System.out.println(c2 == c3); //true
    }
}

1.2 获取类的构造器

获取到类的字节码对象之后。接下来,我们学习一下通过字节码对象获取构造器,并使用构造器创建对象。

获取构造器,需要用到Class类提供的几个方法,如下图所示:

方法说明
Constructor<?>[] getContructors()获取全部构造器
Constructor<?>[] getDeclaredConstructors()获取全部构造器(只要存在就能拿到)
Constructor getConstructor(Class<?>… parameterTypes)获取某个构造器 (只能获取public修饰的)
Constructor getDeclaredConstructor(Class<?>…parameterTypes)获取某个构造器 (只要存在就能拿到)

按照规律来记就很方便了。

get:获取
Declared: 有这个单词表示可以获取任意一个,没有这个单词表示只能获取一个public修饰的
Constructor: 构造方法的意思
后缀s: 表示可以获取多个,没有后缀s只能获取一个

假设现在有一个Student类,里面有几个构造方法,代码如下

package com.study.day0811;

public class Student extends Person {

    private int sno;
    double height;
    protected double weight;
    public double socre;

    public Student() {

    }

    public Student(double height, double weight) {
        this.height = height;
        this.weight = weight;
    }

    Student(int sno, double height, double weight, double socre) {
        this.sno = sno;
        this.height = height;
        this.weight = weight;
        this.socre = socre;
    }

    public void study() {
        System.out.println("Student study");
    }

    public String showInformation(){
        return "Student showInformation";
    }

    private String showInformation(String name){
        return "Student showInformation" + name;
    }

    @Override
    public String toString() {
        return "Student{" +
                "sno=" + sno +
                ", height=" + height +
                ", weight=" + weight +
                ", socre=" + socre +
                '}';
    }
}

    1. 接下来,我们来尝试获取类中所有的构造器
package com.study.day0811;

import java.lang.reflect.Constructor;

public class Demo2 {
    public static void main(String[] args) throws NoSuchMethodException {
        Class<Student> cls = Student.class;

        //getConstructors只能获取到被public修饰的构造方法
        System.out.println("getConstructors只能获取到被public修饰的构造方法");
        Constructor<?>[] constructors = cls.getConstructors();
        for (Constructor<?> constructor : constructors) {
            System.out.println(constructor);
        }
        System.out.println("---------------------------------");
        //getDeclaredConstructors获取到所有的构造方法
        System.out.println("getDeclaredConstructors获取到所有的构造方法");
        Constructor<?>[] declaredConstructors = cls.getDeclaredConstructors();
        for (Constructor<?> declaredConstructor : declaredConstructors) {
            System.out.println(declaredConstructor);
        }
        System.out.println("---------------------------------");
        //获取无参的构造方法        
        System.out.println("获取无参的构造方法");
        Constructor<Student> constructor = cls.getConstructor();
        System.out.println(constructor);

        //获取有参的构造方法
        System.out.println("获取有参的构造方法");
        Constructor<Student> constructor1 = cls.getConstructor(double.class, double.class);
        System.out.println(constructor1);
    }
}

运行测试方法打印结果如下

image-20230811100436403

    1. 演示获取单个构造器试一试
//获取单个构造器
    public static void test() throws NoSuchMethodException {
        Class<Student> cls = Student.class;
        //获取类public修饰的空参数构造器
        Constructor constructor1 = cls.getConstructor();
        System.out.println(constructor1.getName() + " 参数个数: " + constructor1.getParameterCount());
        //获取private修饰的有两个参数(double,double)的构造器
        Constructor constructor2 = cls.getDeclaredConstructor(double.class,double.class);
        System.out.println(constructor2.getName() + " 参数个数: " + constructor2.getParameterCount());
    }

打印结果如下

image-20230811101241693

1.3 反射获取构造器的作用

上一节我们已经获取到了Student类中的构造器。获取到构造器后,有什么作用呢?

其实构造器的作用:初始化对象并返回

这里我们需要用到如下的两个方法,注意:这两个方法时属于Constructor的,需要用Constructor对象来调用。

Constructor提供的方法说明
T newInstance(Object… initargs)调用此构造器对象表示的构造器,并传入参数,完成对象的初始化并返回
public void setAccessible(boolean flag)设置为true,表示禁止检查访问控制(暴力反射)

如下图所示,constructor1和constructor2分别表示Student类中的两个构造器。现在我要把这两个构造器执行起来

获得的构造器如下:

获得的构造器

由于构造器是private修饰的,先需要调用setAccessible(true) 表示禁止检查访问控制,然后再调用newInstance(实参列表) 就可以执行构造器,完成对象的初始化了。

//获取单个构造器
public static void test() throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
    Class<Student> cls = Student.class;
    //获取类public修饰的空参数构造器
    Constructor constructor1 = cls.getConstructor();
    System.out.println(constructor1.getName() + " 参数个数: " + constructor1.getParameterCount());
    constructor1.setAccessible(true);//表示禁止检查访问控制
    Student s1 = (Student) constructor1.newInstance();//执行构造器,完成对象的初始化。
    System.out.println(s1);
    System.out.println("-----------------------------------------");
    //获取private修饰的有两个参数(double,double)的构造器
    Constructor constructor2 = cls.getDeclaredConstructor(double.class,double.class);
    System.out.println(constructor2.getName() + " 参数个数: " + constructor2.getParameterCount());
    constructor2.setAccessible(true);//表示禁止检查访问控制
    Student s2 = (Student) constructor2.newInstance(175,65);//执行构造器,完成对象的初始化。
    System.out.println(s2);
}

代码的执行结果如下:

image-20230811105512459

1.4 反射获取成员变量&使用

同学们,上一节我们已经学习了获取类的构造方法并使用。接下来,我们再学习获取类的成员变量,并使用。

其实套路是一样的,在Class类中提供了获取成员变量的方法,如下图所示。

方法说明
public Field[] getFields()获取类的全部成员变量(只能获取public修饰的)
public Field[] getDeclaredFields()获取类的全部成员变量(只要存在就能拿到)
public Field getField(String name)获取类的某个成员变量(只能获取public修饰的)
public Field getDeclaredField(String name)获取类的某个成员变量 (只要存在就能拿到)

这些方法的记忆规则,如下

get:获取
Declared: 有这个单词表示可以获取任意一个,没有这个单词表示只能获取一个public修饰的
Field: 成员变量的意思
后缀s: 表示可以获取多个,没有后缀s只能获取一个
  • 用Class类提供 的方法将成员变量的对象获取出来。
Class<Student> cls = Student.class;
//getFields只能获取被public修饰的类的全部成员变量,包括本类的和父类的
System.out.println("getFields只能获取被public修饰的类的全部成员变量,包括本类的和父类的");
Field[] fields = cls.getFields();
for (Field field : fields) {
    System.out.println(field);
}
System.out.println("----------------------");
//getDeclaredFields获取所有的属性
System.out.println("getDeclaredFields获取所有的属性");
Field[] declaredFields = cls.getDeclaredFields();
for (Field declaredField : declaredFields) {
    System.out.println(declaredField);
}
System.out.println("------------------------");

执行完上面的代码之后,我们可以看到控制台上打印输出了,每一个成员变量的名称和它的类型。

image-20230811104204691

  • 获取到成员变量的对象之后该如何使用呢?

在Filed类中提供给给成员变量赋值和获取值的方法,如下图所示。

方法说明
void set(Object obj,object value):赋值
Object get(Object obj)取值
public void setAccessible(boolean flag)设置为true,表示禁止检查访问控制(暴力反射)

再次强调一下设置值、获取值的方法时Filed类的需要用Filed类的对象来调用,而且不管是设置值、还是获取值,都需要依赖于该变量所属的对象。代码如下

System.out.println("------------------------");
System.out.println("获取某个成员变量(只要存在就能拿到)");
Field sno = cls.getDeclaredField("sno");
//获取属性名称
System.out.println(sno.getName());
//获取属性的类型
Class<?> type = sno.getType();
String name = type.getName();
System.out.println(name);
//获取修饰符
int modifiers = sno.getModifiers();
System.out.println(Modifier.toString(modifiers));
System.out.println("--------------------");
Student student = cls.newInstance();
//通过set方法为属性赋值
sno.setAccessible(true);
sno.set(student,12);
System.out.println(student);

执行代码,控制台会有如下的打印

image-20230811104510375

image-20230811104520315

1.5 反射获取成员方法

各位同学,上面几节我们已经学习了反射获取构造方法、反射获取成员变量,还剩下最后一个就是反射获取成员方法并使用了。

在Java中反射包中,每一个成员方法用Method对象来表示,通过Class类提供的方法可以获取类中的成员方法对象。如下下图所示

方法说明
Method[] getMethods()获取类的全部成员方法(只能获取public修饰的
Method[] getDeclaredMethods()获取类的全部成员方法(只要存在就能拿到)
Method getMethod(string name,Class<… parameterTypes)获取类的某个成员方法(只能获取public修饰的)
Method getDeclaredMethod(String name, Class<?>… parameterTypes)获取类的某个成员方法(只要存在就能拿到)
//只能获取被public修饰的方法
        System.out.println("只能获取被public修饰的方法");
        Method[] methods = cls.getMethods();
        for (Method method : methods) {
            System.out.println(method);
        }
        System.out.println("----------------------------");

获得所有被public修饰的方法

image-20230811111506796

//获取所有的方法
System.out.println("获取所有的方法");
Method[] declaredMethods = cls.getDeclaredMethods();
for (Method declaredMethod : declaredMethods) {
    //获取方法名
    System.out.println("获取方法名:" + declaredMethod.getName());
    //获取方法的返回值类型
    System.out.println("获取方法的返回值类型:" + declaredMethod.getReturnType());
    //获取方法的修饰符
    System.out.println("获取方法的修饰符:" + Modifier.toString(declaredMethod.getModifiers()));
    //获取方法的参数列表
    System.out.println("获取方法的参数列表:" + Arrays.toString(declaredMethod.getParameterTypes()));

    System.out.println(declaredMethod);
    System.out.println("===============================");
}

打印输出每一个成员方法的名称、参数格式、返回值类型,运行结果

image-20230811111314356

image-20230811111338346

也能获取单个指定的成员方法,如下图所示

1668581678388


获取到成员方法之后,有什么作用呢?

在Method类中提供了方法,可以将方法自己执行起来。

Method提供的方法说明
public Object invoke(Object obj,Object… args)触发某个对象的该方法执行。
public void setAccessible(boolean flag)设置为true,表示禁止检查访问控制(暴力反射)

新建一个Dog类演示一下,把run()方法和eat(String name)方法执行起来。看分割线之下的代码

public class DogDemo {
    public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        //反射第一步:先获取到Class对象
        Class<Dog> dogClass = Dog.class;

        //获取类中的全部方法
        Method[] methods = dogClass.getDeclaredMethods();
        for (Method m: methods) {
            System.out.println("方法名--" +m.getName() + ",参数数--" + m.getParameterCount() + ",返回值--" + m.getReturnType());
        }
        System.out.println("------------------------------");

        //获取private修饰的run方法,得到Method对象
        Method run = dogClass.getDeclaredMethod("run", String.class);
        //执行run方法,在执行前需要取消权限检查
        Dog dog = new Dog("富贵");
        run.setAccessible(true);
        Object r = run.invoke(dog,"富贵");
        System.out.println(r);

        //获取private 修饰的eat(String name)方法,得到Method对象
        Method eat = dogClass.getDeclaredMethod("eat");
        eat.setAccessible(true);
        Object e = eat.invoke(dog);
        System.out.println(e);
    }
}

打印结果如下图所示:run()方法执行后打印修勾run:富贵摇着yi巴跑过来,返回null; eat()方法执行完,直接返回修勾eat:富贵啃骨头

image-20230811112845471

反射:框架设计的灵魂

* 框架:半成品软件。可以在框架的基础上进行软件开发,简化编码
* 反射:将类的各个组成部分封装为其他对象,这就是反射机制
	* 好处:
		1. 可以在程序运行过程中,操作这些对象。
		2. 可以解耦,提高程序的可扩展性。


* 获取Class对象的方式:
	1. Class.forName("全类名"):将字节码文件加载进内存,返回Class对象
		* 多用于配置文件,将类名定义在配置文件中。读取文件,加载类
	2. 类名.class:通过类名的属性class获取
		* 多用于参数的传递
	3. 对象.getClass():getClass()方法在Object类中定义着。
		* 多用于对象的获取字节码的方式

	* 结论:
		同一个字节码文件(*.class)在一次程序运行过程中,只会被加载一次,不论通过哪一种方式获取的Class对象都是同一个。


* Class对象功能:
	* 获取功能:
		1. 获取成员变量们
			* Field[] getFields() :获取所有public修饰的成员变量
			* Field getField(String name)   获取指定名称的 public修饰的成员变量

			* Field[] getDeclaredFields()  获取所有的成员变量,不考虑修饰符
			* Field getDeclaredField(String name)  
		2. 获取构造方法们
			* Constructor<?>[] getConstructors()  
			* Constructor<T> getConstructor(类<?>... parameterTypes)  

			* Constructor<T> getDeclaredConstructor(类<?>... parameterTypes)  
			* Constructor<?>[] getDeclaredConstructors()  
		3. 获取成员方法们:
			* Method[] getMethods()  
			* Method getMethod(String name, 类<?>... parameterTypes)  

			* Method[] getDeclaredMethods()  
			* Method getDeclaredMethod(String name, 类<?>... parameterTypes)  

		4. 获取全类名	
			* String getName()  


* Field:成员变量
	* 操作:
		1. 设置值
			* void set(Object obj, Object value)  
		2. 获取值
			* get(Object obj) 

		3. 忽略访问权限修饰符的安全检查
			* setAccessible(true):暴力反射



* Constructor:构造方法
	* 创建对象:
		* T newInstance(Object... initargs)  

		* 如果使用空参数构造方法创建对象,操作可以简化:Class对象的newInstance方法


* Method:方法对象
	* 执行方法:
		* Object invoke(Object obj, Object... args)  

	* 获取方法名称:
		* String getName:获取方法名


* 案例:
	* 需求:写一个"框架",不能改变该类的任何代码的前提下,可以帮我们创建任意类的对象,并且执行其中任意方法
		* 实现:
			1. 配置文件
			2. 反射
		* 步骤:
			1. 将需要创建的对象的全类名和需要执行的方法定义在配置文件中
			2. 在程序中加载读取配置文件
			3. 使用反射技术来加载类文件进内存
			4. 创建对象
			5. 执行方法

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值