反射 及 Reflections反射框架

Posted wx62b6dba7e04cf

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了反射 及 Reflections反射框架相关的知识,希望对你有一定的参考价值。

一、类加载过程

反射
要知道反射机制,还需要理解类的加载过程。总的来说,类加载的五个过程:加载、验证、准备、解析、初始化。
除了加载(装载)阶段,用户可以通过自定义的类加载器参与,其他阶段都完全由虚拟机主导和控制。
反射

(1)装载

加载指的是把class字节码文件从各个来源通过类加载器装载入内存中。有两处需要说明一下:

  1. 字节码来源。一般的加载来源包括从本地路径下编译生成的.class文件,从jar包中的.class文件,从远程网络,以及动态代理实时编译而来的.class文件
  2. 类加载器。一般包括启动类加载器,扩展类加载器,应用类加载器,以及用户的自定义类加载器。(为什么会有自定义类加载器?一方面是由于java代码很容易被反编译,如果需要对自己的代码加密的话,可以对编译后的代码进行加密,然后再通过实现自己的自定义类加载器进行解密,最后再加载。另一方面也有可能从非标准的来源加载代码,比如从网络来源,那就需要自己实现一个类加载器,从指定源进行加载。)

具体的,在加载阶段,虚拟机主要完成三件事:

  1. 通过一个类的全限定名(包名与类名)来获取定义此类的二进制字节流(Class文件)。而获取的方式,可以通过jar包、war包、网络中获取、JSP文件生成等方式。
  2. 将这个字节流所代表的静态存储结构转化为方法区域的运行时数据结构(方法区就是用来存放已被加载的类信息,常量,静态变量,编译后的代码的运行时内存区域),这里只是结构的转化,不涉及其他操作。
  3. 在方法区中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口。(这个Class对象并没有规定是在Java堆内存中,它比较特殊,虽为对象,但存放在方法区中。)
(2)验证(链接过程)

类的加载过程后生成了类的java.lang.Class对象,接着会进入链接阶段,连接阶段负责将类的二进制数据合并入JRE(Java运行时环境)中。类的连接大致分三个阶段,首先是验证:即验证被加载后的类是否有正确的结构,类数据是否会符合虚拟机的要求,确保不会危害虚拟机安全。
这个验证很好理解,加载过程只是完成了.class文件的导入,但导入的文件是否有效有用符合规范就不能保证了,因此需要验证。

(3)准备(链接过程)

准备就是为类的静态变量、静态常量在方法区分配内存,并为静态变量赋默认初值(0值或null值)。如static int a = 100; 静态变量a就会在准备阶段被赋默认值0。(普通的成员变量是在类实例化时候,随对象一起分配在堆内存中。)

(4)解析(链接过程)

将类的二进制数据中的符号引用换为直接引用。(就像学号和学生姓名映射关系一样,利用学号找到姓名再找到这个学生就是符号引用,学号就是符号;利用姓名直接找到学生就是直接引用)

  • 符号引用(Symbolic References):以一组符号来描述所引用的目标,符号可以是任何形式的字面量,只要使用时能够无歧义的定位到目标即可。符号引用的字面量形式明确定义在Java虚拟机规范的Class文件格式中。例如,在Class文件中它以CONSTANT_Class_info、CONSTANT_Methodref_info等类型的常量出现。符号引用与虚拟机的内存布局无关,引用的目标并不一定加载到内存中。在Java中,一个java类将会编译成一个class文件。在编译时,java类并不知道所引用的类的实际地址,因此只能使用符号引用来代替。比如org.simple.People类引用了org.simple.Language类,在编译时People类并不知道Language类的实际内存地址,因此只能使用符号org.simple.Language(假设是这个,当然实际中是由类似于CONSTANT_Class_info的常量来表示的)来表示Language类的地址。各种虚拟机实现的内存布局可能有所不同,但是它们能接受的符号引用都是一致的,因为符号引用的字面量形式明确定义在Java虚拟机规范的Class文件格式中。
  • 直接引用:
    直接引用可以是(1)直接指向目标的指针(比如,指向“类型”【Class对象】、类变量、类方法的直接引用可能是指向方法区的指针)(2)相对偏移量(比如,指向实例变量、实例方法的直接引用都是偏移量)(3)一个能间接定位到目标的句柄。直接引用是和虚拟机的布局相关的,同一个符号引用在不同的虚拟机实例上翻译出来的直接引用一般不会相同。如果有了直接引用,那引用的目标必定已经被加载入内存中了。
