当Java 8中的方法参数未经检查转换时,为什么会删除返回类型的泛型?

时间:2015-06-18 14:32:58

标签: java generics java-8

请考虑以下代码示例:

import java.util.ArrayList;
import java.util.List;

public class Main {
    public static void main(String[] args) {
        List list = new ArrayList<Integer>();
        String response = getProducer(list).get();
    }
    static Producer<String> getProducer(List<Integer> list) {
        return new Producer<String>();
    }
}
class Producer<T> {
    T get() {
        return null;
    }
}

在Java 7中编译时,它只会为getProducer(list)生成预期警告:

  

警告:(7,39)java:未经检查的转换   必填:java.util.List<java.lang.Integer>   发现:java.util.List

但是,在Java 8中编译时,它会为response = getProducer(list).get()赋值产生以下错误:

  

错误:(7,48)java:不兼容的类型:java.lang.Object无法转换为java.lang.String

显然,getProducer(list)返回的类型不是Producer<String>,而是删除了Producer(这也是通过IDE中的“提取变量”功能确认的)。这非常令人费解,因为getProducer方法始终返回Producer<String>

奇怪的是,可以通过在调用getProducer方法时避免未经检查的转换来修复它,或者通过:

  • getProducer的参数类型从List<Integer>更改为List
  • list变量的类型从List更改为List<Integer>

更新

  • 使用的Java是Oracle JDK 1.8.0_40
  • 我也尝试过使用Java 8编译器从1.5到1.7的源和目标选项,结果是一样的。

问题

  • 传递的参数的泛型类型如何影响方法返回值的泛型类型,而返回值的泛型类型在方法签名中是固定的?
  • 为什么Java 7和Java 8之间存在这种向后不兼容的行为变化?

4 个答案:

答案 0 :(得分:17)

这看起来像已知的兼容性问题herehere

从第二个链接:

  

以下在JDK 7中编译并带有警告的代码不会   在JDK 8中编译:

import java.util.List;
class SampleClass {

     static class Baz<T> {
         public static List<Baz<Object>> sampleMethod(Baz<Object> param) {
             return null;
         }
     }

     private static void bar(Baz arg) {
         Baz element = Baz.sampleMethod(arg).get(0);
     }
}
  

在JDK 8中编译此代码会产生以下错误:

     

SampleClass.java:12:错误:不兼容的类型:对象不能   转换为Baz

Baz element = Baz.sampleMethod(arg).get(0);
     

注意:SampleClass.java使用未经检查或不安全的操作。注意:   使用-Xlint重新编译:取消选中以获取详细信息。 1错误

由此得出,OP的代码可以通过替换这一行来修复(右侧的类型声明让我失望 - 我把它读作一个不是的类型数组列表):

List list = new ArrayList<Integer>();

List<Integer> list = new ArrayList<Integer>();

不会导致从方法getProducer(List<Integer> list)

的返回类型中删除类型

再次引用第二个链接:

  

在此示例中,将原始类型传递给   sampleMethod(Baz<Object>)方法适用于子类型(请参阅   JLS,Java SE 7 Edition,第15.12.2.2节)。一个未经检查的   转换对于适用的方法是必要的,因此它的返回   类型被删除(参见JLS,Java SE 7 Edition,第15.12.2.6节)。在   在这种情况下,sampleMethod(Baz<Object>)的返回类型是   java.util.List代替java.util.List<Baz<Object>>,而不是get(int)   返回类型ObjectBaz,与分配不兼容   与{{1}}。

答案 1 :(得分:13)

让我们看一下Java语言规范:

Java 8

  

15.12.2.6。方法调用类型

     

...

     
      
  • 如果所选方法不是通用的,那么:

         
        
    • 如果要应用的方法需要未经检查的转换,则调用类型的参数类型是方法类型的参数类型,返回类型和抛出类型由返回类型和抛出类型的删除给出该方法的类型。
    •   
         

    ...

  •   

Java 7

  

15.12.2.6。方法结果和引发类型

     

所选方法的结果类型确定如下:

     
      
  • 如果声明所选方法的返回类型为void,则结果为void。

  •   
  • 否则,如果该方法需要未经检查的转换,则结果类型是方法声明的返回类型的擦除(§4.6)。

  •   

理解“通用方法”的含义非常重要:

Reference

  

8.4.4。通用方法

     

如果方法声明了一个或多个类型变量(§4.4),则该方法是泛型

换句话说,方法

static Producer<String> getProducer(List<Integer> list)

具有泛型参数和返回类型,但不是泛型,因为它不声明类型变量。

因此引用的部分适用,尽管在先决条件方面存在差异,但他们同意这种特定方法调用的后果,“如果未经检查的转换对于适用的方法是必要的,那么结果类型是擦除方法声明的返回类型“。

因此,使用返回类型Producer<String>而不是删除Producer的旧编译器违反了规范。

答案 2 :(得分:3)

真正的问题是;为什么你可以使用原始类型?为了向后兼容。如果是为了向后兼容,则假设只应使用原始类型。

  

传递参数的泛型类型如何影响方法返回值的泛型类型,而泛型类型的返回值在方法签名中是固定的?

方法或构造函数具有两种模式之一。它要么是完全通用的,要么是完全原始类型以便向后兼容。没有模式使用模式,它部分是原始类型,部分是通用的。原始类型是一个选项的唯一原因是向后兼容性,在这种情况下,它假定所有类型都是原始类型。

  

为什么Java7和Java8之间存在这种向后不兼容的行为变化?

由于Java 1.4不再受支持,并且已经有一段时间没有了,因此向后兼容性参数的含义并不强,并且可以为原始类型提供当前语言的位置。

答案 3 :(得分:1)

我猜这个推理是这样的,专注于“迁移”兼容性 -

如果调用为泛型类型的方法参数提供了原始类型参数,则调用代码很可能是预通用代码,而方法声明也是预先通用的,从那时起就被“演变”为使用泛型类型。

为了保持完美的兼容性,这种万无一失的解决方案是擦除方法类型,因此它就像预先通用版本一样。这样,调用的含义保持完全相同。

例如,对于JLS来说,更复杂的解决方案可能过于复杂 - 如果存在原始类型参数,我们如何进行类型推断。

今天,这个假设可能不再适用 - 调用更可能是后通用代码,由于各种原因,它仍然使用原始类型。

最好“纠正”原始类型
List list0 = ...; // got it from somewhere, possibly from an old API

@SuppressWarnings("unchecked")
List<Integer> list = list0;