Как это компилируется? - PullRequest
0 голосов
/ 01 октября 2018

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

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.

Ответы [ 4 ]

0 голосов
/ 02 октября 2018

Относительно оригинального кода в вопросе: вы можете удалить Extractor и использовать необработанный Comparable:

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

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

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

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

0 голосов
/ 01 октября 2018

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

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));
0 голосов
/ 01 октября 2018
  1. Если я не определю класс Extractor, он не скомпилируется.Я не могу напрямую иметь Function s или какой-то функциональный интерфейс.

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

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

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

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

Но в этом нет необходимости:

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

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

0 голосов
/ 01 октября 2018

Это работает для меня без класса 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 с, т.е. приведенный выше код работает со следующим классом 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 + "']";
    }
}

Вот демоверсия .

РЕДАКТИРОВАТЬ: Обратите внимание, что в Java 8 последняя строка метода parseKeysAscending может быть:

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

В то время как для более новых версий Java вы должныпредоставить явные универсальные типы:

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