Почему интерфейс для 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.