(5)初始化

到了初始化阶段才真正执行Java代码。
类的初始化的主要工作是为静态变量赋程序设定的初值。
如static int a = 100;在准备阶段,a被赋默认值0,在初始化阶段就会被赋值为100。

二、什么是反射

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

反射就是在运行前只知道这个类名称是啥,在运行时才知道要操作的类是什么,并可以在运行时获取类的完整构造,并调用对应的方法。

反射之中包含了一个「反」字,所以想要解释反射就必须先从「正」开始解释。
「正」常的,我们使用某个类时必定知道它是什么类,是用来做什么的。先导入,然后对这个类进行实例化,之后使用这个类对象进行操作。
「反」过来,反射是开始并不知道我要初始化的类对象是什么,无法使用 new 关键字来创建对象。但是由上面类的加载过程可知,类加载器在加载好某个类的.class文件后,都会在方法区中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口,这就为使用这个类提供大门。(Class对象的由来是将class文件读入内存,并为之创建一个Class对象,每个类只有唯一的Class对象)
总的来说,反射是动态加载,也就是在运行的时候才会加载,而不是在编译的时候。
正常导入后再使用如下:

package Reflect;

public class Ball
private int price;
public int getPrice()
return price;

public void setPrice(int price)
this.price = price;

package Reflect;

public class Demo1
public static void main(String[] args)
Ball ball = new Ball();
ball.setPrice(100);
System.out.println(ball.getPrice());

通过JDK 提供的反射 API 进行反射调用如下:

public class Demo1 
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException
// 正常调用
Ball ball = new Ball();
ball.setPrice(66);
System.out.println("正常调用" + ball.getPrice());
// 反射调用
// (此时只知道这个类叫啥,这个类的内部信息还一无所知)
Class clz = Class.forName("Reflect.Ball");
// (通过这个类的Class对象去了解这个类的内部)
Method setMethod = clz.getMethod("setPrice", int.class);
Constructor constructor = clz.getConstructor();
Object object = constructor.newInstance();
setMethod.invoke(object, 66);
Method getMethod = clz.getMethod("getPrice");
System.out.println("反射调用" + getMethod.invoke(object));

正常调用66
反射调用66

上面两段代码的执行结果,其实是完全一样的。但是其思路完全不一样,第一段代码在未运行时就已经确定了要运行的类(Ball),而第二段代码则是在运行时通过字符串值才得知要运行的类(Reflect.Ball)。

三、Class对象特点

  1. Class 类的实例对象表示正在运行的 Java 应用程序中的类和接口。也就是jvm中有很多的实例,每个类都有唯一的Class对象。
  2. Class 类没有公共构造方法。Class 对象是在加载类时由 Java 虚拟机自动构造的。也就是说我们不需要创建,JVM已经帮我们创建了。
  3. Class 对象用于提供类本身的信息,比如有几种构造方法, 有多少属性,有哪些普通方法

四、反射的用途

反射机制允许程序在运行时取得任何一个已知名称的class的内部信息,包括其modifiers(修饰符),fields(属性),methods(方法)等,并可于运行时改变fields内容或调用methods。那么我们便可以更灵活的编写代码,代码可以在运行时装配,无需在组件之间进行源代码链接,降低代码的耦合度;还有动态代理的实现等等。

说完是不是很糊涂?

