Почему интерфейс для Java-класса должен быть предпочтительным?

PMD сообщит о нарушении для:

ArrayList<Object> list = new ArrayList<Object>();

Нарушение было следующим: "Избегайте использования типов реализации, таких как ArrayList; используйте вместо этого интерфейс".

Следующая строка исправит нарушение:

List<Object> list = new ArrayList<Object>();

Почему последний с List использоваться вместо ArrayList?

8 ответов

Решение

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

Это даже хорошая идея следовать этой практике при написании собственных API. Если вы это сделаете, позже вы обнаружите, что проще добавить модульные тесты в ваш код (с использованием методов Mocking) и изменить базовую реализацию, если это потребуется в будущем.

Вот хорошая статья на эту тему.

Надеюсь, поможет!

Это предпочтительнее, потому что вы отделяете свой код от реализации списка. Использование интерфейса позволяет легко изменить реализацию, в данном случае ArrayList, на другую реализацию списка, не меняя остальную часть кода, если он использует только методы, определенные в List.

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

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

Для интереса я провел эксперимент, который сгенерировал десять миллиардов последовательных обращений к ArrayList длиной 1 миллион. На моем MacBook с частотой 2,4 ГГц доступ к ArrayList через интерфейс List в среднем занимал 2,10 секунды, а при объявлении его типа ArrayList - в среднем 1,67 секунды.

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

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

Это контрастирует, скажем, с коллекцией и списком, которые являются разными вещами (список подразумевает сортировку, а коллекция - нет).

Почему последний с List должен использоваться вместо ArrayList?

Это хорошая практика: программа для интерфейса, а не реализации

Заменяя ArrayList с List, ты можешь измениться List реализация в будущем, как показано ниже, в зависимости от вашего бизнес-использования.

List<Object> list = new  LinkedList<Object>(); 
/* Doubly-linked list implementation of the List and Deque interfaces. 
 Implements all optional list operations, and permits all elements (including null).*/

ИЛИ ЖЕ

List<Object> list = new  CopyOnWriteArrayList<Object>(); 
/* A thread-safe variant of ArrayList in which all mutative operations
 (add, set, and so on) are implemented by making a fresh copy of the underlying array.*/

ИЛИ ЖЕ

List<Object> list = new  Stack<Object>(); 

/* The Stack class represents a last-in-first-out (LIFO) stack of objects.*/

ИЛИ ЖЕ

несколько других List конкретная реализация.

List Интерфейс определяет контракт и конкретную реализацию List может быть изменено. Таким образом, интерфейс и реализация слабо связаны.

Связанный вопрос SE:

Что значит "программировать на интерфейс"?

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

class Counter {
    static int sizeOf(List<?> items) {
        return items.size();
    }
}

В этом случае требуется использование интерфейса. Потому что я хочу посчитать размер каждой возможной реализации, включая мою собственную. class MyList extends AbstractList<String>...,

Свойства ваших классов / интерфейсов должны быть представлены через интерфейсы, потому что это дает вашим классам контракт на использование, независимо от реализации.

Тем не мение...

В объявлениях локальных переменных не имеет смысла делать это:

public void someMethod() {
List theList = new ArrayList();
//do stuff with the list
}

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

Даже для локальных переменных помогает использование интерфейса над конкретным классом. Вы можете в конечном итоге вызвать метод, который находится за пределами интерфейса, и тогда будет сложно изменить реализацию списка, если это необходимо. Кроме того, лучше всего использовать наименее определенный класс или интерфейс в объявлении. Если порядок элементов не имеет значения, используйте коллекцию вместо списка. Это дает вашему коду максимальную гибкость.

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

Один интерфейс также имеет множественную реализацию. На основе сценария система будет работать с разными сценариями (реализация интерфейса).

дайте мне знать, если вам нужно больше объяснений.

Интерфейс часто лучше представлен в представлении отладчика, чем конкретный класс.

Spring Framework очень хорошо применяет концепцию интерфейсов - http://www.springframework.org/

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

Изучение Spring иллюстрирует преимущества программирования на основе интерфейса в Java.

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