Использовать интерфейс или тип для определения переменной в java?

ArrayList aList = new ArrayList();

List aList = new ArrayList();

Какая разница между этими двумя, и что лучше использовать и почему?

Ответ 1

List<T> является интерфейсом, где ArrayList<T> является классом, и этот класс реализует интерфейс List<T>.

Я бы предпочел вторую форму, которая является более общей, т.е. если вы не используете методы, специфичные для ArrayList<T>, вы можете объявить ее тип как тип интерфейса List<T>. Со второй формой будет проще изменить реализацию из ArrayList<T> в другой класс, реализующий интерфейс List<T>.

EDIT: Как многие пользователи SO прокомментировали обе формы, могут быть аргументы для любого метода, принимающего List<T> или ArrrayList<T>. Но когда я объявляю метод, я бы предпочел интерфейс:

 void showAll(List <String> sl) ...

и используйте:

 void showAllAS(ArrayList <String> sl) ...

только когда мой метод использует метод, специфичный для ArrayList<T>, например ensureCapacity().

Ответ с информацией, которую мы должны использовать набранный List<T>, а не только List, очень хорош (конечно, если мы не используем древнюю Java).

Ответ 2

Список - это интерфейс, а ArrayList - реализация этого интерфейса.

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

Ответ 3

Обе версии устарели, поскольку Java 1.5.

Это должно быть:

List<String> list = new ArrayList<String>();
// or whatever data type you are using in your list

Пожалуйста, прочитайте Эффективная Java от Джошуа Блоха, особенно эти два элемента:

  • 23: Не используйте исходные типы в новом коде (это даже доступно в Интернете)
  • 52: обратитесь к объектам по их интерфейсы

Кстати, если вы используете Guava, вы a factory для построения ArrayList, поэтому вам не нужно повторять параметр типа:

List<String> list = Lists.newArraylist();

Ответ 4

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

Ответ 5

Все эти ответы - одни и те же слова из какой-то догмы, которую они где-то читали.

Заявление об объявлении переменной и инициализация Type x = new Constructor();, безусловно, является частью детализации реализации. Он не является частью общедоступного API (если он не является public final, но List изменен, поэтому он не подходит)

Как деталь реализации, кто вы пытаетесь обмануть свои абстракции? Лучше сохранить тип как можно более конкретным. Это список массивов или связанный список? Должен ли он быть потокобезопасным или нет? Выбор важен для вашей реализации, вы тщательно выбрали конкретный список impl. Тогда вы заявляете это как просто List, как будто это не имеет значения, и вам все равно?

Единственная законная причина объявить ее как List - я слишком ленив для ввода. Это также охватывает аргумент, что, если мне нужно перейти на другой список, у меня есть еще одно место для изменения.

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

Ответ 6

Это причуда Java из-за ограниченного типа вывода и доктрины ООП. Для локальных переменных это в основном стиль; если вам нужна какая-то конкретная особенность подтипа, используйте подтип (примеры ниже), но в остальном это прекрасно использовать.

Java-стиль должен использовать супертип, чтобы обеспечить взаимодействие интерфейсов даже в пределах совокупности реализаций, а также для согласованности с видимыми типами (эффективное Java 2nd Edition: пункт 52: обратитесь к объектам по их интерфейсам). В языках с большим числом типов, таких как С++/С#/Go/etc., Вам не нужно явно указывать тип, а локальная переменная будет иметь определенный тип.

Для видимых типов (общедоступных или защищенных: полей или параметров и возвращаемого значения методов) вы почти всегда хотите использовать наиболее общий тип: интерфейс или абстрактный класс, чтобы обеспечить лучшую гибкость (эффективная 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 для локальная переменная означает, что вам не нужно обрабатывать исключение, которое на самом деле не может произойти, и значительно уменьшает шаблон.

Ответ 7

Если вы используете Java 1.4 или ранее, я бы использовал второй. Всегда лучше объявлять свои поля как можно больше, если вам нужно сделать что-то еще позже, например Vector, и т.д.

Начиная с 1.5, я бы пошел со следующим

List<String> x = new ArrayList<String>();

Это дает вам немного безопасности типов. Список "x" может добавить объект String и его. Когда вы получаете элемент из списка "x" , вы можете рассчитывать на то, что String вернется. Это также помогает, удаляя ненужное кастинг, что может затруднить чтение кода, когда вы вернетесь через 6 месяцев и попытайтесь вспомнить, что делает ваш код. Ваш компилятор /IDE поможет вам вспомнить, какой тип должен быть включен в список "x" , отображая ошибку, если вы попытаетесь добавить любой другой тип объекта.

Если вы хотите добавить несколько типов объектов в список, вы можете добавить аннотацию для подавления ошибки компиляции

@SuppressWarnings("unchecked")
List y = new ArrayList();

Ответ 8

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

Я исправил ошибку в некотором коде, где я объявил переменную как 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, а затем она будет работать, но теперь вы вынуждены объявлять свою переменную конкретным классом, а не более гибким интерфейсом...