Тип переменной Java Collection для HashSet или других реализаций? - PullRequest
8 голосов
/ 10 апреля 2019

Я часто видел объявления типа List<String> list = new ArrayList<>(); или Set<String> set = new HashSet<>(); для полей в классах. Для меня имеет смысл использовать интерфейсы для типов переменных, чтобы обеспечить гибкость в реализации. Приведенные выше примеры все еще определяют, какой тип Collection необходимо использовать, соответственно, какие операции разрешены и как они должны вести себя в некоторых случаях (из-за документов).

Теперь рассмотрим случай, когда на самом деле для использования поля в классе требуется только функциональность интерфейса Collection (или даже Iterable), а тип Collection на самом деле не имеет значения, или я не понимаю не хочу переоценивать это. Поэтому я выбираю, например, HashSet в качестве реализации и объявляю поле как Collection<String> collection = new HashSet<>();.

Должно ли тогда поле действительно иметь тип Set в этом случае? Является ли такая декларация плохой практикой, если да, то почему? Или целесообразно указывать фактический тип как можно меньше (и при этом предоставлять все необходимые методы). Причина, по которой я спрашиваю об этом, заключается в том, что я почти никогда не видел такого объявления, и в последнее время я все больше понимаю, что мне нужно только указать функциональность интерфейса Collection.

Пример:

// Only need Collection features, but decided to use a LinkedList
private final Collection<Listener> registeredListeners = new LinkedList<>();

public void init() {
    ExampleListener listener = new ExampleListener();
    registerListenerSomewhere(listener);
    registeredListeners.add(listener);
    listener = new ExampleListener();
    registerListenerSomewhere(listener);
    registeredListeners.add(listener);
}

public void reset() {
    for (Listener listener : registeredListeners) {
        unregisterListenerSomewhere(listener);
    }

    registeredListeners.clear();
}

Ответы [ 3 ]

5 голосов
/ 10 апреля 2019

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

В зависимости от того, как он используется, возможно, стоит объявить более конкретный интерфейс для поля,Объявление значения List означает, что дубликаты разрешены и что порядок важен.Объявление значения Set означает, что дубликаты не допускаются и что порядок не имеет значения.Вы могли бы даже объявить поле, чтобы иметь определенный класс реализации, если есть что-то существенное в нем.Например, объявление его как LinkedHashSet означает, что дубликаты недопустимы, но этот порядок имеет значение .

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

public ??? getRegisteredListeners() {
    return ...
}

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

Большинство API Java SE возвращают Collection.Это обеспечивает достаточную степень абстрагирования от базовой реализации, но также предоставляет вызывающей стороне разумный набор операций.Вызывающая сторона может выполнить итерацию, получить размер, выполнить проверку содержимого или скопировать все элементы в другую коллекцию.

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

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

Обратите внимание, что если вы решите вернуть Collectionили Iterable, вам необходимо убедиться, что вы возвращаете неизменяемое представление или делаете защитную копию.В противном случае вызывающие могут изменить внутренние данные вашего класса, что, вероятно, приведет к ошибкам.(Да, даже Iterable может разрешить изменение! Подумайте о получении Iterator и вызове метода remove().) Если вы возвращаете Stream, вам не нужно об этом беспокоиться, так как вы можете '* Используйте 1033 * для изменения базового источника.

Обратите внимание, что я превратил ваш вопрос об объявлении поля в вопрос об объявлении возвращаемых методов.Существует идея «программы для интерфейса», которая довольно распространена в Java.По моему мнению, это не имеет большого значения для локальных переменных (именно поэтому обычно хорошо использовать var), и это не имеет большого значения для частных полей, так как те (почти) по определению влияют только на класс, в котором они 'объявлен.Однако принцип «программа к интерфейсу» очень важен для сигнатур API, поэтому вам нужно подумать о типах интерфейса.Частных полей, не так уж много.

(Последнее замечание: есть случай, когда вам нужно заботиться о типах частных полей, и именно тогда вы используете отражающую среду, которая напрямую управляет частными полями.В этом случае вам нужно думать об этих полях как об открытых - так же, как о типах возвращаемых методов - даже если они не объявлены public.)

3 голосов
/ 10 апреля 2019

Как и все вещи, это вопрос компромиссов. Есть две противоборствующие силы.

  • Чем более универсален тип, тем больше свободы в реализации. Если вы используете Collection, вы можете использовать ArrayList, HashSet или LinkedList, не затрагивая пользователя / абонента.

  • Чем более общий тип возвращаемого значения, тем меньше возможностей доступно для пользователя / абонента. A List обеспечивает поиск на основе индекса. A SortedSet позволяет легко получать смежные подмножества через headSet, tailSet и subSet. A NavigableSet обеспечивает эффективные методы O (log n) бинарного поиска. Если вы вернете Collection, ни один из них не будет доступен. Можно использовать только самые общие функции доступа.

Кроме того, подтипы гарантируют специальные свойства, которые Collection не имеют: Set s содержит уникальные предметы. SortedSet s отсортированы. List имеют заказ; они не неупорядоченные сумки предметов. Если вы используете Collection, то пользователь / вызывающая сторона не обязательно может предполагать, что эти свойства выполнены Они могут быть вынуждены защищать код и, например, обрабатывать дубликаты, даже если вы знаете, что дубликатов не будет.

Разумный процесс принятия решения может быть:

  1. Если O (1) индексированный доступ гарантирован, используйте List.
  2. Если элементы отсортированы и уникальны, используйте SortedSet или NavigableSet.
  3. Если уникальность элемента гарантирована, а порядок - нет, используйте Set.
  4. В противном случае используйте Collection.
2 голосов
/ 10 апреля 2019

Это действительно зависит от того, что вы хотите сделать с объектом коллекции.

Collection<String> cSet = new HashSet<>();
Collection<String> cList = new ArrayList<>();

Вот в этом случае, если вы хотите, вы можете сделать:

cSet = cList;

Но если вы делаетенапример:

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

вышеуказанная операция недопустима, хотя вы можете создать новый список с помощью конструктора.

 Set<String> set = new HashSet<>();
 List<String> list = new ArrayList<>();
 list = new ArrayList<>(set);

Таким образом, в основном в зависимости от использования вы можете использовать интерфейс Collection или Set.

...