Python中文网

一个关于 编程问题的解答网站.

有 Java 编程相关的问题?

你可以在下面搜索框中键入要查询的问题!

在Java8中,如果方法参数的转换未经检查,为什么返回类型的泛型会被删除?

考虑下面的代码示例:

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)生成预期的警告:

Warning:(7, 39) java: unchecked conversion required: java.util.List<java.lang.Integer> found: java.util.List

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

Error:(7, 48) java: incompatible types: java.lang.Object cannot be converted to 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.040
  • 我还尝试在Java8编译器中使用1.5到1.7的源代码和目标选项,结果是一样的

问题

  • 当返回值的泛型类型在方法签名中固定时,传递的参数的泛型类型如何影响方法返回值的泛型类型
  • 为什么Java 7和Java 8之间的行为会发生如此向后不兼容的变化

共 (3) 个答案

  1. # 1 楼答案

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

    How generic type of passed argument could affect generic type of method return value while generic type of return value is fixed in method signature?

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

    Why there is such backward incompatible change in behavior between Java7 and Java8?

    由于Java 1.4不再受支持,而且已经有一段时间不受支持了,因此向后兼容性的论点不那么有力,在当前的语言中给原始类型一个位置是有意义的

  2. # 2 楼答案

    我猜原因是这样的,关注“迁移”兼容性-

    如果调用为泛型类型的方法参数提供了原始类型参数,那么调用代码很可能是前泛型代码,并且用于前泛型的方法声明也已“进化”为使用泛型类型

    为了保持完美的兼容性,傻瓜式的解决方案是删除方法类型,使其与泛型之前的版本一样。这样,调用的含义保持完全相同

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


    如今,这种假设可能不再成立——调用更可能是后泛型代码,出于各种原因,它仍然使用原始类型。最好尽早“纠正”原始类型

    List list0 = ...; // got it from somewhere, possibly from an old API
    
    @SuppressWarnings("unchecked")
    List<Integer> list = list0; 
    
  3. # 3 楼答案

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

    从第二个链接:

    The following code which compiled, with warnings, in JDK 7 will not compile in 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);
         }
    }
    

    Compiling this code in JDK 8 produces the following error:

    SampleClass.java:12: error:incompatible types: Object cannot be converted to Baz

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

    Note: SampleClass.java uses unchecked or unsafe operations. Note: Recompile with -Xlint:unchecked for details. 1 error

    由此衍生出,OP的代码可以通过替换这一行来修复(右侧的类型声明让我感到不适——我将其解读为一个类型化数组列表,但它不是):

    List list = new ArrayList<Integer>();
    

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

    这不会导致从方法getProducer(List<Integer> list)的返回类型中删除类型

    再次引用第二链接:

    In this example, a raw type is being passed to the sampleMethod(Baz<Object>) method which is applicable by subtyping (see the JLS, Java SE 7 Edition, section 15.12.2.2). An unchecked conversion is necessary for the method to be applicable, so its return type is erased (see the JLS, Java SE 7 Edition, section 15.12.2.6). In this case the return type of sampleMethod(Baz<Object>) is java.util.List instead of java.util.List<Baz<Object>> and thus the return type of get(int) is Object, which is not assignment-compatible with Baz.