Java: ArrayList для списка, HashMap для карты и HashSet для набора?

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

Иногда я создаю ArrayList с начальной емкостью, превышающей значение по умолчанию, равное 10, и HashMap с количеством блоков по умолчанию, превышающим 16, но обычно я (особенно для бизнес-CRUD) никогда не думаю, что думаю: "хммм... должен ли я использовать LinkedList вместо ArrayList, если я просто собираюсь вставить и перебрать весь список?"

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

10 ответов

Решение

Это определенно мой вариант по умолчанию, хотя часто LinkedList на самом деле был бы лучшим выбором для списков, так как подавляющее большинство списков, кажется, просто итерируют по порядку или в любом случае преобразуются в массив через Arrays.asList.

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

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

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

Для каких-то списков (например, слушателей) имеет смысл использовать CopyOnWriteArrayList вместо обычного ArrayList. Почти для всего остального упомянутые базовые реализации достаточны.

Да, я использую их как значения по умолчанию. Обычно у меня есть правило, согласно которому в методах открытого класса я всегда возвращаю тип интерфейса (т. Е. Map, Set, List и т. Д.), Поскольку другим классам (обычно) не нужно знать, что представляет собой конкретный конкретный класс. Внутри методов класса я буду использовать конкретный тип, только если мне нужен доступ к любым дополнительным методам, которые у него могут быть (или если это облегчает понимание кода), в противном случае используется интерфейс.

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

Действительно, всегда используйте базовые интерфейсы Collection, List, Map вместо их реализаций. Чтобы сделать мышление еще более гибким, вы можете скрыть свои реализации за статическими фабричными методами, которые позволяют переключаться на другую реализацию в случае, если вы найдете что-то лучшее (я сомневаюсь, что в этой области будут большие изменения, но вы никогда не знаете). Другое преимущество заключается в том, что синтаксис укорочен благодаря генерикам.

Map<String, LongObjectClasName> map = CollectionUtils.newMap();

instead of 

Map<String, LongObjectClasName> map = new HashMap<String, LongObjectClasName>();


public class CollectionUtils {
.....

public <T> List<T> newList() {
        return new ArrayList<T>();
    }

    public <T> List<T> newList(int initialCapacity) {
        return new ArrayList<T>(initialCapacity);
    }

    public <T> List<T> newSynchronizedList() {
        return new Vector<T>();
    }

    public <T> List<T> newConcurrentList() {
        return new CopyOnWriteArrayList<T>();
    }

    public <T> List<T> newSynchronizedList(int initialCapacity) {
        return new Vector<T>(initialCapacity);
    }

...
}

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

Например, если я создаю список с большим количеством случайных обращений к нему, я буду использовать ArrayList, потому что его производительность произвольного доступа хорошая, но если я много чего вставляю в список, я могу выбрать LinkedList вместо этого. (Я знаю, что современные реализации устраняют много барьеров производительности, но это был первый пример, который пришел в голову.)

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

У меня нет "по умолчанию", хотя я полагаю, что чаще использую реализации, перечисленные в вопросе. Я думаю о том, что подойдет для какой-то конкретной проблемы, над которой я работаю, и использую ее. Я не просто слепо по умолчанию использовать ArrayListЯ потратил 30 секунд на размышления в духе "ну, я собираюсь сделать много итераций и удалять элементы в середине этого списка, поэтому я должен использовать LinkedList".

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

LinkedList<Item> queue = new LinkedList<Item>();

когда программист имел в виду:

Queue<Item> queue = new LinkedList<Item>();

Я также использую Iterable интерфейс изрядное количество.

Если вы используете LinkedList для очереди, вы можете рассмотреть возможность использования интерфейса Deque и класса реализации ArrayDeque (представлен в Java 6). Чтобы процитировать Javadoc для ArrayDeque:

Этот класс, вероятно, будет быстрее, чем Stack, когда используется в качестве стека, и быстрее, чем LinkedList, когда используется в качестве очереди.

Используя тип интерфейса (List, Map) вместо типа реализации (ArrayList, HashMap) не имеет отношения к методам - ​​это в основном важно для общедоступных API, т. е. сигнатур методов (и "общедоступный" не обязательно означает "предназначенный для публикации вне вашей команды)".

Когда метод принимает ArrayList как параметр, и у вас есть что-то еще, вы облажались и должны копировать ваши данные бессмысленно. Если тип параметра Listабоненты гораздо более гибкие и могут, например, использовать Collections.EMPTY_LIST или же Collections.singletonList(),

Я склонен использовать один из классов *Queue для очередей. Однако LinkedList - хороший выбор, если вам не нужна безопасность потоков.

Я тоже обычно использую ArrayList, но я буду использовать TreeSet или HashSet в зависимости от обстоятельств. Однако при написании тестов часто используются Arrays.asList и Collections.singletonList. Я в основном писал код для локального потока, но я также мог видеть использование различных параллельных классов.

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

Другие вопросы по тегам