你真的了解Java的反射机制吗?

2023年 11月 1日 50.4k 0

书写代码必须符合高质量高性能要求,这也是能够在视觉上和其他程序员拉开差距的技能,同时也是一个优秀程序员的基本要求。

  • 何为高质量:代码具备可维护性,可读性,可扩展性,灵活性,简洁性,可复用性, 可测试性。
  • 何为高性能:代码能尽可能的提高处理效率。

今天我们说一说反射,反射不是设计模式,但是反射机制作为java的基础之一,在众多框架的源码中大量使用,是很多设计模式,框架,组件的重要基础。比如我们知道的spring aop底层是jdk的动态代理,而动态代理依赖的就是反射机制。

一、反射机制

1.概念

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

先了解下类对象的概念:

我们知道类的加载过程为加载 验证 准备 解析 初始化 销毁,在加载阶段,jvm会根据类的全限定名找到二进制字节流,并把这个二进制字节流加载进内存,转为运行时数据区的存储结构,最后创建一个java.lang.Class类型的实例作为方法区这个类型的访问入口,这里所说的Class实例就是类对象,这个类对象创建完成后会存放在堆区,这个动作是在加载阶段完成。

方法区中存放的是类的元数据,包括静态变量,有哪些属性,有哪些方法,继承的父类,实现的接口,异常相关的信息等等,而类对象就是这些信息的访问入口,Class类提供了很多api,这些api大多是native方法,也就说明这个类对象只是这个类在堆区的一个接口,由jvm底层来实现,jvm底层会根据每个api的功能去方法区拿类的信息。

2.反射的应用

public static void main(String[] args) {

    UserService userService = new UserService();
    System.out.println("new关键字创建对象:"+userService);
    
    Class userClass= UserService.class;
      Class userClass1= Class.forName("UserService");
      
    Constructor[] constructors=userClass.getDeclaredConstructors();
    Constructor constructor=constructors[0];
    Object user=constructor.newInstance("333333","666666");
    System.out.println("反射创建对象:"+user;
}

通过上面的这个例子,我们可以看到反射是如何应用的:

  • 首先通过.class或者Class.forName()方法,获取一个Class实例,这个实例就是类对象,
  • 然后通过调用这个Class实例的方法获取类的构造方法,得到构造方法Constructor实例
  • 然后调用Constructor实例的newInstance方法进行实例化对象。

以上是利用反射机制创建对象,当然除了创建对象,还可以获取类的属性实例Field和方法实例Method,通过方法实例和属性实例的api对对象的方法和属性进行设置或者执行。这便是反射的应用。

3.反射的特点

new关键字创建对象是加载类完成后接着走创建对象过程,而反射过程是Class.forName()触发加载类,但是不会创建对象,只有在调用newInstance方法时候才会创建对象,也就是把加载类和创建对象分为两个部分完成,但是调用newInstance方法创建前必须保证类已经加载完成。

new关键字创建对象是静态编译,而反射创建对象是动态编译:

  • 静态编译:在编译的时候就已经知道要创建什么对象,就会把对应类加载(忽略懒加载)
  • 动态编译:在编译的时候不知道要创建什么对象,等到运行到这段代码的时候才知道要创建什么对象。

比如下面的代码,编译阶段是不知道是否要创建UserService类的对象的,所以UserService不会被加载:

public void reflex(String str) {
  
  if("UserService".equals(str)){
   Class userClass= Class.forName("UserService");
   Constructor[] constructors=userClass.getDeclaredConstructors();
   Constructor constructor=constructors[0];
   Object UserService=constructor.newInstance("333333","666666");
   System.out.println(UserService.toString());
  }
    
}

以java8为讨论基础,网上所说的反射只能通过无参构造方法创建对象是不正确的,事实证明,反射不仅仅可以通过有参构造方式创建对象,而且还可以通过私有构造方法创建对象,而且这种通过私有构造方法创建对象的方式会破坏单例模式,你想一下,单例模式中的构造方法之所以是私有就是为了不允许外部创建单例对象。而通过反射可以创建的话,那不是违背了单例模式的定理吗。

例:只是为了说明反射,所有代码中的单例只是一个简单的饿汉式单例:

public class IdGenerator {
   private String k;
   private static final IdGenerator instance = new IdGenerator();

   private IdGenerator(String k) {
    this.k=k;
   }

   public static IdGenerator getInstance() {
    return instance;
   }
  }
    
public class reflex {

    public static void main(String[] args){
  
  Class idGeneratorClass= Class.forName("IdGenerator");
  Constructor[] constructors=idGeneratorClass.getDeclaredConstructors();
  Constructor constructor=constructors[0];
    constructor.setAccessible(true);//暴力反射,可以突破私有权限
  Object idGenerator=constructor.newInstance("333333");
  System.out.println(idGenerator==IdGenerator.getInstance());
    
    }
}

这个例子既验证了反射调用有参构造方法创建实例,又验证了反射破坏单例模式。

反射会造成泛型擦除:

List list=new ArrayList();

  list.add(new UserService());
  list.add(new UserService());
  list.add(new UserService());

  Class[] constructors=userClass.getDeclaredConstructors();
Constructor constructor=constructors[0];
Object UserService=constructor.newInstance("333333","666666"); 

所谓暴力反射,就是当类中有私有构造方法,私有属性,私有方法的时候,对这些对象进行反射调用的时候会报错,原因是无法突破私有权限,反射调用前先调用对象的setAccessible方法,设置为true,就可以突破私有权限,代码可以看上面破坏单例的例子。

相关文章

JavaScript2024新功能:Object.groupBy、正则表达式v标志
PHP trim 函数对多字节字符的使用和限制
新函数 json_validate() 、randomizer 类扩展…20 个PHP 8.3 新特性全面解析
使用HTMX为WordPress增效:如何在不使用复杂框架的情况下增强平台功能
为React 19做准备:WordPress 6.6用户指南
如何删除WordPress中的所有评论

发布评论