Использование лямбды препятствует выводу переменной типа - PullRequest
0 голосов
/ 21 сентября 2018

У меня есть следующий код, который успешно компилируется:

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 в следующее лямбда-выражение, код больше не будет компилироваться:

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.

Почему код с лямбдой не компилируется, хотя он эквивалентен не лямбда-версии?Это известное / предполагаемое ограничение Java или это ошибка?

Ответы [ 3 ]

0 голосов
/ 21 сентября 2018

Если я использую:

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

Он просто отлично работает и все типы корректно выводятся компилятором.

Если я пытаюсь выполнить yo, то сделайте:

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

Компиляция не удалась, и это правильно, потому что метод doSomething ожидает аргумент Supplier<? extends List<? extends V>>, а () -> () -> 1 - нет.

И если я это сделаю:

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

Этоработает, как и ожидалось.

Так что здесь не нужно ничего разыгрывать, просто используйте лямбда-выражения и позволяйте компилятору выполнять свою работу.


РЕДАКТИРОВАТЬ:

И если я сделаю это:

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

Компилируется без ошибок.

Итак, суть в том, что компилятор считает , что List<Object> - это не то же самое, что List<? extends Object>, и когда вы используете лямбда-выражения, он просто жалуется на это (ошибочно).Однако он не жалуется на анонимные внутренние классы, поэтому все указывает на то, что это ошибка.

0 голосов
/ 21 сентября 2018

Проблема вызвана использованием ? extends V с List в doSomething определении метода, но когда вы вызываете метод, вы используете 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 тип.

но для лямбды он не сгенерирует соответствующий анонимный класс, например:

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

вышеупомянутый анонимный класс не сгенерирует, а lambda будет использовать invokedynamic вызов инструкции непосредственно, как:

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

Так что все равно попытайтесь вывести ? extends V, это приведет к компиляции файловls .

Ссылка:

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

или этот пример:

Is Подкласс List?Почему дженерики Java не являются неявно полиморфными?

0 голосов
/ 21 сентября 2018

В этой конкретной ситуации может помочь явное приведение:

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

ожидаемый тип возврата в лямбда-выражении:

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>>
...