Зачем использовать вспомогательный метод захвата карты?

Ссылаясь на: Способы помощи подстановочных знаков

В нем говорится создать вспомогательный метод для захвата wild card.

public void foo(List<?> i) {
    fooHelper(i);
}        
private <T> void fooHelper(List<T> l) {
    l.set(0, l.get(0));
}

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

public <T> void foo(List<T> l) {
    l.set(0, l.get(0));
}

Я думал, что этот вопрос действительно сводится к: какая разница между подстановочными знаками и дженериками? Итак, я пошел на это: разница между подстановочными знаками и дженериками. В нем говорится использовать параметры типа:

1) Если вы хотите применить некоторые отношения к различным типам аргументов метода, вы не можете сделать это с помощью подстановочных знаков, вы должны использовать параметры типа.

Но разве это не то, что на самом деле делает шаблон с вспомогательной функцией? Разве это не обеспечивает связь между различными типами аргументов метода с его настройкой и получением неизвестных значений?

Мой вопрос: если вам нужно определить что-то, что требует отношения к различным типам аргументов метода, то зачем использовать подстановочные знаки в первую очередь, а затем использовать для него вспомогательную функцию?

Похоже на хакерский способ включения подстановочных знаков.

Ответ 1

В этом конкретном случае это потому, что метод List.set(int, E) требует, чтобы тип был таким же, как тип в списке.

Если у вас нет вспомогательного метода, компилятор не знает, является ли ? одинаковым для List<?> и возврат из get(int), поэтому вы получаете ошибку компилятора:

The method set(int, capture#1-of ?) in the type List<capture#1-of ?> is not applicable for the arguments (int, capture#2-of ?)

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

Итак, почему метод не-помощник?

Дженерики не были введены до Java 5, поэтому существует много кода, который предшествует дженерикам. Предварительно Java 5 List теперь является List<?>, поэтому, если вы пытались скомпилировать старый код в общем компиляторе, вам придется добавлять эти вспомогательные методы, если вы не можете изменить сигнатуры метода.

Ответ 2

Вы правы, что нам не нужно использовать подстановочную версию.

Это сводится к тому, что API выглядит/чувствует себя "лучше", что является субъективным

    void foo(List<?> i) 
<T> void foo(List<T> i)

Я скажу, что первая версия лучше.

Если существуют оценки

    void foo(List<? extends Number> i) 
<T extends Number> void foo(List<T> i)

1-я версия выглядит еще более компактной; информация о типе находится в одном месте.

В этот момент версия подстановочного знака является идиоматическим способом, и она более знакома программистам.

В определениях метода JDK существует множество подстановочных знаков, особенно после введения лямбда/потока java8. По общему признанию, они очень уродливы, потому что у нас нет типов дисперсий. Но подумайте, насколько уродливее будет, если мы разберем все подстановочные знаки, чтобы напечатать vars.

Ответ 3

Я согласен: удалите вспомогательный метод и введите публичный API. Нет причин для этого, и не все причины.

Просто подведем итог необходимости помощника с подстановочной версией: хотя это очевидно для нас как людей, компилятор не знает, что неизвестный тип, возвращаемый из l.get(0), - это тот же самый неизвестный тип самого списка. т.е. он не учитывает, что параметр вызова set() поступает из того же объекта списка, что и целевой, поэтому он должен быть безопасным. Он только замечает, что тип, возвращаемый из get(), неизвестен, а тип целевого списка неизвестен, а два неизвестных не гарантируют того же типа.