emptyList() против emptySet(), есть ли какая-либо причина выбирать один над другим, если необходим экземпляр Collection?

#java #collections

#java #Коллекции

Вопрос:

В JDK есть Collection.emtpyList() и Collection.emptySet() . Оба сами по себе. Но иногда все, что нужно, это пустой, неизменяемый экземпляр Collection . Для меня нет причин выбирать один над другим, поскольку оба реализуют все операции Collection эффективным способом и с одинаковыми результатами. Тем не менее, каждый раз, когда мне нужна такая пустая коллекция, я размышляю, какую из них использовать в течение секунды из двух.

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

В ответе должна быть указана хотя бы одна причина, по которой предпочтение отдается одному из Collection.emtpyList() и Collection.emptySet() над другим в контексте, где они функционально эквивалентны.Ответ лучше, если указанная причина находится в верхней части этого списка:

  • Существует случай, когда система типов более довольна одним по сравнению с другим (например, вывод типа позволяет использовать более короткий код с одним, чем с другим).

  • Существует разница в производительности, возможно, в каком-то особом случае (например, если пустая коллекция передается в качестве аргумента некоторым статическим методам или методам экземпляра collection framework, таким как Collections.sort() или Collection.removeAll() ).

  • Выбор одного над другим «имеет больше смысла» в общем случае, если вы подумаете об этом.

Примеры, в которых возникает этот вопрос

Чтобы дать некоторый контекст, вот два примера, где мне нужна пустая, неизменяемая коллекция.

Это пример API, который позволяет создавать некоторый объект, необязательно указывая коллекцию объектов, которые используются при создании. Второй метод просто вызывает первый с пустой коллекцией:

 static void createObjectWithTheseThings(Collection<Thing> things) {
    ...
}

static void createObjectWithoutAnyThings() {
    createObjectWithTheseThings(Collections.emptyXXX());
}
  

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

 class Example {
    // Initialized to an empty collection.
    private Collection<T> containedThings = Collections.emptyXXX();

    ...
}
  

Комментарии:

1. Я думаю, вы не приняли бы » Collection.emptySet() экономит одно нажатие клавиши» в качестве ответа?

2. @a_horse_with_no_name — оба метода возвращают неизменяемые версии list / set . Где упоминается, что только emptyList() возвращает неизменяемый список, а emptySet() — нет?

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

4. @Duncan -В исходном коде комментарии, предоставленные для обоих методов (empty# ), одинаковы (и возвращаемые значения неизменяемы для обоих).. Мне просто интересно, почему a_horse_with_no_name упоминает то же самое только для emptyList() .

5. @ammoQ Если StackOverflow окончательно определит, что между ними нет разницы, я думаю, что я собираюсь основывать решение именно на этой разнице. 🙂

Ответ №1:

К сожалению, у меня нет ответа, который займет первое место в вашем списке приоритетов, но на вашем месте я бы остановился на

 Collections.emptySet
  
  • Вывод типа был вашим первым приоритетом, но я не знаю, может ли выбор / должен влиять на это, учитывая, что вы искали emptyCollection()

  • Во втором приоритете подумайте о любом api, который принимает коллекцию, которая работает по-разному (случайно / намеренно) на основе субинтерфейсов переданного конкретного объекта. Разве они с большей вероятностью не будут предлагать различную производительность в зависимости от конкретных реализаций (как в случае с ArrayList или LinkedList )? Пустой набор / список в любом случае не моделируются на каких-либо пустых структурах данных; они являются фиктивными реализациями — следовательно, никакой реальной разницы

  • На основе моделирования Java этих интерфейсов (что, по общему признанию, не идеально), коллекция очень похожа на Set . На самом деле я думаю, что методы почти точно такие же. Логически тоже все выглядит нормально, поскольку List является типом specific-sub, который добавляет дополнительные проблемы с упорядочением.

Теперь коллекция и набор выглядят очень похожими (с точки зрения Java), возникает вопрос. Если вы используете тип коллекции, ясно, что это не тот список, который вам нужен. Теперь вопрос в том, уверены ли вы, что не имеете в виду Set . Если вы этого не сделаете, то используете ли вы что-то вроде Bag (конечно, должны быть конкретные экземпляры, которые не являются пустыми в общей логике). Итак, если вас интересует, скажем, сумка, то не должен ли api Bag предоставлять метод emptyBag()? Просто интересно. кстати, я бы пока придерживался emptySet() 🙂

Комментарии:

1. Мне нравится ваше предложение о том, что набор больше похож на коллекцию , чем на список . Пакет , конечно, был бы еще более похожим, но, по моему опыту, коллекции, у которых нет порядка, обычно также не имеют повторяющихся элементов.

2. Концептуальные различия, похоже, единственные, которые могут быть выдвинуты, поэтому я придерживаюсь этого ответа. И не только emptySet() на один символ короче, чем emtpyList() , из-за симметрии это подразумевает предпочтение singleton() singletonList() , что экономит 4 символа! 😉

Ответ №2:

Для emptyXXX() это действительно не имеет никакого значения — поскольку они оба пустые (и они не поддаются модификации, поэтому они всегда остаются пустыми), это вообще не имеет значения. Они будут одинаково подходить для всех операций, предлагаемых коллекцией.

Взгляните на то, что Collections действительно дает вам там: специальные реализации (экземпляры являются общими для всех вызовов!). Все соответствующие операции являются фиктивными реализациями, которые либо возвращают постоянный результат, либо немедленно выбрасывают. Даже iterator() — это просто фиктивный объект без состояния.

Это не будет иметь никакого заметного значения вообще.

Редактировать: вы могли бы сказать, что для особого случая emptyList / Set они семантически и по сложности одинаковы на уровне интерфейса Collecton . Все операции, доступные в коллекции, реализуются emptySet / List как O (1) операции. И поскольку они следуют обоим контрактам, определенным Collection , они также семантически идентичны.

Комментарии:

1. EmptySet.add(E element) вероятно, на несколько циклов процессора быстрее, чем EmptyList.add(E element) ; не то, чтобы это имело значение, поскольку оба генерируют UnsupportedOperationException()

2. @ammoQ Как вы пришли к такому выводу?

3. @Feuermurmel Он проверил исходный код и увидел этот список.add(e) делегирует другой подписи, чтобы, наконец, вызвать исключение во время Set.add(e) напрямую выдает.

4. @Durandal: Точно. Извините, что не дал объяснения в моем первом комментарии; каким-то образом это потерялось где-то между моим разумом и моей клавиатурой.

Ответ №3:

Единственная ситуация, которую я могу представить, что это имеет значение, — это если код, который будет использовать ваш Collection , делает что-то вроде этого:

 Collection<T> collection = ...
List<T> asAList;
if (collection instanceof List) {
    asAList = (List<T>) collection;
} else {
    asAList = new ArrayList<T>(collection);
}
  

Очевидно, что в подобном случае вы хотели бы использовать emptyList() , в то время как если бы секретный целевой тип был a Set , вы бы хотели emptySet() .

В противном случае, с точки зрения того, что «имеет больше смысла», я согласен с логикой @ac3 в том, что generic Collection похож на Bag, а пустой неизменяемый набор и пустой неизменяемый пакет — это практически одно и то же. Однако человеку, который очень привык использовать неизменяемые списки, может показаться, что о них легче думать.