Why does Function.identity() break type reification but t -> t does not?
Asked Answered
D

2

11

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?

Dekameter answered 26/6, 2016 at 3:34 Comment(5)
Can you share some details about your env? This compiles just fine on mine.Ramie
I can reproduce this with Java 1.8.0_92. (OpenJDK)Spiraea
@StephenC which build of 1.8.0_92? I'm using 1.8.0_92-b14 and can't reproduce.Ramie
$ java -version ==> openjdk version "1.8.0_92" OpenJDK Runtime Environment (build 1.8.0_92-b14) OpenJDK 64-Bit Server VM (build 25.92-b14, mixed mode) .... On Fedora LinuxSpiraea
The error is reproduceable in javac not in ecj, so no error in eclipse doesnt say much.Unctuous
U
5

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:

  • Function.identity() is Function<T,T>
  • t->t in that case is Function<? super Integer, ? extends Integer>

So t->t is more flexible in the methods it can match to.

Unctuous answered 26/6, 2016 at 5:35 Comment(2)
it compiles fine with javac 1.8.0_60 after specifying type IntegerSciential
I added a follow-up question.Dekameter
S
1

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);
}
Scutate answered 15/3, 2021 at 13:8 Comment(1)
I know that you're trying to help but your answer doesn't actually answer the question. I know that i -> i is behaving differently than Function.identity() but what makes you believe that it should? Does the JLS specify that i -> i is supposed to allow upcasts? A reference would be helpful.Dekameter

© 2022 - 2024 — McMap. All rights reserved.