Есть ли веская причина использовать интерфейс Java Collection?

11

Я слышал аргумент, что вы должны использовать самый общий доступный интерфейс, чтобы вы не были привязаны к конкретной реализации этого интерфейса. Применяется ли эта логика к интерфейсам, таким как java.util.Collection ?

Я бы предпочел увидеть что-то вроде следующего:

List<Foo> getFoos()

или же

Set<Foo> getFoos()

вместо

Collection<Foo> getFoos()

В последнем случае я не знаю, с каким набором данных я имею дело, тогда как в первых двух случаях я могу сделать некоторые предположения о порядке и уникальности. Есть ли полезность java.util.Collection помимо того, что он является логическим родителем для наборов и списков?

Если вы сталкивались с кодом, который использовал Collection при выполнении обзора кода, как бы вы определили, является ли его использование оправданным, и какие предложения вы бы предложили для его замены более конкретным интерфейсом?

Fil
источник
3
Вы заметили в java.security.cert, что один тип возвращаемого значения Collection<List<?>>? Разговор о кодировании ужасов!
Макнейл
1
@Macneil Я не знаю, на какой класс вы ссылаетесь, но такой тип возврата действительно может быть весьма разумным. По сути, это говорит о том, что у вас есть коллекция (т.е. содержащая кучу вещей, у которых нет разумного упорядочения) списков (то есть содержащих вещи, которые имеют разумное упорядочение) объектов (то есть предметов, типы которых мы статически не знаем для по любой причине). Не кажется мне необоснованным.
Ноль3

Ответы:

13

Абстракции живут дольше реализаций

В общем, чем абстрактнее ваш дизайн, тем дольше он, вероятно, останется полезным. Таким образом, поскольку Collection более абстрактен в том, что он является подчиненным интерфейсом, тогда дизайн API на основе Collection с большей вероятностью останется полезным, чем дизайн на основе List.

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

Замечание об общем дизайне интерфейса

Поскольку вы заинтересованы в использовании интерфейса Collection с генериками, вам может быть полезно следующее. «Эффективная Java » Джошуа Блоха рекомендует следующий подход при разработке интерфейса, который будет опираться на дженерики: продюсеры расширяют, потребители супер

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

public void pushAll(Collection<? extends E> producerCollection) {}

Таким образом, тип ввода может быть E или любым подклассом E (E определяется как супер- и подкласс самого себя в языке Java).

И наоборот, универсальная коллекция, которая передается для использования данных, должна иметь такую ​​подпись:

public void popAll(Collection<? super E> consumerCollection) {}

Метод будет корректно работать с любым суперклассом E. В целом, использование этого подхода сделает ваш интерфейс менее удивительным для ваших пользователей, потому что вы сможете пройти Collection<Number>и Collection<Integer>правильно их обработать.

Гэри Роу
источник
6

CollectionИнтерфейс, и наиболее разрешающая форма Collection<?>, отлично подходит для параметров , которые вы принимаете. Основанный на использовании в самой библиотеке Java, он более распространен как тип параметра, чем как тип возвращаемого значения.

Что касается типов возврата, я думаю, что ваша точка зрения верна: если от людей ожидают, что они получат к ней доступ, они должны знать порядок (в смысле Big-O) выполняемой операции. Я бы перебрал Collectionвозвращаемый результат и добавил его в другую коллекцию, но было бы немного глупо вызывать containsего, не зная, является ли это операцией O (1), O (log n) или O (n). Конечно, просто потому, что у вас есть Setхэш-набор или отсортированный набор, но в какой-то момент вы сделаете предположение, что интерфейс был реализован разумно (и тогда вам нужно будет перейти к плану B, если ваше предположение показывается неверным).

Как упоминает Том, иногда вам нужно возвращать a Collectionдля поддержания инкапсуляции: вы не хотите, чтобы детали реализации просачивались, даже если вы могли бы вернуть что-то более конкретное. Или в случае, упомянутом Томом, вы можете вернуть более конкретный контейнер, но тогда вам придется его создать.

Макнейл
источник
2
Я думаю, что второй момент немного слаб. Вы не знаете, как будет работать коллекция, независимо от того, является ли она коллекцией или списком, поскольку они являются просто абстракциями. Если у вас нет конкретного финального класса, вы не можете сказать точно.
Марк Н
Кроме того, если вам известно только то, что что-то является коллекцией, вы не представляете, может ли оно содержать дубликаты или нет. Обращаем это внимание на то, что когда-то может быть уместным возвращать коллекцию, если у вас есть коллекция, которая не содержит дубликатов и не имеет значительного порядка (который, естественно, будет множеством), но где по какой-то веской причине, реализация метода возврата использует список. Вы не захотите возвращать список, потому что это подразумевает значительный порядок, но вы не можете вернуть набор, не пройдя через сложную процедуру его создания. Итак, вы возвращаете коллекцию.
Том Андерсон
@ Том: Хороший вопрос!
Макнейл
5

Я смотрю на это с совершенно противоположной точки зрения и спрашиваю:

Если вы столкнулись с кодом, который использовал List <> при проверке кода, как бы вы определили, является ли его использование оправданным?

Это довольно легко оправдать. Список используется, когда вам нужны некоторые функции, которые не предлагаются коллекцией. Если вам не нужна эта дополнительная функциональность - какое у вас обоснование? (И я не буду покупать, «Я бы предпочел увидеть это»)

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

Чтобы привести реальный пример. Скажем, я выполняю простой запрос к базе данных. ( SELECT id,name,rep FROM people WHERE name LIKE '%squared') Я собираю соответствующие данные, заполняю объекты Person и помещаю их в PersonList)

  • Нужен ли мне доступ по индексу? - было бы бессмысленно. Там нет сопоставления между индексом и ID.
  • Нужно ли вставлять в индекс? - Нет, СУБД сама решит, куда ее поставить, если я вообще добавлю.

Итак, какое оправдание я буду иметь для этих дополнительных методов? (который в любом случае не будет реализован в моем PersonList)

Марк Н
источник
Честная оценка. Я предполагаю, что мой вопрос относится к конкретному случаю, когда во время анализа кода я продолжаю видеть DAO, которые возвращают Коллекции, но я знаю, что эти вызовы DAO всегда будут возвращать наборы сущностей; Я утверждаю, что в этих случаях возвращаемый тип указывает на уникальность, и эта информация полезна для тех, кто должен использовать этот метод (например, мне не нужно проверять дубликаты).
Fil
Если вы запросили БД, сравнение двух результирующих объектов с помощью equals () не должно приводить к значению true, поэтому вам потребуется другой способ сравнения объектов для дублирования (например, имеют ли они одинаковое имя, одинаковый идентификатор, обе?). Вам нужно будет рассказать своему DAO, как их сравнивать, если он хочет удалить дубликаты сам - но так как вы - пользователь, который решает, существуют ли дубликаты - проще сделать это с коллекцией из вызывающего кода. (Чтобы избежать большего количества уровней абстракции, сообщите ДАО, как выполнять все возможные проверки на равенство на планете.)
Марк Н
Согласен, но мы используем Hibernate и гарантируем, что наши сущности реализуют equals (). Поэтому, когда DAO возвращает сущности, мы можем очень быстро создать новый HashSet (). AddAll (results) и вернуть его обратно вызываемому методу.
Fil