❓❓❓反射是啥呀相信许多学java的同学非常困惑在学的时候,总是感觉懂了却又没懂或者直接忽略过去了,那么本文就带大家探讨一下什么是反射在java中以及它的机制和运用。
⭐️什么是反射:
首先我们知道一些知识:
维基百科的解释
在计算机学中,反射式编程(英语:reflective programming)或反射(英语:reflection),是指计算机程序在运行时(runtime)可以访问、检测和修改它本身状态或行为的一种能力.用比喻来说,反射就是程序在运行的时候能够"观察"并且修改自己的行为。
要注意术语"反射"和"内省"(type introspection)的关系。内省(或称"自省")机制仅指程序在运行时对自身信息(称为元数据)的检测;反射机制不仅包括要能在运行时对程序自身信息进行检测,还要求程序能进一步根据这些信息改变程序状态或结构。
这个解释用在java就是通过jvm创建class示例进行操作反过来又能对对象进行访问和修改。反射(Reflection)是 Java 的一种特性,它可以让程序在运行时获取自身的信息,并且动态地操作类或对象的属性、方法和构造器等。通过反射功能,可以让我们在不知道具体类名的情况下,依然能够实例化对象,调用方法以及设置属性。
首先知道一个加载过程
Java语言的编译过程主要包括以下几个步骤:
词法分析:
词法分析器(Lexer)读取源代码文件,将源代码中的字符序列转换为记号(Token)序列。这些记号是编译器进一步处理的基本单元。
语法分析:
语法分析器(Parser)根据Java语言的语法规则,将词法分析器输出的记号序列组织成语法树(Abstract Syntax Tree, AST)。语法树表示了源代码中的程序结构。
语义分析:
语义分析器检查语法树是否满足语义规则,比如变量是否已经声明、类型是否匹配、表达式是否有意义等。这个阶段还会进行一些类型推断和转换。
字节码生成:
编译器将经过语义分析后的语法树转换成字节码(Bytecode)。字节码是一种平台无关的中间代码,它可以在任何安装了Java虚拟机(JVM)的平台上运行。
具体来说,Java编译过程如下:
源代码到字节码:
使用javac命令或Java编译器API将.java源文件编译成.class字节码文件。这一步包含了上述的词法分析、语法分析、语义分析和字节码生成。
字节码验证:
当.class文件被加载到JVM时,字节码验证器会检查字节码文件,确保其遵循Java语言规范,没有安全问题,比如确保不会发生数组越界、栈溢出等。
类加载:
类加载器(ClassLoader)负责将.class文件加载到JVM中,并为字节码分配内存,将其转换为运行时数据结构。
运行时优化:
JVM可能会对字节码进行即时编译(Just-In-Time, JIT)优化,将字节码转换成本地机器码以提高执行效率。
执行:
最终,在JVM上执行这些字节码,程序开始运行。
这个过程确保了Java语言的"一次编写,到处运行"的特性,因为编译生成的字节码可以在任何有JVM的平台上执行。
然后我们正式开始:
反射之中包含了一个「反」字,所以想要解释反射就必须先从「正」开始解释。
一般情况下,我们使用某个类时必定知道它是什么类,是用来做什么的。于是我们直接对这个类进行实例化,之后使用这个类对象进行操作。
c
Apple apple = new Apple(); //直接初始化,「正射」
apple.setPrice(4);
上面这样子进行类对象的初始化,我们可以理解为「正」。
而反射则是一开始并不知道我要初始化的类对象是什么,自然也无法使用 new 关键字来创建对象了。
这时候,我们使用 JDK 提供的反射 API 进行反射调用:
c
Class clz = Class.forName("com.muggle.reflect.Apple");
Method method = clz.getMethod("setPrice", int.class);
Constructor constructor = clz.getConstructor();
Object object = constructor.newInstance();
method.invoke(object, 4);
上面两段代码的执行结果,其实是完全一样的。但是其思路完全不一样,第一段代码在未运行时就已经确定了要运行的类(Apple),而第二段代码则是在运行时通过字符串值才得知要运行的类(com.chenshuyi.reflect.Apple)。
所以说什么是反射?
反射就是在运行时才知道要操作的类是什么,并且可以在运行时获取类的完整构造,并调用对应的方法。
(1)Java反射机制的核心是在程序运行时动态加载类并获取类的详细信息,从而操作类或对象的属性和方法。本质是JVM得到class对象之后,再通过class对象进行反编译,从而获取对象的各种信息。
(2)Java属于先编译再运行的语言,程序中对象的类型在编译期就确定下来了,而当程序在运行时可能需要动态加载某些类,这些类因为之前用不到,所以没有被加载到JVM。通过反射,可以在运行时动态地创建对象并调用其属性,不需要提前在编译期知道运行的对象是谁。
⭐️获取Class对象的三种方式
java
package fanshe;
/**
* 获取Class对象的三种方式
* 1 Object ------> getClass();
* 2 任何数据类型(包括基本数据类型)都有一个"静态"的class属性
* 3 通过Class类的静态方法:forName(String className)(常用)
*
*/
public class Fanshe {
public static void main(String[] args) {
//第一种方式获取Class对象
Student stu1 = new Student();//这一new 产生一个Student对象,一个Class对象。
Class stuClass = stu1.getClass();//获取Class对象
System.out.println(stuClass.getName());
//第二种方式获取Class对象
Class stuClass2 = Student.class;
System.out.println(stuClass == stuClass2);//判断第一种方式获取的Class对象和第二种方式获取的是否是同一个
//第三种方式获取Class对象
try {
Class stuClass3 = Class.forName("fanshe.Student");//注意此字符串必须是真实路径,就是带包名的类路径,包名.类名
System.out.println(stuClass3 == stuClass2);//判断三种方式是否获取的是同一个Class对象
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
⭐️反射的运用
框架:半成品软件。可以在框架的基础上进行软件开发,简化编码
反射:将类的各个组成部分封装为其他对象,这就是反射机制
好处:
可以在程序运行过程中,操作这些对象。
可以解耦,提高程序的可扩展性。
像咱们平时大部分时候都是在写业务代码,很少会接触到直接使用反射机制的场景。但是,这并不代表反射没有用。相反,正是因为反射,你才能这么轻松地使用各种框架。像 Spring/Spring Boot、MyBatis 等等框架中都大量使用了反射机制。这些框架中也大量使用了动态代理,而动态代理的实现也依赖反射。比如下面是通过 JDK 实现动态代理的示例代码,其中就使用了反射类 Method 来调用指定的方法。
java
public class DebugInvocationHandler implements InvocationHandler {
/**
* 代理类中的真实对象
*/
private final Object target;
public DebugInvocationHandler(Object target) {
this.target = target;
}
public Object invoke(Object proxy, Method method, Object[] args) throws InvocationTargetException, IllegalAccessException {
System.out.println("before method " + method.getName());
Object result = method.invoke(target, args);
System.out.println("after method " + method.getName());
return result;
}
}
另外,像 Java 中的一大利器 注解 的实现也用到了反射。为什么你使用 Spring 的时候 ,一个@Component注解就声明了一个类为 Spring Bean 呢?为什么你通过一个 @Value注解就读取到配置文件中的值呢?究竟是怎么起作用的呢?这些都是因为你可以基于反射分析类,然后获取到类/属性/方法/方法的参数上的注解。你获取到注解之后,就可以做进一步的处理。谈谈反射机制的优缺点优点:可以让咱们的代码更加灵活、为各种框架提供开箱即用的功能提供了便利缺点:让我们在运行时有了分析操作类的能力,这同样也增加了安全问题。比如可以无视泛型参数的安全检查(泛型参数的安全检查发生在编译时)。
在使用 JDBC 连接数据库时使用 Class.forName()通过反射加载数据库的驱动程序,如果是mysql则传入mysql的驱动类,而如果是oracle则传入的参数就变成另一个了。
Spring 框架的 IOC(动态加载管理 Bean),Spring通过配置文件配置各种各样的bean,你需要用到哪些bean就配哪些,spring容器就会根据你的需求去动态加载,你的程序就能健壮地运行。
还有Spring AOP(动态代理)功能都和反射有关系。
除此之外还有很多框架:mybatis、dubbo、rocketmq等等都会用到反射机制。
参考
javaguide
java非常重要的基础概念之一
反射