Как это компилируется?
Я пишу функцию, которая берет список функций 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
- Если я не определяю класс
Extractor
, это не будет компилироваться. Я не могу напрямую использовать Function
или какой-то функциональный интерфейс.
Нет вы можете. Вы можете определить любую Function<X, Y>
с помощью лямбда или ссылки на метод или анонимного класса.
List<Function<Data, Integer>> extractors = List.of(Data::getA, Data::getB);
- Если я
.map(e → e)
строку отображения функции идентификации .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>
.
Ответ 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 Но я не вижу, как избавиться от необработанного типа здесь...