Использовать интерфейс или тип для определения переменных в Java?
ArrayList aList = new ArrayList();
List aList = new ArrayList();
В чем разница между этими двумя и что лучше использовать и почему?
8 ответов
List<T>
это интерфейс, где ArrayList<T>
это класс, и этот класс реализует List<T>
интерфейс.
Я бы предпочел 2-ую форму, которая является более общей, т.е. если вы не используете методы, специфичные для ArrayList<T>
Вы можете объявить его тип в качестве интерфейса List<T>
тип. Со 2-й формой легче будет изменить реализацию с ArrayList<T>
в другой класс, который реализует List<T>
интерфейс.
РЕДАКТИРОВАТЬ: Как многие пользователи SO прокомментировали обе формы могут быть аргументами для любого метода, который принимает List<T>
или же ArrrayList<T>
, Но когда я объявляю метод, я бы предпочел интерфейс:
void showAll(List <String> sl) ...
и использовать:
void showAllAS(ArrayList <String> sl) ...
только когда мой метод использует метод, специфичный для ArrayList<T>
, лайк ensureCapacity()
,
Ответ с информацией, которую мы должны использовать напечатано List<T>
вместо просто List
это очень хорошо (конечно, если мы не используем древнюю Java).
List - это интерфейс, а ArrayList - реализация этого интерфейса.
Второе лучше, потому что это означает, что вы можете изменить свой ArrayList для другой реализации List позже без необходимости изменять остальную часть вашего приложения. Возможно, вы захотите сделать это из соображений производительности или из-за других аспектов поведения реализации List, которую вы выбрали / выберете.
Оба устарели начиная с Java 1.5.
Так должно быть:
List<String> list = new ArrayList<String>();
// or whatever data type you are using in your list
Пожалуйста, прочитайте " Эффективная Java " Джошуа Блоха, особенно эти два пункта:
- 23: не используйте необработанные типы в новом коде (это даже доступно онлайн)
- 52: ссылаются на объекты по их интерфейсам
Кстати, если вы используете Guava, у вас есть фабричный метод для создания ArrayList, поэтому вам не нужно повторять параметр type:
List<String> list = Lists.newArraylist();
Все эти ответы повторяют некоторые догмы, которые они где-то читали.
Объявление переменной и оператор инициализации, Type x = new Constructor();
, безусловно, является частью реализации деталей. Это не часть публичного API (если только public final
, но List
изменчиво, так что это неуместно)
В качестве детали реализации, кого вы пытаетесь обмануть своими абстракциями? Лучше, чтобы тип был как можно более конкретным. Это список массивов или связанный список? Должно ли это быть потокобезопасным или нет? Выбор важен для вашей реализации, вы тщательно выбрали конкретный список. Затем вы объявляете это просто List
как будто это не имеет значения, и вам все равно?
Единственная законная причина объявить это List
это мне лень набирать. Это также относится к аргументу, что если мне нужно перейти в другой список, то у меня меньше места для изменения.
Эта причина допустима только тогда, когда область действия переменной мала, и вы можете увидеть все ее применения одним взглядом. В противном случае сохраните наиболее конкретный тип, чтобы его производительность и семантические характеристики проявлялись во всем коде, использующем переменную.
Это причуды Java, из-за ограниченного вывода типа и ООП доктрины. Для локальных переменных это в основном стиль; если вам нужна определенная особенность подтипа, используйте подтип (примеры ниже), но в остальном можно использовать любой из них.
Стиль Java состоит в том, чтобы использовать супертип, применять интерфейсы даже внутри тела реализаций и обеспечивать согласованность с видимыми типами (Эффективное Java, 2-е издание: Элемент 52: Ссылка на объекты по их интерфейсам). В языках с большим количеством выводов типов, таких как C++/C#/Go/ и т. Д., Вам не нужно явно указывать тип, и локальная переменная будет иметь определенный тип.
Для видимых типов (public или protected: поля или параметры и возвращаемое значение методов) почти всегда требуется использовать наиболее общий тип: интерфейс или абстрактный класс для обеспечения большей гибкости (Эффективная Java: пункт 40: Тщательно подписывайте методы разработки)). Однако для типов, которые не видны (частные или закрытые для пакета члены или локальные переменные), можно использовать либо (это просто стиль), а иногда необходимо использовать более конкретные типы, включая конкретные классы.
См. Эффективная Java для стандартных рекомендаций; личные мысли следуют.
Причина использования более общих типов, даже если они не видны, заключается в уменьшении шума: вы заявляете, что вам нужен только более общий тип. Использование общих типов на элементах, которые не видны (например, закрытые методы), также уменьшает отток пользователей при изменении типов. Однако это не относится к локальным переменным, где в любом случае просто меняется одна строка: ConcreteFoo foo = new ConcreteFoo();
в OtherConcreteFoo foo = new OtherConcreteFoo();
,
Случаи, когда вам нужен подтип, включают:
- Вам нужны члены, присутствующие только в подтипе, например:
- некоторая особенность реализации, например
ensureCapacity
заArrayList<T>
- (часто встречается в тестовом коде) некоторый член фальшивого класса, например (гипотетически)
FakeFileSystem#createFakeFile
,
- некоторая особенность реализации, например
- Вы полагаетесь на поведение подтипа, особенно в переопределениях методов супертипа, таких как:
- имеющий более общий тип параметра,
- более конкретный тип возврата, или
- бросать более конкретные или меньше типов исключений.
Как пример последнего, см. Должен ли я закрыть StringReader?: StringReader.html#close переопределяет Reader.html#close и не выбрасывает IOException
так что используя StringReader
вместо Reader
для локальной переменной означает, что вам не нужно обрабатывать исключение, которое на самом деле не может произойти, и значительно сокращает шаблон.
Я предпочитаю второй в большинстве случаев, потому что он означает, что вы не используете ничего конкретного в API ArrayList, и, если вам потребуется позже, вы можете заменить любой другой тип List без необходимости изменения какого-либо кода, кроме первой строки.
Если вы используете Java 1.4 или более раннюю версию, я бы использовал второй вариант. Всегда лучше объявлять ваши поля как можно более обобщенно, на случай, если позже вам понадобится преобразовать их во что-то другое, например, в вектор и т. Д.
С 1.5 я бы пошел со следующим
List<String> x = new ArrayList<String>();
Это дает вам немного безопасности типов. Список 'x' может добавить объект String, и все. Когда вы получаете элемент из списка 'x', вы можете рассчитывать на то, что строка вернется. Это также помогает, удаляя ненужное приведение, которое может затруднить чтение кода, когда вы вернетесь через 6 месяцев назад и попытаетесь вспомнить, что делает ваш код. Ваш компилятор /IDE поможет вам вспомнить, какой тип должен входить в Список 'x', отображая ошибку, если вы пытаетесь добавить любой другой тип объекта.
Если вы хотите добавить несколько типов объектов в список, вы можете добавить аннотацию для подавления ошибки компиляции.
@SuppressWarnings("unchecked")
List y = new ArrayList();
Мне известен хотя бы один случай, когда объявление переменной с интерфейсом не работает. Когда вы хотите использовать отражение.
Я сделал исправление ошибки в некотором коде, где я объявил переменную как Map<String, MyObject>
и назначил ему экземпляр HashMap<String, MyObject>
, Эта переменная использовалась в качестве параметра в вызове метода, доступ к которому осуществлялся посредством отражения. Проблема в том, что рефлексия пыталась найти метод с HashMap
подпись а не заявленная Map
подпись. Так как не было никакого метода с HashMap
в качестве параметра я не смог найти метод по отражению.
Map<String, Object> map = new HashMap<String, Object>();
public void test(Map<String, Object> m) {...};
Method m = this.getClass().getMethod("test", new Class<?>[]{map.getClass()});
Не найдете метод, который использует интерфейс. Если вы делаете другую версию теста, которая использует HashMap
вместо этого тогда это будет работать - но теперь вы вынуждены объявить вашу переменную с конкретным классом, а не с более гибким интерфейсом...