Разница между списком, списком <?>, Списком <T>, списком <E> и списком <объект>

194

Каковы различия между List, List<?>, List<T>, List<E>, и List<Object>?

1. Список

List: является необработанным типом, поэтому нет typesafe. Он будет генерировать ошибку времени выполнения только при плохом приведении. Мы хотим, чтобы ошибка времени компиляции была плохой. Не рекомендуется использовать.

2. Список <?>

List<?>является неограниченным символом подстановки. Но я не уверен, для чего это? Я могу напечатать List<?>без проблем:

public static void test(List<?> list){
    System.out.println(list);   // Works
}

Почему я не могу добавить элементы в List<?>?

public static void test(List<?> list){
    list.add(new Long(2));     // Error
    list.add("2");             // Error
    System.out.println(list);
}

3. Список <T>

public static void test(List<T> list){   // T cannot be resolved
    System.out.println(list);
}

Я не понимаю этот синтаксис. Я видел что-то вроде этого, и это работает:

public <T> T[] toArray(T[] a){
    return a;   
}

Иногда я вижу <T>, или <E>, или <U>, <T,E>. Они все одинаковые или представляют что-то другое?

4. Список <Объект>

Это дает ошибку «Метод test(List<Object>)не применим для аргумента List<String>»:

public static void test(List<Object> list){
    System.out.println(list);
}

Если бы я попробовать это , то я получил «Не отлиты из List<String>к List<Object>»:

test((List<Object>) names);

Я смущен. Stringявляется подклассом Object, так почему не List<String>подкласс List<Object>?

Тханг Фам
источник

Ответы:

77

1) исправить

2) Вы можете думать о нем как о «доступном только для чтения» списке, где вас не волнует тип элементов. Например, может использоваться методом, который возвращает длину списка.

3) T, E и U одинаковы, но люди склонны использовать, например, T для типа, E для элемента, V для значения и K для ключа. Метод, который компилирует, говорит, что он взял массив определенного типа и возвращает массив того же типа.

4) Нельзя смешивать апельсины и яблоки. Вы сможете добавить объект в свой список строк, если сможете передать список строк в метод, который ожидает списки объектов. (И не все объекты являются строками)

Кая
источник
2
+1 для списка только для чтения на 2. Я пишу несколько кодов, чтобы продемонстрировать это в 2. Tyvm
Тханг Фам
Почему люди используют List<Object>для?
Тханг Фам
3
Это способ создать список, который принимает элементы любого типа, вы редко используете его.
Кай
На самом деле я не думаю, что кто-то будет использовать его, учитывая, что вы не можете иметь идентификатор вообще.
if_zero_equals_one
1
@if_zero_equals_one Да, но тогда вы получите предупреждение компилятора (оно предупредит и скажет, что вы используете необработанные типы), и вы никогда не захотите компилировать свой код с предупреждениями.
Кай
26

Для последней части: хотя String является подмножеством Object, но List <String> не наследуется от List <Object>.

Фаршид Закер
источник
11
Очень хороший момент; многие полагают, что поскольку класс C наследуется от класса P, этот List <C> также наследуется от List <P>. Как вы указали, это не тот случай. Причина была в том, что если бы мы могли приводить из List <String> к List <Object>, то мы могли бы помещать Objects в этот список, тем самым нарушая первоначальный контракт List <String> при попытке извлечь элемент.
Питер
2
+1. Хороший вопрос также. Так зачем людям использовать List<Object>?
Тханг Фам
9
List <Object> может использоваться для хранения списка объектов разных классов.
Фаршид Закер
20

Обозначение List<?>означает «список чего-либо (но я не говорю, что)». Поскольку код testработает для любого типа объекта в списке, он работает как формальный параметр метода.

Использование параметра типа (как в пункте 3) требует, чтобы параметр типа был объявлен. Синтаксис Java для этого - поставить <T>перед функцией. Это в точности аналогично объявлению имен формальных параметров для метода перед использованием имен в теле метода.

Что касается List<Object>неприятия а List<String>, это имеет смысл, потому что а Stringне Object; это подкласс Object. Исправление должно объявить public static void test(List<? extends Object> set) .... Но тогда extends Objectэто избыточно, потому что каждый класс прямо или косвенно расширяется Object.

