企业🤖AI智能体构建引擎,智能编排和调试,一键部署,支持私有化部署方案 广告
### [泛化的`Class`引用](https://lingcoder.gitee.io/onjava8/#/book/19-Type-Information?id=%e6%b3%9b%e5%8c%96%e7%9a%84-class-%e5%bc%95%e7%94%a8) `Class`引用总是指向某个`Class`对象,而`Class`对象可以用于产生类的实例,并且包含可作用于这些实例的所有方法代码。它还包含该类的`static`成员,因此`Class`引用表明了它所指向对象的确切类型,而该对象便是`Class`类的一个对象。 但是,Java 设计者看准机会,将它的类型变得更具体了一些。Java 引入泛型语法之后,我们可以使用泛型对`Class`引用所指向的`Class`对象的类型进行限定。在下面的实例中,两种语法都是正确的: ~~~ // typeinfo/GenericClassReferences.java public class GenericClassReferences { public static void main(String[] args) { Class intClass = int.class; Class<Integer> genericIntClass = int.class; genericIntClass = Integer.class; // 同一个东西 intClass = double.class; // genericIntClass = double.class; // 非法 } } ~~~ 普通的类引用不会产生警告信息。你可以看到,普通的类引用可以重新赋值指向任何其他的`Class`对象,但是使用泛型限定的类引用只能指向其声明的类型。通过使用泛型语法,我们可以让编译器强制执行额外的类型检查。 那如果我们希望稍微放松一些限制,应该怎么办呢?乍一看,下面的操作好像是可以的: ~~~ Class<Number> geenericNumberClass = int.class; ~~~ 这看起来似乎是起作用的,因为`Integer`继承自`Number`。但事实却是不行,因为`Integer`的`Class`对象并不是`Number`的`Class`对象的子类(这看起来可能有点诡异,我们将在[泛型](https://lingcoder.gitee.io/onjava8/#/./20-Generics)这一章详细讨论)。 为了在使用`Class`引用时放松限制,我们使用了通配符,它是 Java 泛型中的一部分。通配符就是`?`,表示“任何事物”。因此,我们可以在上例的普通`Class`引用中添加通配符,并产生相同的结果: ~~~ // typeinfo/WildcardClassReferences.java public class WildcardClassReferences { public static void main(String[] args) { Class<?> intClass = int.class; intClass = double.class; } } ~~~ 使用`Class<?>`比单纯使用`Class`要好,虽然它们是等价的,并且单纯使用`Class`不会产生编译器警告信息。使用`Class<?>`的好处是它表示你并非是碰巧或者由于疏忽才使用了一个非具体的类引用,而是特意为之。 为了创建一个限定指向某种类型或其子类的`Class`引用,我们需要将通配符与`extends`关键字配合使用,创建一个范围限定。这与仅仅声明`Class<Number>`不同,现在做如下声明: ~~~ // typeinfo/BoundedClassReferences.java public class BoundedClassReferences { public static void main(String[] args) { Class<? extends Number> bounded = int.class; bounded = double.class; bounded = Number.class; // Or anything else derived from Number. } } ~~~ 向`Class`引用添加泛型语法的原因只是为了提供编译期类型检查,因此如果你操作有误,稍后就会发现这点。使用普通的`Class`引用你要确保自己不会犯错,因为一旦你犯了错误,就要等到运行时才能发现它,很不方便。 下面的示例使用了泛型语法,它保存了一个类引用,稍后又用`newInstance()`方法产生类的对象: ~~~ // typeinfo/DynamicSupplier.java 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; } 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); } } ~~~ 输出结果: ~~~ 10 11 12 13 14 ~~~ 注意,这个类必须假设与它一起工作的任何类型都有一个无参构造器,否则运行时会抛出异常。编译期对该程序不会产生任何警告信息。 当你将泛型语法用于`Class`对象时,`newInstance()`将返回该对象的确切类型,而不仅仅只是在`ToyTest.java`中看到的基类`Object`。然而,这在某种程度上有些受限: ~~~ // typeinfo/toys/GenericToyTest.java // 测试 Class 类 // {java typeinfo.toys.GenericToyTest} package typeinfo.toys; public class GenericToyTest { public static void main(String[] args) throws Exception { Class<FancyToy> ftClass = FancyToy.class; // Produces exact type: FancyToy fancyToy = ftClass.newInstance(); Class<? super FancyToy> up = ftClass.getSuperclass(); // This won't compile: // Class<Toy> up2 = ftClass.getSuperclass(); // Only produces Object: Object obj = up.newInstance(); } } ~~~ 如果你手头的是超类,那编译器将只允许你声明超类引用为“某个类,它是`FancyToy`的超类”,就像在表达式`Class<? super FancyToy>`中所看到的那样。而不会接收`Class<Toy>`这样的声明。这看上去显得有些怪,因为`getSuperClass()`方法返回的是基类(不是接口),并且编译器在编译期就知道它是什么类型了(在本例中就是`Toy.class`),而不仅仅只是"某个类"。不管怎样,正是由于这种含糊性,`up.newInstance`的返回值不是精确类型,而只是`Object`。