Как это компилируется?

Я пишу функцию, которая берет список функций keyExtractor для создания компаратора (представьте, что у нас был объект со многими многими свойствами и он хотел иметь возможность произвольно сравнивать большое количество свойств в любом порядке).

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;
    }
}

Для меня есть три основных смысла:

1). Если я не определяю класс Extractor, это не будет компилироваться. Я не могу напрямую иметь функции или какой-то функциональный интерфейс.

2). Если я удалю строку отображения функции идентификации ".map(e → e)", это не будет вводить проверку.

3). Моя IDE говорит, что моя функция принимает список функций типа Data ->? который не соответствует границам функции parseKeysAscending.

Ответ 1

Он работает для меня без класса Extractor а также без вызова map(e → e) в потоковом конвейере. Фактически, потоковый список экстракторов вообще не нужен, если вы используете правильные общие типы.

Что касается того, почему ваш код не работает, я не совсем уверен. Дженерики - это жесткий и непроницаемый аспект Java... Все, что я делал, это настройка подписи метода parseKeysAscending, чтобы он соответствовал тому, что действительно ожидает Comparator.comparing.

Здесь метод parseKeysAscending:

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));
    }
}

И вот демо с вызовом:

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']]

Единственный способ сделать компиляцию кода без предупреждений - объявить список функций как List<Function<Data, Integer>>. Но это работает только с геттерами, которые возвращают Integer. Я предполагаю, что вам может понадобиться сравнить любое сочетание Comparable s, то есть приведенный выше код работает со следующим классом Data:

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 + "']";
    }
}

Вот демо.

EDIT: Обратите внимание, что с Java 8 последняя строка метода parseKeysAscending может быть:

return Comparator.comparing(firstSortKey)
        .thenComparing(parseKeysAscending(restOfSortKeys));

Хотя для более новых версий Java вы должны предоставить явные общие типы:

return Comparator.<T, S>comparing(firstSortKey)
        .thenComparing(parseKeysAscending(restOfSortKeys));

Ответ 2

После того, как Федерико исправил меня (спасибо!), Это единственный способ, которым вы могли бы это сделать:

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);
}

И использование будет:

// 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));

Ответ 3

  1. Если я не определяю класс Extractor, это не будет компилироваться. Я не могу напрямую использовать Function или какой-то функциональный интерфейс.

Нет вы можете. Вы можете определить любую Function<X, Y> с помощью лямбда или ссылки на метод или анонимного класса.

List<Function<Data, Integer>> extractors = List.of(Data::getA, Data::getB);
  1. Если я .map(e → e) строку отображения функции идентификации .map(e → e), это не будет вводить проверку.

Он по-прежнему будет, но результат может оказаться неприемлемым для метода. Вы всегда можете явно определить общие параметры, чтобы убедиться, что все идет так, как вы ожидаете.

extractors.<Function<Data, Integer>>stream().collect(Collectors.toList())

Но здесь нет необходимости:

Comparator<Data> test = parseKeysAscending(extractors);
  1. Моя IDE заявляет, что моя функция принимает List Function типа Data,? который не соответствует границам функции parseKeysAscending.

Да, должно. Вы передаете List<Extractor<Data,?>> и компилятор не может понять ? часть. Это может быть или не быть Comparable то время как для вашего метода явно требуется S extends Comparable<S>.

Ответ 4

Что касается исходного кода в вопросе: вы можете удалить Extractor и использовать raw Comparable:

List<Function<Data, Comparable>> extractors = new ArrayList<>();

extractors.add(Data::getA);
extractors.add(Data::getB);

@SuppressWarnings("unchecked")
Comparator<Data> test = parseKeysAscending(extractors);

PS Но я не вижу, как избавиться от необработанного типа здесь...