Java反射概述

反射是运行时类型信息(RunTime Type Information, RTTI)的一种实现方式。运行时类型信息使得程序开发人员可以在程序运行时发现和使用类型信息。
Java反射就是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性,并且能改变它的属性值。

RTTI

面向对象编程基本的目的是:让代码只操纵对基类的引用。这样,如果需要添加一个新类来扩展程序,就不需要修改客户端代码。也即多态。
但是,如果碰到一个特殊的编程问题————如果能够知道某个泛化引用的确切类型,就可以使用最简单的方式去使用它。使用RTTI,可以查询某个实体类的对象的确切类型,然后选择或剔除特例。

Class对象

Class对象是用来创建类的所有的“常规”对象的,它包含了与类有关的信息。Class类提供了大量的使用RTTI的方式。
类是程序的一部分,每个类都有一个Class对象。这个Class对象就是被保存在同名.class文件中。为了构造这个类的对象,JVM将使用被称为“类加载器”的子系统。类加载器从Class文件中将描述类的数据加载到内存,并对数据进行校验、解析和初始化,最终形成可直接使用的Class对象。一旦某个类的Class对象被载入内存,它就被用来创建这个类的所有对象。
Java程序在运行之前并非一次将所有的类都加载完,而是按需加载。动态加载使能的行为,使Java语言区分于C++这样的静态加载语言。

  • (1) Class.forName
    在运行时使用类型信息,必须先获得对恰当的Class对象的引用。Class.forName方法是实现此功能的便捷途径。如果Class.forName找不到需要加载的类,它就会抛出ClassNotFoundException异常。注意,在传递给forName的参数时,必须使用全限定名(包含包名)。

      public void getClassReference() {
          Class currentClass = null;
          try {
              currentClass = Class.forName("io.github.courage007.object.CustomObject");
          } catch (ClassNotFoundException ex) {
              throw new RuntimeException("found class failed");
          }
          Object curentObjectInstance = null;
          try {
              curentObjectInstance = currentClass.newInstance();
          } catch (InstantiationException | IllegalAccessException ex) {
              throw new RuntimeException("create instance failed");
          }
          CustomObject instance = (CustomObject) curentObjectInstance;
          // 调用CustomObject中定义方法
      }
    
  • (2) getClass方法
    如果已经拥有一个感兴趣的类型的对象,那就可以通过调用继承的getClass方法来获取Class引用。这个方法属于根类Object的一部分,它将返回表示该对象的实际类型的Class引用。

  • (3) 类字面量
    Java还可以使用类字面量来生成对Class对象的引用。示例代码如下:

      public void getClassReference() {
          Class currentClass = CustomObject.class;
          Object curentObjectInstance = null;
          try {
              curentObjectInstance = currentClass.newInstance();
          } catch (InstantiationException | IllegalAccessException ex) {
              throw new RuntimeException("create instance failed");
          }
          CustomObject instance = (CustomObject) curentObjectInstance;
          // 调用CustomObject中定义方法
      }
    

    相比Class.forName方法,类字面量的方式,不仅简单,而且安全,因为它在编译时就会受到检查(因此不需要置于try语句块中)。相对地,这种方式相比forName的灵活性差一些,不能支撑运行时获取Class引用的场景。

为什么学习反射

如果不知道某个对象的确切类型,可以使用RTTI获知。但是使用RTTI有一个限制:这个类型在编译时,必须已知。这样才能使用RTTI识别它,并利用这些信息做一些有用的事。
JVM 提供动态加载的能力,所以在实际应用中,存在在运行时获取类的信息的需求。举例来说,在“基于构件的编程”中,要求构件是可实例化的,并且要暴露其部分信息,以允许程序员读取和修改构件的属性。反射提供一种机制——用来检查可用的方法,并返回方法名。另一个场景是,如果希望在跨网络的远程平台上创建和运行对象的能力,这被称为远程方法调用(RMI),它允许一个Java程序将对象分布到多台机器上。

使用反射

Class类java.lang.reflect类库一起对反射的概念进行了支持,该类库包含了Field、Method以及Constructor类(每个类都实现了Member接口)。这些类型的对象是由JVM在运行时创建的,用以表示未知类里对应的成员。这样既能使用Constructor创建新的对象,用get方法和set方法读写与Field对象关联的字段,用invoke方法调用与Method对象关联的方法,等等。这样,匿名对象的类信息就能在运行时被完全确定下来,而不需在编译时知道任何事情。
当通过反射与未知类型的对象打交道时,JVM只是简单地检查这个对象,看它属于哪个特定的类(RTTI),并加载那个类的Class对象。所以RTTI和反射之间真正的区别在于,对RTTI来说,编译器在编译时打开和检查.class文件。而对反射来说,.class文件在编译时是不可获取的,所以是在运行时打开和检查.class文件。反射在帮忙创建更加动态的代码时会很有用,如对象序列化和JavaBean。
Class.forName方法生成的结果在编译时是不可知的,因此所有的方法签名信息都是在执行时被提取出来的。反射机制提供了足够的支持,使得能够创建一个在编译时完全未知的对象,并调用此对象的方法。

动态代理

代理是基本的设计模式之一,代理通常充当中间人的角色,负责与“实际”对象的通信。
动态代理比代理的思想更向前一步,因为它可以动态地创建代理类并动态地处理对所有代理方法的调用。在动态代理上所做的所有调用都会被重定向到单一的调用处理器上,它的工作是揭示调用的类型并确定相应的对策。动态代理本质上是反射的一种应用。

反射的缺点

尽管使用反射能让我们代码更加灵活。但是反射也会带来增加系统的性能消耗,增加代码复杂性等。在使用中,还要根据具体的场景选择相应的技术。

参考

https://zhuanlan.zhihu.com/p/86293659 详解面试中常考的 Java 反射机制
https://www.cnblogs.com/ysocean/p/6516248.html Java 反射详解
https://zhuanlan.zhihu.com/p/80519709 Java反射使用总结
《Java编程思想》(第四版) Bruce Eckel [译]陈昊鹏 P313-P351

原创不易,如果本文对您有帮助,欢迎关注我,谢谢 ~_~

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值