Lambda Expression and Method Overloading Doubts

Java 8 Lambda expression - Method overloading doubts

A functional interface must is a SAM interface:

a functional interface has exactly one abstract method. Since default methods have an implementation, they are not abstract. If an interface declares an abstract method overriding one of the public methods of java.lang.Object, that also does not count toward the interface's abstract method count since any implementation of the interface will have an implementation from java.lang.Object or elsewhere.

your interface has declared 2 abstract methods that the lambda expression don't know where to going, and the lambda expression is an instance of that interface which means that must implements all the abstract methods declared in the interface. but you can adding default method to solve your problem in this case, for example:

interface MathOperartor{
//it also can be removed, since a int can cast to a float automatically
default Object operate(int a, int b){
return operate((float)a, (float)b);
}
public Object operate(float a, float b);
}

Lambda expression and method overloading doubts

I think you found this bug in the compiler: JDK-8029718 (or this similar one in Eclipse: 434642).

Compare to JLS §15.12.2.1. Identify Potentially Applicable Methods:

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

Note the clear distinction between “void compatible blocks” and “value-compatible blocks”. While a block might be both in certain cases, the section §15.27.2. Lambda Body clearly states that an expression like () -> {} is a “void compatible block”, as it completes normally without returning a value. And it should be obvious that i -> {} is a “void compatible block” too.

And according to the section cited above, the combination of a lambda with a block that is not value-compatible and target type with a (non-void) return type is not a potential candidate for the method overload resolution. So your intuition is right, there should be no ambiguity here.

Examples for ambiguous blocks are

() -> { throw new RuntimeException(); }
() -> { while (true); }

as they don’t complete normally, but this is not the case in your question.

Java8: ambiguity with lambdas and overloaded methods

There is a lot of complexity at the intersection of overload resolution and type inference. The current draft of the lambda specification has all the gory details. Sections F and G cover overload resolution and type inference, respectively. I don't pretend to understand it all. The summary sections in the introduction are fairly understandable, though, and I recommend that people read them, particularly the summaries of sections F and G, to get an idea of what's going on in this area.

To recap the issues briefly, consider a method call with some arguments in the presence of overloaded methods. Overload resolution has to choose the right method to call. The "shape" of the method (arity, or number of arguments) is most significant; obviously a method call with one argument can't resolve to a method that takes two parameters. But overloaded methods often have the same number of parameters of different types. In this case, the types start to matter.

Suppose there are two overloaded methods:

    void foo(int i);
void foo(String s);

and some code has the following method call:

    foo("hello");

Obviously this resolves to the second method, based on the type of the argument being passed. But what if we are doing overload resolution, and the argument is a lambda? (Especially one whose types are implicit, that relies on type inference to establish the types.) Recall that a lambda expression's type is inferred from the target type, that is, the type expected in this context. Unfortunately, if we have overloaded methods, we don't have a target type until we've resolved which overloaded method we're going to call. But since we don't yet have a type for the lambda expression, we can't use its type to help us during overload resolution.

Let's look at the example here. Consider interface A and abstract class B as defined in the example. We have class C that contains two overloads, and then some code calls the apply method and passes it a lambda:

    public void apply(A a)    
public B apply(B b)

c.apply(x -> System.out.println(x));

Both apply overloads have the same number of parameters. The argument is a lambda, which must match a functional interface. A and B are actual types, so it's manifest that A is a functional interface whereas B is not, therefore the result of overload resolution is apply(A). At this point we now have a target type A for the lambda, and type inference for x proceeds.

Now the variation:

    public void apply(A a)    
public <T extends B> T apply(T t)

c.apply(x -> System.out.println(x));

Instead of an actual type, the second overload of apply is a generic type variable T. We haven't done type inference, so we don't take T into account, at least not until after overload resolution has completed. Thus both overloads are still applicable, neither is most specific, and the compiler emits an error that the call is ambiguous.

You might argue that, since we know that T has a type bound of B, which is a class, not a functional interface, the lambda can't possibly apply to this overload, thus it should be ruled out during overload resolution, removing the ambiguity. I'm not the one to have that argument with. :-) This might indeed be a bug in either the compiler or perhaps even in the specification.

I do know that this area went through a bunch of changes during the design of Java 8. Earlier variations did attempt to bring more type checking and inference information into the overload resolution phase, but they were harder to implement, specify, and understand. (Yes, even harder to understand than it is now.) Unfortunately problems kept arising. It was decided to simplify things by reducing the range of things that can be overloaded.

Type inference and overloading are ever in opposition; many languages with type inference from day 1 prohibit overloading (except maybe on arity.) So for constructs like implicit lambdas, which require inference, it seems reasonable to give up something in overloading power to increase the range of cases where implicit lambdas can be used.

-- Brian Goetz, Lambda Expert Group, 9 Aug 2013

(This was quite a controversial decision. Note that there were 116 messages in this thread, and there are several other threads that discuss this issue.)

One of the consequences of this decision was that certain APIs had to be changed to avoid overloading, for example, the Comparator API. Previously, the Comparator.comparing method had four overloads:

    comparing(Function)
comparing(ToDoubleFunction)
comparing(ToIntFunction)
comparing(ToLongFunction)

The problem was that these overloads are differentiated only by the lambda return type, and we actually never quite got the type inference to work here with implicitly-typed lambdas. In order to use these one would always have to cast or supply an explicit type argument for the lambda. These APIs were later changed to:

    comparing(Function)
