Подтвердить что ты не робот

Тип List vs type ArrayList в Java

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

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

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

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

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

4b9b3361

Ответ 1

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

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

Ответ 2

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

Да. Но редко по уважительной причине.

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

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

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

Например, в fooobar.com/questions/17312/... у постели возникли проблемы с "разрезанием", потому что ArrayList.sublist(...) не возвращает ArrayList... и он разработал свой код, чтобы использовать ArrayList как тип всех его переменных списка. Он решил "решить" проблему, скопировав подсписку в новый ArrayList.

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

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

"Как часто" часть вопроса объективно неопровержима.

(и могу ли я привести пример)

Иногда приложение может требовать, чтобы вы использовали методы в API ArrayList, которые не входят в API List. Например, 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 не будет сериализовать, если какой-либо элемент не может быть сериализуем.)

Ответ 3

Например, вы можете решить, что 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

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

Ответ 4

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

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

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

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

public static void print(Set<String> s)

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

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

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

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

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

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

Ответ 5

Я использую (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);
    }
}

Ответ 7

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

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

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

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

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

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

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

Ответ 8

На самом деле бывают случаи, когда (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

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

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

Ответ 10

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

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

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

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

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

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

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

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

Ответ 11

Единственный случай, когда я знаю, где (2) может быть лучше, - это использовать GWT, поскольку он уменьшает площадь приложения (не моя идея, но команда разработчиков веб-инструментов google так говорит). Но для регулярной java-операции внутри JVM (1), вероятно, всегда лучше.

Ответ 12

Список - это интерфейс. У него нет методов. Когда вы вызываете метод в ссылке List. Фактически он вызывает метод ArrayList в обоих случаях.

И для будущего вы можете изменить List obj = new ArrayList<> на List obj = new LinkList<> или другой тип, который реализует интерфейс Список.

Ответ 13

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

public static void main(String[] args) {
    List<String> list = new ArrayList<String>();
    list.add("a");
    list.add("b");

    ArrayList<String> aList = new ArrayList<String>();
    aList.add("a");
    aList.add("b");

}

Если мы используем просмотрщик байт-кода (я использовал http://asm.ow2.org/eclipse/index.html), мы увидим следующее (только инициализация и назначение списка) для нашего list:

   L0
    LINENUMBER 9 L0
    NEW ArrayList
    DUP
    INVOKESPECIAL ArrayList.<init> () : void
    ASTORE 1
   L1
    LINENUMBER 10 L1
    ALOAD 1: list
    LDC "a"
    INVOKEINTERFACE List.add (Object) : boolean
    POP
   L2
    LINENUMBER 11 L2
    ALOAD 1: list
    LDC "b"
    INVOKEINTERFACE List.add (Object) : boolean
    POP

и для alist:

   L3
    LINENUMBER 13 L3
    NEW java/util/ArrayList
    DUP
    INVOKESPECIAL java/util/ArrayList.<init> ()V
    ASTORE 2
   L4
    LINENUMBER 14 L4
    ALOAD 2
    LDC "a"
    INVOKEVIRTUAL java/util/ArrayList.add (Ljava/lang/Object;)Z
    POP
   L5
    LINENUMBER 15 L5
    ALOAD 2
    LDC "b"
    INVOKEVIRTUAL java/util/ArrayList.add (Ljava/lang/Object;)Z
    POP

Разница list заканчивается вызовом INVOKEINTERFACE, тогда как aList вызывает INVOKEVIRTUAL. Согласование с ссылкой на плагин Bycode Outline,

invokeinterface используется для вызова метода, объявленного в Java Интерфейс

в то время как invokevirtual

вызывает все методы, кроме методов интерфейса (которые используют invokeinterface), статические методы (которые используют invokestatic), и несколько специальные случаи, обрабатываемые специальными вызовами.

Таким образом, invokevirtual pops objectref от стека, а для invokeinterface

интерпретатор выводит "n" элементы из стека операндов, где "n" - это 8-битная беззнаковая целочисленный параметр, взятый из байт-кода. Первый из этих пунктов objectref, ссылка на объект, метод которого вызывается.

Если я правильно понимаю это, разница заключается в том, как каждый способ извлекает objectref.

Ответ 14

Я бы сказал, что 1 является предпочтительным, если

  • вы зависите от реализации необязательного поведения * в ArrayList, в этом случае явное использование ArrayList более ясно.
  • Вы будете использовать ArrayList в вызове метода, который требует ArrayList, возможно, для дополнительного поведения или характеристик производительности.

Моя догадка заключается в том, что в 99% случаев вы можете получить список, который является предпочтительным.

  • например removeAll, или add(null)

Ответ 15

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

ArrayList<Object> myMethod (ArrayList<Object> input) {
   // body
}

вы можете использовать его только с ArrayList, а не с LinkedList, но вы можете разрешить использовать любой из классов List в других местах, где он использует метод, это просто ваш выбор, поэтому использование интерфейса позволяет это:

List<Object> myMethod (List<Object> input) {
   // body
}

В этом аргументе метода вы можете использовать любой из классов List который вы хотите использовать:

List<Object> list = new ArrayList<Object> ();

list.add ("string");

myMethod (list);

ВЫВОД:

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