Unbounded Instance Method References – Functional-Style Programming

Unbounded Instance Method References

In the case of an unbounded instance method reference, the target reference is determined when the method reference is executed, as it is the first argument passed to the method reference. This is embodied in the following rule:

A lambda expression of the form

Click here to view code image

(arg0, rest) -> arg0.instanceMethod(rest)

is semantically equivalent to the unbounded instance method reference:

RefType::instanceMethod

where RefType is the reference type of the target reference arg0. The names of the reference type and the instance method are separated by the double-colon (::) delimiter.

The instance method is invoked on the object denoted by the target reference arg0 (i.e., the first argument) when the method reference is executed, and any remaining arguments are passed to the instance method at the same time.

In the code below, the type of the unbounded instance method reference String::length at (1) is String -> int, the same as the function type of the target type ToIntFunction<String>. Invoking the functional method applyAsInt() on the reference lenMR results in the method length() being invoked on the string “Java” that was passed as a parameter.

Click here to view code image

// String -> int
ToIntFunction<String> lenLE = s -> s.length();
ToIntFunction<String> lenMR = String::length;                  // (1)
System.out.println(lenMR.applyAsInt(“Java”));                  // 4
// Calls “Java”.length() that returns the int value 4.

The static method listBuilder() in Example 13.6, p. 714, creates a list from an array by applying a Function<T, R> to each array element. An instance of the Function<T, R> is passed as a parameter to the method. Both lines of code below create a list of Integer from an array of String, where the functional interface parameter in the method call is inferred to be Function<String, Integer>. The method length() is executed on the first argument of the unbounded instance method reference— that is, on each String element of the list.

Click here to view code image

List<Integer> intList1 = listBuilder(strArray, s -> s.length()); // Lambda expr.
List<Integer> intList2 = listBuilder(strArray, String::length);  // Method ref.

The code below illustrates the case where the unbounded instance method reference String::concat at (2) requires two arguments. Its target type is BinaryOperator<String> that has the function type (String, String) -> String. The instance method concat() is invoked on the first argument, and the second argument is passed to the method as a parameter.

Click here to view code image

// (String, String) -> String
BinaryOperator<String> concatOpLE = (s1, s2) -> s1.concat(s2);
BinaryOperator<String> concatOpMR = String::concat;           // (2)
System.out.println(concatOpMR.apply(“Java”, ” Jive”));        // Java Jive
// Calls “Java”.concat(” Jive”) that returns the string “Java Jive”.

The code below illustrates using parameterized types in method references. At (3), the type of the argument of the generic interface List<T> in the method reference is inferred from the context to be String. At (4), the type of the argument is explicitly specified to be String. This can be necessary if the compiler cannot infer it from the context. The type of the List::contains instance method reference is (List<String>, String) -> boolean, which is compatible with the function type of the parameterized functional interface BiPredicate<List<String>, String>.

Click here to view code image

// (List<String>, String) -> boolean
BiPredicate<List<String>, String> containsLE
    = (list, element) -> list.contains(element);
BiPredicate<List<String>, String> containsMR1 = List::contains;          // (3)
BiPredicate<List<String>, String> containsMR2 = List<String>::contains;  // (4)
System.out.println(containsMR2.test(words, “BOB”));  // words is a List<String>.
// Calls words.contains(“BOB”) that returns a boolean value.

If the method in the unbounded instance method reference requires several arguments, compatible target types can be defined by either defining new functional interfaces with the appropriate arity for their functional method, or applying the currying technique (p. 723).

Leave a Comment