Список типов против типа ArrayList в Java
(1) List<?> myList = new ArrayList<?>();
(2) ArrayList<?> myList = new ArrayList<?>();
Я понимаю, что с помощью (1) реализации интерфейса List можно поменять местами. Кажется, что (1) обычно используется в приложении независимо от необходимости (сам я всегда использую это).
Мне интересно, использует ли кто-нибудь (2)?
Кроме того, как часто (и могу ли я, пожалуйста, привести пример) ситуация на самом деле требует использования (1) вместо (2) (т. Е. Там, где (2) было бы недостаточно .. помимо кодирования интерфейсов и лучших практик и т.д.)
Переведено автоматически
Ответ 1
Почти всегда List
предпочтительнее, чем ArrayList
потому что, например, List
может быть переведен в LinkedList
, не затрагивая остальную кодовую базу.
Если использовать ArrayList
вместо List
, то трудно заменить ArrayList
реализацию на LinkedList
другую, потому что ArrayList
в кодовой базе использовались определенные методы, которые также потребовали бы реструктуризации.
Вы можете прочитать о List
реализациях здесь.
Вы можете начать с ArrayList
, но вскоре обнаружите, что более подходящим выбором является другая реализация.
Ответ 2
Мне интересно, использует ли кто-нибудь (2)?
ДА. Но редко по уважительной причине (IMO).
И люди обжигаются, потому что они использовали ArrayList
когда они должны были использовать List
:
Служебные методы, такие как
Collections.singletonList(...)
илиArrays.asList(...)
, не возвращаютArrayList
.Методы в
List
API не гарантируют возврата списка того же типа.
Например, кто-то обжегся, в https://javalang.ru/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
это не единственный класс list, который реализует RandomAccess
. Другие включают CopyOnWriteList
, Stack
и Vector
.
Я видел, как люди приводили тот же аргумент по поводу Serializable
(потому что List
это не реализует) ... но описанный выше подход решает и эту проблему. (В той степени, в какой это вообще решаемо с использованием типов среды выполнения. В ArrayList
произойдет сбой сериализации, если какой-либо элемент не является сериализуемым.)
Наконец, я не собираюсь говорить "потому что это хороший стиль". Эта "причина" является как циклическим аргументом ("Почему это "хороший стиль"?"), так и ссылкой на неустановленный (и, вероятно, несуществующий!) более высокий авторитет ("Кто сказал, что это "хороший стиль"?").
(Я действительно думаю, что это хороший стиль программирования для интерфейса, но я не собираюсь приводить это в качестве причины. Вам лучше понять реальные причины и прийти к (IMO) правильным выводам для себя. Правильный вывод может не всегда совпадать ... в зависимости от контекста.)
Ответ 3
Например, вы можете решить, что LinkedList
это лучший выбор для вашего приложения, но затем позже решите, что ArrayList
может быть лучшим выбором по соображениям производительности.
Использование:
List list = new ArrayList(100); // will be better also to set the initial capacity of a collection
Вместо:
ArrayList list = new ArrayList();
Для справки:
(опубликовано в основном для диаграммы коллекции)
Ответ 4
Считается хорошим стилем хранить ссылку на HashSet
or 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 для произвольного доступа, хотя эти методы очень неэффективны для связанных списков.
Вы не сможете написать эффективный код, если не знаете, эффективен ли произвольный доступ или нет.
Это явно серьезная ошибка проектирования в стандартной библиотеке, и по этой причине я не могу рекомендовать использовать интерфейс List.
Чтобы увидеть, насколько неприятна эта ошибка, взгляните на исходный код для binarySearch
метода класса Collections. Этот метод принимает параметр List, но бинарный поиск не имеет смысла для связанного списка. Затем код неуклюже пытается определить, является ли список связанным списком, а затем переключается на линейный поиск!
Set
Интерфейс и Map
interface, хорошо разработаны, и вы должны их использовать.