使用lambda会阻止类型变量的推断

时间:2018-09-21 13:50:41

标签: java generics lambda java-8

我有以下代码可以成功编译:

import java.lang.String;
import java.util.List;
import java.util.Arrays;

interface Supplier<R> {
    Foo<R> get();
}

interface Foo<R> {
    public R getBar();
    public void init();  
}

public class Main {

    static private <V> void doSomething(final Supplier<? extends List<? extends V>> supplier) {
    // do something
    }

    static public void main(String[] args) {
        doSomething(new Supplier<List<Object>>(){
           @Override
           public Foo<List<Object>> get() {
               return new Foo<List<Object>>(){
                   @Override
                   public List<Object> getBar() {
                       return null;
                   }
                   @Override
                   public void init() {
                      // initialisation
                   }
               };
            }
       });
    }
}

但是,如果我将Supplier转换为以下lambda表达式,则代码将不再编译:

doSomething(() -> new Foo<List<Object>>(){
    @Override
    public List<Object> getBar() {
        return null;
    }
});

编译器错误为:

Main.java:22: error: method doSomething in class Main cannot be applied to given types;
    doSomething(() -> new Foo<List<Object>>(){
    ^
  required: Supplier<? extends List<? extends V>>
  found: ()->new Fo[...]; } }
  reason: cannot infer type-variable(s) V
    (argument mismatch; bad return type in lambda expression
      <anonymous Foo<List<Object>>> cannot be converted to Foo<List<? extends V>>)
  where V is a type-variable:
    V extends Object declared in method <V>doSomething(Supplier<? extends List<? extends V>>)

如果我将供应商的声明更改为Supplier<? extends List<V>>,则两个变体都将成功编译。

我使用Java 8编译器编译代码。

为什么带有lambda的代码虽然与非lambda版本等效,却不能编译? 这是Java的已知/预期限制还是错误?

3 个答案:

答案 0 :(得分:4)

如果我使用:

doSomething(() -> () -> null);

它工作得很好,并且编译器可以正确推断所有类型。

如果我尝试做:

doSomething(() -> () -> 1);

编译失败,这是正确的,因为doSomething方法需要一个Supplier<? extends List<? extends V>>参数,而() -> () -> 1不是。

如果我这样做:

doSomething(() -> () -> Arrays.asList(1, 2, 3));

它按预期工作。

因此无需在此处强制转换任何内容,只需使用lambdas并让编译器执行工作即可。


编辑:

如果我这样做:

doSomething(() -> new Foo<List<? extends Object>>() {
    @Override
    public List<? extends Object> getBar() {
        return null;
    }
});

它编译没有错误。

因此,最重要的是,问题在于编译器认为 List<Object>List<? extends Object>不同,当您使用lambda表达式时,它只是抱怨(错误)。不过,它并没有抱怨匿名内部类,所以这一切都表明这是一个错误。

答案 1 :(得分:2)

在这种特殊情况下,显式强制转换可以帮助:

doSomething((Supplier<List<Object>>) () -> new Foo<List<Object>>() {
                       ^
    @Override
    public List<Object> getBar() {
        return null;
    }
});

或更简单:

doSomething((Supplier<List<Object>>) () -> (Foo<List<Object>>) () -> null);

doSomething(() -> () -> null);

有时,Java编译器无法推断与您的意图匹配的类型,因此您可以显式指定它。另一个没有强制转换的示例(请查看声明的左侧一侧):

Supplier<List<Object>> supplier = () -> new Foo<List<Object>>() {
    @Override
    public List<Object> getBar() {
        return null;
    }
};

doSomething(supplier);

同时,当您写:

static <V> void doSomething(final Supplier<? extends List<? extends V>> supplier) {
}

doSomething(() -> new Foo<List<Object>>() {
    @Override
    public List<Object> getBar() {
        return null;
    }
});

lambda表达式中的预期返回类型为:

Foo<List<? extends V>>

与实际不同:

Foo<List<Object>>

编译器会在输出中告诉您有关

reason: cannot infer type-variable(s) V
    argument mismatch; bad return type in lambda expression
        <anonymous Foo<List<Object>>> cannot be converted to Foo<List<? extends V>>

答案 2 :(得分:2)

此问题是由于在? extends V方法定义中将ListdoSomething一起使用引起的,但是在调用方法时,您是直接使用new Foo<List<Object>>

其中List<? extends Object>不等于List<Object>,例如,由于? extends Object与类型Object是协方差的,因此您不能将Object类型元素添加到List<? extends Object>中,因为编译器无法推断出类型为? extends Object

因此,在您的示例中,您可以尝试直接使用new Foo<List<? extends Object>>()来修复它,例如:

    doSomething(() -> new Foo<List<? extends Object>>() {
        @Override
        public List<Object> getBar() {
            return null;
        }
    });

以及为什么使用匿名类可以在这里工作,请尝试反编译匿名类

class Main$1$1 implements Foo<java.util.List<java.lang.Object>> 
...
final class Main$1 implements SupplierT<java.util.List<java.lang.Object>> {
...

如您所见,它以? extends V类型覆盖Object

但是对于lambda,它将不会生成相应的匿名类,例如:

class Main$1 implements SupplierT<java.util.List<java.lang.Object>>

上述匿名类将不会生成,并且 lambda 将使用invokedynamic指令直接调用,如下所示:

   0: invokedynamic #5,  0              // InvokeDynamic #0:get:()LSupplierT;

因此,仍然尝试推断? extends V,这将导致编译失败

参考:

https://docs.oracle.com/javase/tutorial/java/generics/subtyping.html

或以下示例:

Is List a subclass of List? Why are Java generics not implicitly polymorphic?