Answers found at Java 8 lambdas, Function.identity() or t->t seem to imply that Function.identity()
is almost always equivalent to t -> t
. However, in the testcase seen below, replacing t -> t
by Function.identity()
results in a compiler error. Why is that?
public class Testcase {
public static <T, A, R, K, V> Collector<T, A, R> comparatorOrdering(
Function<? super T, ? extends K> keyMapper,
Function<? super T, ? extends V> valueMapper,
Comparator<? super K> keyComparator,
Comparator<? super V> valueComparator) {
return null;
}
public static void main(String[] args) {
Map<Integer, String> case1 = Stream.of(1, 2, 3).
collect(comparatorOrdering(t -> t, t -> String.valueOf(t),
Comparator.naturalOrder(), Comparator.naturalOrder()));
Map<Integer, String> case2 = Stream.of(1, 2, 3).
collect(comparatorOrdering(Function.identity(), t -> String.valueOf(t),
Comparator.naturalOrder(), Comparator.naturalOrder()));
}
}
Case 1 compiles just fine but case 2 fails with:
method comparatorOrdering in class Testcase cannot be applied to given types;
collect(comparatorOrdering(Function.identity(), t -> String.valueOf(t),
required: Function<? super T#1,? extends K>,Function<? super T#1,? extends V>,Comparator<? super K>,Comparator<? super V>
found: Function<Object,Object>,(t)->Strin[...]Of(t),Comparator<T#2>,Comparator<T#3>
reason: inferred type does not conform to upper bound(s)
inferred: Object
upper bound(s): Comparable<? super T#4>,T#4,Object
where T#1,A,R,K,V,T#2,T#3,T#4 are type-variables:
T#1 extends Object declared in method <T#1,A,R,K,V>comparatorOrdering(Function<? super T#1,? extends K>,Function<? super T#1,? extends V>,Comparator<? super K>,Comparator<? super V>)
A extends Object declared in method <T#1,A,R,K,V>comparatorOrdering(Function<? super T#1,? extends K>,Function<? super T#1,? extends V>,Comparator<? super K>,Comparator<? super V>)
R extends Object declared in method <T#1,A,R,K,V>comparatorOrdering(Function<? super T#1,? extends K>,Function<? super T#1,? extends V>,Comparator<? super K>,Comparator<? super V>)
K extends Object declared in method <T#1,A,R,K,V>comparatorOrdering(Function<? super T#1,? extends K>,Function<? super T#1,? extends V>,Comparator<? super K>,Comparator<? super V>)
V extends Object declared in method <T#1,A,R,K,V>comparatorOrdering(Function<? super T#1,? extends K>,Function<? super T#1,? extends V>,Comparator<? super K>,Comparator<? super V>)
T#2 extends Comparable<? super T#2>
T#3 extends Comparable<? super T#3>
T#4 extends Comparable<? super T#4> declared in method <T#4>naturalOrder()
My environment is Windows 10, 64-bit, Oracle JDK build 1.8.0_92-b14.
UPDATE: Seeing as this compiles under ecj, I have a follow-up question: Is this a bug in javac
? What does the JLS have to say about this case?
The difference between i -> i
and Function.identity()
is apparent in situation where (1) collectors are nested and (2) upcast is needed somewhere in deep level of nesting.
Example: Suppose we are to classify elements in List<Object>
into map with particular lists by element class. (It resembles Guava ClassToInstanceMap except the value is List
, so something like hypothetical ClassToInstanceMultimap
.) The downstream collector toList()
normally collects values into List<Object>
. However, if the value type of map is wildcard type List<?>
, type inference cannot simply match it. The solution is to adapt collector to pretend it collects List<?>
, which is done by intermediate collectingAndThen
collector.
The point is now, what should the finisher function look like? Lambda i -> i
behaves like Function<List<Object>, List<?>>
, which allows upcast. Function.identity()
with fixed input and output type T
gives no room for upcast we need, hence code won't compile.
import static java.util.function.Function.identity;
import static java.util.stream.Collectors.collectingAndThen;
import static java.util.stream.Collectors.groupingBy;
import static java.util.stream.Collectors.toList;
public static void main(String[] args) {
class Apple {}
class Pear {}
List<Object> list = List.of(new Apple(), new Pear(), new Apple());
Map<Class<?>, List<Object>> obj;
obj = list.stream().collect(groupingBy(Object::getClass, toList())); // compiles
Map<Class<?>, List<?>> wild;
wild = list.stream().collect(groupingBy(Object::getClass, toList())); // wont compile
wild = list.stream().collect(groupingBy(Object::getClass, collectingAndThen(toList(), i -> i))); // compiles
wild = list.stream().collect(groupingBy(Object::getClass, collectingAndThen(toList(), identity()))); // wont compile
wild = list.stream().collect(groupingBy(Object::getClass, collectingAndThen(toList(), (List<Object> i) -> (List<?>)i))); // compiles
System.out.println(wild);
}
Ecj is able to infere the correct(?) type argument (Integer) to match the constraints. Javac for some reason comes to a different result.
Thats not the first time javac/ecj behave differently in inference of type parameters.
In that case you can give javac a hint with Function.<Integer>identity() to make it compileable with javac.
For the difference between Function.identity() and t->t:
So t->t is more flexible in the methods it can match to.
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