Зачем использовать вспомогательный метод захвата карты?
Ссылаясь на: Способы помощи подстановочных знаков
В нем говорится создать вспомогательный метод для захвата 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()
, неизвестен, а тип целевого списка неизвестен, а два неизвестных не гарантируют того же типа.