6

Please consider this example:

import java.util.function.Consumer;

public class Example {
    public static void main(String[] args) {
        Example example = new Example();

        example.setConsumer(test -> System.out.println("passed string is " + test)); //uses MyConsumer, why ?
        example.getConsumer().accept("Test 1");

        example.setConsumer((MyConsumer<String>)test -> System.out.println("passed string is " + test)); //uses MyConsumer
        example.getConsumer().accept("Test 2");

        example.setConsumer((Consumer<String>)test -> System.out.println("passed string is " + test)); //uses Consumer
        example.getConsumer().accept("Test 3");
    }

    private Consumer<String> consumer;

    public Consumer<String> getConsumer() {
        return consumer;
    }

    public void setConsumer(Consumer<String> consumer) {
        this.consumer = consumer;
    }

    public void setConsumer(MyConsumer<String> consumer) {
        this.consumer = consumer;
    }

    @FunctionalInterface
    public interface MyConsumer<T> extends Consumer<T> {
        @Override
        default void accept(T value) {
            System.out.println("In consumer string: " + value); //example thing to do
            receive(value);
        }

        void receive(T value);
    }
}

What interests me here is the first test. Why is it using MyConsumer instead of Consumer ? What if I had more different possible Consumers with the same lambda structure, who has priority ? Plus, the cast I do on Test 2 is marked as Redundant by my IDE. That means the lamdba is created as a MyConsumer first. Why so ?

I'm using IntelliJ Idea with Javac.

jub0bs
  • 60,866
  • 25
  • 183
  • 186
Winter
  • 3,894
  • 7
  • 24
  • 56
  • I don't know which IDE you're using. I think it's the JVM, using the runtime type of the object, that "decides". – duffymo Feb 01 '17 at 14:05
  • 3
    I think Java chooses the most specific type, which in this case is `MyConsumer`. If `MyConsumer` wasn't a subinterface of `Consumer`, I think you would have gotten an error saying it is an ambigious call. – marstran Feb 01 '17 at 14:07
  • 7
    It chooses the most specific method. If you had for instance `MyConsumer2 extends Consumer` and `setConsumer(MyConsumer2 consumer)` then the first call would be ambiguous and you would have a compile time error. See also [JLS 15.12.2.5](https://docs.oracle.com/javase/specs/jls/se8/html/jls-15.html#jls-15.12.2.5). – Alexis C. Feb 01 '17 at 14:07
  • 2
    The question by itself leads to the recommendation: don't use different functional interfaces as the criterion for method overloading. Overloading by itself can be hard to understand. Overloading with type inference for lambdas against different target types is bad idea from the outset. In some situations even compiler writers have to debug their own compiler to find out the correct answer to overload resolution. Regular programmers don't have a chance here. – Stephan Herrmann Feb 02 '17 at 18:32

1 Answers1

7

It's according to the procedure of choosing the most specific method as defined by the language specification:

If more than one member method is both accessible and applicable to a method invocation, it is necessary to choose one to provide the descriptor for the run-time method dispatch. The Java programming language uses the rule that the most specific method is chosen.

...

A functional interface type S is more specific than a functional interface type T for an expression e if T is not a subtype of S and one of the following is true (where U1 ... Uk and R1 are the parameter types and return type of the function type of the capture of S, and V1 ... Vk and R2 are the parameter types and return type of the function type of T):

  • If e is an explicitly typed lambda expression (§15.27.1), then one of the following is true:
  • R2 is void.

  • R1 <: R2.

  • R1 and R2 are functional interface types, and there is at least one result expression, and R1 is more specific than R2 for each result expression of e.

    (The result expression of a lambda expression with a block body is defined in §15.27.2; the result expression of a lambda expression with an expression body is simply the body itself.)

  • R1 is a primitive type, and R2 is a reference type, and there is at least one result expression, and each result expression of e is a standalone expression (§15.2) of a primitive type.

  • R1 is a reference type, and R2 is a primitive type, and there is at least one result expression, and each result expression of e is either a standalone expression of a reference type or a poly expression.

  • If e is an exact method reference expression (§15.13.1), then i) for all i (1 ≤ i ≤ k), Ui is the same as Vi, and ii) one of the following is true:
  • R2 is void.

  • R1 <: R2.

  • R1 is a primitive type, R2 is a reference type, and the compile-time declaration for the method reference has a return type which is a primitive type.

  • R1 is a reference type, R2 is a primitive type, and the compile-time declaration for the method reference has a return type which is a reference type.

  • If e is a parenthesized expression, then one of these conditions applies recursively to the contained expression.

  • If e is a conditional expression, then for each of the second and third operands, one of these conditions applies recursively.

Therefore, MyConsumer is more specific than Consumer because Consumer (T in the spec) is not a subtype and both have a return value of void.

M A
  • 71,713
  • 13
  • 134
  • 174