How does this compile?

Billy the Kid :

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.

Federico Peralta Schaffner :

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 Comparables, 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));

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=35469&siteId=1