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

Max*_*sev 31 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;
    }
}
Run Code Online (Sandbox Code Playgroud)

在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方法时避免未经检查的转换来修复,或者通过:

  • 更改参数类型getProducerList<Integer>List
  • list变量类型更改ListList<Integer>

更新

  • 使用的Java是Oracle JDK 1.8.0_40
  • 我还尝试使用Java 8编译器从1.5到1.7的源和目标选项,结果是相同的.

问题

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

6to*_*ton 17

这看起来像此处此处报告的已知兼容性问题.

从第二个链接:

在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);
     }
}
Run Code Online (Sandbox Code Playgroud)

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

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

Baz element = Baz.sampleMethod(arg).get(0);
Run Code Online (Sandbox Code Playgroud)

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

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

List list = new ArrayList<Integer>();
Run Code Online (Sandbox Code Playgroud)

List<Integer> list = new ArrayList<Integer>();
Run Code Online (Sandbox Code Playgroud)

这不会导致从返回类型的方法中删除类型 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)Object,它与赋值兼容Baz.

  • 看起来更像是Java 7中的一个错误 - 事实上,只要你将非泛型和通用混合在一起,你最终会得到一些非泛型的东西,这已经是一个扩展的Java益智游戏.所以看起来这只是因为一个bug而习惯了. (7认同)

Hol*_*ger 13

让我们看看Java语言规范:

Java 8

15.12.2.6.方法调用类型

...

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

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

    ...

Java 7

15.12.2.6.方法结果和引发类型

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

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

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

重要的是要理解,"通用方法"的含义是:

参考

8.4.4.通用方法

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

换句话说,该方法

static Producer<String> getProducer(List<Integer> list)
Run Code Online (Sandbox Code Playgroud)

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

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

因此,旧的编译器通过使用返回类型Producer<String>而不是擦除来违反规范Producer.