为什么要添加“.map(a - > a)”允许这个编译? [英] Why does adding ".map(a -> a)" allow this to compile?

查看:133
本文介绍了为什么要添加“.map(a - > a)”允许这个编译?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

这与我对流式缩减不兼容类型的回答有关。我不知道为什么我建议的作品,并 Holger 正确地向我强调这一点。但即使他似乎没有一个明确的解释,为什么它的作品。所以,让我们问它作为自己的问题:



下面的代码不能在 javac (对于下面的ideone链接,这是 sun-jdk-1.8.0_51 ,每个 http://ideone.com/faq ):

  public< T>带有(Stream< Predicate< ;? super T>>谓词)的对象{
返回predicates.reduce(Predicate :: or);

$ / code>

正确如此:将这个流中的两个谓词组合起来就像写:

 谓词<?超T> a = null; 
谓词<?超T> b = null;
a.or(b); //编译器错误!

然而,它会在intellij中编译,尽管在 Predicate ::或方法参考。显然,它也会在eclipse中编译(根据原始问题)。



但是这段代码确实如此:

  public< T>带有(Stream< Predicate<?super T>>谓词)的对象{
返回predicates.map(a - > a).reduce(Predicate :: or);
// ^ ---------- ^新增
}

Ideone demo



<尽管我想这样做,但我不清楚为什么这会起作用。我的手边的解释是, .map(a - > a)的作用类似于cast,并且使类型推断算法更灵活地选择类型它允许应用 reduce 。但我不确定究竟是什么类型。



请注意,这不等同于使用 .map(Function.identity() ),因为这被限制为返回输入类型。 ideone demo

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




<详细一点:



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

  public< T>可选< ;?扩展谓词<? super T>> (
Stream< Predicate<?super T>>谓词){
返回predicates.map(a - > a).reduce(Predicate :: or);
}

这是使用 -XDverboseResolution =所有。不完全确定这是否是我可以用来调试类型推断的最相关的输出;请告知是否有更好的方法:

  Interesting.java:5:注意:解析方法< init> Object类型到候选类型0 
类有趣的{
^
阶段:BASIC
with actuals:无参数
with type-args:无参数
候选:
#0找到的适用方法:Object()

Interesting.java:7:注意:将类型流中的方法映射解析为候选0
返回predicates.map(a - > a).reduce(Predicate :: or);
^
阶段:基本
包含实际值:<无>
with type-args:no arguments
candidates:
#0找到适用的方法:< R> map(Function< ;? super T#1,?extends R>)
(部分实例化为:(函数<超级谓词<超级T#2>,扩展对象>)流< Object>)
其中R,T#1,T#2是类型变量:
R extends在方法< R> map中声明的对象(函数< ;? super T#1,?extends R>)
T#1 extends在接口Stream中声明的对象
T#2 extends对象声明(Stream< Predicate< super T#2>)

Interesting.java:7:注意:方法< R>映射的延迟实例化(函数< ;?super T#1,?extends R>)
返回predicates.map(a - > a).reduce(Predicate :: or);
^
实例化的签名:(函数<超级谓词<超级T#2>,?扩展谓词< CAP#1>)流<谓词< CAP#1>>
target-type:< none>
其中R,T#1,T#2是类型变量:
R extends在方法< R>映射中声明的对象(Function< super T#1,?extends R>)
T#1 extends接口Stream中声明的对象
T#2扩展在< T#2>方法中声明的对象(Stream< Predicate< ;? super T#2>>)
其中CAP #1是一个新鲜的类型变量:
CAP#1扩展Object super:T#2捕获? super T#2

Interesting.java:7:注意:解析方法将类型为Stream的元素减少为候选元素1
返回predicates.map(a - > a).reduce(Predicate ::要么);
^
阶段:基本
包含实际值:<无>
with type-args:no arguments
candidate:
#0不适用找到的方法:< U> reduce(U,BiFunction< U,?super T,U> BinaryOperator< U> ;)
(不能推断类型变量U
(实际和形式参数列表的长度不同))
#1找到的适用方法:reduce(BinaryOperator< T>)
#2不适用找到的方法reduce(T,BinaryOperator< T>)
(实际和形式参数列表长度不同)
其中U,T是类型变量:
U (U,BiFunction< U,?super T,U>,BinaryOperator< U>)
T扩展在接口Stream

中声明的对象有趣的。 java:7:注意:将类型LambdaMetafactory中的方法metafactory解析为候选0
返回predicates.map(a - > a).reduce(Predicate :: or);
$
阶段:BASIC
带实际值:查找,字符串,方法类型,方法类型,方法处理,方法类型
带有类型参数:无参数
候选者:
#0找到的适用方法:metafactory(Lookup,String,MethodType,MethodType,MethodHandle,MethodType)

Interesting.java:7:注意:将类型LambdaMetafactory中的方法metafactory解析为候选0
返回predicates.map(a - > a).reduce(Predicate :: or);
$
阶段:BASIC
带实际值:查找,字符串,方法类型,方法类型,方法处理,方法类型
带有类型参数:无参数
候选者:
#0找到适用的方法:metafactory(Lookup,String,MethodType,MethodType,MethodHandle,MethodType)


解决方案

除非我在FunctionalInterface推理的发生方面丢失了一些东西,否则很明显你不能在Stream上调用Reduce< ? super predicate>,因为它没有足够的类型被推断为BinaryOperator。

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

p>

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

如果您删除对映射的调用,编译器将无法正确输入流满足第二个捕获要求。在map中,编译器给定了确定满足捕获所需的类型的纬度,但是如果没有泛型的具体绑定,那么两个捕获只能通过可能通过map()得到的对象流满足。 / p>

现在实现的Predicate接口只是构建一个链,但预计这个用途是一个合成实体。假定它只接受一个参数,但实际上由于Java泛型的缺点,AND和OR的性质需要两个参数而没有类型保证。通过这种方式,API似乎不是理想的设计。



对map()的调用将输入的控制从显式的Predicates流转换为编译器可以保证会满足所有的捕获。



以下两个都满足

  public< T>可选< ;?扩展谓词<? super T>> (Stream< Predicate< Object>>谓词)
public< T>可选< ;?扩展谓词<? super T>>与(Stream< Predicate< T>谓词)

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


This is related to my answer to "stream reduction incompatible types". I don't know why what I suggested works, and Holger rightly pressed me on this. But even he doesn't seem to have a clear explanation for why it works. So, let's ask it as its own question:

The following code does not compile in javac (for the links to ideone below, this is sun-jdk-1.8.0_51, per http://ideone.com/faq):

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

And rightly so: or-ing together two predicates from this stream is like writing:

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

However, it does compile in intellij, although with a raw type warning on the Predicate::or method reference. Apparently, it would also compile in eclipse (according to the original question).

But this code does:

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

Ideone demo

Despite the fact I thought to try this, it's not exactly clear to me why this would work. My hand-wavy explanation is that .map(a -> a) acts like a "cast", and gives the type inference algorithm a bit more flexibility to pick a type which allows the reduce to be applied. But I'm not sure exactly what that type is.

Note that this isn't equivalent to using .map(Function.identity()), because that is constrained to return the input type. ideone demo

Can anybody explain why this works with reference to the language spec, or if, as suggested by Holger, it is a compiler bug?


A bit more detail:

The return type of the method can be made a bit more specific; I omitted it above so that the nasty generics on the return type wouldn't get in the way:

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

This is the output of compiling with -XDverboseResolution=all. Not entirely sure if this is the most relevant output I can post to debug the type inference; please advise if there is something better:

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)

解决方案

Unless I am missing something in how FunctionalInterface inferences occur, it seems pretty obvious that you can't call reduce on a Stream < ? super Predicate > because it does not have sufficient typing to be inferred as a BinaryOperator.

The method reference hides a very important part of the story, the second parameter.

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

If you remove the call to map, the compiler does not have the opportunity to type the stream appropriately to satisfy the second capture requirements. With map the compiler is given the latitude to determine the types required to satisfy the captures, but without a concrete binding of the generics the two captures can only be satisfied with a Stream of Object which is likely what would result through map().

The Predicate interface as implemented now is simply building a chain, but the use is expected to be a composed entity. It is assumed to take a single parameter, but in fact the nature of AND and OR require two parameters without a type guarantee because of the shortcomings of Java's generics. In this way the API seems to be less than ideally designed.

The call to map() cedes the control of the typing, from the explicit Stream of Predicates, to one the compiler can guarantee will satisfy all captures.

The following both satisfy the compiler in IDEone, by directly inducing a flexible enough type in the case of Object, or a known type in the case of 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 generics still need a way to force capture type equivalence, as the helper methods are clearly not enough.

这篇关于为什么要添加“.map(a - &gt; a)”允许这个编译?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