获取Class类

image.png
image.png
image.png
image.png

获取字段与实例的字段值

image.png
image.png

  1. public class Main {
  2. public static void main(String[] args) throws Exception {
  3. Object p = new Person("Xiao Ming");
  4. Class c = p.getClass();
  5. Field f = c.getDeclaredField("name");
  6. Object value = f.get(p);
  7. System.out.println(value); // "Xiao Ming"
  8. }
  9. }
  10. class Person {
  11. private String name;
  12. public Person(String name) {
  13. this.name = name;
  14. }
  15. }

上述代码先获取Class实例,再获取Field实例,然后,用Field.get(Object)获取指定实例的指定字段的值。

运行代码,如果不出意外,会得到一个IllegalAccessException,这是因为name被定义为一个private字段,正常情况下,Main类无法访问Person类的private字段。要修复错误,可以将private改为public,或者,在调用Object value = f.get(p);前,先写一句:f.setAccessible(true);

调用Field.setAccessible(true)的意思是,别管这个字段是不是public,一律允许访问。
可以试着加上上述语句,再运行代码,就可以打印出private字段的值。

有童鞋会问:如果使用反射可以获取private字段的值,那么类的封装还有什么意义?
答案是正常情况下,我们总是通过p.name来访问Person的name字段,编译器会根据public、protected和private决定是否允许访问字段,这样就达到了数据封装的目的。
而反射是一种非常规的用法,使用反射,首先代码非常繁琐,其次,它更多地是给工具或者底层框架来使用,目的是在不知道目标实例任何信息的情况下,获取特定字段的值。
此外,setAccessible(true)可能会失败。如果JVM运行期存在SecurityManager,那么它会根据规则进行检查,有可能阻止setAccessible(true)。例如,某个SecurityManager可能不允许对java和javax开头的package的类调用setAccessible(true),这样可以保证JVM核心库的安全。

反射不仅可以获取某一个实例的值,也可以修改其中的值:
f.set(p, “Xiao Hong”);

获取某一个类的方法

image.png
image.png
image.png
image.png
image.png
image.png
image.png
image.png

什么是反射机制?

“Java的反射(reflection)机制是指在程序的运行状态中,可以构造任意一个类的对象,可以了解任意一个对象所属的类,可以了解任意一个类的成员变量和方法,可以调用任意一个对象的属性和方法。这种动态获取程序信息以及动态调用对象的功能称为Java语言的反射机制。反射被视为动态语言的关键。”——百度百科


从上面这段话可以看出,反射能够做到的事情至少有以下四点:

  • 通过反射获取对于class的对象
  • 获取class中的所有字段
  • 获取class中的所有方法

看起来反射机制破坏了类的封装——私有变量在类外都可以访问,这一点不可否认,但与此同时,Java的反射机制其实只是提供了一个手段——走后门的手段,毕竟不是所有的封装都是天衣无缝,且与时俱进。

反射API

跟类的成员相匹配,反射API主要分为以下几类
Java反射机制 - 图15
举例说明:

  1. public class Apple {
  2. private int price;
  3. public int getPrice() {
  4. return price;
  5. }
  6. public void setPrice(int price) {
  7. this.price = price;
  8. }
  9. public static void main(String[] args) throws Exception{
  10. //正常的调用
  11. Apple apple = new Apple();
  12. apple.setPrice(5);
  13. System.out.println("Apple Price:" + apple.getPrice());
  14. //使用反射调用
  15. Class clz = Class.forName("com.chenshuyi.api.Apple");
  16. Method setPriceMethod = clz.getMethod("setPrice", int.class);
  17. Constructor appleConstructor = clz.getConstructor();
  18. Object appleObj = appleConstructor.newInstance();
  19. setPriceMethod.invoke(appleObj, 14);
  20. Method getPriceMethod = clz.getMethod("getPrice");
  21. System.out.println("Apple Price:" + getPriceMethod.invoke(appleObj));
  22. }
  23. }

原理

其实平时所用的Spring Boot之类的框架,很多都有反射的身影,例如Bean的生成。
先在pom.xml中添加类一个Bean(或是利用相应的注解):

  1. <bean class="com.chenshuyi.Apple">
  2. </bean>

在application启动的时候,就会利用反射机制去加载对应的类,并获取相应的方法
实际的 MethodAccessor 实现有两个版本,一个是 Native 版本,一个是 Java 版本
Native 版本一开始启动快,但是随着运行时间边长,速度变慢。Java 版本一开始加载慢,但是随着运行时间边长,速度变快。正是因为两种存在这些问题,所以第一次加载的时候我们会发现使用的是 NativeMethodAccessorImpl 的实现,而当反射调用次数超过 15 次之后,则使用MethodAccessorGenerator 生成的 MethodAccessorImpl 对象去实现反射。
Method 类的 invoke 方法整个流程可以表示成如下的时序图:Java反射机制 - 图16

参考资料

https://www.liaoxuefeng.com/wiki/1252599548343744/1264799402020448
简单讲解Java反射
Java反射机制的底层讲解