Java反射介绍

Java反射

今天来学习一下Java一个非常重要的特性:反射

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

image-20220915212404324

当然这里不理解没关系,我们来详细解释一下Java反射机制,在介绍之前,先了解几个基本概念:动态语言静态语言

动态语言VS静态语言

动态语言:

是一类在运行时可以改变其结构的语言:例如新的函数、对象、甚至代码可以 被引进,已有的函数可以被删除或是其他结构上的变化。

主要动态语言:Object-C、C#、JavaScript、PHP、Python、Erlang。

简单说就是动态语言在运行时代码可以根据某些条件改变自身结构,比如运行时新加个函数并调用。

静态语言:

与动态语言相对应的,运行时结构不可变的语言就是静态语言。如Java、C、C++。Java不是动态语言,但Java可以称之为“准动态语言”。即Java有一定的动态性,我们可以利用反射机制、字节码操作获得类似动态语言的特性。 Java的动态性让编程的时候更加灵活!

而静态语言就是编译完成后,运行时无法动态的添加新的函数新的变量之类的。我们可以看到Java不是动态语言,但是个准动态语言这是为什么呢?这就是反射的功劳了。

Java语言的编译和运行过程

ok,为了帮助理解,我们从Java编译运行开始介绍:

Java编译运行过程一般分为以下三个阶段:

第一阶段:源码阶段。通俗来说就是我们编写的demo.java源文件,我们使用javac demo.java可以生成字节码文件demo.class,字节码文件中存放着我们编写的变量,方法等,此时字节码文件只是存在硬盘上,还没有进入内存调用。

第二阶段:Class类对象阶段。通过类加载器(ClassLoader)把字节码文件demo.class加载进内存。在Java语言中,万物皆可对象。那我们自己编写的demo.class也是个对象喽,那这个Class对象的类是谁呢?当然就是Class类了。Class类是一个特殊类,它用于表示JVM运行时类或接口的信息。第二阶段就是通过Class这个类对象描述demo.class这个字节码内存空间,Class类对象里面就有三个比较重要的东西,成员变量,成员方法和构造方法;把这三部分同样都封装为对象,成员变量封装为Field对象,构造方法封装为Constructor对象,成员方法封装为Method对象。因为成员变量可能不止一个,所以一般都用数组Field[ ] fields来描述成员变量,其他两部分也是用数组的形式。这实际上就是反射的基础,这就是为什么说反射就是把java类中的各种成分映射成一个个的Java对象

image-20220915214625379

第三阶段:Runtime运行时阶段。这个时候JVM内存中就有了我们编写的dome.class的类对象了我们就可以new demo()生成一个对象并调用执行啦。

经过上述三个阶段,你是不是会发现一些奇怪的东西:既然.class文件是以Class类对象存在的,并且.class中的方法也是一个对象,那我岂不是可以随便访问各个Class类对象,随便执行各个类方法喽?没错,实际上这就是反射,实现Java反射机制的类都位于java.lang.reflect包中:

  • java.lang.Class类:代表整个字节码。代表一个类型,代表整个类

  • java.lang.reflect.Field类:代表字节码中的属性字节码。代表类中的成员变量(静态变量+实例变量)。

  • java.lang.reflect.Method类:代表字节码中方法字节码。代表类的方法

  • java.lang.reflect.Constructor类:代表字节码中的构造方法字节码。代表类的构造方法

  • Array类:提供了动态创建数组,以及访问数组的元素的静态方法

当然我们首先需要拿到这个类对象,我们怎么拿到整个类对象呢???

Class加载器

在将硬盘文件.class加载至JVM内存中时,我们使用到了ClassLoader类加载器,类加载器主要分为三大类:

  • 根加载器(null):

    它是由本地代码(c/c++)实现的,你根本拿不到他的引用,但是他实际存在,并且加载一些重要的类,它加载(%JAVA_HOME%\jre\lib),如rt.jar(runtime)、i18n.jar等,这些是Java的核心类。

  • 平台类加载器(PlatformClassLoader):

    虽说能拿到,但是我们在实践中很少用到它,它主要加载扩展目录下的jar包, %JAVA_HOME%\lib\ext

  • 应用类加载器(appClassLoader):

    它主要加载我们应用程序中的类,如Test,或者用到的第三方包,如jdbc驱动包等。这里的父类加载器与类中继承概念要区分,它们在class定义上是没有父子关系的。

当一个类被加载时,存在着委托加载机制,比如当我们加载demo.class时,他首先会启动应用类加载器加载demo类,但是这个加载器不会直接去加载demo.class,而是先看看是否有父加载器,结果有是扩展类加载器;扩展类加载器同样的,先查看是否有父加载器,结果是根类加载器。所以首先由根类加载器去加载这个类,根类加载器会在%JAVA_HOME%\jre\lib寻找这个类,结果没找到,返回扩展加载器;扩展加载器在%JAVA_HOME%\lib\ext寻找,没找到继续返回应用类加载器;应用加载器在当前应用的classpath中搜索,结果找到了。

