Тип списка против типа ArrayList в Java - PullRequest
517 голосов
/ 17 февраля 2010
(1) List<?> myList = new ArrayList<?>();

(2) ArrayList<?> myList = new ArrayList<?>();

Я понимаю, что с помощью (1) реализации интерфейса List можно поменять местами. Кажется, что (1) обычно используется в приложении независимо от необходимости (я сам всегда использую это).

Мне интересно, кто-нибудь использует (2)?

Кроме того, как часто (и я могу привести пример) ситуация фактически требует использования (1) over (2) (то есть, где (2) будет недостаточно ... кроме кодирования интерфейсы и лучшие практики и т. д.)

Ответы [ 15 ]

410 голосов
/ 17 февраля 2010

Почти всегда первое предпочтительнее второго.Первое имеет преимущество в том, что реализация List может измениться (например, на LinkedList), не затрагивая остальную часть кода.Это будет трудной задачей для ArrayList не только потому, что вам нужно будет изменить ArrayList на LinkedList везде, но и потому, что вы могли использовать ArrayList специальные методы.Вы можете прочитать о List реализациях здесь .Вы можете начать с ArrayList, но вскоре обнаружите, что другая реализация более подходящая.

105 голосов
/ 17 февраля 2010

Мне интересно, кто-нибудь использует (2)?

Да. Но редко по веской причине (ИМО).

И люди получают ожоги, потому что они использовали ArrayList, когда они должны были использовать List:

  • Служебные методы, такие как Collections.singletonList(...) или Arrays.asList(...), не возвращают ArrayList.

  • Методы в List API не гарантируют возвращение списка того же типа.

Например, кто-то обгорел, в https://stackoverflow.com/a/1481123/139985 у плаката были проблемы с "нарезкой", потому что ArrayList.sublist(...) не возвращает ArrayList ... и он разработал свой код для использования ArrayList как тип всех его переменных списка. В итоге он «решил» проблему, скопировав подсписок в новый ArrayList.

Аргумент о том, что вам нужно знать, как ведет себя List, в значительной степени рассматривается с помощью интерфейса маркера RandomAccess. Да, это немного неуклюже, но альтернатива хуже.

Кроме того, как часто ситуация на самом деле требует использования (1) над (2) (т. Е. Где (2) будет недостаточно… кроме «кодирования для интерфейсов» и лучших практик и т. Д.)

Часть вопроса «как часто» объективно не может быть решена.

(и могу ли я получить пример)

Иногда приложение может требовать использования методов в ArrayList API, которые не в List API. Например, ensureCapacity(int), trimToSize() или removeRange(int, int). (И последний из них возникнет, только если вы создали подтип ArrayList, который объявляет метод public.)

Это единственная разумная причина для кодирования класса, а не интерфейса, IMO.

(Теоретически возможно, что вы получите небольшое улучшение производительности ... при определенных обстоятельствах ... на некоторых платформах ... но если вам действительно не нужны эти последние 0,05%, делать это не стоит. это не веская причина, ИМО.)


Вы не можете написать эффективный код, если не знаете, эффективен ли произвольный доступ или нет.

Это верный момент. Тем не менее, Java предоставляет лучшие способы справиться с этим; например, * * тысяча шестьдесят-шести

public <T extends List & RandomAccess> void test(T list) {
    // do stuff
}

Если вы вызовете это со списком, который не реализует RandomAccess, вы получите ошибку компиляции.

Вы также можете динамически тестировать ... используя instanceof ... если статическая типизация слишком неудобна. И вы могли бы даже написать свой код для использования различных алгоритмов (динамически) в зависимости от того, поддерживает ли список произвольный доступ.

Обратите внимание, что ArrayList - не единственный класс списка, который реализует RandomAccess. Другие включают CopyOnWriteList, Stack и Vector.

Я видел, как люди приводят один и тот же аргумент в отношении Serializable (потому что List не реализует его) ... но описанный выше подход также решает эту проблему. (В той степени, в которой это разрешимо на всех с использованием типов времени выполнения. ArrayList не сможет сериализоваться, если какой-либо элемент не сериализуем.)

37 голосов
/ 09 сентября 2014

Например, вы можете решить, что LinkedList - лучший выбор для вашего приложения, но затем решите, что ArrayList может быть лучшим выбором для повышения производительности.

Использование:

List list = new ArrayList(100); // will be better also to set the initial capacity of a collection 

Вместо:

ArrayList list = new ArrayList();

Для справки:

enter image description here

(размещено в основном для диаграммы сбора)

23 голосов
/ 21 августа 2013

Это считается хорошим стилем для хранения ссылки на HashSet или TreeSet в переменной типа Set.

Set<String> names = new HashSet<String>();

Таким образом, вам нужно изменить только одну строку, если вы решите использовать TreeSet.

Кроме того, методы, которые работают с наборами, должны указывать параметры типа Set:

public static void print(Set<String> s)

