4 泛化的 Class
引用

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

在运行期识别对象和类的信息有两种方式:
编程
“传统” RTTI数组
编译时就已知全部的类型安全
反射机制服务器
运行时发现和使用类的信息微信
1 为何须要 RTTI
面向对象编程的一个基本目的:代码只操纵对基类(这里即 Shape
)的引用。所以一般建立一个具体的对象(Circle
、Square
或者 Triangle
),把它向上转型成 Shape
,忽略对象的具体类型,而且在后面的程序中使用 Shape
引用来调用在具体对象中被重载的方法。网络
在把 Shape
对象放入 Stream<Shape>
中时就会进行隐式向上转型,但在向上转型的时候也丢失了这些对象的具体类型。对 stream
而言,它们只是 Shape
对象。app
Stream<Shape>
其实是把放入其中的全部对象都当作 Object
,只是取元素自动将类型转为 Shape
。这也是 RTTI 最基本的使用形式ide
全部类型转换的正确性检查都是在运行时。这也正是 RTTI 的含义所在:在运行时,识别一个对象的类型。flex
该例中,类型转换并不完全:Object
被转型为 Shape
,而不是 Circle
、Square
或者 Triangle
。这是由于目前咱们只能确保这个 Stream<Shape>
保存的都是 Shape
:
编译期,
stream
和 Java 泛型系统确保放入stream
的都是Shape
对象(Shape
子类的对象也可视为Shape
的对象),不然编译器会报错运行时,自动类型转换确保了从
stream
中取出的对象都是Shape
类型
Shape
对象实际执行的代码,由引用的具体对象决定。
一般,咱们但愿大部分代码尽量少了解对象具体类型,而是只与对象家族中的一个通用的高层抽象打交道。这样,代码更易读和维护;设计也更容易实现,更易于理解和修改。
但有时但愿知道 Stream<Shape>
里边的形状具体类型。使用 RTTI,便可查询某个 Shape
引用所指向对象的确切类型。
2 2 Class 对象
由 Class
特殊对象完成,它包含了与类有关的信息。实际上,Class
对象就是用来建立该类全部”常规”对象的。Java 使用 Class
对象来实现 RTTI,即使是类型转换这样的操做都是用 Class
对象实现的。跟其余普通对象同样,咱们也能够控制它的引用。
类是程序的一部分,每一个类都有一个 Class
对象。每编译一个新类,就产生一个 Class
对象,即保存在一个同名 .class
文件。
为了生成这个类的对象,JVM先会调用”类加载器”把该类加载到内存。
类加载器子系统包含一条类加载器链,但有且只有一个原生类加载器。
原生类加载器加载的是”可信类”(如Java API),一般从本地盘加载。在这条链中,一般不须要添加额外的类加载器,可是若是你有特殊需求(例如以某种特殊的方式加载类,以支持 Web 服务器应用,或者经过网络下载类),也能够挂载额外的类加载器。
类在第一次被使用时动态加载到JVM,即程序建立第一个对类的静态成员的引用时。构造器(new 时调用)实质也是静态方法。因此Java 程序不少部分在须要时才加载。
类加载器首先检查类的 Class
对象是否已加载
还没有加载,则默认类加载器根据类名查找
.class
文件类的字节码被加载后,JVM 会对其进行验证
一旦某个类的 Class
对象被载入内存,就可用来建立该类的全部对象。
结果
可见Class
对象仅在须要的时候才会被加载,static
初始化是在类加载时进行的。
2.3 Class.forName(className)
Class
类的静态方法,可使用其根据类名获得Class
对象:
上面程序forName()
调用是为了获得它产生的“反作用”:若是 Gum
类未被加载,那么就加载之。因而在加载的过程当中,Gum
的 static
初始化块也被执行了。
想在运行时使用类型信息,就必须获得其 Class
对象的引用。
使用该方法无需先持有这个类型的对象。
若是已持有类对象,可调用 getClass()
方法来获取 Class
引用:
这个方法来自根类
Object
,它将返回表示该对象实际类型的 Class
对象的引用。产生完整类名。
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
,那么在对它访问时,总要求在它被读取前,先进行连接(为字段分配存储空间)和初始化(初始化该存储空间)
Class
对象可产生类的实例,包含可做用于这些实例的方法,还包含类的 static
成员,所以咱们说 Class
引用代表了它所指向对象的确切类型:Class
类的一个对象。
Java 设计者将它的类型变得更具体。Java 引入泛型,限定 Class
引用所指向的 Class
对象的类型。
普通的类引用不会产生警告信息,能够从新赋值任何其余的 Class
对象
可是当使用泛型限定类引用后,只能指向其声明的类型,让编译器强制执行额外的类型检查。
放宽限制
这彷佛起做用,由于 Integer
继承自 Number
。如上所见其实不行,由于 Integer
的 Class
对象并非 Number
的 Class
对象的子类。
为了在使用
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++;
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; } 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源创计划”,欢迎正在阅读的你也加入,一块儿分享。