Коллекторы имеют три параметра типа :
T
- тип элементов ввода для операции восстановления
A
- изменяемое накоплениетип операции сокращения (часто скрытый как деталь реализации)
R
- тип результата операции сокращения
Для некоторых коллекторов, таких как toList
,типы A
и R
одинаковы, поскольку сам результат используется для накопления.
Фактический тип коллектора, возвращаемый из toList
, будет Collector<T, List<T>, List<T>>
.
* 1024.* (Примером коллектора, который накапливается с типом, отличным от его результата, является
Collectors.joining()
, который
использует StringBuilder
.)
ТипАргумент к A
в большинстве случаев является подстановочным знаком, потому что нам, как правило, все равно, что это на самом деле.Его фактический тип используется только внутри коллектора, и мы можем захватить его, если нам нужно обратиться к нему по имени :
// Example of using a collector.
// (No reason to actually write this code, of course.)
public static <T, R> collect(Stream<T> stream,
Collector<T, ?, R> c) {
return captureAndCollect(stream, c);
}
private static <T, A, R> captureAndCollect(Stream<T> stream,
Collector<T, A, R> c) {
// Create a new A, whatever that is.
A a = c.supplier().get();
// Pass the A to the accumulator along with each element.
stream.forEach(elem -> c.accumulator().accept(a, elem));
// (We might use combiner() for e.g. parallel collection.)
// Pass the A to the finisher, which turns it in to a result.
return c.finisher().apply(a);
}
Вы также можете увидеть в коде дляtoList
что он указывает Collectors.CH_ID
в качестве своих характеристик, что указывает на завершение личности.Это означает, что его финишер ничего не делает, кроме как возвращает то, что ему передано.
(На этот раздел ссылается мой комментарий ниже.)
Вот несколько альтернативных вариантов переносапараметр типа для аккумулятора.Я думаю, они иллюстрируют, почему фактический дизайн класса Collector
хорош.
Просто используйте Object
, но мы в конечном итоге разыгрываем много.
interface Collector<T, R> {
Supplier<Object> supplier();
BiConsumer<Object, T> accumulator();
BiFunction<Object, Object, Object> combiner();
Function<Object, R> finisher();
}
static <T> Collector<T, List<T>> toList() {
return Collector.of(
ArrayList::new,
(obj, elem) -> ((List<T>) obj).add(elem),
(a, b) -> {
((List<T>) a).addAll((List<T>) b);
return a;
},
obj -> (List<T>) obj);
}
Скрыть аккумулятор как деталь реализации Collector
, так как сам Collector
выполняет накопление внутри.Я думаю, что это может иметь смысл, но это менее гибко, и шаг объединителя становится более сложным.
interface Collector<T, R> {
void accumulate(T elem);
void combine(Collector<T, R> that);
R finish();
}
static <T> Collector<T, List<T>> toList() {
return new Collector<T, List<T>>() {
private List<T> list = new ArrayList<>();
@Override
public void accumulate(T elem) {
list.add(elem);
}
@Override
public void combine(Collector<T, List<T>> that) {
// We could elide calling finish()
// by using instanceof and casting.
list.addAll(that.finish());
}
@Override
public List<T> finish() {
return new ArrayList<>(list);
}
};
}