In Java Precisely 3rd Ed., there's the following code snippet:
BiConsumer<Double[], Comparator<Double>> arraySorter = Arrays::<Double>sort;
However, I noticed that even when I leave out <Double>
after ::
, the method reference is still valid (which makes sense due to type parameters for BiConsumer
).
However, I'm pretty confused about whether there are cases in which ::<T>
would be necessary in a method reference, and if so, an example would be very helpful.
There are four kinds of method references: Static methods. Instance methods of particular objects. Instance methods of an arbitrary object of a particular type.
Notice that between a static method and a static method reference, instead of the . operator, we use the :: operator, and that we don't pass arguments to the method reference. In general, we don't have to pass arguments to method references. However, arguments are treated depending on the type of method reference.
A type parameter, also known as a type variable, is an identifier that specifies a generic type name. The type parameters can be used to declare the return type and act as placeholders for the types of the arguments passed to the generic method, which are known as actual type arguments.
The parameters are used in the method body and at runtime will take on the values of the arguments that are passed in. Note: Parameters refers to the list of variables in a method declaration. Arguments are the actual values that are passed in when the method is invoked.
I figured Java 10's local variable type inference (var name = ...;
) would be the answer to this conundrum. Instead of the destination variable type providing the type for the method reference, the right-hand-side would need to fully specify the type, necessitating the type parameter (::<T>
) on the method reference.
First thought out the gate ...
var arraySorter = Arrays::<Double>sort;
... but method references, by themselves, do not define a type. They need to be translated to a functional object by the compiler, and the compiler won't search for known functional interfaces looking for the appropriate type, even if there was exactly one.
Next thought was to use a method reference as an argument to a method which returns a type based on the method's argument.
class Spy {
static <T> Function<T,T> f2(Function<T,T> f) {
return f.andThen(f);
}
static <T> T identity(T t) {
return t;
}
}
Using this, we can create our local variable passing a method reference to our method:
Function<Double,Double> double_identity = f2(Spy::<Double>identity);
As expected, we can remove the ::<Double>
Function<Double,Double> double_identity = f2(Spy::identity);
Unexpectedly, local variable type inference is fine with it.
var double_identity = f2(Spy::identity); // Infers <Object>!
Object obj = null;
double_identity.apply(obj);
But the real surprise comes when use the method reference type to override it.
var double_identity = f2(Spy::<Double>identity); // Error: Double != Object
After a bit of fighting, I figured out why. We have to apply the type to the f2
method itself:
var double_identity = Spy.<Double>f2(Spy::identity); // Works.
In retrospect, this makes some sense. The type of the variable generally provides context for the outer function. Assigning the result to a Function<Double,Double>
variable lets the compiler infer the type of f2(...)
, which then passes that type to the arguments. With var name = ...
, without an explicit type, the only type is has available is Object
, so the compiler infers Spy.<Object>f2(...)
, and then determines the argument type must be a Function<Object,Object>
.
Unfortunately, it doesn't seem to parse from the inside out, so that Spy::<Double>identity
doesn't cause the function to be inferred as Spy.<Double>f2(...)
and the variable as Function<Double,Double>
. Maybe Java 11? Maybe it would break too much, and can't work.
It does, however, put an end to my attempts to abuse var name = ...;
to solve the OP's conundrum.
Many thanks to @Eugene for critiquing my previous attempts prior to Java 10's release.
This is type inference in action, in the majority of the cases the compiler will infer the types for you and thus meaning you need not explicitly provide them.
However, there are certain circumstances in which you need to manually provide type hints.
Anyhow, Java 8 enhances the inference of generic arguments.
So, doing the following:
BiConsumer<Double[], Comparator<Double>> arraySorter = Arrays::sort;
in completly valid due to type inference.
A couple of examples which I can think of right now that would not have worked in Java-7 but works in Java-8 is something like:
void exampleMethod(List<Person> people) {
// do logic
}
exampleMethod(Collections.emptyList())
Another example:
someMethodName(new HashMap<>());
...
void someMethodName(Map<String, String> values);
You were required to explicitly provide the type arguments previously.
Also, because of the aforementioned type inference, it's the exact reason why we can now do something like:
...
...
.collect(Collectors.toList());
instead of this guy:
...
...
.collect(Collectors.<Person>toList());
Whether you should provide type arguments explicitly or not is a matter of preference in some cases and in others, you're forced to do so in order to help the compiler do its work.
Java 8 implements Generalized Target-Type Inference (JEP 101) that allows the compiler to infer the type parameter of a generic method. In your example, the Java 8 compiler infers the type parameter of the method sort
from the right hand side of the assignment.
JEP 101 also proposed generalized target-type inference for chained method calls, but it was not implemented because of the complexity that it would have introduced to the inference algorithm (discussed here and here). So, chained generic-method calls is an example in which the type parameter of a generic method cannot be inferred.
Consider the following code snippet:
class Main {
String s = MyList.nil().head(); // Incompatible types. Required: String. Found: Object.
static class MyList<E> {
private E head;
static <Z> MyList<Z> nil() { return new MyList(); }
E head() { return head; }
}
}
The compiler fails to infer the type parameter for the generic method nil()
in String s = MyList.nil().head()
. So, we have to provide more information to the inference algorithm, either by adding a type parameter
String s = MyList.<String>nil().head();
or by splitting the chained calls
MyList<String> ls = MyList.nil();
String s = ls.head();
Note: The chained-calls example does not contain a method reference to a generic method (::<>
syntax) as in the original question, but the inference technique invoked in both examples is the same. Therefore, the limitations of the inference are also the same.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With