Тогда метод можно использовать для всех реализаций набора .

Теоретически, мы должны сделать ту же рекомендацию для связанных списков, а именно: сохранить Ссылки LinkedList в переменных типа List. Однако в библиотеке Java интерфейс List является общим как для класса ArrayList, так и для класса LinkedList. В частности, он имеет методы get и set для произвольного доступа, хотя эти методы очень неэффективны для связанных списков.

Вы не можете написать эффективный код , если не знаете, эффективен ли произвольный доступ или нет.

Это явно серьезная ошибка проектирования в стандартной библиотеке, и я не могу рекомендовать использовать интерфейс списка по этой причине.

Чтобы увидеть, насколько смущает эта ошибка, взгляните на исходный код метода binarySearch класса Collections . Этот метод требует Параметр списка, но бинарный поиск не имеет смысла для связанного списка. Код то коряво пытается выяснить, является ли список связанным списком, а затем переключается на линейный поиск!

Интерфейс Set и Map хорошо спроектированы, и вы должны их использовать.

12 голосов
/ 21 апреля 2013

Я использую (2), если код является «владельцем» списка. Это, например, верно для локальных переменных. Нет смысла использовать абстрактный тип List вместо ArrayList. Еще один пример демонстрации права собственности:

public class Test {

    // This object is the owner of strings, so use the concrete type.
    private final ArrayList<String> strings = new ArrayList<>();

    // This object uses the argument but doesn't own it, so use abstract type.
    public void addStrings(List<String> add) {
        strings.addAll(add);
    }

    // Here we return the list but we do not give ownership away, so use abstract type. This also allows to create optionally an unmodifiable list.
    public List<String> getStrings() {
        return Collections.unmodifiableList(strings);
    }

    // Here we create a new list and give ownership to the caller. Use concrete type.
    public ArrayList<String> getStringsCopy() {
        return new ArrayList<>(strings);
    }
}
11 голосов
/ 05 декабря 2013

Когда вы пишете List, вы фактически говорите, что ваш объект реализует только интерфейс List, но вы не указываете, к какому классу принадлежит ваш объект.

Когда вы пишете ArrayList, вы указываете, что ваш класс объекта является массивом с изменяемым размером.

Итак, первая версия делает ваш код более гибким в будущем.

Посмотрите на документы Java:

Класс ArrayList - Реализация массива изменяемого размера интерфейса List.

Интерфейс List - Упорядоченная коллекция (также известная как последовательность). Пользователь этого интерфейса имеет точный контроль над тем, где в списке каждый элемент вставлен.

Array - контейнерный объект, который содержит фиксированное количество значений одного типа.

9 голосов
/ 14 ноября 2016

На самом деле бывают случаи, когда (2) является не только предпочтительным, но и обязательным, и я очень удивлен, что никто не упоминает об этом здесь.

Сериализация!

Если у вас есть сериализуемый класс и вы хотите, чтобы он содержал список, то вы должны объявить поле конкретного и сериализуемого типа, например ArrayList, поскольку интерфейс List не расширяет java.io.Serializable

Очевидно, что большинство людей не нуждаются в сериализации и забывают об этом.

Пример:

public class ExampleData implements java.io.Serializable {

// The following also guarantees that strings is always an ArrayList.
private final ArrayList<String> strings = new ArrayList<>();
9 голосов
/ 17 февраля 2010

Я думаю, что люди, которые используют (2), не знают принцип замены Лискова или принцип инверсии зависимости . Или они действительно должны использовать ArrayList.

9 голосов
/ 17 февраля 2010

(3) Коллекция myCollection = new ArrayList ();

Я обычно использую это. И только , если мне нужны методы List, я буду использовать List. То же самое с ArrayList. Вы всегда можете переключиться на более «узкий» интерфейс, но вы не можете переключиться на более «широкий».

7 голосов
/ 03 июля 2015

Из следующих двух:

(1) List<?> myList = new ArrayList<?>();
(2) ArrayList<?> myList = new ArrayList<?>();

Первый обычно предпочтителен. Поскольку вы будете использовать методы только из интерфейса List, это даст вам свободу использовать другую реализацию List, например. LinkedList в будущем. Так что это отделяет вас от конкретной реализации. Теперь стоит упомянуть два момента:

  1. Мы всегда должны программировать для интерфейса. Подробнее здесь .
  2. Вы почти всегда будете использовать ArrayList вместо LinkedList. Подробнее здесь .

Мне интересно, если кто-нибудь использует (2)

Да, иногда (читай редко). Когда нам нужны методы, которые являются частью реализации ArrayList, но не являются частью интерфейса List. Например ensureCapacity.

Кроме того, как часто (и я могу привести пример) ситуация фактически требует использования (1) над (2)

Почти всегда вы предпочитаете вариант (1). Это классический шаблон проектирования в ООП, где вы всегда пытаетесь отделить свой код от конкретной реализации и программы до интерфейса.

...