那就举个例子,例如:在日常的第三方应用开发过程中,经常会遇到某个类的某个成员变量、方法或是属性是私有的或是只对系统应用开放,这时候就可以利用Java的反射机制通过反射来获取所需的私有成员或是方法。另外虽然反射并不能方便你去创建一个对象,但会让代码更加灵活,降低耦合,提高代码的自适应能力。

还是有点糊涂?OK,那就再具体点:

① 假如我需要实例化一个HashMap,代码就会是这样子:

Map<Integer, Integer> map = new HashMap<>();
map.put(1, 1);

② 某一天发现,该段程序不适合用 HashMap 存储键值对,更倾向于用LinkedHashMap存储,怎么办?只能改代码:

Map<Integer, Integer> map = new LinkedHashMap<>();
map.put(1, 1);

③ 假如又有一天,发现数据还是适合用 HashMap来存储,难道又要重新修改源码吗?
发现问题了吗?我们每次改变一种需求,都要去重新修改源码,然后对代码进行编译,打包,再到 JVM 上重启项目。这么些步骤下来,效率非常低。
④ 这种需求频繁变更但变更不大的场景,频繁地更改源码肯定是一种不允许的操作,你可能想到了使用if/else,判断什么时候使用哪一种数据结构,修改成下面的:

public Map<Integer, Integer> getMap(String param) 
Map<Integer, Integer> map = null;
if (param.equals("HashMap"))
map = new HashMap<>();
else if (param.equals("LinkedHashMap"))
map = new LinkedHashMap<>();
else if (param.equals("WeakHashMap"))
map = new WeakHashMap<>();

return map;

通过传入参数param决定使用哪一种数据结构,可以在项目运行时,通过动态传入参数决定使用哪一个数据结构。
貌似解决了,但真的以后都不用改代码了么?
⑤ 如果某一天还想用TreeMap,还是避免不了修改源码。

怎么办?这个时候,反射就派上用场了。
在代码运行之前,不确定将来会使用哪一种数据结构,只有在程序运行时才决定使用哪一个数据类,而反射可以在程序运行过程中动态获取类信息和调用类方法。通过反射构造类实例,代码会演变成下面这样:

public Map<Integer, Integer> getMap(String className) 
Class clazz = Class.forName(className);
Consructor con = clazz.getConstructor();
return (Map<Integer, Integer>) con.newInstance();

无论使用什么 Map,只要实现了Map接口,就可以使用全类名路径传入到方法中,获得对应的 Map 实例。例如java.util.HashMap / java.util.LinkedHashMap····如果要创建其它类例如WeakHashMap,也不需要修改上面这段源码。

所以看出反射的好处了吧?
对于在编译期无法确定使用哪个数据类的场景,通过反射可以在程序运行时构造出不同的数据类实例。
不用修改源码,也就是上面说的可以使代码更灵活,代码可以在运行时装配,无需在组件之间进行源代码链接,降低代码的耦合度。

五、反射机制的相关类

类名

用途

Class类

代表类的实体,在运行的Java应用程序中表示类和接口

Field类

代表类的成员变量(成员变量也称为类的属性)

Method类

代表类的方法

Constructor类

代表类的构造方法

Class类

包含以下方法(黄色标注为分界,获得类、类中属性、类中注解、类中构造器、类中方法):

类名

用途

asSubclass(Class clazz)

把传递的类的对象转换成代表其子类的对象

Cast

把对象转换成代表类或是接口的对象

getClassLoader()

获得类的加载器

getClasses()

返回一个数组,数组中包含该类中所有公共类和接口类的对象

getDeclaredClasses()

返回一个数组,数组中包含该类中所有类和接口类的对象,包括private 声明的和继承类

forName(String className)

根据类名返回类的对象

getName()

获得类的完整路径名字

newInstance()

Java非常好用的反射框架Reflections

java 反射工具包reflections

翻译8 Unity Reflections

Stochastic Screen Space Reflections:SSR

反射工具

反射基础

(c)2006-2024 SYSTEM All Rights Reserved IT常识