I'm writing a function that takes a list of keyExtractor functions to produce a Comparator (imagine we had an object with many many properties and wanted to be able to arbitrarily compare by a large number of properties in any order).
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.function.Function;
import java.util.stream.Collectors;
class Test {
public static <T, S extends Comparable<S>> Comparator<T> parseKeysAscending(List<Function<T, S>> keyExtractors) {
if (keyExtractors.isEmpty()) {
return (a, b) -> 0;
} else {
Function<T, S> firstSortKey = keyExtractors.get(0);
List<Function<T, S>> restOfSortKeys = keyExtractors.subList(1, keyExtractors.size());
return Comparator.comparing(firstSortKey).thenComparing(parseKeysAscending(restOfSortKeys));
}
}
public static void main(String[] args) {
List<Extractor<Data, ?>> extractors = new ArrayList<>();
extractors.add(new Extractor<>(Data::getA));
extractors.add(new Extractor<>(Data::getB));
Comparator<Data> test = parseKeysAscending(
extractors.stream()
.map(e -> e)
.collect(Collectors.toList()));
}
}
class Extractor<T, S extends Comparable<S>> implements Function<T, S> {
private final Function<T, S> extractor;
Extractor(Function<T, S> extractor) {
this.extractor = extractor;
}
@Override
public S apply(T t) {
return extractor.apply(t);
}
}
class Data {
private final Integer a;
private final Integer b;
private Data(int a, int b) {
this.a = a;
this.b = b;
}
public Integer getA() {
return a;
}
public Integer getB() {
return b;
}
}
There are three main points of confusion for me:
1). If I don't define the Extractor class, this will not compile. I cannot directly have Functions or some sort of functional interface.
2). If I remove the identity function mapping line ".map(e -> e)", this will not type check.
3). My IDE says my function is accepting a List of Functions of the type Data -> ? which doesn't comply with the bounds of the parseKeysAscending function.
It works for me without the Extractor
class and also without calling map(e -> e)
in the stream pipeline. Actually, streaming the list of extractors isn't needed at all if you use the correct generic types.
As to why your code doesn't work, I'm not completely sure. Generics is a tough and flaky aspect of Java... All I did was adjusting the signature of the parseKeysAscending
method, so that it conforms to what Comparator.comparing
actually expects.
Here's the parseKeysAscending
method:
public static <T, S extends Comparable<? super S>> Comparator<T> parseKeysAscending(
List<Function<? super T, ? extends S>> keyExtractors) {
if (keyExtractors.isEmpty()) {
return (a, b) -> 0;
} else {
Function<? super T, ? extends S> firstSortKey = keyExtractors.get(0);
List<Function<? super T, ? extends S>> restOfSortKeys =
keyExtractors.subList(1, keyExtractors.size());
return Comparator.<T, S>comparing(firstSortKey)
.thenComparing(parseKeysAscending(restOfSortKeys));
}
}
And here's a demo with the call:
List<Function<? super Data, ? extends Comparable>> extractors = new ArrayList<>();
extractors.add(Data::getA);
extractors.add(Data::getB);
Comparator<Data> test = parseKeysAscending(extractors);
List<Data> data = new ArrayList<>(Arrays.asList(
new Data(1, "z"),
new Data(2, "b"),
new Data(1, "a")));
System.out.println(data); // [[1, 'z'], [2, 'b'], [1, 'a']]
data.sort(test);
System.out.println(data); // [[1, 'a'], [1, 'z'], [2, 'b']]
The only way to make the code compile without warnings was to declare the list of functions as List<Function<Data, Integer>>
. But this works only with getters that return Integer
. I'm assuming that you might want to compare any mix of Comparable
s, i.e. the code above works with the following Data
class:
public class Data {
private final Integer a;
private final String b;
private Data(int a, String b) {
this.a = a;
this.b = b;
}
public Integer getA() {
return a;
}
public String getB() {
return b;
}
@Override
public String toString() {
return "[" + a + ", '" + b + "']";
}
}
Here's the demo.
EDIT: Note that with Java 8, the last line of the parseKeysAscending
method can be:
return Comparator.comparing(firstSortKey)
.thenComparing(parseKeysAscending(restOfSortKeys));
While for newer versions of Java, you must provide explicit generic types:
return Comparator.<T, S>comparing(firstSortKey)
.thenComparing(parseKeysAscending(restOfSortKeys));