12

The following code creates a Collector that produces an UnmodifiableSortedSet:

package com.stackoverflow;

import java.util.Collections;
import java.util.SortedSet;
import java.util.TreeSet;
import java.util.stream.Collector;
import java.util.stream.Collectors;

public class SOExample {

    public static <T extends Comparable<T>> Collector<T, ?, SortedSet<T>> toSortedSet() {
        return Collectors.toCollection(TreeSet::new);
    }

    public static <T extends Comparable<T>> Collector<T, ?, SortedSet<T>> toUnmodifiableSortedSet() {
        return Collectors.collectingAndThen(toSortedSet(), Collections::<T> unmodifiableSortedSet);
    }
}

The codes compiles under the ecj compiler:

$ java -jar ~/Downloads/ecj-3.13.101.jar -source 1.8 -target 1.8 SOExample.java
Picked up _JAVA_OPTIONS: -Duser.language=en -Duser.country=GB

Under javac however:

$ javac -version
Picked up _JAVA_OPTIONS: -Duser.language=en -Duser.country=GB
javac 1.8.0_73

$ javac SOExample.java
Picked up _JAVA_OPTIONS: -Duser.language=en -Duser.country=GB
SOExample.java:16: error: method collectingAndThen in class Collectors cannot be applied to given types;
        return Collectors.collectingAndThen(toSortedSet(), Collections::<T> unmodifiableSortedSet);
                         ^
  required: Collector<T#1,A,R>,Function<R,RR>
  found: Collector<T#2,CAP#1,SortedSet<T#2>>,Collection[...]edSet
  reason: cannot infer type-variable(s) T#3
    (actual and formal argument lists differ in length)
  where T#1,A,R,RR,T#2,T#3 are type-variables:
    T#1 extends Object declared in method <T#1,A,R,RR>collectingAndThen(Collector<T#1,A,R>,Function<R,RR>)
    A extends Object declared in method <T#1,A,R,RR>collectingAndThen(Collector<T#1,A,R>,Function<R,RR>)
    R extends Object declared in method <T#1,A,R,RR>collectingAndThen(Collector<T#1,A,R>,Function<R,RR>)
    RR extends Object declared in method <T#1,A,R,RR>collectingAndThen(Collector<T#1,A,R>,Function<R,RR>)
    T#2 extends Comparable<T#2>
    T#3 extends Object declared in method <T#3>unmodifiableSortedSet(SortedSet<T#3>)
  where CAP#1 is a fresh type-variable:
    CAP#1 extends Object from capture of ?
1 error

If I change the offending line to the following, the code compiles under both compilers:

return Collectors.collectingAndThen(toSortedSet(), (SortedSet<T> p) -> Collections.unmodifiableSortedSet(p));

Is this a bug in ecj, javac or an underspecification that allows for both behaviours?

Javac behaves the same in java 9 and 10.

Robert Bain
  • 9,113
  • 8
  • 44
  • 63

2 Answers2

3

Oracle has accepted this as a compiler bug.

Robert Bain
  • 9,113
  • 8
  • 44
  • 63
2

Interestingly, it compiles without the need for toSortedSet:

public static <T extends Comparable<T>> Collector<T, ?, SortedSet<T>> toUnmodifiableSortedSet() {
    return Collectors.collectingAndThen(Collectors.toCollection(TreeSet::new), Collections::<T>unmodifiableSortedSet);
}

It also compiles if you explicitly pass T to toSortedSet (removing static and using this.<T>toSortedSet() works just as well):

public static <T extends Comparable<T>> Collector<T, ?, SortedSet<T>> toUnmodifiableSortedSet() {
    return Collectors.collectingAndThen(Test.<T>toSortedSet(), Collections::<T>unmodifiableSortedSet);
}

Regarding your question as to why it doesn't compile as is, I suspect it has to do with the capture types not being the same between both methods, and toSortedSet needs the same exact type of T used in toUnmodifiableSortedSet (as you define a generic type T for both methods).

I further believe that's the reason, as you can define a generic type T for your class and use it in both methods (if you remove static):

public class Test<T extends Comparable<? super T>> {
    public static void main(String[] args) throws Exception {
        System.out.println(Stream.of(5, 3, 4, 2, 1, 5, 4, 3, 2, 1)
            .collect(new Test<Integer>().toUnmodifiableSortedSet()));
    }

    public Collector<T, ?, SortedSet<T>> toSortedSet() {
        return Collectors.toCollection(TreeSet::new);
    }

    public Collector<T, ?, SortedSet<T>> toUnmodifiableSortedSet() {
        return Collectors.collectingAndThen(toSortedSet(), Collections::unmodifiableSortedSet);
    }
}

And the above compiles and runs just fine.

Jacob G.
  • 28,856
  • 5
  • 62
  • 116
  • Thanks Jacob, do you think it's a bug in javac? – Robert Bain Apr 10 '18 at 21:20
  • I don't think so; I think it's just because you're defining two different generic types, `T`, for each method. – Jacob G. Apr 10 '18 at 21:21
  • So ecj shouldn't compile it? – Robert Bain Apr 10 '18 at 21:38
  • I would think not, unless ecj implicitly infers that the `T` defined in `toUnmodifiableSortedSet` should be the one used in `toSortedSet`. – Jacob G. Apr 10 '18 at 21:39
  • This explanation seems to have two problems to me: 1. Why does `return Collectors.collectingAndThen(toSortedSet(), (SortedSet p) -> Collections.unmodifiableSortedSet(p));` not run into the same problem? – Alexey Romanov Apr 10 '18 at 21:42
  • 2. Why is javac complaining about failing to infer the `T#3` defined _in `unmodifiableSortedSet`_, not in `toSortedSet`? Why is it inferring it at all when it's given explicitly? – Alexey Romanov Apr 10 '18 at 21:43
  • @AlexeyRomanov To go along with my answer, the only explanation that I would have for (1.) is that explicitly defining `p` as a `SortedSet` would allow the compiler to infer `toSortedSet`, but that still doesn't make much sense. I feel like this *could potentially* be a javac bug, but I don't want to assume anything too quickly. – Jacob G. Apr 10 '18 at 21:47
  • 1
    @JacobG. I agree about not assuming, but can't currently think of anything even approaching an explanation for the error message. – Alexey Romanov Apr 10 '18 at 22:04
  • @JacobG. (And of course would be very happy to see such an explanation) – Alexey Romanov Apr 10 '18 at 22:11
  • @AlexeyRomanov I agree. I'll try looking into it further, but I don't promise I'll find anything. The compiler's error messages regarding generics are notoriously bewildering, even if the architects don't agree :P – Jacob G. Apr 10 '18 at 22:13