为什么对泛型的这种使用不会引发运行时或编译时异常?

时间:2018-09-22 16:35:15

标签: java generics

我在类中有一个方法,该方法具有使用泛型指定的返回类型。

public class SomeMain {

  public static void main(String[] args) {

    Foo<Integer> foo = new Foo<Integer>();
    System.out.println(foo.getFoo()); // Works, prints out "Foo"

  }

  public static class Foo<E>  {
    public E getFoo() {
      return (E) "Foo";
    }
  }
}

对于一般的返回类型,我假设以上示例中的返回将计算为:

return (Integer) "Foo";  // Inconvertible types !!!

返回String并正确打印。

如果将调用更改为:

String fooString = foo.getFoo(); // Compile error, incompatible types found
System.out.println(fooString);

我缺少什么来帮助我了解这里发生了什么以及为什么原始版本没有导致编译错误。

3 个答案:

答案 0 :(得分:18)

这是因为过载解析将您的println调用解析为println(Object),因为没有println(Integer)

请记住,Java的泛型会在运行时删除。像(E) "Foo"这样的演员表也将被删除,并移至呼叫站点。有时这不是必需的,因此仅在需要时才将内容强制转换为正确的类型。

换句话说,在getFoo内部不执行任何强制转换。语言规范支持这一点:

  

第5.5.2节已检查的演员表和未检查的演员表

     
      
  • 演员表是完全未经检查的演员表。

         

    不对此类转换执行运行时操作。

  •   

删除后,getFoo返回Object。并将其传递给println(Object),这很好。

如果我调用此方法并传递foo.getFoo,则会出现错误:

static void f(Integer i) {
    System.out.println(i);
}
// ...
f(foo.getFoo()); // ClassCastException

因为这一次需要被投射。

答案 1 :(得分:3)

System.out.println的过载不超过Integer。所以这句话:

System.out.println(foo.getFoo());

正在呼叫System.out.println(Object);

要验证其是否会失败,请尝试:

Foo<Integer> foo = new Foo<Integer>();
Integer fooInt = foo.getFoo(); //class cast exception

以下内容将以相同的方式失败:

public static void main(String[] args) throws Exception {
    Foo<Integer> foo = new Foo<Integer>();
    print(foo.getFoo()); //Would also fail with a class cast exception
}
static void print(Integer in) {
    System.out.println(in);
}

由于明显的原因,这使编译失败:

String fooString = foo.getFoo(); //can't work

fooFoo<Integer>,并且foo.getFoo()返回一个Integer,编译器可以选择它。

答案 2 :(得分:2)

我想补充一点,在type erasure过程中,Java编译器将无界类型参数E替换为Object,因此Foo类实际上已编译进入:

public static class Foo {
    public Object getFoo() {
        return "Foo";
    }
}

这就是为什么以下代码有效(不需要广播)的原因:

Object obj = foo.getFoo();
System.out.println(obj);

同时,如预期的那样,下一个代码段将产生编译时错误:

Foo<Integer> foo = new Foo<Integer>();
String fooString = foo.getFoo(); // you're trying to trick the compiler (unsuccessfully)
           ^
incompatible types: Integer can not be converted to String

这是泛型的主要职责-编译时检查。

然而,故事还有另一面-执行时强制转换。例如,如果您编写:

Integer value = foo.getFoo();

您会在运行时抛出ClassCastException(Java编译器会插入一条checkcast指令,以检查foo.getFoo()的结果是否可以转换为Integer)。 / p>