Подтвердить что ты не робот

Почему добавление ".map(a → a)" позволяет компилировать?

Это связано с моим ответом на "несовместимые потоки типы" . Я не знаю, почему то, что я предложил, и Holger справедливо нажал на меня. Но даже у него нет четкого объяснения, почему он работает. Итак, позвольте задать его как собственный вопрос:

Следующий код не компилируется в javac (для ссылок на ideone ниже это sun-jdk-1.8.0_51, за http://ideone.com/faq):

public <T> Object with(Stream<Predicate<? super T>> predicates) {
  return predicates.reduce(Predicate::or);
}

И это правильно: объединение двух предикатов из этого потока похоже на запись:

Predicate<? super T> a = null;
Predicate<? super T> b = null;
a.or(b);  // Compiler error!

Однако он компилируется в intellij, хотя с предупреждением о необработанном типе в методе Predicate::or. По-видимому, он также будет компилироваться в затмении (в соответствии с исходным вопросом).

Но этот код делает:

public <T> Object with(Stream<Predicate<? super T>> predicates) {
  return predicates.map(a -> a).reduce(Predicate::or);
                // ^----------^ Added
}

Ideone demo

Несмотря на то, что я решил попробовать это, мне не совсем понятно, почему это сработает. Мое волнообразное объяснение состоит в том, что .map(a -> a) действует как "литье" и дает алгоритму вывода типа немного более гибкое, чтобы выбрать тип, который позволяет применять reduce. Но я точно не знаю, что это за тип.

Обратите внимание, что это не эквивалентно использованию .map(Function.identity()), поскольку это ограничение возвращает тип ввода. Ideone demo

Может кто-нибудь объяснить, почему это работает со ссылкой на спецификацию языка, или если, как было предложено Хольгером, это ошибка компилятора?


Немного подробнее:

Возвращаемый тип метода можно сделать более конкретным; Я опустил его выше, чтобы неприятные дженерики возвращаемого типа не мешали:

public <T> Optional<? extends Predicate<? super T>> with(
    Stream<Predicate<? super T>> predicates) {
  return predicates.map(a -> a).reduce(Predicate::or);
}

Это результат компиляции с -XDverboseResolution=all. Не совсем уверен, что это наиболее релевантный вывод, который я могу опубликовать для отладки вывода типа; пожалуйста, сообщите, есть ли что-то лучше:

Interesting.java:5: Note: resolving method <init> in type Object to candidate 0
class Interesting {
^
  phase: BASIC
  with actuals: no arguments
  with type-args: no arguments
  candidates:
      #0 applicable method found: Object()

Interesting.java:7: Note: resolving method map in type Stream to candidate 0
    return predicates.map(a -> a).reduce(Predicate::or);
                     ^
  phase: BASIC
  with actuals: <none>
  with type-args: no arguments
  candidates:
      #0 applicable method found: <R>map(Function<? super T#1,? extends R>)
        (partially instantiated to: (Function<? super Predicate<? super T#2>,? extends Object>)Stream<Object>)
  where R,T#1,T#2 are type-variables:
    R extends Object declared in method <R>map(Function<? super T#1,? extends R>)
    T#1 extends Object declared in interface Stream
    T#2 extends Object declared in method <T#2>with(Stream<Predicate<? super T#2>>)

Interesting.java:7: Note: Deferred instantiation of method <R>map(Function<? super T#1,? extends R>)
    return predicates.map(a -> a).reduce(Predicate::or);
                         ^
  instantiated signature: (Function<? super Predicate<? super T#2>,? extends Predicate<CAP#1>>)Stream<Predicate<CAP#1>>
  target-type: <none>
  where R,T#1,T#2 are type-variables:
    R extends Object declared in method <R>map(Function<? super T#1,? extends R>)
    T#1 extends Object declared in interface Stream
    T#2 extends Object declared in method <T#2>with(Stream<Predicate<? super T#2>>)
  where CAP#1 is a fresh type-variable:
    CAP#1 extends Object super: T#2 from capture of ? super T#2

Interesting.java:7: Note: resolving method reduce in type Stream to candidate 1
    return predicates.map(a -> a).reduce(Predicate::or);
                                 ^
  phase: BASIC
  with actuals: <none>
  with type-args: no arguments
  candidates:
      #0 not applicable method found: <U>reduce(U,BiFunction<U,? super T,U>,BinaryOperator<U>)
        (cannot infer type-variable(s) U
          (actual and formal argument lists differ in length))
      #1 applicable method found: reduce(BinaryOperator<T>)
      #2 not applicable method found: reduce(T,BinaryOperator<T>)
        (actual and formal argument lists differ in length)
  where U,T are type-variables:
    U extends Object declared in method <U>reduce(U,BiFunction<U,? super T,U>,BinaryOperator<U>)
    T extends Object declared in interface Stream

Interesting.java:7: Note: resolving method metafactory in type LambdaMetafactory to candidate 0
    return predicates.map(a -> a).reduce(Predicate::or);
                          ^
  phase: BASIC
  with actuals: Lookup,String,MethodType,MethodType,MethodHandle,MethodType
  with type-args: no arguments
  candidates:
      #0 applicable method found: metafactory(Lookup,String,MethodType,MethodType,MethodHandle,MethodType)

Interesting.java:7: Note: resolving method metafactory in type LambdaMetafactory to candidate 0
    return predicates.map(a -> a).reduce(Predicate::or);
                                         ^
  phase: BASIC
  with actuals: Lookup,String,MethodType,MethodType,MethodHandle,MethodType
  with type-args: no arguments
  candidates:
      #0 applicable method found: metafactory(Lookup,String,MethodType,MethodType,MethodHandle,MethodType)
4b9b3361

Ответ 1

Если я не пропущу что-то в том, как возникают выражения FunctionalInterface, кажется довольно очевидным, что вы не можете вызвать сокращение на Stream <? super Predicate > , потому что у него нет достаточного набора текста, чтобы быть выведенным как BinaryOperator.

Ссылка на метод скрывает очень важную часть истории, второй параметр.

return predicates.map(a->a).reduce((predicate, other) -> predicate.or(other));

Если вы удалите вызов для сопоставления, компилятор не имеет возможности правильно набирать поток для удовлетворения требований второго захвата. С картой компилятору задается широта для определения типов, необходимых для удовлетворения захватов, но без конкретной привязки дженериков два захвата могут быть удовлетворены только потоком объекта, что, скорее всего, будет результатом map().

Интерфейс Predicate, реализованный в настоящее время, представляет собой просто создание цепочки, но ожидается, что использование будет составной. Предполагается, что он принимает единственный параметр, но на самом деле характер AND и OR требует двух параметров без гарантии типа из-за недостатков генерических Java-генераторов. Таким образом API кажется менее идеальным.

Призыв к map() уступает контроль над типом, из явного потока предикатов, на тот, который может гарантировать компилятор, будет удовлетворять всем захватам.

Следующие оба удовлетворяют компилятору в IDEone, непосредственно вызывая достаточно гибкий тип в случае объекта или известный типа в случае T.

public <T> Optional<? extends Predicate<? super T>> with(Stream<Predicate<Object>> predicates)
public <T> Optional<? extends Predicate<? super T>> with(Stream<Predicate<T>> predicates)

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