铸造成原始类型

时间:2018-04-25 21:41:54

标签: java generics casting raw-types

我遇到了一些代码,我不明白为什么会这样。假设我创建了一个通用接口Foo<T>,如下所示:

interface Foo<T>{
   void set(T item);
}

然后我创建一个名为Bar的类,它实现Foo<String>如下:

class Bar implements Foo<String>{ 
   @override
   public void set(String item){
      //useless body
   }
}

基于此,我们可以编写以下代码:

Bar bar = new Bar();
bar.set("Some string");
Foo rawFoo = (Foo) bar;
rawFoo.set(new Object()); // ClassCastException: Object cannot be cast to string

最后一行是我没有真正得到的。众所周知,当使用原始类型时,通用参数类型将转换为Object。 在这种情况下,代码编译,我们可以将Object传递给set()方法。但是Java如何确定它必须在运行时将Object转换为String?

4 个答案:

答案 0 :(得分:5)

如果您使用Bar反编译javap

class Bar implements Foo<java.lang.String> {
  Bar();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return

  public void set(java.lang.String);
    Code:
       0: return

  public void set(java.lang.Object);
    Code:
       0: aload_0
       1: aload_1
       2: checkcast     #2                  // class java/lang/String
       5: invokevirtual #3                  // Method set:(Ljava/lang/String;)V
       8: return
}

void set(java.lang.Object)是一种合成桥接方法。请注意checkcast指令。等效的“真实”代码如下所示:

public void set(Object object) {
  set((String) object);
}

编译器创建了此方法,以使类在类型擦除下工作;执行工作的实际方法是set(java.lang.String),bridge方法委托给它。

这种桥接方法实际上覆盖了基类中的方法。使用@Override注释声明的方法仅仅是一个过载。

答案 1 :(得分:1)

首先,您所做的只是提供对现有对象的引用,而不是创建新对象。

  

编译扩展参数化类的类或接口时   或实现参数化接口,编译器可能需要   创建一个合成方法,称为桥接方法,作为类型的一部分   擦除过程。

在类型擦除后,你的Foo变为:

interface Foo{
   void set(Object item);
}

你的班级变成了:

class Bar implements Foo{ 
   @override
   public void set(String item){
      //useless body
   }
}

在类型擦除后,方法签名不匹配。

因此,Bar set方法不实现Foo set方法。

要解决此问题并在类型擦除后保留泛型类型的多态性,Java编译器会生成一个桥接方法,以确保子类型按预期工作。对于Bar类,编译器为set生成以下桥接方法:

public void set(Object item){
    set((String) data);
}

https://docs.oracle.com/javase/tutorial/java/generics/bridgeMethods.html

答案 2 :(得分:1)

Java中的泛型是一种编译时类型的安全功能。因为它们是在1.5版本中引入的,所以它们必须与以前的版本向后兼容。这允许您使用原始类型 - 没有类型参数的变量引用具有类型参数的类。

在运行时,大多数类型参数信息都被删除,但不是全部。在this tutorial page about type erasure中,我们了解到编译器将在必要时插入强制转换。

  
      
  • 如果类型参数是无界的,则将泛型类型中的所有类型参数替换为其边界或对象。因此,生成的字节码只包含普通的类,接口和方法。
  •   
  • 如有必要,插入类型转换以保护类型安全。
  •   
  • 生成桥接方法以保留扩展泛型类型中的多态性。
  •   

这里,编译器创建一个桥接方法以在运行时保留多态性,这需要Object,将其参数转换为String,然后调用实际set(String)。 1}}方法。

当您调用rawFoo.set(new Object());时,由于多态性,在Bar中调用此桥接方法。这是您在此处看到的隐式演员,显示为ClassCastException

答案 3 :(得分:0)

事实证明,我的问题的答案是桥梁方法。我目前正在阅读Maurice Naftalin和Philip Wadler撰写的“Java Generics and Collections”一书。我实际上阅读了有关桥接方法的部分,但似乎我没有仔细阅读。我很高兴所有答案都有共同点。现在,我将再次阅读该部分