Чому Enumeration перетворюється на ArrayList, а не List в java.utils?


74

Чи є поважна причина, що метод Collections.list () у java.utilsпакеті повертає ArrayList<T>замість List<T>?

Очевидно, що a ArrayListє List, але у мене складається враження, що загалом є гарною практикою повертати тип інтерфейсу замість типу реалізації.


З Javadoc: " Повертає список масивів [...] ". Чому вони вирішили заблокувати API для повернення ArrayList, невідомо, і ми не можемо відповісти на це питання, навіть дивлячись на вихідний код Collections.
Лаф

може бути тому, що порядок переліків повинен підтримуватися? це згадується у посиланні у вашому запитанні, повертає список масивів, що містить елементи, повернуті зазначеним переліченням, у тому порядку, в якому вони повертаються переліченням
Amit.rk3

5
@ Amit.rk3 a Список завжди зберігає порядок незалежно від того, яку реалізацію ви вибрали.
Jib'z

@ Amit.rk3 A Listзберігає порядок додавання елементів або, принаймні, це загальний контракт, визначений Listінтерфейсом: " Впорядкована колекція (також відома як послідовність) ".
Лаф

@ Amit.rk3 Також зауважте, що перерахування (і перерахування, малі "e", яке воно представляє) відрізняється від enum. Вони поділяють ім’я, але нічого іншого.
yshavit

Відповіді:


54

Застереження: Я не автор JDK.

Я погоджуюсь, що правильно писати власний код в інтерфейсах, але якщо ви збираєтесь повернути змінну колекцію третій стороні, важливо повідомити третю сторону, яку саме інформацію Listвони повертають.

LinkedListі ArrayListдуже різні за ефективністю для різних операцій. Наприклад, видалення першого елемента an ArrayListє O(n), але видалення першого елемента a LinkedListє O(1).

Повністю вказавши тип повернення, автори JDK передають додаткову інформацію в однозначному коді про те, який тип об’єкта вони вам повертають, тому ви можете написати свій код, щоб правильно використовувати цей метод. Якщо вам справді потрібнаLinkedList , ви знаєте, що тут її потрібно вказати.

Нарешті, головна причина кодування інтерфейсу над реалізацією полягає в тому, що ви думаєте, що реалізація зміниться. Автори JDK, мабуть, вважають, що вони ніколи не збираються змінювати цей метод; це ніколи не поверне а LinkedListчи а Collections.UnmodifiableList. Однак у більшості випадків ви, мабуть, все-таки зробите:

List<T> list = Collections.list(enumeration);

19
Здається, це своєрідний Закон Постеля щодо розробки API: будь загальним щодо входів, конкретно з результатами.
Tianxiang Xiong 02

2
Одного разу вони додадуть toLinkedListметод, і тоді назва listверсії ArrayList буде виглядати безглуздо.
user253751

1
Я думаю, що тут важлива інформація, чи є цей список змінним чи ні. Наприклад, які методи інтерфейсу викидають UnsupportedOperationException, а які ні.
keuleJ

32

Повернувшись List, ви будете просувати програму до інтерфейсу , що є дуже гарною практикою. Однак цей підхід має свої обмеження. Наприклад, ви не можете використовувати деякі методи, які визначені ArrayListта не існують в Listінтерфейсі - див. Цю відповідь докладнішу інформацію .

Я цитую дизайн API із підручників Java ™:

... Чудово повернути об’єкт будь-якого типу, який реалізує або розширює один з інтерфейсів колекції. Це може бути один із інтерфейсів або спеціальний тип, який розширює або реалізує один із цих інтерфейсів.

.. В одному сенсі, повернені значення повинні мати протилежну поведінку вхідних параметрів: найкраще повертати найбільш конкретний застосовний інтерфейс збору, а не найбільш загальний . Наприклад, якщо ви впевнені, що завжди будете повертати a SortedMap, вам слід вказати відповідний метод типу return, SortedMapа не Map. SortedMapекземпляри забирають більше часу, ніж звичайні Mapекземпляри, а також є більш потужними. Враховуючи, що ваш модуль вже вклав час на створення a SortedMap, має сенс надати користувачеві доступ до його збільшеної потужності. Крім того, користувач зможе передавати повернутий об'єкт методам, які вимагають a SortedMap, а також тим, які приймають будь-які Map.

Оскільки ArrayList це, по суті, масив, вони є моїм першим вибором, коли мені потрібно мати "колекційний масив". Отже, якщо я хочу перетворити перерахування на список, моїм вибором буде список масивів.

У будь-яких інших випадках все ще можна писати:

List<T> list = Collections.list(e);

4
Що стосується "найбільш конкретного застосовного інтерфейсу колекції, а не найзагальнішого", це має сенс, але ArrayList не є інтерфейсом. У цьому випадку List видається найбільш конкретним інтерфейсом.
Tianxiang Xiong 02

