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

时间:2014-11-26 01:31:39

标签: java generics java-8

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

  1. 为什么foo2()编译但foo3()产生编译错误(详见代码注释)?
  2. 为什么javac不允许在foo3中从Object到T的未经检查的强制转换,但允许从Function< Object,Object>中取消选中的强制转换。功能< T,T>在foo4()?
  3. 在编译的4种方法中,哪一种最好?
  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));
      }
    }
    

3 个答案:

答案 0 :(得分:5)

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

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

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);
}

Java tutorial for generic type inference很好地描述了规则 - “目标类型”部分最相关。

在bar1()和bar3()中,编译器可以从返回值推断emptyList()的泛型参数。这适用于Java 5以上。

从Java 8开始,也可以从方法参数推断泛型类型。所以下面的代码:

public void bar4()
{
    String s = readFirstElement(Collections.emptyList());
}

private <T> T readFirstElement(List<T> list)
{
    return list.get(0);
}

将在Java 8中编译良好,但在早期版本中失败。

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

答案 1 :(得分:4)

  1. foo2有效,因为在Function<T, T> identity = Function.identity();语句中,identity方法的类型参数自动选为T(请参阅javadoc §18.5.1: generic method applicability testing) 。但是在foo3中,Function.identity()创建了一个原始Function对象,而原始apply对象的Function方法的返回类型为Object。 (仅当type参数是赋值表达式之前计算的最后一个表达式的参数时,自动选择type参数才有效)

  2. 允许在foo4中投放,因为只要没有Function永远不能投放到Function<T,T>的信息,就允许显式强制转换。 (见java language spec 15.16)。此外,foo3中也允许实际 的未经检查的广告。以下foo3方法完全有效:

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

答案 2 :(得分:0)

Function.identity()是一个静态泛型方法,它有自己的类型,而T foo3(T t)需要另一个类型T.所以有必要在某个地方进行投射。 (Function.<T>identity()(Function<T, T>) Function.identity())等等......)

不过,它只是关于通用的,它与java-8 / lambda表达无关。