一文带你看懂 Java 的反射机制

点击上方「蓝字」关注咱们java


在运行期识别对象和类的信息有两种方式:
编程

  1. “传统” RTTI数组

    编译时就已知全部的类型安全

  2. 反射机制服务器

    运行时发现和使用类的信息微信


1
为何须要 RTTI


面向对象编程的一个基本目的:代码只操纵对基类(这里即 Shape )的引用。所以一般建立一个具体的对象(CircleSquare 或者 Triangle),把它向上转型成 Shape ,忽略对象的具体类型,而且在后面的程序中使用 Shape 引用来调用在具体对象中被重载的方法。网络

在把 Shape 对象放入 Stream<Shape> 中时就会进行隐式向上转型,但在向上转型的时候也丢失了这些对象的具体类型。stream 而言,它们只是 Shape 对象。app

Stream<Shape> 其实是把放入其中的全部对象都当作 Object ,只是取元素自动将类型转为 Shape这也是 RTTI 最基本的使用形式ide

全部类型转换的正确性检查都是在运行时。这也正是 RTTI 的含义所在:在运行时,识别一个对象的类型。flex

该例中,类型转换并不完全:Object 被转型为 Shape ,而不是 CircleSquare 或者 Triangle。这是由于目前咱们只能确保这个 Stream<Shape> 保存的都是 Shape

  • 编译期,stream 和 Java 泛型系统确保放入 stream 的都是 Shape 对象(Shape 子类的对象也可视为 Shape 的对象),不然编译器会报错

  • 运行时,自动类型转换确保了从 stream 中取出的对象都是 Shape 类型

多态



Shape 对象实际执行的代码,由引用的具体对象决定。

一般,咱们但愿大部分代码尽量少了解对象具体类型,而是只与对象家族中的一个通用的高层抽象打交道。这样,代码更易读和维护;设计也更容易实现,更易于理解和修改。

但有时但愿知道 Stream<Shape> 里边的形状具体类型。使用 RTTI,便可查询某个 Shape 引用所指向对象的确切类型。


2
2 Class 对象


2.1 类型信息在运行时的表示


Class 特殊对象完成,它包含了与类有关的信息。实际上,Class 对象就是用来建立该类全部”常规”对象的。Java 使用 Class 对象来实现 RTTI,即使是类型转换这样的操做都是用 Class 对象实现的。跟其余普通对象同样,咱们也能够控制它的引用。

类是程序的一部分,每一个类都有一个 Class 对象。每编译一个新类,就产生一个 Class 对象,即保存在一个同名 .class 文件。
为了生成这个类的对象,JVM先会调用”类加载器”把该类加载到内存。

类加载器子系统包含一条类加载器链,但有且只有一个原生类加载器
原生类加载器加载的是”可信类”(如Java API),一般从本地盘加载。在这条链中,一般不须要添加额外的类加载器,可是若是你有特殊需求(例如以某种特殊的方式加载类,以支持 Web 服务器应用,或者经过网络下载类),也能够挂载额外的类加载器。

类在第一次被使用时动态加载到JVM,即程序建立第一个对类的静态成员的引用时。构造器(new 时调用)实质也是静态方法因此Java 程序不少部分在须要时才加载。

类加载器首先检查类的 Class 对象是否已加载

  • 还没有加载,则默认类加载器根据类名查找 .class 文件

  • 类的字节码被加载后,JVM 会对其进行验证


一旦某个类的 Class 对象被载入内存,就可用来建立该类的全部对象。


2.2 验证类加载器的工做方式



  • 结果



可见Class 对象仅在须要的时候才会被加载,static 初始化是在类加载时进行的。

    
2.3 Class.forName(className)


  • Class 类的静态方法,可使用其根据类名获得Class 对象:

上面程序forName() 调用是为了获得它产生的“反作用”:若是 Gum 类未被加载,那么就加载之。因而在加载的过程当中,Gum static 初始化块也被执行了。


想在运行时使用类型信息,就必须获得其 Class 对象的引用

使用该方法无需先持有这个类型的对象。

                    2.4 Object.getClass                       


若是已持有类对象,可调用 getClass() 方法来获取 Class 引用:

这个方法来自根类 Object,它将返回表示该对象实际类型的 Class 对象的引用。产生完整类名。

2.5 Class 类的其余API


getSimpleName() 产生不带包名的类名

getCanonicalName() 产生完整类名,除内部类和数组,对类产生的结果与 getName() 相同


isInterface() 判断某个 Class 对象表明的是否为接口

Class.getInterfaces() 返回存放 Class 对象的数组,里面的 Class 对象表示的是那个类实现的接口。

