Java 8 Consumer/Function Lambda Ambiguity

后端 未结 1 1106
庸人自扰
庸人自扰 2020-12-14 16:21

I have an overloaded method that takes a Consumer and a Function object respectively and returns a generic type that matches the corresponding Consumer/Function. I thought t

相关标签:
1条回答
  • 2020-12-14 16:40

    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).

    0 讨论(0)
提交回复
热议问题