2
У випадку з a SortedMapце додає функціональність до звичайного Map, тому я вважаю, що має сенс вказати його як тип повернення. Однак ArrayListдодаток нічого не додає до a List, і його можна поміняти на a, LinkedListі ви не побачите нічого іншого (з точки зору функціональності, а не продуктивності). Тим не менше, я думаю, що ваш довідник дуже цікавий. +1.
Лаф

1
@Laf насправді є багато речей, які слід враховувати при виборі LinkedListабо ArrayList. Як отримання складності в часі ..
Марун

5
Щоб наголосити на цьому: Це має бути дизайнерське рішення , а не «випадковість». Якщо ви зберігаєте ваші дані всередині в SortedMap, ви повинні НЕ повернути його «тому що це один». Ви повинні вирішити, чи повинен той, хто телефонує, ЗНАТИ, що це a SortedMap. І ви повинні знати, що, повернувши a SortedMap, ви ніколи не зможете узагальнити його назад до звичайного Map- тоді як інший напрямок (стаючи більш конкретним і повертаючи SortedMapде спочатку Mapбуло повернуто лише a ) завжди можливо.
Marco13

6

Функції, що повертають "ексклюзивне право власності" на новостворені змінні об'єкти, часто мають бути найбільш конкретним практичним типом; ті, що повертають незмінні об'єкти, особливо якщо вони можуть бути спільними, часто повинні повертати менш конкретні типи.

Причиною розрізнення є те, що в попередньому випадку об'єкт завжди зможе створити новий об'єкт зазначеного типу, і оскільки одержувач буде володіти об'єктом, і немає повідомлень, які дії одержувач може виконати, то там як правило, код, що повертає об’єкт, не міг би знати, чи можуть будь-які альтернативні реалізації інтерфейсу задовольнити потреби одержувача.

В останньому випадку той факт, що об'єкт незмінний, означає, що функція може виявити альтернативний тип, який може зробити все, що може зробити більш складний тип, враховуючи його точний зміст . Наприклад, Immutable2dMatrixінтерфейс може бути реалізований ImmutableArrayBacked2dMatrixкласом і ImmutableDiagonal2dMatrixкласом. Функція, яка повинна повертати квадрат, Immutable2dMatrixможе вирішити повернути ImmutableDiagonalMatrixекземпляр, якщо всі елементи від головної діагоналі дорівнюють нулю, абоImmutableArrayBackedMatrix якщо ні. Перший тип зайняв би набагато менше місця для зберігання, але одержувач не повинен дбати про різницю між ними.

Повернення, Immutable2dMatrixа не конкретне, ImmutableArrayBackedMatrixдозволяє коду вибирати тип повернення на основі того, що містить масив; це також означає, що якщо код, який повинен повернути масив, містить відповідну реалізаціюImmutable2dMatrix він може просто повернути це, а не будувати новий екземпляр. Обидва ці фактори можуть бути головними "перемогами" при роботі з незмінними об'єктами.

Однак при роботі з мінливими об'єктами жоден з факторів не вступає у гру. Той факт, що змінний масив може не мати жодних елементів від основної діагоналі, коли він генерується, не означає, що він ніколи не матиме таких елементів. Отже, хоча a ImmutableDiagonalMatrixфактично є підтипом an Immutable2dMatrix, a MutableDiagonalMatrixне є підтипом a Mutable2dMatrix, оскільки останній може приймати сховища від головної діагоналі, тоді як перший не може. Далі, хоча незмінні об’єкти часто можна і повинні спільно використовувати, змінні об’єкти, як правило, не можуть. Функція, для якої запитується нова змінна колекція, ініціалізована певним вмістом, повинна створити нову колекцію, незалежно від того, чи зберігає її резервна копія запитуваний тип.


1

Існує невелика накладна витрата методів виклику через інтерфейс, а не безпосередньо на об’єкт.

Ці накладні витрати часто складають не більше 1 або 2 інструкцій процесора. Накладні витрати на виклик методу ще нижчі, якщо JIT знає, що метод остаточний. Це не піддається вимірюванню для більшості коду, як ми з вами, але для низькорівневих методів у java.utils може бути використаний у деяких кодах, де це проблема.

Також, як було зазначено в інших відповідях, конкретний тип об'єкта, що повертається (навіть якщо він прихований за інтерфейсом), впливає на продуктивність коду, який його використовує. Ця зміна продуктивності може бути дуже великою, настільки, що викличне програмне забезпечення не працює.

Очевидно, що автори java.utilsне можуть знати, що робить все програмне забезпечення, яке викликає Collections.list (), і не мають можливості повторно протестувати це програмне забезпечення, якщо вони змінюють імплантацію Collections.list (). Тому вони не збираються змінювати імплантацію Collections.list (), щоб повернути інший тип List, навіть якщо система типів це дозволяла!

Створюючи власне програмне забезпечення, ви (сподіваємось) маєте автоматизований тест, який охоплює весь ваш код, і добре розумієте, як взаємозв’язок вашого коду включає знання, де проблема продуктивності. Можливість змінити метод без необхідності змінювати абонентів має велике значення, поки змінюється дизайн програмного забезпечення.

Тому ці два компроміси дуже різні.

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