Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How does this compile?

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.

like image 746
Billy the Kid Avatar asked Oct 01 '18 17:10

Billy the Kid


People also ask

How does compile work?

How compilers work. Compilers are utility programs that take your code and transform it into executable machine code files. When you run a compiler on your code, first, the preprocessor reads the source code (the C++ file you just wrote).

What does it mean to compile a program?

Compile refers to the act of converting programs written in high level programming language, which is understandable and written by humans, into a low level binary language understood only by the computer.

How does code get compiled?

A compiler takes the program code (source code) and converts the source code to a machine language module (called an object file). Another specialized program, called a linker, combines this object file with other previously compiled object files (in particular run-time modules) to create an executable file.


2 Answers

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));
like image 166
fps Avatar answered Oct 17 '22 07:10

fps


After Federico corrected me (thank you!) this is a single method you could do it with:

public static <T, S extends Comparable<? super S>> Comparator<T> test(List<Function<T, S>> list) {
    return list.stream()
            .reduce((x, y) -> 0,
                    Comparator::thenComparing,
                    Comparator::thenComparing);
}

And usage would be:

// I still don't know how to avoid this raw type here
List<Function<Data, Comparable>> extractors = new ArrayList<>();
extractors.add(Data::getA); // getA returns an Integer
extractors.add(Data::getB); // getB returns a String

listOfSomeDatas.sort(test(extractors));
like image 44
Eugene Avatar answered Oct 17 '22 06:10

Eugene