5 min read

Type inference was introduced with Java 5 and has been increasing in coverage ever since. With Java 8, the resolution of overloaded methods was restructured to allow for working with type inference. Before the introduction of lambdas and method references, a call to a method was resolved by checking the types of the arguments that were passed to it (the return type wasn’t considered).

With Java 8, implicit lambdas and implicit method references couldn’t be checked for the types of values that they accepted, leading to restricted compiler capabilities, to rule out ambiguous calls to overloaded methods. However, explicit lambdas and method references could still be checked by their arguments by the compiler. The lambdas that explicitly specify the types of their parameters are termed explicit lambdas. Limiting the compiler’s ability and relaxing the rules in this way was purposeful. It lowered the cost of type-checking for lambdas and avoided brittleness.  Lambda Leftovers proposes using an underscore for unused parameters in lambdas, methods, and catch handlers.

[box type=”shadow” align=”” class=”” width=””]This article is an excerpt taken from the book, “Java 11 and 12 – New Features“, written by Mala Gupta. In this book, you will learn the latest developments in Java, right from variable type inference and simplified multi-threading through to performance improvements, and much more.[/box]

In this article, you will understand the existing issues like resolving overloaded methods – passing lambdas, resolving overloaded methods – passing method references and also a proposed solution to define Lambda Leftover parameters

Issues with resolving overloaded methods – passing lambdas

Let’s cover the existing issues with resolving overloaded methods when lambdas are passed as method parameters. Let’s define two interfaces, Swimmer and Diver, as follows:

interface Swimmer {
    boolean test(String lap);
}
interface Diver {
    String dive(int height);
}

In the following code, the overloaded evaluate method accepts the interfaces Swimmer and Diver as method parameters:

class SwimmingMeet {
    static void evaluate(Swimmer swimmer) {   // code compiles

System.out.println("evaluate swimmer");
}
static void evaluate(Diver diver) { // code compiles

System.out.println("evaluate diver");
}
}

Let’s call the overloaded evaluate() method in the following code:

class FunctionalDisambiguation {
    public static void main(String args[]) {
        SwimmingMeet.evaluate(a -> false); // This code WON'T compile
    }
}

Revisit the lambda from the preceding code:

a -> false                               // this is an implicit lambda

Since the preceding lambda expression doesn’t specify the type of its input parameter, it could be either String (the test() method and the Swimmer interface) or int (the dive() method and the Diver interface). Since the call to the evaluate() method is ambiguous, it doesn’t compile.

Let’s add the type of the method parameter to the preceding code, making it an explicit lambda:

SwimmingMeet.evaluate((String a) -> false);         // This compiles!!

The preceding call is not ambiguous now; the lambda expression accepts an input parameter of the String type and returns a boolean value, which maps to the evaluate() method which accepts Swimmer as a parameter (the functional test() method in the Swimmer interface accepts a parameter of the String type).

Let’s see what happens if the Swimmer interface is modified, changing the data type of the lap parameter from String to int. To avoid confusion, all of the code will be repeated, with the modifications in bold:

interface Swimmer {                            // test METHOD IS 
                                               // MODIFIED
    boolean test(int lap);      // String lap changed to int lap

}
interface Diver {
String dive(int height);
}
class SwimmingMeet {
static void evaluate(Swimmer swimmer) { // code compiles

System.out.println("evaluate swimmer");
}
static void evaluate(Diver diver) { // code compiles
System.out.println("evaluate diver");
}
}

Consider the following code, thinking about which of the lines of code will compile:

1. SwimmingMeet.evaluate(a -> false);
2. SwimmingMeet.evaluate((int a) -> false);

In the preceding example, the code on both of the line numbers won’t compile for the same reason—the compiler is unable to determine the call to the overloaded evaluate() method. Since both of the functional methods (that is, test() in the Swimmer interface and dive() in the Diver interface) accept one method parameter of the int type, it isn’t feasible for the compiler to determine the method call.

As a developer, you might argue that since the return types of test() and dive() are different, the compiler should be able to infer the correct calls. Just to reiterate, the return types of a method don’t participate in method overloading. Overloaded methods must return in the count or type of their parameters.

Issues with resolving overloaded methods – passing method references

Overloaded methods can be defined with different parameter types, as follows:

However, the following code doesn’t compile:

someMethod(Chamionship::reward);                     // ambiguous call

In the preceding line of code, since the compiler is not allowed to examine the method reference, the code fails to compile. This is unfortunate since the method parameters to the overloaded methods are Integer and String—no value can be compatible with both.

The proposed solution

The accidental compiler issues involved with overloaded methods that use either lambda expressions or method references can be resolved by allowing the compiler to consider their return type as also. The compiler would then be able to choose the right overloaded method and eliminate the unmatched option.

Summary

For Java developers working with lambdas and method references, this article demonstrates what Java has in the pipeline to help ease problems.

Lambda Leftovers plans to allow developers to define lambda parameters that can overshadow variables with the same name in their enclosing block. The disambiguation of functional expressions is an important and powerful feature. It will allow compilers to consider the return types of lambdas in order to determine the right overloaded methods.

To know more about the exciting capabilities that are being added to the Java language in pattern matching and switch expressions, head over to the book, Java 11 and 12 – New Features.

Read Next

Using lambda expressions in Java 11 [Tutorial]
How to deploy Serverless Applications in Go using AWS Lambda [Tutorial]
Java 11 is here with TLS 1.3, Unicode 11, and more update

A born storyteller turned writer!