Тед Хопп
источник
Почему люди используют List<Object>для?
Тханг Фам
10
Я думаю, что «список чего-то» является лучшим значением, List<?>поскольку список имеет определенный, но неизвестный тип. List<Object>будет действительно «список чего угодно», так как он действительно может содержать что угодно.
ColinD
1
@ColinD - я имел в виду «что-нибудь» в смысле «что-нибудь одно». Но ты прав; это значит, «список чего-то, но я не собираюсь говорить вам, что».
Тед Хопп
@ColinD это он имел в виду, почему ты повторил его слова? да, это было написано с немного другими словами, но смысл тот же ...
user25
14

Причина, по которой вы не можете использовать List<String>это, List<Object>заключается в том, что это позволит вам нарушить ограничения List<String>.

Подумайте о следующем сценарии: если у меня есть List<String>, он должен содержать только объекты типа String. (Который является finalклассом)

Если я могу привести это к a List<Object>, то это позволит мне добавить Objectк этому списку, нарушив тем самым первоначальный контракт List<String>.

Таким образом, в общем, если класс Cнаследует от класса P, вы не можете сказать, что он GenericType<C>также наследует от GenericType<P>.

NB Я уже прокомментировал это в предыдущем ответе, но хотел бы расширить его.

Питер
источник
tyvm, я загружаю и ваш комментарий, и ваш ответ, так как это очень хорошее объяснение. Теперь, где и почему люди будут использовать List<Object>?
Тханг Фам
3
Как правило, вы не должны использовать, так List<Object>как это побеждает назначение дженериков. Однако в некоторых случаях старый код может Listпринимать различные типы, поэтому вы можете захотеть модифицировать код, чтобы использовать параметризацию типа, чтобы избежать предупреждений компилятора для необработанных типов. (Но функциональность не изменилась)
Питер
5

Я бы посоветовал читать головоломки Java. Он довольно хорошо объясняет наследование, обобщения, абстракции и шаблоны в объявлениях. http://www.javapuzzlers.com/

if_zero_equals_one
источник
5

Давайте поговорим о них в контексте истории Java;

  1. List:

Список означает, что он может включать любой объект. Список был в выпуске до Java 5.0; Java 5.0 представила список для обратной совместимости.

List list=new  ArrayList();
list.add(anyObject);
  1. List<?>:

?означает неизвестный объект, а не любой объект; ?введение с подстановочными знаками предназначено для решения проблемы, построенной с использованием Generic Type; увидеть подстановочные знаки ; но это также вызывает другую проблему:

Collection<?> c = new ArrayList<String>();
c.add(new Object()); // Compile time error
  1. List< T> List< E>

Означает обобщенную декларацию при условии отсутствия типа T или E в вашем проекте Lib.

  1. List< Object> означает общую параметризацию.
Фил
источник
5

В третьем пункте, «T» не может быть разрешен, потому что он не объявлен, обычно, когда вы объявляете универсальный класс, вы можете использовать «T» в качестве имени параметра связанного типа , во многих онлайн-примерах, включая руководства oracle, «T» используется в качестве имя параметра типа, например, вы объявляете класс как:

public class FooHandler<T>
{
   public void operateOnFoo(T foo) { /*some foo handling code here*/}

}

Вы говорите, что FooHandler's operateOnFooметод ожидает переменную типа "T", которая объявлена ​​в самом объявлении класса, с учетом этого вы можете позже добавить другой метод, такой как

public void operateOnFoos(List<T> foos)

во всех случаях T, E или U все идентификаторы параметра типа, вы можете даже иметь более одного параметра типа, который использует синтаксис

public class MyClass<Atype,AnotherType> {}

в вашем четвертом понятии, хотя Sting является подтипом Object, в классах generics такой связи нет, List<String>это не подтип, поскольку List<Object>они представляют собой два разных типа с точки зрения компилятора, это лучше всего объяснить в этой записи блога.

Harima555
источник
5

теория

String[] может быть приведен к Object[]

но

List<String>не может быть приведен к List<Object>.

практика

