Список типов vs тип ArrayList в Java

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

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

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

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

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

15 ответов


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

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


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

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

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

  • Utility методы, такие как 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 завершится с ошибкой сериализации, если какой-либо элемент не сериализуется.)


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

(опубликовано в основном для схема сбора)


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

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

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

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

public static void print(Set<String> s)

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

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

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

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

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

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


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

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


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

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

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

посмотрите на Java docs:

класс ArrayList - изменение размера-реализация массива List взаимодействие.

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

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


на самом деле есть случаи, когда (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<>();

(3) коллекция myCollection = новый ArrayList ();

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


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

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

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

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

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

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

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

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


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


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

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


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

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) weĺl увидеть следующее (только список инициализации и присваивания) для наших список фрагмент:

   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

и алист:

   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

разница составляет список заканчивает вызов INVOKEINTERFACE а алист звонки INVOKEVIRTUAL. Accoding к Bycode выделить ссылку плагином,

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

пока invokevirtual

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

In резюме, invokevirtual pops objectref от стека, в то время как для invokeinterface

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

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


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

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

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

  • для пример removeAll или add(null)

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);

вывод:

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