美文网首页简友广场Android开发
Java | 反射:在运行时访问类型信息(含 Kotlin)

Java | 反射:在运行时访问类型信息(含 Kotlin)

作者: 彭旭锐 | 来源:发表于2020-08-23 19:21 被阅读0次

    前言

    • 反射(Reflection)是一种在运行时动态访问类型信息的机制。
    • 在这篇文章里,我将带你梳理Java & Kotlin反射的使用攻略,追求简单易懂又不失深度,如果能帮上忙,请务必点赞加关注!

    延伸文章


    目录

    1. 类型系统的基本概念

    首先,梳理一一下类型系统的基础概念:

    • 问:什么是强 / 弱类型语言?
      答:强 / 弱类型语言的区分,关键在于变量是否(倾向于)类型兼容。例如,Java 是强类型语言,变量有固定的类型,以下代码在 Java 中是非法的:
    public class MyRunnable {
        public abstract void run();
    }
    
    // 编译错误:Incompatible types
    java.lang.Runnable runnable = new MyRunnable() { 
        @Override
        public void run() {
            
        }
    }
    runnable.run(); // X
    

    相对地,JavaScript 是弱类型语言,一个变量没有固定的类型,允许接收不同类型的值:

    function MyRunnable(){
        this.run = function(){
        }
    }
    function Runnable(){
        this.run = function(){
        }
    }
    var ss = new MyRunnable();
    ss.run(); // 只要对象有相同方法签名的方法即可
    ss = new Runnable();
    ss.run();
    

    更具体地描述,Java的强类型特性体现为:变量仅允许接收相同类型或子类型的值。嗯(黑人问号脸)?和你的理解一致吗?请看下面代码,哪一行是有问题的:

    注意,请读者假设 1 ~ 4 号代码是单独运行的
    
    long numL = 1L;
    int numI = 0;
    numL = numI; // 1
    numI = (int)numL; // 2
    
    Integer integer = new Integer(0);
    Object obj = new Object();
    integer = (Integer) obj; // 3 ClassCastException
    obj = integer; // 4
    
    • 1:调用字节码指令 i2l,将 int 值转换为 long 值\scriptsize \color{#999999}{(此时,numL 变量接收的是相同类型的值,命题正确)}
    • 2:调用字节码指令 l2i,将 long 值转换为 int 值\scriptsize \color{#999999}{(此时,numI 变量接收的是相同类型的值,命题正确)}
    • 3:调用字节码指令 checkcast,发现 obj 变量的值不是 Integer 类型,抛出 ClassCastException \scriptsize \color{#999999}{(此时,Integer 变量不允许接收 Object 对象,命题正确)}
    • 4:integer 变量的值是 obj 变量的子类型,可以接收 \scriptsize \color{#999999}{(此时,Object 变量允许接收 Integer 对象,命题正确)}

    用一张图概括一下:

    • 问:什么是静态 / 动态类型语言?
      答:静态 / 动态类型语言的区分,关键在于类型检查是否(倾向于)编译时执行。例如, Java & C/C++ 是静态类型语言,而 JavaScript 是动态类型语言。需要注意的是,这个定义并不是绝对的,例如 Java 也存在运行时类型检查的方式,例如上面提到的 checkcast 指令本质上是在运行时检查变量的类型与对象的类型是否相同。那么 Java 是如何在运行时获得类型信息的呢?这就是我们下一节要讨论的问题。

    2. 反射的基本概念

    • 问:什么是反射?为什么要使用反射?
      答:反射(Reflection)是一种在运行时动态访问类型信息的机制。Java 是静态强类型语言,它倾向于在编译时进行类型检查,因此当我们访问一个类时,它必须是编译期已知的,而使用反射机制可以解除这种限制,赋予 Java 语言动态类型的特性。例如:

      void func(Object obj) {
          try {
              Method method = obj.getClass().getMethod("run",null);
              method.invoke(obj,null);
          } 
          ... 省略 catch 块
      }
      func(runnable); 调用 Runnale#run()
      func(myRunnable); 调用 MyRunnale#run()
      
    • 问:Java 运行时类型信息是如何表示的?
      所有的类在第一次使用时动态加载到内存中,并构造一个 Class 对象,其中包含了与类有关的所有信息,Class 对象是运行时访问类型信息的入口。需要注意的是,每个类 / 内部类 / 接口都拥有各自的 Class 对象。

    • 问:获取 Class 对象有几种方式,有什么区别?
      答:获取 Class 对象是反射的起始步骤,具体来说,分为以下三种方式:

    • 问:为什么反射性能差,怎么优化?
      答:Editting...

    3. 反射调用的实现

    Editting...


    4. 反射的应用场景

    4.1 类型判断

    4.2 创建对象 & 数组

    • 创建对象
      创建对象有两种方式:
      1、使用 Class.newInstance(),适用于类拥有无参构造方法
      Class<?> classType = Class.forName("java.lang.String");
      String str= (String) classType.newInstance();
      ---------------------------------------------------
      2、Constructor.newInstance(),适用于使用带参数的构造方法
      Class<?> classType = Class.forName("java.lang.String");
      Constructor<?> constructor = classType.getConstructor(new Class[]{String.class});
      constructor.setAccessible(true);
      String employee3 = (String) constructor.newInstance(new Object[]{"123"});
      
    • 创建数组
      创建数组需要元素的 Class 对象作为 ComponentType:
      1、创建一维数组
      Class<?> classType = Class.forName("java.lang.String");
      String[] array = (String[]) Array.newInstance(classType, 5);  长度为5
      Array.set(array, 3, "abc");  设置元素
      String string = (String) Array.get(array,3);  读取元素
      ---------------------------------------------------
      2、创建多维数组
      Class[] dimens = {3, 3};
      Class[][] array = (Class[][]) Array.newInstance(int.class, dimens);
      

    4.3 访问字段、方法

    Editting...

    4.4 获取泛型信息

    我们知道,编译期会进行类型擦除,Code 属性中的类型信息会被擦除,但是在类常量池属性(Signature属性、LocalVariableTypeTable属性)中还保留着泛型信息,因此我们可以通过反射来获取这部分信息。在这篇文章里,我们详细讨论:《Java | 关于泛型能问的都在这里了(含Kotlin)》,请关注!

    4.5 获取运行时注解信息

    注解是一种添加到声明上的元数据,而RUNTIME注解在类加载后会保存在 Class 对象,可以反射获取。在这篇文章里,我们详细讨论:《Java | 这是一篇全面的注解使用攻略(含 Kotlin)》,请关注!


    参考资料

    • 《Kotlin实战》 (第10章)—— [俄] Dmitry Jemerov,Svetlana Isakova 著
    • 《Kotlin 核心编程》(第8章)—— 水滴技术团队 著
    • 《深入理解JVM字节码》(第3.5节)—— 张亚 著
    • 《Java编程思想》 (第19、23章)—— [美] Bruce Eckel 著
    • 《深入理解Java虚拟机(第3版)》(第8、10章)—— 周志明 著

    推荐阅读

    感谢喜欢!你的点赞是对我最大的鼓励!欢迎关注彭旭锐的简书!

    相关文章

      网友评论

        本文标题:Java | 反射:在运行时访问类型信息(含 Kotlin)

        本文链接:https://www.haomeiwen.com/subject/zbvdjktx.html