Для списков это сложнее, потому что во время компиляции тип параметра List, передаваемого методу, не проверяется. Определение метода может также сказать List<?>- с точки зрения компилятора это эквивалентно. Вот почему в примере 2 OP выдает ошибки времени выполнения, а не ошибки компиляции.

Если вы List<Object>тщательно обрабатываете параметр, переданный методу, чтобы не вызывать проверку типа ни для одного элемента списка, вы можете определить свой метод с использованием, List<Object>но фактически принять List<String>параметр из вызывающего кода.

О. Таким образом, этот код не будет давать ошибок компиляции или времени выполнения и будет фактически (и может быть удивительно?) Работать:

public static void main(String[] args) {
    List argsList = new ArrayList<String>();
    argsList.addAll(Arrays.asList(args));
    test(argsList);  // The object passed here is a List<String>
}

public static void test(List<Object> set) {
    List<Object> params = new ArrayList<>();  // This is a List<Object>
    params.addAll(set);       // Each String in set can be added to List<Object>
    params.add(new Long(2));  // A Long can be added to List<Object>
    System.out.println(params);
}

B. Этот код выдаст ошибку времени выполнения:

public static void main(String[] args) {
    List argsList = new ArrayList<String>();
    argsList.addAll(Arrays.asList(args));
    test1(argsList);
    test2(argsList);
}

public static void test1(List<Object> set) {
    List<Object> params = set;  // Surprise!  Runtime error
}

public static void test2(List<Object> set) {
    set.add(new Long(2));       // Also a runtime error
}

C. Этот код выдаст ошибку времени выполнения ( java.lang.ArrayStoreException: java.util.Collections$UnmodifiableRandomAccessList Object[]):

public static void main(String[] args) {
    test(args);
}

public static void test(Object[] set) {
    Object[] params = set;    // This is OK even at runtime
    params[0] = new Long(2);  // Surprise!  Runtime error
}

В B параметр setне является типизированным Listво время компиляции: компилятор видит его так List<?>. Существует ошибка во время выполнения, потому что во время выполнения setстановится фактическим переданным объектом main(), и это List<String>. А List<String>не может быть приведен к List<Object>.

В C параметр setтребует Object[]. Нет ошибки компиляции и ошибки времени выполнения, когда она вызывается с String[]объектом в качестве параметра. Это потому , что String[]слепки с Object[]. Но фактический объект, полученный, test()остается String[], он не изменился. Таким образом, paramsобъект также становится String[]. И элемент 0 a String[]не может быть присвоен a Long!

(Надеюсь, у меня все в порядке, если мои рассуждения ошибочны, я уверен, что сообщество скажет мне. ОБНОВЛЕНО: я обновил код в примере A, чтобы он фактически компилировался, при этом все еще показывая сделанную точку.)

radfast
источник
Я попробовал ваш пример, А это не работает List<Object> cannot be applied to List<String>. Вы не можете перейти ArrayList<String>к методу, который ожидает ArrayList<Object>.
парсер
Спасибо, довольно поздно в дате, я настроил пример А, чтобы он теперь работал. Основным изменением было определение общего argsList в main ().
Радфаст
4

Проблема 2 в порядке, потому что "System.out.println (set);" означает «System.out.println (set.toString ());» set является экземпляром List, поэтому complier вызовет List.toString ();

public static void test(List<?> set){
set.add(new Long(2)); //--> Error  
set.add("2");    //--> Error
System.out.println(set);
} 
Element ? will not promise Long and String, so complier will  not accept Long and String Object

public static void test(List<String> set){
set.add(new Long(2)); //--> Error
set.add("2");    //--> Work
System.out.println(set);
}
Element String promise it a String, so complier will accept String Object

Проблема 3: эти символы одинаковы, но вы можете дать им разные спецификации. Например:

public <T extends Integer,E extends String> void p(T t, E e) {}

Проблема 4: Коллекция не допускает ковариации параметра типа. Но массив допускает ковариацию.

yoso
источник
0

Вы правы: String - это подмножество Object. Поскольку String более «точен», чем Object, вы должны привести его к использованию в качестве аргумента для System.out.println ().

patapizza
источник