Какая разница между неограниченным подстановочным типом List <?> И сырым списком типов?
Не могли бы вы помочь мне понять разницу между неограниченным типом списка подстановок и списком сырых типов?
List<?> b; // unbounded wildcard type
List a; // raw type
Наряду с этим может кто-нибудь помочь мне понять, что такое список параметров ограниченного типа?
List<E extends Number> c;
Ответы
Ответ 1
Вот краткое изложение трех:
-
List
: список без параметра типа. Это список, элементы которого имеют любой тип - , элементы могут быть разных типов.
-
List<?>
: Список с параметром неограниченного типа. Его элементы имеют специфический, но неизвестный тип; все элементы должны быть одного типа.
-
List<T extends E>
: Список с параметром типа T
. Поставляемый тип для T
должен иметь тип, который расширяет E
, или он не является допустимым типом для параметра.
Ответ 2
Вы действительно должны взглянуть на Эффективную Java, Пункт 23: Не использовать необработанные типы в новом коде.
Чтобы использовать пример из этой книги, рассмотрим следующий пример... что, если у вас есть коллекция, в которой вам все равно, какие типы элементов в ней. Например, вы хотите увидеть, сколько элементов существует между двумя наборами. Вы можете придумать следующее:
public static int numElementsInCommon(Set s1, Set s2) {
int result = 0;
for (Object o : s1) {
if (s2.contains(o)) {
++result;
}
}
return result;
}
Этот пример, хотя он работает, не рекомендуется использовать из-за использования необработанных типов. Необработанные типы просто не безопасны для всех... вы можете в конечном итоге изменить набор таким образом, чтобы он не был безопасным и не испортил вашу программу. Вместо этого, ошибайтесь на стороне осторожности и используйте альтернативу типа безопасного типа:
public static int numElementsInCommon(Set<?> s1, Set<?> s2) {
int result = 0;
for (Object o : s1) {
if (s2.contains(o)) {
++result;
}
}
return result;
}
Разница в том, что вы можете добавить null
в Set<?>
, и вы НЕ МОЖЕТЕ принять что-либо о элементе, который вы вынимаете из Set<?>
. Если вы используете raw Set
, вы можете добавить все, что хотите. Метод numElementsInCommon
является хорошим примером, когда вам даже не нужно ничего добавлять, и вам не нужно ничего принимать о том, что находится в наборе. Вот почему это хороший кандидат на использование шаблона ?
.
Надеюсь, это поможет. Прочтите весь элемент в Эффективной Java, и это станет действительно понятным.
Чтобы ответить на вторую часть вашего вопроса... помните, что я сказал, когда вы используете подстановочный символ ?
, вы ничего не можете принять в отношении элемента, который вы вынимаете из набора? Что делать, если вам нужно сделать предположение о интерфейсе объекта, который вы удалили из набора. Например, предположим, что вы хотите отслеживать набор Cool
вещей.
public interface Cool {
// Reports why the object is cool
void cool();
}
Тогда у вас может быть такой код:
public static void reportCoolness(Set s) {
for (Object item : s) {
Cool coolItem = (Cool) item;
coolItem.cool();
}
}
Это не безопасный тип... вам нужно убедиться, что вы передали в набор только объекты Cool
. Чтобы исправить это, вы можете сказать:
public static void reportCoolness(Set<Cool> s) {
for (Cool coolItem : s) {
coolItem.cool();
}
}
Это здорово! Это именно то, что вы хотите, и безопасно. Но что, если позже у вас есть это:
public interface ReallyCool extends Cool {
// Reports why the object is beyond cool
void reallyCool();
}
Поскольку все ReallyCool
объекты Cool
, вы должны иметь возможность сделать следующее:
Set<ReallyCool> s = new HashSet<ReallyCool>();
// populate s
reportCoolness(s);
Но вы не можете этого сделать, потому что generics имеют следующее свойство: Предположим, что B
является подклассом A
, тогда Set<B>
НЕ является подклассом Set<A>
. Технический разговор для этого - "Родовые типы являются инвариантными". (В отличие от ковариантного).
Чтобы получить последний пример для работы, вам нужно создать Set<Cool>
путем литья (безопасно) каждого элемента в Set<ReallyCool>
. Чтобы не допустить, чтобы клиенты вашего api прошли через этот неприятный, ненужный код, вы можете просто сделать метод reportCoolness
более гибким следующим образом:
public static void reportCoolness(Set<? extends Cool> s) {
for (Cool coolItem : s) {
coolItem.cool();
}
}
Теперь ваш метод принимает любой Set
, который содержит элементы Cool
или любой подкласс Cool
. Все эти типы придерживаются Cool
api... поэтому мы можем безопасно вызвать метод cool()
для любого элемента
Имеют смысл? Надеюсь, это поможет.
Ответ 3
В вашем первом вопросе разница между List
и List<?>
:
Одно существенное различие между ними состоит в том, что когда у вас есть подстановочный знак в качестве типа, тип Collection
неизвестен, поэтому метод add
будет вызывать ошибку времени компиляции.
Вы все еще можете получать значения из List<?>
, но вам нужен явный приведение.
Ответ 4
Разница между неограниченным подстановочным знаком типа List и необработанным типом List.
Оба случая позволяют поместить в эту переменную любой тип списка:
List nothing1 = new ArrayList<String>();
List nothing2 = new ArrayList();
List nothing3 = new ArrayList<>();
List nothing4 = new ArrayList<Integer>();
List<?> wildcard1 = new ArrayList<String>();
List<?> wildcard2 = new ArrayList();
List<?> wildcard3 = new ArrayList<>();
List<?> wildcard4 = new ArrayList<Integer>();
Но какие элементы мы можем поместить в эти объекты?
Мы можем поместить только строку в List<String>
:
List<String> strings = new ArrayList<>();
strings.add("A new string");
Мы можем поместить любой объект в список:
List nothing = new ArrayList<>();
nothing.add("A new string");
nothing.add(1);
nothing.add(new Object());
И мы ничего не можем (кроме нуля) поместить в List<?>
! Потому что мы используем общие. И Java знает, что это типизированный List, но не знает, какой это тип. И не позволяет нам ошибиться.
Вывод: List<?>
, Который является универсальным List, дает нам безопасность типов.
PS Никогда не используйте необработанные типы в вашем коде.