Java 尝试在泛型类中应用lambda表达式时出现类型不匹配编译错误

Java 尝试在泛型类中应用lambda表达式时出现类型不匹配编译错误,java,generics,java-8,Java,Generics,Java 8,在下面的Java 8示例代码中,所有方法都是等效的,除了foo3()之外,所有方法都是可编译的 为什么foo2()会编译,而foo3()会产生编译错误(有关详细信息,请参阅代码注释) 为什么javac不允许在foo3中从对象到t进行未经检查的强制转换,而允许在foo4()中从函数到函数进行未经检查的强制转换 在4种编译方法中,哪一种是最好的 我怀疑这里涉及到Java编译时泛型的一些更好的方面,但它们对我来说似乎不清楚,也不明显 import java.util.function.Function

在下面的Java 8示例代码中,所有方法都是等效的,除了foo3()之外,所有方法都是可编译的

  • 为什么foo2()会编译,而foo3()会产生编译错误(有关详细信息,请参阅代码注释)
  • 为什么javac不允许在foo3中从对象到t进行未经检查的强制转换,而允许在foo4()中从函数到函数进行未经检查的强制转换
  • 在4种编译方法中,哪一种是最好的
  • 我怀疑这里涉及到Java编译时泛型的一些更好的方面,但它们对我来说似乎不清楚,也不明显

    import java.util.function.Function;
    
    public class LambdaTest<T> {
    
      public T foo1(T t) {
        Function<T, T> myIdentity = r -> r;
        return myIdentity.apply(t);
      }
    
      public T foo2(T t) {
        Function<T, T> identity = Function.identity();
        return identity.apply(t);
      }
    
      public T foo3(T t) {
        /*  XXX Compile error!
         *  java.lang.Error: Unresolved compilation problem:
         *  Type mismatch: cannot convert from Object to T
         */
        return Function.identity().apply(t);
      }
    
      @SuppressWarnings("unchecked")
      public T foo4(T t) {
        // unchecked conversion from Function<Object, Object> to Function<T, T>
        return ((Function<T, T>) Function.identity()).apply(t);
      }
    
      public T foo5(T t) {
        // provide an explicit type hint to the compiler
        return Function.<T>identity().apply(t);
      }
    
      public static void main(String[] args) {
        String hello = "Hello world!";
        LambdaTest<String> test = new LambdaTest<>();
        System.out.println("1. " + test.foo1(hello));
        System.out.println("2. " + test.foo2(hello));
        System.out.println("3. " + test.foo3(hello));
        System.out.println("4. " + test.foo4(hello));
        System.out.println("5. " + test.foo5(hello));
      }
    }
    
    import java.util.function.function;
    公共类LambdaTest{
    公共交通1(T){
    函数myIdentity=r->r;
    返回myIdentity.apply(t);
    }
    公共交通2(T){
    函数标识=Function.identity();
    返回标识。应用(t);
    }
    公共交通3(T){
    /*XXX编译错误!
    *java.lang.Error:未解决的编译问题:
    *类型不匹配:无法从对象转换为T
    */
    返回函数.identity().apply(t);
    }
    @抑制警告(“未选中”)
    公共交通4(T){
    //未经检查的从函数到函数的转换
    return((Function)Function.identity()).apply(t);
    }
    公共T-foo5(T-T){
    //向编译器提供显式类型提示
    返回函数.identity().apply(t);
    }
    公共静态void main(字符串[]args){
    String hello=“hello world!”;
    LambdaTest test=新的LambdaTest();
    System.out.println(“1.”+test.foo1(hello));
    System.out.println(“2.”+test.foo2(hello));
    System.out.println(“3.”+test.foo3(hello));
    System.out.println(“4.”+test.foo4(hello));
    System.out.println(“5.”+test.foo5(hello));
    }
    }
    
  • foo2
    之所以有效,是因为在语句中
    Function identity=Function.identity()
    自动选择
    identity
    方法的类型参数为
    T
    (请参阅)。但是在
    foo3
    中,
    Function.identity()
    创建一个原始
    函数
    对象,原始
    函数
    对象的
    apply
    方法的返回类型为
    对象
    。(仅当类型参数是赋值表达式之前计算的最后一个表达式的参数时,自动选择类型参数才有效)

  • 允许在
    foo4
    中强制转换,因为只要没有
    Function
    永远无法强制转换到
    Function
    的信息,就允许显式强制转换。(见附件)。在
    foo3
    中也允许未经检查的强制转换。以下
    foo3
    方法完全有效:

    public T foo3(T t) {
        return (T)(Function.identity().apply(t));
    }
    
  • foo1
    foo2
    foo5
    具有大致相同的“质量”。我会选择
    foo5
    ,因为它是最短的方法,并且至少与其他两种方法一样可读

  • Function.identity()
    是一种静态泛型方法,它有自己的类型,
    T foo3(T T)
    需要另一个类型T。因此有必要在某个地方进行强制转换。(
    Function.identity()
    (Function)Function.identity())
    ,等等。)

    顺便说一句,它只是泛型,与java-8/lambda表达式无关。

    foo3()
    不起作用,因为它没有被赋予计算它的类型

    Collections.emptyList()和此代码(没有lambda)也会出现类似的问题:

    public void bar1()
    {
        //Works
        List<String> list = Collections.emptyList();
        String s = list.get(0);
    }
    
    public void bar2()
    {
        //Compile error
        String s = Collections.emptyList().get(0);
    }
    
    public void bar3()
    {
        //Works
        String s = Collections.<String>emptyList().get(0);
    }
    
    在Java8中可以很好地编译,但在早期版本中会失败


    但是编译器不会从链式方法调用中推断泛型参数,这就是为什么您需要像foo5()那样为编译器提供类型参数的显式“提示”。

    我的首选是
    foo5
    。至于为什么
    foo3
    没有编译:我不太理解所有的类型推断规则,但我猜你问得太多了。谢谢,回答得很好,也很正确。但愿我能接受不止一个。被接受的答案更清楚地解决了问题2和3。就我的理解而言,JLS§5
    foo4
    应该在没有警告的情况下编译,因为强制转换上下文应该为泛型调用的类型推断提供目标类型(以及在推断
    函数的
    之后。identity()
    cast可以在编译时被证明是正确的)。但我并不惊讶于实际的
    javac
    实现有不同的观点…
    public void bar4()
    {
        String s = readFirstElement(Collections.emptyList());
    }
    
    private <T> T readFirstElement(List<T> list)
    {
        return list.get(0);
    }