Різниця між списком, списком <?>, Списком <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. Список <Object>

Це дає помилку "Метод 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
Thang Pham

Навіщо людям користуватися List<Object>?
Thang Pham

3
Це спосіб створити список, який приймає будь-який тип елементів, ви рідко використовуєте його.
Кай

Насправді я б не думав, що хтось буде ним користуватися, вважаючи, що зараз ви не можете мати ідентифікатор.
if_zero_equals_one

1
@if_zero_equals_one Так, але ви отримаєте попередження про компілятор (воно попередить і скаже, що ви використовуєте необроблені типи), і ви ніколи не хочете складати свій код із попередженнями.
Кай

26

Остання частина: Хоча String є підмножиною Об'єкта, але Список <String> не успадковується зі списку <Object>.


11
Дуже гарна точка; багато хто припускає, що оскільки клас C успадковує від класу P, цей список <C> успадковує також і список <P>. Як ви вказали, це не так. Причиною цього було те, що якщо ми могли перейти зі списку <String> до списку <об'єкт>, то ми могли б помістити об’єкти у цей список, порушивши, таким чином, оригінальний контракт списку <String> при спробі отримати елемент.
Пітер

2
+1. Гарний момент також. То навіщо людям користуватися List<Object>?
Тханг Фам

9
Список <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>?
Thang Pham

10
Я думаю, що "перелік чогось" є кращим значенням, List<?>оскільки список має певний, але невідомий тип. List<Object>насправді був би "списком будь-чого", оскільки він дійсно може містити все, що завгодно.
ColinD

1
@ColinD - я мав на увазі "що завгодно" у значенні "будь-яка одна річ". Але ти маєш рацію; це означає, "перелік чогось, але я не збираюсь розповідати вам".
Тед Хопп

@ColinD це він мав на увазі, чому ти повторив його слова? так, це було написано трохи іншими словами, але значення однакове ...
користувач25

14

Причина, яку ви не можете подати, полягає List<String>в List<Object>тому, що це дозволить вам порушити обмеження List<String>.

Подумайте про наступний сценарій: Якщо у мене є List<String>, він повинен містити лише об'єкти типу String. (Що таке finalклас)

Якщо я можу подати це до а List<Object>, це дозволяє мені додати Objectдо цього списку, порушуючи тим самим оригінальний контракт List<String>.

Таким чином, загалом, якщо клас Cуспадковує від класу P, ви не можете сказати, що він GenericType<C>також успадковує від GenericType<P>.

NB Я вже коментував це в попередній відповіді, але хотів розширити його.


tyvm, я завантажую як ваш коментар, так і вашу відповідь, оскільки це дуже хороше пояснення. Зараз де і навіщо користуватися людьми List<Object>?
Thang Pham

3
Як правило, ви не повинні використовувати, List<Object>оскільки це свого роду перемагає призначення генериків. Однак бувають випадки, коли старий код може Listприймати різні типи, тож ви, можливо, захочете вдосконалити код для використання параметризації типів, щоб уникнути попереджень компілятора для необроблених типів. (Але функціонал незмінний)
Пітер

5

Я б радив читати головоломки Java. Це пояснює наслідування, генеричні дані, абстракції та символи символів у деклараціях досить добре. http://www.javapuzzlers.com/


5

Поговоримо про них у контексті історії Java;

  1. List:

Список означає, що він може включати будь-який об’єкт. Список був у випуску до Java 5.0; Java 5.0 представив список, для зворотної сумісності.

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

?означає невідомий Об'єкт не будь-який Об'єкт; ?введення підстановки для вирішення проблеми, побудованої за загальним типом; побачити підстановку ; але це також спричиняє іншу проблему:

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, у класах генерики такого співвідношення немає, List<String>не є підтипом, List<Object>вони є двома різними типами з точки зору компілятора, це найкраще пояснюється в цьому записі в блозі


5

Теорія

String[] можна віддати Object[]

але

List<String>не може бути передано List<Object>.

Практика

Для списків це більш тонко, ніж це, тому що під час компіляції тип параметра List, переданого методу, не перевіряється. Визначення методу може також сказати List<?>- з точки зору компілятора воно рівнозначне. Ось чому приклад №2 ОП дає помилки виконання, а не компіляцію помилок.

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

A. Отже, цей код не буде створювати помилки компіляції або виконання, і фактично (а може, дивно?) Спрацює:

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(), стає a List<String>. Не List<String>може бути призначено List<Object>.

В C параметру setпотрібно an Object[]. Немає помилки компіляції та помилки виконання, коли вона викликається з String[]об'єктом як параметром. Це тому, що String[]кидає Object[]. Але фактичний об'єкт, який отримав, test()залишається a String[], він не змінився. Таким чином paramsоб'єкт також стає а String[]. І елемент 0 a String[]не може бути призначений a Long!

(Сподіваюся, у мене все тут правильно, якщо мої міркування неправильні, я впевнений, що громада скаже мені. ОНОВЛЕНО: Я оновив код у прикладі А, щоб він насправді компілювався, все ще показуючи зроблений пункт.)


Я спробував ваш приклад A, це не працює : List<Object> cannot be applied to List<String>. Ви не можете перейти ArrayList<String>до очікуваного методу ArrayList<Object>.
розбір

Дякую, досить пізно в дату я підробив приклад А, щоб він тепер працював. Основна зміна полягала у визначенні argsList загалом у main ().
radfast

4

З проблемою 2 добре, оскільки "System.out.println (встановлено);" означає "System.out.println (set.toString ());" set є екземпляром List, тому компілятор зателефонує 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: Колекція не дозволяє коваріацію параметрів типу. Але масив дозволяє коваріацію.


0

Ви праві: String - це підмножина Object. Оскільки String є більш "точним", ніж "Object", вам слід надати його, щоб використовувати його як аргумент для System.out.println ().

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.