Java 8 Consumer/Function Lambda Ambiguity

大兔子大兔子 提交于 2019-11-30 06:33:21

This line is definitely ambiguous:

doStuff(getPattern(x -> String.valueOf(x)));

Reread this from the linked JLS chapter:

A lambda expression (§15.27) is potentially compatible with a functional interface type (§9.8) if all of the following are true:

  • The arity of the target type's function type is the same as the arity of the lambda expression.

  • If the target type's function type has a void return, then the lambda body is either a statement expression (§14.8) or a void-compatible block (§15.27.2).

  • If the target type's function type has a (non-void) return type, then the lambda body is either an expression or a value-compatible block (§15.27.2).

In your case for Consumer you have a statement expression as any method invocation can be used as statement expression even if the method is non-void. For example, you can simply write this:

public void test(Object x) {
    String.valueOf(x);
}

It makes no sense, but compiles perfectly. Your method may have a side-effect, compiler doesn't know about it. For example, were it List.add which always returns true and nobody cares about its return value.

Of course this lambda also qualifies for Function as it's an expression. Thus it's ambigue. If you have something which is an expression, but not a statement expression, then the call will be mapped to Function without any problem:

doStuff(getPattern(x -> x == null ? "" : String.valueOf(x)));

When you change it to { return String.valueOf(x); }, you create a value-compatible block, so it matches the Function, but it does not qualify as a void-compatible block. However you may have problems with blocks as well:

doStuff(getPattern(x -> {throw new UnsupportedOperationException();}));

This block qualifies both as a value-compatible and a void-compatible, thus you have an ambiguity again. Another ambigue block example is an endless loop:

doStuff(getPattern(x -> {while(true) System.out.println(x);}));

As for System.out.println(x) case it's a little bit tricky. It surely qualifies as statement expression, so can be matched to Consumer, but seems that it matches to expression as well as spec says that method invocation is an expression. However it's an expression of limited use like 15.12.3 says:

If the compile-time declaration is void, then the method invocation must be a top level expression (that is, the Expression in an expression statement or in the ForInit or ForUpdate part of a for statement), or a compile-time error occurs. Such a method invocation produces no value and so must be used only in a situation where a value is not needed.

So compiler perfectly follows the specification. First it determines that your lambda body is qualified both as an expression (even though its return type is void: 15.12.2.1 makes no exception for this case) and a statement expression, so it's considered an ambiguity as well.

Thus for me both statements compile according to the specification. ECJ compiler produces the same error messages on this code.

In general I'd suggest you to avoid overloading your methods when your overloads has the same number of parameters and has the difference only in accepted functional interface. Even if these functional interfaces have different arity (for example, Consumer and BiConsumer): you will have no problems with lambda, but may have problems with method references. Just select different names for your methods in this case (for example, processStuff and consumeStuff).

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!