【问题标题】:Does the javac compiler create a different class for every type when use generics? [duplicate]javac 编译器在使用泛型时是否为每种类型创建不同的类? [复制]
【发布时间】:2014-05-16 14:00:19
【问题描述】:

如果我有一个泛型类,编译器会为我使用的每种类型创建一个不同的类吗?让我们考虑一下Class<T>。如果我创建两个Class<Integer>Class<String> 类型的实例,编译器会创建两个不同的类吗?

如果答案是否定的:扩展泛型类的类怎么可能继承具有不同类型的相同方法或属性(来自单个类)?

另一个问题:为什么我不能使用参数化类型而不是ClassClass<?> 来检查var instanceof Class<Integer>

如果我尝试这样做,我会收到以下错误: “无法对参数化类型 Test<Integer> 执行 instanceof 检查。请改用 Test<?> 形式,因为更多的泛型类型信息将在运行时被删除”

你能给我更多关于泛型的信息吗?

【问题讨论】:

  • 简短的回答是不。类型参数就是...参数。
  • 查找类型擦除。
  • 不,这就是为什么在 rutime 的许多情况下,您实际上无法分辨出使用了哪种泛型类型。这是一个编译时特性。
  • 这道题是一本教书例子的副本……太常见了,在Oracle教程中有专门的章节。请参考重复。

标签: java class generics compiler-construction bytecode


【解决方案1】:

如果我有一个泛型类,编译器是否会为我使用的每种类型创建一个不同的类?让我们考虑这个类。如果我创建两个 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&lt;String&gt;,则生成的实例只知道它是一个ArrayList。在这种情况下,检查instance instanceof ArrayList&lt;String&gt; 不会产生有意义的结果。由于此类检查不能可靠地1产生有意义的结果,因此不允许使用。


1有趣的是,上面的StringComparator 类的实例确实知道它实现了Comparator&lt;String&gt;,因为通用超类型信息保留在元数据中.

【讨论】:

    【解决方案2】:

    不,任何&lt;XXX&gt; 的类都相同。泛型只存在于编译之前,而不存在于字节码中。这样做是为了向后兼容。

    有关更多信息,您可能需要阅读http://docs.oracle.com/javase/tutorial/java/generics/

    【讨论】:

      【解决方案3】:

      不,只有一个类,因为参数化类型只有在运行时才知道,编译器无法为每个匹配的类型创建类。

      当你扩展一个泛型类时,方法和成员会被继承。我不明白你的目的... 例如:

      public class MyClass<T> extends AbstractMyClass<T> {
      
          @Override
          public void add(T element) {
              // do something with element...
          }
      
      
      }
      
         public class MyClass<T> {
             private final List<T> store = new ArrayList<T>();
      
             protected void add(T element) {
                store.add(element);
             }
        }
      

      [edition] 好的,我想我第一次没有理解你关于延长的问题......对不起。[/edition]

      而且你不能检查 instanceof 因为参数化类型在运行时是未知的。 您可以尝试类似的方法:how to instanceof List<MyType>? 来检查您的参数化类型是否相同。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2023-03-17
        • 2012-01-29
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多