如果我有一个泛型类,编译器是否会为我使用的每种类型创建一个不同的类?让我们考虑这个类。如果我创建两个 Class 和 Class 类型的实例,编译器会创建两个不同的类吗?
不,只有一个类,并且在字节码中,类型变量的所有外观都有效地替换为它们的上限(通常是 Object,但对于 @987654324 形式的类型变量,可能有一些类型 U @)。这个概念被称为类型擦除,因为类型变量被有效地擦除(并替换为它们的上限)。
如果答案是否定的:扩展泛型类的类怎么可能继承具有不同类型的相同方法或属性(来自单个类)?
有趣的问题!假设您有两个不同的类实现Comparator<T>。一个实现Comparator<String>,另一个实现Comparator<Integer>。
Comparator<T> 定义如下方法:
int compare(T p0, T p1)
那么两个不同的泛型实例如何用不同的参数类型实现相同的方法呢?好吧,代码中实现的方法实际上并没有覆盖Comparator.compare()。 Comparer.compare() 接受两个 Object 参数,但 Comparator<String>.compare() 接受两个 String 参数。它们不是同一种方法。那么,为什么它表现 像一个覆盖?因为编译器会为你生成一个隐藏的桥接方法。通过反编译器或反汇编器运行通用实现以亲自查看。以下是我自己的Procyon反编译器使用--show-synthetic运行的输出:
public enum StringComparator implements Comparator<String> {
ORDINAL {
@Override
public int compare(final String s1, final String s2) {
if (s1 == null) {
return (s2 == null) ? 0 : -1;
}
if (s2 == null) {
return 1;
}
return s1.compareTo(s2);
}
@Override
public /* bridge */ int compare(final Object x0, final Object x1) {
return this.compare((String)x0, (String)x1);
}
},
...
}
第一个compare()方法是StringComparator类的作者写的实际实现。第二种方法是隐藏的,由编译器生成。它的存在是为了用它的“擦除”定义来“桥接”通用实现,正是这个桥接方法实现了接口方法Comparator.compare()。请注意桥接方法如何使用类型转换来强制将T 绑定到String。这在类型擦除的世界中提供了一种安全措施。它确保以下产生异常:
class IntegerComparator implements Comparator<Integer> { ... }
// 'c' is a raw Comparator, or effectively a Comparator<Object>
// (note the lack of type arguments).
Comparator c = new IntegerComparator();
int result = c.compare(1, "hello");
上面的代码编译得很好,因为Comparator.compare() 的原始形式接受两个Object 参数。但在运行时,调用会触发ClassCastException,因为IntegerComparator 中的桥接方法会尝试将字符串“hello”转换为Integer。
另一个问题:为什么我不能使用参数化类型而不是 Class 或 Class> 来检查 var instanceof Class?
由于具体泛型类型的所有实例共享同一个类,其中所有类型变量都已被删除,因此泛型类实例除了其原始类型之外没有任何身份感。它不知道实例化它所使用的类型参数,因为该信息在编译期间已被煮沸。如果您实例化一个ArrayList<String>,则生成的实例只知道它是一个ArrayList。在这种情况下,检查instance instanceof ArrayList<String> 不会产生有意义的结果。由于此类检查不能可靠地1产生有意义的结果,因此不允许使用。
1有趣的是,上面的StringComparator 类的实例确实知道它实现了Comparator<String>,因为通用超类型信息保留在元数据中.