为什么添加".map(a - > a)"允许这个编译?

And*_*ner 23 java generics language-specifications java-8

这与我对"流减少不兼容类型"的回答有关.我不知道为什么我的建议有用,而霍尔格正确地向我施压.但即使他似乎也没有清楚解释它为何起作用.那么,让我们问它自己的问题:

以下代码无法编译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);
}
Run Code Online (Sandbox Code Playgroud)

这是正确的:或者将这个流中的两个谓词组合在一起就像写:

Predicate<? super T> a = null;
Predicate<? super T> b = null;
a.or(b);  // Compiler error!
Run Code Online (Sandbox Code Playgroud)

但是,它确实在intellij中编译,尽管在Predicate::or方法引用上有原始类型警告.显然,它也会在eclipse中编译(根据原始问题).

但是这段代码确实:

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

Ideone demo

尽管我想要尝试这个,但我并不完全清楚为什么这会起作用.我的手工波形解释是,.map(a -> a)行为类似于"强制转换",并为类型推断算法提供了更多的灵活性来选择允许reduce应用的类型.但我不确定那种类型到底是什么.

请注意,这不等于使用.map(Function.identity()),因为它被约束为返回输入类型.ideone demo

任何人都可以解释为什么这可以参考语言规范,或者如Holger建议的那样,它是一个编译器错误?


更详细一点:

该方法的返回类型可以更具体一些; 我在上面省略了它,以便返回类型上令人讨厌的泛型不会妨碍:

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

这是编译的输出-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)
Run Code Online (Sandbox Code Playgroud)

Mic*_*bay 2

除非我遗漏了 FunctionInterface 推理如何发生的某些内容,否则很明显您不能在 Stream < ? super Predicate > 因为它没有足够的类型来推断为 BinaryOperator。

方法引用隐藏了故事中非常重要的部分,即第二个参数。

return predicates.map(a->a).reduce((predicate, other) -> predicate.or(other));
Run Code Online (Sandbox Code Playgroud)

如果删除对 map 的调用,编译器就没有机会适当地键入流来满足第二次捕获要求。使用map,编译器可以自由地确定满足捕获所需的类型,但是如果没有泛型的具体绑定,则两个捕获只能通过对象流来满足,这很可能是通过map()产生的结果。

现在实现的 Predicate 接口只是简单地构建一条链,但使用时预计是一个组合实体。假设采用单个参数,但实际上由于 Java 泛型的缺点,AND 和 OR 的本质需要两个参数,而没有类型保证。从这个角度来看,API 的设计似乎不太理想。

对 map() 的调用将类型控制从显式谓词流转移到编译器可以保证满足所有捕获的类型。

以下都满足IDEone中的编译器,通过在 Object 的情况下直接引入足够灵活的类型,或在 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)
Run Code Online (Sandbox Code Playgroud)

Java 泛型仍然需要一种方法来强制捕获类型等效性,因为辅助方法显然是不够的。