Java基础之反射初步理解

由于在学习框架时,经常会遇到反射,故此篇文章用于对反射的基本学习。

一、概述

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

对其简单的理解就是将类的各个组成部分封装为其他对象,以便我们能够更加细化的使用。同时,我们也都知道,Java中程序是运行在虚拟机中,我们平常用文本编辑器或者是IDE编写的程序都是.java格式的文件,这是最基础的源码,但这类文件是不能直接运行的,必须经过编译成.class字节码文件进而加载进内存供JVM虚拟机执行。要想理解反射,就先需要谈起Java代码在计算机中经历的三个阶段,见下图。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Sif8Mn61-1617344009442)(https://s1.ax1x.com/2020/09/04/wkOWlQ.png)]

第一个阶段 – 源代码阶段

定义一个了类,继而将一个类进行编译.class文件。

在第一阶段中,可以通过Class.forName("全类名")的方式获取Class类对象。

第二阶段 – 加载进内存阶段(Class对象阶段)

类加载器(ClassLoader):负责将字节码文件加载进内存。

在内存中需要由一个对象来描述加载进内存中这个Class文件 – Class类对象。

Class类对象的基本组成:

  • 成员变量 封装成Field[] fields对象
  • 构造方法 Constructor[] cons
  • 成员方法 Method[] methods

继而通过Class类对象创建真正的对象,供我们使用。

这一阶段可以通过类名.class的方式获取到Class类对象。

第三阶段 – Runtime阶段

通过类的实例化创建出对象。Runtime运行阶段 new 出类的实例化对象。

通过对象.getClass()方式获取到Class类对象。

综上上面的三个阶段对Class类对象的获取:同一个字节码文件(*.class)在一次程序运行过程中,只会被加载一次,不论通过哪一种方式获取的Class对象都是同一个(内存中的位置同一个,本质就是一个class文件)。

好处:

  1. 可以在程序运行过程中,操作这些对象。(例如:获取一个对象的方法,将一个类进行拆解,对其各个组成部分进行操作,获取到一个类对象在内存中的状态)
  2. 可以解耦,提高程序的可扩展性(在不改变原有代码的基础上,对功能进行增强实现)。

二、具体使用

Java反射相关的类:

类名用途
Class类代表类的实体,在运行的Java应用程序中表示类和接口
Field类代表类的成员变量(成员变量也称为类的属性)
Method类代表类的方法
Constructor类代表类的构造方法
2.1 Class对象功能
获取成员变量们获取构造方法们获取成员方法们获取全类名
Field[] getFields()Constructor<?>[] getConstructors()Method[] getMethods()String getName()
Field getField(String name)Constructor getConstructor(类<?>… parameterTypes)Method getMethod(String name, 类<?>… parameterTypes)
Field[] getDeclaredFields()Constructor getDeclaredConstructor(类<?>… parameterTypes)Method[] getDeclaredMethods()
Field getDeclaredField(String name)Constructor<?>[] getDeclaredConstructors()Method getDeclaredMethod(String name, 类<?>… parameterTypes)

其中*Declared*代表忽视权限修饰符的安全检查,可以获取一切权限的(成员变量、构造方法,成员方法);而上方的只能获取到public修饰的(成员变量,构造方法,成员方法)。

在忽视权限修饰符的同时,如果想对其成员变量\构造方法\成员方法使用等,可采用暴力反射(不推荐,降低了安全机制)。

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

  1. 成员变量Field

    1.1 设置值

    • void set(Object obj, Object value)

    1.2 获取值

    • get(Object obj)
  2. 构造方法Constructor

    2.1 创建对象

    • T newInstance(Object... initargs) – 非空参时,获取到Class类对象的对应的构造方法,再填入对应参数。
    • 如果使用空参数构造方法创建对象,操作可以简化:Class对象的newInstance方法。例如:Object obj = cls.newInstance
  3. 方法对象 – Method

    3.1 执行方法

    • Object invoke(Object obj, Object... args)

    3.2 获取方法名称

    • String getName:获取方法名
  4. ClassLoader

    class类对象.getClassLoader得到类加载器对象(获取到这个字节码文件对应的类加载器),负责将这个类加载进行内存。ClassLoader对象可以获取到内存中当前类路径下的文件信息。

三、代码实操

首先定义一个实体类:

Student实体类
public class Student implements Serializable {

    private String name;
    private String age;

    public Student() {
    }

    public Student(String name, String age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getAge() {
        return age;
    }

    public void setAge(String age) {
        this.age = age;
    }

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

    public void eat(String food) {
        System.out.println("eat.." + food + "...");
    }

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age='" + age + '\'' +
                '}';
    }
}

以上的Student实体类中,包含:

  • 成员变量:nameage
  • 构造方法:
    • public Student()
    • public Student(String name, String age)
  • 成员方法:gettersetterpublic void eat()public void eat(String food)
3.1 反射基本方法使用
3.1.1 各阶段对Class对象的获取
// 1.通过全类名获取class对象 -- 源码阶段
Class cls = Class.forName("cn.lizhi.domain.Student");
// 2.通过类名的方式获取class对象 -- 加载进行内存后
Class cls1 = Student.class;
// 3.类的实例化对象获取class对象 -- Runtime阶段
Student student = new Student();
Class cls2 = student.getClass();

其中clscls1cls2为同一个对象,内存地址值相等。

3.1.2 Class对象功能使用 – 成员属性实例
Student student = new Student();
Field[] fields = cls.getDeclaredFields(); // 获取全部的成员属性
for (Field field : fields) {
System.out.println(field);
}
Field name = cls.getDeclaredField("name"); // 获取指定的成员属性
name.setAccessible(true);  // 暴力反射
name.set(student, "Tom"); // 设置属性值(可以不改变原代码)
String value_name = (String) name1.get(student); // Tom
System.out.println(student);
3.1.3 Class对象功能使用 – 创建对象
// 有参构造方法
Constructor cs = cls.getDeclaredConstructor(String.class, String.class);
Object o = cs.newInstance("张三", "6"); // 通过获取构造方法创建对象
System.out.println(o);
// 空参构造方法
Object o1 = cls.newInstance();
3.1.4 Class对象功能使用 – 方法调用
Method eat = cls.getMethod("eat", String.class); // 确定带参的eat()方法
eat.invoke(o, "food"); // 传入对象与参数,执行方法
3.2 实例

需求:不改变该类的任何代码的前提下,可以帮我们创建任意类的对象,并且执行其中任意方法。

  1. 定义一个配置文件,配置文件中配置类名、方法名

    className=cn.lizhi.domain.Student
    methodName=eat
    
  2. 代码编写

        public static void main(String[] args) throws Exception {
            Properties pro = new Properties();
            // 类加载器 ClassLoader负责将这个类加载进内存
            InputStream is = InflectDemo01.class.getClassLoader().getResourceAsStream("pro.properties");
            pro.load(is);
            String className = pro.getProperty("className");
            String methodName = pro.getProperty("methodName");
            Class cls = Class.forName(className); // 获取class对象
            Object obj = cls.newInstance(); // 创建对象
            Method method = cls.getMethod(methodName,String.class); // 加载重载的方法
            method.invoke(obj,"fish"); // 执行方法
        }
    

    当我们需要创建其它类的对象和执行它的方法时,我们只需要修改配置文件即可,方便我们的解耦开发。

参考文献

[1] Java高级特性——反射

[2] 百度百科

[3] 黑马讲义

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值