使用 lambda 妨碍类型变量的推理

2022-09-04 22:29:52

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

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

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

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的已知/预期限制还是一个错误?


答案 1

如果我使用:

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

它工作正常,编译器可以正确推断所有类型。

如果我试试哟做:

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

编译失败,这是正确的,因为该方法需要一个参数,而不是。doSomethingSupplier<? 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;
    }
});

它编译时没有错误。

所以底线,问题是编译器认为这与 不同,当你使用lambda表达式时,它只是抱怨它(错误地)。不过,它不会抱怨匿名内部类,所以这一切都表明这是一个错误。List<Object>List<? extends Object>


答案 2

此问题是由使用 with in 方法定义引起的,但在调用方法时,您将直接使用。? extends VListdoSomethingnew Foo<List<Object>>

在那裡,不等於,因為是與類型的協方差,例如不能把type元素放進去,因為編譯器無法推斷出該類型應該是什麼。List<? extends Object>List<Object>? extends ObjectObjectObjectList<? 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>> {
...

如您所见,它覆盖了 as 类型。? extends VObject

但对于 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

或此示例:

列表是列表的子类吗?为什么 Java 泛型不是隐式多态的?


推荐