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

Я пытаюсь написать некоторый код, который получает различные реализации классов в пакете java.util.function.*, но я продолжаю работать с определенной ошибкой компилятора для определенного синтаксиса, который я действительно хотел бы обойти (аргументы явного типа, такие как obj.<Type>foo(/*...*/) работают, но им не хватает благодати).

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

Предположим, что следующие два класса (реализация не имеет значения):

Некоторый класс сущности с методами получения/установки:

class Thing {
  public List<String> getThings() {
    return null;
  }

  public void setThings(List<String> things) {
  // ...
  }
}

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

class FooBar<A> {
  public <B> void foo(Function<A, List<B>> f) {
    // ...
  }

  public <B> void bar(BiConsumer<A, List<B>> f) {
    // ...
  }

  public <B> void wuz(Function<A, List<B>> x, BiConsumer<A, List<B>> y) {
    // ...
  }
}

При выполнении вызовов этих методов компилятор выдает мне разные ошибки:

// create an instance of the handler class
FooBar<Thing> fb = new FooBar<>();

Бывший. 1) Вызов метода, который ожидает функцию, работает нормально, ошибок компиляции нет:

fb.foo(Thing::getThings);

Бывший. 2) Тем не менее, вызов метода, который ожидает биконсумента, дает мне следующее:

fb.bar(Thing::setThings);
// The type Thing does not define setThings(Thing, List<B>) that is applicable here

Бывший. 3) Как и ожидалось, явное указание типа работает нормально, без ошибок компиляции:

fb.<String>bar(Thing::setThings);

Бывший. 4) Тем не менее, когда я пишу лямбда от руки, это дает мне другую ошибку компиляции (хотя указание типов в лямбда-аргументе работает нормально):

fb.bar((thing, things) -> thing.setThings(things));
// The method setThings(List<String>) in the type Thing is not applicable for the arguments (List<Object>)

Бывший. 5) И при вызове метода, который ожидает оба, я получаю другую ошибку компиляции для каждого аргумента:

fb.wuz(
  Thing::getThings,
  // The type of getThings() from the type Thing is List<String>, this is incompatible with the descriptor's return type: List<B>

  Thing::setThings
  // The type Thing does not define setThings(Thing, List<B>) that is applicable here
);

Бывший. 6) Опять же, как и ожидалось, явное указание типа снова работает нормально, без ошибок компиляции:

fb.<String>wuz(Thing::getThings, Thing::setThings);

Бывший. 7) И это то, что меня больше всего озадачивает: для метода, который ожидает и функцию, и биконсумер, записывая только биконсумер (без типов) и используя ссылки на методы для функции, работает нормально, нет ошибок компиляции (?!):

fb.wuz(Thing::getThings, (thing, things) -> thing.setThings(things));

Чего я не понимаю, так это того, что, по-видимому, компилятор обрабатывает явные лямбда-выражения и ссылки на методы по-разному при определении стирания типа/типа во время выполнения в разных сценариях, а именно:

  1. Функциональный интерфейс, который получает аргументы и возвращает значение
  2. Функциональный интерфейс, который получает аргументы, но не возвращает значения
  3. Метод с аргументами для типов 1. и 2.

Кажется, это происходит только тогда, когда тип функционального интерфейса сам по себе является универсальным типом (в данном примере это список), что наводит меня на мысль, что это имеет какое-то отношение к стиранию типа, но я в недоумении относительно отвечать.

Я хотел бы иметь возможность писать...

fb.wuz(Thing::getThings, Thing::setThings);

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

Если есть способ реорганизовать методы в FooBar для поддержки этого, мне бы очень хотелось знать.

Конечно, если кто-то может объяснить эти различные варианты поведения компилятора, я тоже буду очень признателен! :-)

ИЗМЕНИТЬ

Мне особенно интересно узнать, почему примеры № 1 и № 7 работают, но почему пример № 4 сам по себе не работает (а затем, почему пример # 2 тоже не работает).


person Yuthura    schedule 07.03.2015    source источник
comment
Я не знаю, может ли это помочь, но вот оно: class FooBar<A, B>; удалить ‹B› из сигнатур методов; FooBar<Thing, String> fb = new FooBar<>();   -  person Javi Mollá    schedule 10.03.2015
comment
Это действительно могло бы решить эту проблему, но это означало бы, что B предопределен для всего экземпляра, тогда как мне действительно нужно, чтобы он был другим при каждом вызове метода. Мне особенно любопытно, почему лямбда-функция с одним аргументом не работает, в то время как лямбда-функция с двумя аргументами работает (мне бы очень хотелось понимаю почему).   -  person Yuthura    schedule 10.03.2015
comment
Какую версию jdk вы используете? В моем случае все вызовы, кроме одного, компилируются без ошибок. Я загружаю самую последнюю версию прямо сейчас, чтобы проверить результаты.   -  person Holger    schedule 23.03.2015
comment
Я использую jdk 1.8.0_25, но вы заставили меня задуматься. Я скомпилировал пример вручную, и он отлично работает, как вы сказали! Я посмотрел немного дальше, и это похоже на ошибку в редакторе Eclipse. Я обновил Eclipse с 4.4.0 до 4.4.2, после чего редактор тоже заработал нормально. Большое спасибо за то, что помогли мне понять это, не могу поверить, что не подумал об этом раньше. Это не первый раз, когда редактор Eclipse заставляет меня часами что-то выяснять, просто чтобы выяснить, что это ошибка в Eclipse!   -  person Yuthura    schedule 24.03.2015


Ответы (1)


Заданный вопрос на самом деле не является ошибкой компиляции, а скорее ошибкой в ​​редакторе Eclipse. Версия Eclipse, в которой мне представилась ошибка, была Luna EE 4.4.0. После обновления до Luna EE 4.4.2 редактор вел себя так, как я ожидал (и так уже вел себя компилятор Java).

person Yuthura    schedule 24.03.2015