getSuperclass() 获得父类的 Class 对象,父类的 Class 对象继续调用可得完整的类继承结构。

newInstance() 实现“虚拟构造器”:在不知类的确切类型时,建立该类的对象

使用 newInstance() 来建立的类,必须带无参构造器


3
类字面常量

另外一种生成类对象的引用之法。

形如


优势


简单安全,由于编译时就会检查(所以没必要放在 try 中)

相比 forName() 方法调用,效率也更高。



适用范围



普通类、接口、数组及基本数据类型。


TYPE


对于基本数据类型的包装类,还有一个字段 TYPE

TYPE 字段是一个引用,指向对应的基本数据类型的 Class 对象

…等价于…
boolean.class Boolean.TYPE
char.class Character.TYPE
byte.class Byte.TYPE
short.class Short.TYPE
int.class Integer.TYPE
long.class Long.TYPE
float.class Float.TYPE
double.class Double.TYPE
void.class Void.TYPE

建议使用 .class 形式,以保持统一。


初始化有效地实现了尽量的“惰性”


  • 使用 .class 语法建立对 Class 对象的引用时,不会自动初始化该 Class 对象

  •  Class.forName() 产生 Class 引用,会当即进行对象的初始化


若是一个 static final 值是“编译期常量”,那么不须要初始化所在类就可被读取。以下:

但只将字段设为 static  final,并不足以确保这种行为。例如,对 Initable.staticFinal2 的访问将强制进行类的初始化,由于它不是一个编译期常量。以下:

若是一个 static 字段非 final,那么在对它访问时,总要求在它被读取前,先进行连接(为字段分配存储空间)和初始化(初始化该存储空间)








4 泛化的 Class 引用


Class 对象可产生类的实例,包含可做用于这些实例的方法,还包含类的 static 成员,所以咱们说 Class 引用代表了它所指向对象的确切类型:Class 类的一个对象。

Java 设计者将它的类型变得更具体。Java 引入泛型,限定 Class 引用所指向的 Class 对象的类型。

普通的类引用不会产生警告信息,能够从新赋值任何其余的 Class 对象

可是当使用泛型限定类引用后,只能指向其声明的类型,让编译器强制执行额外的类型检查。







放宽限制


这彷佛起做用,由于 Integer 继承自 Number。如上所见其实不行,由于 IntegerClass 对象并非 NumberClass 对象的子类。

  • 为了在使用 Class 引用时放松限制,使用通配符 ?,表示“任何事物”







使用 Class<?> 代替 Class 


虽然它们等价,且单纯使用 Class 不会产生警告。

Class<?> 的好处:表示你并不是是碰巧或者疏忽才使用了一个非具体的类引用,而是故意的。






限定类型的 Class 引用


建立指向限定类型的 Class 引用,须要搭配通配符与 extends ,建立范围限定。这与仅仅声明 Class<Number> 是不一样的!

 Class 引用添加泛型只是为了提供编译期类型检查。而使用普通的 Class 引用,一旦你犯了错误,就要到运行时才能发现。


下面的示例使用了泛型语法,它保存了一个类引用,稍后又用 newInstance() 方法产生类的对象:

package typeinfo.toys;
import java.util.function.*;import java.util.stream.*;
class CountedInteger { private static long counter; private final long id = counter++;
@Override public String toString() { return Long.toString(id); }}
public class DynamicSupplier<T> implements Supplier<T> { private Class<T> type; public DynamicSupplier(Class<T> type) { this.type = type; } @Override public T get() { try { return type.newInstance(); } catch (InstantiationException | IllegalAccessException e) { throw new RuntimeException(e); } }
public static void main(String[] args) { Stream.generate( new DynamicSupplier<>(CountedInteger.class)) .skip(10) .limit(5) .forEach(System.out::println); }}

将泛型语法用于 Class 对象时,newInstance() 将返回该对象的确切类型,而不只是Object

然而,这有些受限:


若是你想获得超类,那编译器将只容许你声明超类引用为“某个类,它是 FancyToy 的超类”,即 Class<? super FancyToy>不会接受Class<Toy

看上去很怪,由于 getSuperClass() 返回的是基类,编译器在编译期就知道它的类型( Toy.class),而不只仅只是”某个类”。

正是这种含糊性,up.newInstance 的返回值不是精确类型,而是 Object


扫码二维码

获取更多精彩

JavaEdge


好文!必须在看

本文分享自微信公众号 - JavaEdge(Java-Edge)。
若有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一块儿分享。

相关文章
相关标签/搜索