这就死Java中著名的委托加载机制:

image-20220916090759134

参考链接:

一看你就懂,超详细java中的ClassLoader详解

获取Class类对象

在上面三个阶段中,我们都可以获得Class类对象,只不过方式不同。

获取Class对象的方式:
 1. Class.forName("全类名"):将字节码文件加载进内存,返回Class对象。(全类名:包名+类名)
 		- 多用于配置文件,将全类名定义在配置文件中。读取文件,加载类。
 		- 方法实际上也是调用的CLassLoader来实现的
 2. 类名.class:通过类名的属性class获取
 		- 多用于参数的传递,有时候传参需要传一个Class对象
 3. 对象.getClass():getClass方法在Object中定义着
 		- 多用于对象获取字节码的方式
 4. 使用类加载器进行加载:classLoader.loadClass()。
 		- 与1类似,实际上就是1的内部实现
package main.test;

import java.io.IOException;
import main.test.Person;

public class demo {
    /**
     * @param args
     * @throws ClassNotFoundException
     */
    public static void main(String[] args) throws ClassNotFoundException {
        // Class.forName("全类名")。
        Class cls1 = Class.forName("main.test.Person");
        System.out.println(cls1);

        // 类名.class
        Class cls2 = Person.class;
        System.out.println(cls2);

        // 对象.getClass()
        Person person = new Person();
        Class cls3 = person.getClass();
        System.out.println(cls3);
        
        //类加载器获取类对象
        ClassLoader classLoader = demo.class.getClassLoader();
        Class cls4 = classLoader.loadClass("main.test.Person");
        System.out.println(cls4);
    }
}

image-20220915222931192

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

总结一下,Java整个加载与反射机制如下图所示:demo.java内容调用Student类时,JVM发现内存中不存在Student类对象,因此JVM会动态读取硬盘上Student.class源文件,将Student.class加载至内存中;同时会使用Class类对象描述Student.class,当然同一个.class只会生成一个Class对象,之后再次调用也是使用的这一个类对象。反射的本质就是通过获得Class对象后,可以反向获取Studnet对象的各种信息。

image-20220915215013037

Class对象功能

获取成员变量

 + Field[ ] getFields() :获取所有public的成员变量。
 + Filed[ ] getField(String name): 获取指定名称的public修饰的成员变量。
 
 + Field[ ] getDeclaredFields() : 获取所有成员变量,无视修饰符。
 + Field[ ] getDeclaredField(String name): 获取指定名称的成员变量,无视修饰符。

image-20220916195524963

获取或修改变量值

 + Object get(Object obj)  :获取值
 + void set(Object obj, Object value)  : 设置值
 + setAccessible(true) : 暴力反射,忽略访问权限修饰符的安全检查

image-20220916200858752

获取构造方法

 + Constructor<?>[] getConstructors()  
 + Constructor<T> getConstructor(类<?>... parameterTypes)  

 + Constructor<T> getDeclaredConstructor(类<?>... parameterTypes)  
 + Constructor<?>[] getDeclaredConstructors() 

image-20220916222315689

 + 创建对象
 + T newInstance(Object... initargs)  

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

image-20220916223349730

获取成员方法

 + Method[] getMethods()  获得所有public方法,包括从父类继承的
 + Method getMethod(String name, 类<?>... parameterTypes)  

 + Method[] getDeclaredMethods()  获得本类声明的所有方法
 + Method getDeclaredMethod(String name, 类<?>... parameterTypes)  

image-20220916223723397

执行方法:
Object invoke(Object obj, Object... args)  
获取方法名
Method.getName()

image-20220916224609295

获取类名

 + 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 object,Object… args)
获取方法的名称:
String getName()
以Class类型,返回方法类型【一般配合Class类的getSimpleName()方法使用】
public Class<?> getReturnType()
返回方法的修饰符列表(一个方法的参数可能会有多个。)【结果集一般配合Class类的getSimpleName()方法使用】
public Class<?>[] getParameterTypes()

源码:

