I'm studying functional composition and have an example:
Function<String, String> test = (s) -> s.concat("foo");
String str = test.andThen(String::toUpperCase).apply("bar");
This example compiles and works as expected. However, if I change the order of the composition by using compose()
, an explicit cast is required:
String str = test.compose((Function <String, String>)
String::toUpperCase).apply("bar");
Without explicitly casting String::toUpperCase
to Function <String, String>
, there is a compiler error:
Error:
incompatible types: cannot infer type-variable(s) V
(argument mismatch; invalid method reference
incompatible types: java.lang.Object cannot be converted to java.util.Locale)
String s = test.compose(String::toUpperCase).apply("bar");
^-------------------------------^
The question is why compose()
requires explicit cast and andThen()
does not in this case?
When you are assigning a larger type to a smaller type, then Explicit Casting is required We all know that when we are assigning larger type to a smaller type, then we need to explicitly type cast it. Lets take the same above code with a slight modification
A cast operation between reference types does not change the run-time type of the underlying object; it only changes the type of the value that is being used as a reference to that object. For more information, see Polymorphism. In some reference type conversions, the compiler cannot determine whether a cast will be valid.
No Explicit casting required for the above mentioned sequence. We all know that when we are assigning smaller type to a larger type, there is no need for a casting required. Same applies to the class type as well. Lets look into the below code.
Type casting are of two types they are Automatic type conversion can happen if both type are compatible and target type is larger than source type. No Explicit casting required for the above mentioned sequence.
As the error message shows, the problem is to do with the fact that toUpperCase
has an overload which accepts a Locale
as an argument, so the compiler error is caused by not knowing which overload String::toUpperCase
is supposed to refer to. In principle the compiler should be able to know that a method reference with two arguments (the String
object itself is the first argument) can't be a Function
, but I suppose there just isn't a rule for this - or rather, there doesn't seem to be a rule for this during inference of generic type parameters, which is why the explicit cast solves the problem.
We can confirm that the overload is indeed what's causing the problem by trying a different method reference like String::trim
, which does not have overloads:
// works without an explicit cast
String str = test.compose(String::trim).apply("bar");
So the question now is why the overload doesn't cause a problem when composing with andThen
. The difference is that andThen
composes the two functions in the opposite order, so the type parameter which needs to be inferred is the output type of String::toUpperCase
, which is String
regardless of which overload is chosen. So it seems that the compiler has a rule to handle the ambiguity of overloads during type parameter inference when the return type of the method reference will be used for inference, and the overloads have the same return type. Note that a similar rule can't exist for overloads with the same parameter types, because overloads cannot have the same parameter types.
Let's break this down as an attempt (a rough one) to explore why the compiler issues the error.
andThen
and compose
in this contextHere are the signatures of the two methods (omitted default
):
<V> Function<T, V> andThen(Function<? super R, ? extends V> after)
<V> Function<V, R> compose(Function<? super V, ? extends T> before)
Remembering that Function
is declared with two type variables, <T, R>
, the most significant difference between after
and before
in this context is that after
is expected to take in a parameter whose data type is R
, the current function's return type, whereas the data type of before
's parameter is compose
's local type variable, V
, which the compiler must infer.
Why is this relevant?
test.andThen(String::toUpperCase)
work?Among the many things that the compiler is trying to do, it's trying to validate that String::toUpperCase
is a valid argument to andThen
and compose
. To do that, it needs to establish the type of String::toUpperCase
, meaning it needs to infer the data type of the method reference in that invocation context. Well, the compiler knows that String::toUpperCase
must be a Function
, so it just needs to infer the type arguments for Function
, which is where the difference above applies:
after
(the parameter of andThen
), the first parameter of Function<? super R, ? extends V>
, is known to be (roughly) R
, which has to be the same as the current function's (test
's) return type. And the second type parameter, V
, which also corresponds to andThen
's local type variable, is inferred to whatever the method reference will be declared to return.before
(the parameter of compose
), the first parameter's type is unknown, and has to be inferred in context. It corresponds to compose
's type variable V
. This is where the first difference is.In Eclipse, the compiler error was The type String does not define toUpperCase(Object) that is applicable here
, which gives the clue that the error is related to matching the argument type for String#toUpperCase
.
Now, if you see above:
after
is a Function
that takes a String
parameter, which makes it somehow simple for the compiler to resolve the String
method that String::toUpperCase
is targeting. This is a massive topic on its own, but it boils down to the compiler choosing to link the function roughly as the lambda expression (String s) -> s.toUpperCase()
. Method references can resolve to instance methods like that, where the first parameter of the function becomes the target of the method invocation (without parameters in the case of Function<T, U>
).before
could have been resolved in the same way, except that the compiler does NOT know for sure what the type of Function
's first type argument is. And because String#toUpperCase
is overloaded, the compiler can't just decide to infer it as (String s) -> s.toUpperCase()
, because V
could have been meant to be of a data type that would force it to resolve the method reference to the overload, String.toUpperCase(Locale)
. In other words, the compiler is dealing with a chicken-and-the-egg situation (where it needs to know the parameter type V
to decide which String#toUpperCase
method to link, but can't use String#toUpperCase
signature to infer V
, because there are two possibilities making it ambiguous).You got this code to compile by casting String::toUpperCase
to Function<String, String>
. What did that do? It simply got the compiler out of the above dilemma: you told the compiler that V
is String
, not Locale
or any other possibility, which caused the compiler to resolve the method reference as (String s) -> s.toUpperCase()
(leaving no room for String.toUpperCase(Locale)
to be a valid option)
You can effectively do the same thing by forcing an explicit type argument for before
's V
:
test.compose((String s) -> s.toUpperCase()).apply("bar");
will compile because (String s)
explicitly types the data type of before
's parametertest.<String>compose(String::toUpperCase).apply("bar");
does the same thing by helping the compiler's inference logic, telling it that V
is a String
, which avoids the chicken-and-the-egg situation referred to above.This type of solution is not some strange approach, you're essentially doing the same thing that the cast in System.out.println((String)null)
does when System.out.println(null)
is rejected by the compiler, although these Function
methods include generics as a spice.
Because of its definition. The method Function#compose
is defined as this:
<V> Function<V, R> compose(Function<? super V, ? extends T> before)
... where V
is the extra type of input to the before function, and to the composed function. Therefore it additionally requires defining a generic parameter type, because the Function<T, R>
knows only T
and R
. No explicit casting is needed:
String str = test.<String>compose(String::toUpperCase).apply("bar");
Interference doesn't work here because of the ambiguity of String::toUpperCase
. More self-explanatory example is a function chain of LocalDateTime
-> LocalDate
-> String
:
Function<LocalDate, String> test = LocalDate::toString;
String str = test.compose(LocalDateTime::toLocalDate).apply(LocalDateTime.now());
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