Почему Enumeration преобразуется в ArrayList, а не List в java.utils?

Есть ли веская причина, что метод Collections.list() в пакете java.utils возвращает ArrayList<T> вместо List<T>?

Очевидно, что ArrayList - это List, но у меня создается впечатление, что обычно рекомендуется возвращать тип интерфейса вместо типа реализации.

Ответ 1

Отказ от ответственности: я не автор JDK.

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

LinkedList и ArrayList очень разные, с точки зрения производительности, для различных операций. Например, удаление первого элемента ArrayList равно O(n), но удаление первого элемента a LinkedList равно O(1).

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

Наконец, основная причина для кода для интерфейса над реализацией - если вы считаете, что реализация изменится. Авторы JDK, вероятно, полагают, что они никогда не изменят этот метод; он никогда не вернет a LinkedList или Collections.UnmodifiableList. Однако, в большинстве случаев, вы, вероятно, все еще будете делать:

List<T> list = Collections.list(enumeration);

Ответ 2

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

Я цитирую API Design из Учебников Java ™:

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

.. В каком-то смысле возвращаемые значения должны иметь противоположное поведение входных параметров: лучше всего возвращать наиболее специфический применимый интерфейс коллекции, а не самый общий. Например, если вы уверены, что всегда будете возвращать SortedMap, вы должны предоставить соответствующему методу тип возврата SortedMap, а не Map. SortedMap экземпляры занимают больше времени, чем обычные экземпляры Map, а также более мощные. Учитывая, что ваш модуль уже вложил время на создание SortedMap, имеет смысл дать пользователю доступ к его увеличенной мощности. Кроме того, пользователь сможет передать возвращаемый объект методам, требующим SortedMap, а также те, которые принимают любой Map.

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

В любых других случаях он все еще действителен для записи:

List<T> list = Collections.list(e);

Ответ 3

Функции, которые возвращают "эксклюзивное владение" вновь созданных изменяемых объектов, часто должны быть наиболее конкретным типом практического; те, которые возвращают неизменяемые объекты, особенно если они могут быть разделены, должны часто возвращать менее конкретные типы.

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

В последнем случае тот факт, что объект неизменен, означает, что функция может идентифицировать альтернативный тип, который может делать все, что может сделать более сложный тип, с учетом его точного содержимого. Например, интерфейс Immutable2dMatrix может быть реализован классом ImmutableArrayBacked2dMatrix и ImmutableDiagonal2dMatrix. Функция, которая должна возвращать квадрат Immutable2dMatrix, может решить вернуть экземпляр ImmutableDiagonalMatrix, если все элементы с главной диагональю будут равны нулю или ImmutableArrayBackedMatrix, если нет. Первый тип займет гораздо меньше места для хранения, но получателю не стоит заботиться о различии между ними.

Возврат Immutable2dMatrix, а не конкретный ImmutableArrayBackedMatrix позволяет коду выбирать тип возврата, основанный на том, что содержит массив; это также означает, что если код, который должен возвращать массив, имеет подходящую реализацию Immutable2dMatrix, он может просто вернуть это, вместо того, чтобы создавать новый экземпляр. Оба эти фактора могут быть основными "победами" при работе с неизменяемыми объектами.

Однако при работе с изменяемыми объектами никто не играет роль. Тот факт, что изменяемый массив может не иметь каких-либо элементов с главной диагонали при его создании, не означает, что у него никогда не будет таких элементов. Следовательно, хотя a ImmutableDiagonalMatrix является фактически подтипом a Immutable2dMatrix, a MutableDiagonalMatrix не является подтипом a Mutable2dMatrix, так как последний может принимать магазины с главной диагонали, в то время как первый не может. Кроме того, хотя неизменяемые объекты часто могут и должны использоваться совместно, изменяемые объекты вообще не могут. Функция, запрашиваемая для новой изменчивой коллекции, инициализированной определенным контентом, должна создать новую коллекцию, независимо от того, соответствует ли ее хранилище поддержки запрашиваемому типу.

Ответ 4

Наверху небольшой накладные расходы при вызове методов var интерфейс, а не непосредственно на объект.

Эти накладные расходы часто составляют не более 1 или 2 инструкций процессора. Накладные расходы на вызов метода еще ниже, если JIT знает, что метод является окончательным. Это не поддается измерению для большинства кодов, которые вы и я правильно, но для низкоуровневых методов в java.utils можно использовать в некотором коде, где это проблема.

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

Понятно, что авторы java.utils не знают, что делает программное обеспечение, которое вызывает Collections.list(), с результатом и не может повторно протестировать это программное обеспечение, если они меняют имплантацию Collections.list(), Поэтому они не собираются изменять имплантацию Collections.list(), чтобы возвращать другой тип List, даже если система типов допустила это!

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

Поэтому два набора компромиссов очень разные.