comparingDouble(ToDoubleFunction)
comparingInt(ToIntFunction)
comparingLong(ToLongFunction)

which is somewhat clumsy, but it's entirely unambiguous. A similar situation occurs with Stream.map, mapToDouble, mapToInt, and mapToLong, and in a few other places around the API.

The bottom line is that getting overload resolution right in the presence of type inference is very difficult in general, and that the language and compiler designers traded away power from overload resolution in order to make type inference work better. For this reason, the Java 8 APIs avoid overloaded methods where implicitly typed lambdas are expected to be used.

How does Java know which overloaded method to call with lambda expressions? (Supplier, Consumer, Callable, ...)

I believe I have found where this is described in official documentation, although a bit hard to read.

Here is mentioned:

15.27.3. Type of a Lambda Expression

Note that while boxing is not allowed in a strict invocation context,
boxing of lambda result expressions is always allowed - that is, the
result expression appears in an assignment context, regardless of the
context enclosing the lambda expression. However, if an explicitly
typed lambda expression is an argument to an overloaded method, a
method signature that avoids boxing or unboxing the lambda result is
preferred by the most specific check (§15.12.2.5).

and then here (15.12.2.5) is described analytically how the most specific method is chosen.

So according to this for example as described

One applicable method m1 is more specific than another applicable
method m2, for an invocation with argument expressions e1, ..., ek, if
any of the following are true:

m2 is generic, and m1 is inferred to be more specific than m2 for
argument expressions e1, ..., ek

So

// Callable -> why is it not a Supplier?
execute(() -> null); <-- Callable shall be picked from 2 options as M2 is generic and M1 is inferred to be more specific

void execute(Callable<Void> callable) { // <------ M1
try {
callable.call();
} catch (Exception e) {
e.printStackTrace();
}
}

<T> T execute(Supplier<T> supplier) { // <------ M2 is Generic
return supplier.get();
}

Why M1 is inferred to be more specific can be traced down from this process described here (18.5.4 More Specific Method Inference)

New Feature Lambda Overloading in Java

You've considered one side of the problem - which method the lambda expression should correspond to. What you haven't considered is what happens to all other methods in the interface.

The language could be specified so that the lambda expression corresponds to one method, and all the others throw a RuntimeException - but that would rarely be useful. Consider how the Foo would be used. You'd end up with an object that you could only call some methods on, and you wouldn't even know which methods you could call safely.

If you know you're only going to call one method (e.g. count in your example), then that's one standalone piece of functionality, and can be encapsulated in an interface on its own - at which point existing lambda expression functionality is fine. If you don't know that you only want to use a single method, then your proposal won't help anyway.

Java 8 Lambda Ambiguous with Overloaded Functions

You can just indicate the correct type using a cast like syntax;

// bg.Play(Bounceable)
bg.play((Bounceable) x -> System.out.println("lambda: Ball bouncing "+ x));

// kg.Play(Fly)
kg.play((Fly) x -> System.out.println("lambda: Ball bouncing "+ x));

You can find more info on how this works in this answer.

Why does a lambda change overloads when it throws a runtime exception?

First, according to §15.27.2 the expression:

() -> { throw ... }

Is both void-compatible, and value-compatible, so it's compatible (§15.27.3) with Supplier<CompletionStage<Void>>:

class Test {
void foo(Supplier<CompletionStage<Void>> bar) {
throw new RuntimeException();
}
void qux() {
foo(() -> { throw new IllegalArgumentException(); });
}
}

(see that it compiles)

Second, according to §15.12.2.5 Supplier<T> (where T is a reference type) is more specific than Runnable:

Let:

  • S := Supplier<T>
  • T := Runnable
  • e := () -> { throw ... }

So that:

  • MTs := T get() ==> Rs := T
  • MTt := void run() ==> Rt := void

And:

  • S is not a superinterface or a subinterface of T
  • MTs and MTt have the same type parameters (none)
  • No formal parameters so bullet 3 is also true
  • e is an explicitly typed lambda expression and Rt is void

Why can't we overload a abstract method in a functional interface? (Java)

In languages without method overloading, methods are uniquely identified by their name in that class (ignoring overriding for the moment).

In Java things are a little different though. Citing from the oracle docs:

Overloading Methods

The Java programming language supports overloading methods, and Java can distinguish between methods with different method signatures. This means that methods within a class can have the same name if they have different parameter lists (there are some qualifications to this that will be discussed in the lesson titled "Interfaces and Inheritance").

So we know that methods are also identified by their signature. If two methods share a name but don't have the same signature, they are different methods. Don't let their shared name fool you into thinking that they are somehow related.

Considering this fact, we can easily create an example in which undefined behavior would occur if methods behaved the way you described:

Ball ba = (boolean miss) -> System.out.println(miss);
someFunction(ba)
public void someFunction(Ball ball) {
ball.hit();
}

What behavior would you expect in this case? It is undefined!


You can — however — make use of default methods. I don't know your situation well enough to judge if this is a suitable approach, but you can do this:

@FunctionalInterface
public interface Ball
{
default void hit() {
hit(true);
}

void hit(boolean miss);
}

Why this works is explained in the documentation for FunctionalInterface:

Conceptually, a functional interface has exactly one abstract method. Since default methods have an implementation, they are not abstract

Java 8 Lambda overloading

test is a variable, it is not a method. You cannot overload a variable.

You might try to make an interface with two method signatures, but the result wouldn't be a functional interface and you couldn't implement it with a lambda. So again, we fail.



Related Topics



Leave a reply



Submit