【问题标题】:Java type erasure and multiple boundsJava 类型擦除和多重边界
【发布时间】:2016-03-31 23:31:04
【问题描述】:

我知道在 Java 泛型中,当使用具有多个边界的类型参数时,编译器会将类型信息擦除到“最左边的边界”(即列表中的第一个类/枚举或接口)。 那么为什么下面的代码编译没有问题呢?

public class Generic<T extends Object & Appendable & AutoCloseable> {

  T t;

  T method() throws Exception {
    t.close();
    char c='\u0000';
    t.append(c);
    return t;
  }

  public <T> T method2(T t) {
    return t;
  }  

}

不应该把类型参数T当作Object吗? (因此不允许我调用 close() 或 append())??

【问题讨论】:

  • 哪一部分不明白?method?
  • @SMA 类型为类型参数的变量应该只能使用参数擦除类型的成员(据说它是列表中最左边的绑定)。我的代码中不是这种情况。
  • 编译器没有这个问题。你不应该问为什么运行时能够调用t.close()尽管类型擦除?
  • @wero 为什么会这样?该类型被擦除为AutoCloseable,并且OP希望它被擦除为Object。另请参阅我的答案(这还不是真正的答案)

标签: java generics type-parameter type-bounds


【解决方案1】:

您应该阅读桥接方法 here

在编译扩展参数化类或实现参数化接口的类或接口时,编译器可能需要创建一个称为桥接方法的合成方法,作为类型擦除过程的一部分。您通常不需要担心桥接方法,但如果某个出现在堆栈跟踪中,您可能会感到困惑。

【讨论】:

  • 我知道他们。这不是我问的问题,我没有在我的代码中扩展泛型类或实现泛型接口。
  • 好的,所以我使用 jd-gui 查看 .class 文件,发现了这样的代码T method() throws Exception { ((AutoCloseable)this.t).close(); char c = '\000'; ((Appendable)this.t).append(c); return (T)this.t; }。抱歉多次修改
【解决方案2】:

您的案例包含multiply bound

具有多个边界的类型变量是所有类型的子类型 列于界。如果其中一个边界是一个类,它必须是 首先指定。

因此调用AutoCloseable接口中的close()方法和Appendable接口中的append()在语法上是合法的。

【讨论】:

  • 它确实是一个子类型(因为编译器不允许传递不是列表中所有类型的子类型的类型参数),但是由于类型参数擦除为一个类型(编译器不能替换字节码中的多种类型)我应该只能调用擦除类型的成员。
  • 所有类型信息在编译时都可用于编译器。这就是它编译的原因。类型信息仅在字节码中丢失。一个有趣的问题是,如果您在其他编译单元中将编译后的二进制类作为依赖项重用会怎样。
【解决方案3】:

这是反汇编的代码。 变量t 是对象类型。 在调用接口方法之前生成指令checkcast。 如果t的值没有实现接口,则会抛出ClassCastException。

Compiled from "Generic.java"
public class Generic<T extends java.lang.Appendable & java.lang.AutoCloseable> {
  T t;

  public Generic();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return

  T method() throws java.lang.Exception;
    Code:
       0: aload_0
       1: getfield      #2                  // Field t:Ljava/lang/Object;
       4: checkcast     #3                  // class java/lang/AutoCloseable
       7: invokeinterface #4,  1            // InterfaceMethod java/lang/AutoCloseable.close:()V
      12: iconst_0
      13: istore_1
      14: aload_0
      15: getfield      #2                  // Field t:Ljava/lang/Object;
      18: checkcast     #5                  // class java/lang/Appendable
      21: iload_1
      22: invokeinterface #6,  2            // InterfaceMethod java/lang/Appendable.append:(C)Ljava/lang/Appendable;
      27: pop
      28: aload_0
      29: getfield      #2                  // Field t:Ljava/lang/Object;
      32: areturn

  public <T> T method2(T);
    Code:
       0: aload_1
       1: areturn
}

【讨论】:

  • 不应该是java.lang.Appendable吗?我理解演员表,但除此之外,“擦除到最左边的边界”是什么意思??
【解决方案4】:

这本身不是一个答案,只是这个 leftmost 规则 并不完全相关 - 我仍在尝试为此找出正确的 JLS 部分(不像我那么容易希望是这样),但您可以从这里看到该类型 不是 Object:

<T extends Object & Appendable & AutoCloseable> void whatType(T... args) {
    System.out.println(args.getClass().getComponentType().getSimpleName());
}


new DeleteMe().whatType(); // prints AutoCloseable

【讨论】:

  • 您所展示的是编译器在 call-site 处对实际数组类型的决定。擦除的方法参数类型仍然是Object[]。不同的调用者可能使用不同的数组类型;您可以添加另一个呼叫者whatType(new StringWriter()),该呼叫者将使用StringWriter[]。这个决定背后的规则是一个完全不同的问题,因为 OP 假设 T 被删除为 Object 是正确的,尽管我不知道为什么 OP 认为这有任何相关性。
  • @Holger 谢谢,所以它总是擦除Object.. 但是&lt;T extends AutoCloseable &amp; Runnable&gt; 这个使用的类型是AutoCloseable&lt;T extends AutoCloseable &amp; Serializable&gt;Serializable?最左边的功能适合这里吗?
  • 我不知道 javac 在这里使用什么策略,规范似乎没有支持这一点。但真正有趣的是,这种行为会导致不兼容的数组分配(Serializable[] 被传递给类型为 AutoCloseable[] 的形式参数)在 JVM 的雷达下飞行,同时显式转换为已经出现的内容失败。但是,如前所述,这完全是一个现在的问题(并且似乎打开了一个非常大的兔子洞)。
  • @Holger 我不想在这里获得荣誉......问题最初是在这里发布的stackoverflow.com/questions/50177313/… 虽然有很多赞成票的答案,但我很难过理解它
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-04-07
  • 1970-01-01
  • 2019-09-05
  • 2012-01-17
相关资源
最近更新 更多