Почему Java Collector.toList () требует заполнителя типа подстановки в своем возвращаемом типе? - PullRequest
0 голосов
/ 26 мая 2018

Я занимался некоторыми манипуляциями с Java Streams, и, конечно, он не любит мой код и отказывается предоставлять полезные сообщения об ошибках.(Для справки, у меня нет проблем вообще с C # и Linq, поэтому я концептуально понимаю все, что я пытаюсь сделать.) Поэтому я начал копаться в добавлении явных универсальных типов к каждому методу в моем коде, чтобы я мог найти источникпроблема, как показывает прошлый опыт, это успешный путь вперед.

Оглядываясь вокруг, я наткнулся на то, чего не понимаю.Рассмотрим следующий код из исходного кода Java (немного переформатированный):

public static <T> Collector<T, ?, List<T>> toList() {
    return new Collectors.CollectorImpl<>(
        (Supplier<List<T>>) ArrayList::new,
        List::add,
        (left, right) -> {
             left.addAll(right);
             return left;
        },
        Collectors.CH_ID
    );
}

Почему для сигнатуры метода toList требуется подстановочный знак ? в возвращаемом типе?Когда я удаляю его, я получаю

Wrong number of type arguments: 2; required: 3

и

Incompatible types.
Required: Collector<T, List<T>, >
Found: CollectorImpl<java.lang.Object, List<T>, java.lang.Object>

Когда я изменяю ? на Object, яget (ссылаясь на эти строки / методы в вышеприведенном коде):

List::add – Cannot resolve method 'add'
left.addAll – Cannot resolve method 'addAll(java.lang.Object)'

Когда я возвращаю подстановочный знак и проверяю эти два, они:

List – public abstract boolean add(T e)
List – public abstract boolean addAll(Collection<? extends T> c)

Дальнейшие трюки меня ничему не научили.

Я понимаю, что в одном сценарии, таком как ? extends T, подстановочный знак в Java может быть переведен в C # какновый аргумент универсального типа с where TWildCard : T.Но что происходит с toList выше, где возвращаемый тип имеет пустой символ подстановки?

1 Ответ

0 голосов
/ 26 мая 2018

Коллекторы имеют три параметра типа :

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 хорош.

  1. Просто используйте 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);
    }
    
  2. Скрыть аккумулятор как деталь реализации 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);
            }
        };
    }
    
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...