package main.test;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class demo {
    /**
     * @param args
     * @throws ClassNotFoundException
     * @throws SecurityException
     * @throws NoSuchFieldException
     * @throws IllegalAccessException
     * @throws IllegalArgumentException
     * @throws InvocationTargetException
     * @throws InstantiationException
     * @throws NoSuchMethodException
     */
    public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException, InstantiationException, InvocationTargetException, NoSuchMethodException {
        // Class.forName("全类名"),获取Class对象
        // Class.forName("全类名")。
        Class cls1 = Class.forName("main.test.Person");
        System.out.println("Class.forName获取类对象:"+cls1);

        // 类名.class
        Class cls2 = Person.class;
        System.out.println("类名.class获取类对象:"+cls2);

        // 对象.getClass()
        Person person = new Person();
        Class cls3 = person.getClass();
        System.out.println("对象.getClass()获取对象:"+cls3);

        // 类加载器获取类对象
        ClassLoader classLoader = demo.class.getClassLoader();
        Class cls4 = classLoader.loadClass("main.test.Person");
        System.out.println("类加载器获取类对象:"+cls4);


        // 获取public修饰的成员变量
        Field[] fields = cls1.getFields();
        for (Field field : fields) {
            System.out.println("类对象公共成员变量:" + field);
        }
        
        // 获得所有声明的成员变量
        Field[] fields2 = cls1.getDeclaredFields();
        for (Field field : fields2) {
            System.out.println("类对象所有声明成员变量:" + field);
        }

        // 获得变量值
        Field name = cls1.getDeclaredField("name");
        // 暴力反射,忽略访问权限修饰符的安全检查
        name.setAccessible(true);
        Person p=new Person();
        Object value = name.get(p);
        System.out.println("初始值:"+value);
        name.set(p, "123");
        value=name.get(p);
        System.out.println("修改值:"+value);

        // 获取类对象构造方法
        Constructor[] constructors=cls1.getConstructors();
        for (Constructor constructor : constructors) {
            System.out.println("类对象构造方法" + constructor);
        }
        // 创建对象
        Object o = constructors[1].newInstance("张三", 22, "未知");
        System.out.println(o);

        // 获取类对象成员方法
        Method[] methods = cls1.getDeclaredMethods();
        for (Method method : methods) {
            System.out.println("类对象成员方法" + method);
        }

        // 调用类对象方法
        Method show = cls1.getDeclaredMethod("show");
        show.invoke(o);
    }
}

反射有啥用?

看到这里会觉得反射很麻烦,没有体现出它的价值。接下来看个案例,就会一目了然了。这个案例就是写一个类,在不改变该类任何代码的前提下,可以帮助我们创建任意类的对象,并且执行其中任意方法。

步骤

 1. 将需要创建的对象的全类名和需要执行的方法定义在配置文件
 2. 在程序中加载读取配置文件
 3. 使用反射技术来加载类文件进入内存
 4. 创建对象
 5. 执行方法

Person类

package main.test;

public class Person {
    private String name = "tian";
    private int age = 22;
    public String sex = "男";

    public Person() {
    }

    public Person(String n, int a, String s) {
        name = n;
        age = a;
        sex = s;
    }

    public void eat() {
    }

    private void sleep() {

    }

    public void show() {
        System.out.println(name+Integer.toString(age)+sex);
    }
}

pro.properties配置文件:

className=main.test.Person
methodName=show

ReflectTest类:

package main.test;

import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Properties;

public class ReflectTest {
    public static void main(String[] args)
            throws ClassNotFoundException, NoSuchFieldException, SecurityException, IllegalArgumentException,
            IllegalAccessException, InstantiationException, InvocationTargetException, NoSuchMethodException, IOException {
        // 前提:不能修改该类的代码
        // Person p = new Person();
        // p.show();

        // 1.加载配置文件
        // 1.1 创建Properties对象
        Properties pro = new Properties();
        // 1.2 加载配置文件,转化为一个集合
        // 1.2.1 获取class目录下的配置文件
        ClassLoader classLoader = ReflectTest.class.getClassLoader();// 通过类加载器将字节码文件加载进内存
        InputStream ins = classLoader.getResourceAsStream("pro.properties");
        // 这里的pro.properties是包的位置,比如说我这个是main.test包内的,所以pro.properties是在/bin/内,/bin内存放是/main/test包
        pro.load(ins);

        // 2.获取配置文件中定义的数据
        String className = pro.getProperty("className");
        String methodName = pro.getProperty("methodName");

        // 3.加载该类进内存
        Class cls = Class.forName(className);
        // 4.创建对象
        Object o = cls.newInstance();
        // 5.获取方法对象
        Method method = cls.getMethod(methodName);
        // 6.执行方法
        method.invoke(o);

    }
}

image-20220916233917844

将需要生成的对象的信息放在配置文件中,这样我们不需要去修改ReflectTest的代码,只需要修改配置文件中className,methodName即可;如果不使用配置文件,就需要在ReflectTest类中写死,Person p = new Person(); p.show();那如果想要生成别的对象每次都需要去找到ReflectTest类,修改Student为别的类。使用反射后,增加了程序的灵活性,避免将代码写死,降低了耦合性。在框架的设计思想里用到的就是反射。

参考链接

Java基础之—反射(非常重要)

Java反射技术详解

java 反射机制,为什么要用反射?

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值