Список типов vs тип ArrayList в Java
(1) List<?> myList = new ArrayList<?>();
(2) ArrayList<?> myList = new ArrayList<?>();
Я понимаю, что с (1), реализаций список интерфейс можно менять местами. Кажется, что (1) обычно используется в приложении независимо от необходимости (сам я всегда использую это).
Мне интересно, если кто-нибудь использует (2)?
кроме того, как часто (и могу ли я получить пример) ситуация на самом деле требует использования (1) над (2) (т. е. где (2) было бы недостаточно..в сторону кодирование интерфейсов и лучшие практики etc.)
15 ответов:
почти всегда первый предпочтительнее второго. Первый имеет то преимущество, что реализация
Listможет измениться (наLinkedListнапример), не затрагивая остальную часть кода. Это будет трудная задача, чтобы сделать сArrayListне только потому, что вам придется менятьArrayListдоLinkedListвезде, но и потому, что вы можете использоватьArrayListспецифические методы.вы можете прочитать о
Listреализаций здесь. Вы можете начать сArrayList, но вскоре обнаруживают, что другая реализация является более целесообразным.
мне интересно, если кто-нибудь использует (2)?
да. Но редко по уважительной причине (ИМО).
и люди получают ожоги, потому что они использовали
ArrayList, когда они должны были использоватьList:
утилиты методы, такие как
Collections.singletonList(...)илиArrays.asList(...)не возвратитьArrayList.методы
ListAPI не гарантирует возврат списка того же типа.например, кто-то обжегся, в https://stackoverflow.com/a/1481123/139985 у плаката были проблемы с "нарезкой", потому что
ArrayList.sublist(...)не возвращаетArrayList... и он разработал свой код, чтобы использоватьArrayListкак тип всех его переменных списка. Он закончил тем, что" решил " проблему, скопировав подсписок в новыйArrayList.аргумент, что вы должны знать, как
Listповедение в основном рассматривается используяRandomAccessинтерфейс маркер. Да, это немного неуклюже, но альтернатива хуже.кроме того, как часто ситуация на самом деле требует использования (1) над (2) (т. е. где (2) не будет достаточно..помимо "кодирования для интерфейсов" и передовой практики и т. д.)
часть вопроса "как часто" объективно неразрешима.
(и я могу, пожалуйста, получить пример)
иногда приложение может потребовать, чтобы вы использовали методы в
ArrayListAPI, которые не наListAPI. Например,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();
Для справки:
(опубликовано в основном для схема сбора)
это считается хорошим стилем сохранить ссылку на тег
HashSetилиTreeSetв переменной типа Set.
Set<String> names = new HashSet<String>();таким образом, вы должны изменить только одну строку, если вы решили использовать
TreeSetвместо.кроме того, методы, которые работают с наборами, должны указывать параметры набора типов:
public static void print(Set<String> s)затем метод можно использовать для всего набора реализации.
теоретически, мы должны сделать ту же рекомендацию для связанных списков, а именно сохранить Ссылки LinkedList в переменных типа List. Однако в библиотеке Java интерфейс списка является общим для обоих
ArrayListиLinkedListкласса. В частности, он имеет методы get и set для произвольного доступа, хотя эти методы очень неэффективны для связанных списков.вы не могу писать эффективный код если вы не знаете, является ли произвольный доступ является эффективным или нет.
это явно серьезная ошибка проектирования в стандартной библиотеке, и я не могу рекомендовать использовать интерфейс списка по этой причине.
чтобы увидеть, насколько неловко, что ошибка, взгляните на исходный код
binarySearchметод коллекции класса. Этот метод принимает Параметр списка, но двоичный поиск не имеет смысла для связанного список. Код тогда неуклюже пытается определить, является ли список связанным списком, а затем переключается на линейный поиск!The и
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) Collection myCollection = new ArrayList ();
Я использую обычно. И только если мне нужны методы списка, я буду использовать список. То же самое с ArrayList. Вы всегда можете переключиться на более "узкий" интерфейс, но вы не можете переключиться на более "широкий".
из следующих двух:
(1) List<?> myList = new ArrayList<?>(); (2) ArrayList<?> myList = new ArrayList<?>();во-первых, как правило, предпочтительнее. Как вы будете использовать методы от
Listтолько интерфейс, он предоставляет вам свободу использовать некоторые другие реализацииListнапримерLinkedListв будущем. Таким образом, он отделяет вас от конкретной реализации. Теперь есть два момента, о которых стоит упомянуть:
- мы всегда должны программировать интерфейс. Еще здесь.
- вы почти всегда будете использовать
ArrayListoverLinkedList. Еще здесь.мне интересно, если кто-нибудь использует (2)
да, иногда (читай редко). Когда нам нужны методы, которые являются частью реализации
ArrayListно не часть интерфейсаList. НапримерensureCapacity.кроме того, как часто (и могу ли я получить пример) ситуация на самом деле требуется использовать (1) над (2)
почти всегда вы предпочитаю вариант (1). Это классический шаблон проектирования в ООП, где вы всегда пытаетесь отделить свой код от конкретной реализации и программы к интерфейсу.
единственный случай, когда я знаю, где (2)может быть лучше при использовании GWT, потому что это уменьшает объем приложения (не моя идея, но команда google web toolkit так говорит). Но для обычной java, работающей внутри JVM (1), вероятно, всегда лучше.
список является interface.It не имеет методов. При вызове метода по ссылке на список. Он фактически вызывает метод 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) мы увидим следующее (только инициализация списка и назначение) для нашего список фрагмент:
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 Outline,
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);вывод:
используйте интерфейсы везде, где это возможно, не ограничивайте вас или других использовать различные методы, которые они хотят использовать.

Comments