UnsupportedOperationException в интерфейсах платформы Java-коллекций

12

Просматривая Java Collections Framework, я заметил, что довольно много интерфейсов имеют комментарий (optional operation). Эти методы позволяют реализовать классы, UnsupportedOperationExceptionесли они просто не хотят реализовывать этот метод.

Примером этого является addAllметод в Set Interface.

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

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

и

Интерфейс - это описание действий, которые объект может выполнять ... например, когда вы нажимаете выключатель света, свет включается, вам все равно, как, просто так и происходит. В объектно-ориентированном программировании интерфейс - это описание всех функций, которые должен иметь объект, чтобы быть «Х».

и

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

Какой смысл в интерфейсе?

Что такое интерфейсы?

Интерфейс + расширение (mixin) против базового класса

Учитывая, что цель интерфейсов состоит в том, чтобы определить контракт и сделать ваши зависимости слабо связанными, разве некоторые методы не бросают своего UnsupportedOperationExceptionрода поражение цели? Это значит, что я больше не могу пройти Setи просто использовать addAll. Скорее, я должен знать, какую реализацию Setя прошел, чтобы я мог знать, могу ли я использовать addAllили нет. Это кажется довольно бесполезным для меня.

Так в чем смысл UnsupportedOperationException? Это просто компенсирует устаревший код, и им нужно очистить свои интерфейсы? Или у меня есть более чувственная цель, которую мне не хватает?

MirroredFate
источник
Я не знаю, какой JRE вы используете, но мой Oracle версии 8 не определяет addAllв HashSet. Он сдвинуто реализации по умолчанию , в AbstractCollectionкотором большинство , конечно же , не бросать UnsupportedOperationException.
@ Снеговик Ты прав. Я скучаю по документам. Я отредактирую свой вопрос.
MirroredFate
1
Мне нравится запускать Eclipse и смотреть на исходный код, ориентируясь на ссылки и определения кода, чтобы убедиться, что я правильно понял. Пока JRE связана с src.zipним, он прекрасно работает. Это помогает точно знать, какой код JRE иногда выполняет, и не откладывать на JavaDoc, который может быть немного многословным.

Ответы:

12

Посмотрите на следующие интерфейсы:

Все эти интерфейсы объявляют мутирующие методы как необязательные. Это неявно документирует тот факт, что класс Collections может возвращать реализации тех интерфейсов, которые являются неизменяемыми: то есть эти дополнительные операции мутации гарантированно завершатся неудачно. Однако в соответствии с контрактом в JavaDoc все реализации этих интерфейсов должны разрешать операции чтения. Это включает в себя «нормальные» реализации, такие как HashSetи LinkedListнеизменные оболочки в Collections.

Контраст с интерфейсами очереди:

Эти интерфейсы не определяют никаких дополнительных операций: очередь, по определению, предназначена для предложения и опроса элементов способом FIFO. Неизменная очередь примерно так же полезна, как и машина без колес.


Одна распространенная идея, которая часто возникает, - это иметь иерархию наследования, которая имеет как изменяемые, так и неизменные объекты. Однако все они имеют недостатки. Сложность мутит воду, фактически не решая проблему.

  • Гипотетический Setможет иметь операции чтения, а подынтерфейс MutableSetможет иметь операции записи. Лисков говорит нам, что MutableSetзатем можно передать все, что нужно Set. Сначала это звучит нормально, но рассмотрим метод, который ожидает, что базовый набор не будет изменен во время чтения: два потока могут использовать один и тот же набор и нарушать неизменяемый инвариант набора. Это может вызвать проблему, например, если метод читает элемент из набора дважды, и это происходит в первый раз, но не во второй раз.

  • Setне может иметь прямых реализаций, вместо этого иметь MutableSetи ImmutableSetкак подынтерфейсы, которые затем используются для реализации классов. Это имеет ту же проблему, что и выше: в какой-то момент в иерархии интерфейс имеет конфликтующие инварианты. Один говорит, что этот набор должен быть изменяемым, а другой говорит, что этот набор не может измениться.

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

  • Мы могли бы иметь единственный интерфейс и позволить реализациям генерировать исключения, если метод не применим к нему. Это путь, по которому пошла Java, и он имеет смысл. Количество интерфейсов сведено к минимуму, и нет никаких инвариантов изменчивости, потому что документированный интерфейс не дает никаких гарантий относительно изменчивости в любом случае . Если требуется неизменный инвариант, используйте обертки в Collections. Если метод не должен изменять коллекцию, просто не меняйте его. Недостатком является то, что метод не может гарантировать, что коллекция не изменится в другом потоке, если ему будет предоставлена ​​коллекция извне, но в любом случае это касается вызывающего метода (или его вызывающего метода).


Связанное чтение: Почему Java 8 не включает неизменяемые коллекции?

Сообщество
источник
1
Но если методы являются необязательными, какое место они занимают в интерфейсе? Не должен ли быть отдельный интерфейс, содержащий дополнительные методы, например MutableCollection?
MirroredFate
Нет. Нет никакого способа иметь изменяемые и неизменные объекты в одной иерархии каким-либо значимым образом. Был недавний вопрос, на котором была хорошая диаграмма, показывающая сложность и объяснение того, почему это плохая идея, но она удалена. Может быть, кто-то знает вопрос, чтобы помочь объяснить это, я не могу ничего найти. Но я уточню свой ответ, чтобы объяснить немного.
Это своего рода широкое утверждение об неизменных очередях. Я использовал один всего пару дней назад, чтобы решить эту проблему .
Карл Билефельдт
@ Снеговик Но это, кажется, показывает, что изменчивые и неизменные объекты противоположны друг другу. Я думаю, что неизменяемые объекты - это на самом деле просто объекты, не имеющие способности мутировать. Честно говоря, то, как оно сейчас, является более сложным и запутанным, потому что вы должны выяснить, что является изменчивой реализацией, а что нет. Мне кажется, единственная разница между размещением всех методов в одном интерфейсе, а не разделением изменяемых методов, заключается в ясности.
MirroredFate
@MirroredFate читайте мои последние изменения.
2

Это в основном ЯГНИ. Все конкретные коллекции в стандартной библиотеке являются изменяемыми, реализуя или наследуя необязательные операции. Они не заботятся о неизменяемых коллекциях общего назначения, равно как и подавляющее большинство разработчиков Java. Они не собираются создавать целую иерархию интерфейса только для неизменяемых коллекций, а затем не включают никаких реализаций.

С другой стороны, есть несколько значений специального назначения или «виртуальных» коллекций, которые могут быть очень полезны как неизменяемые, такие как пустой набор и nCopies . Кроме того, существуют сторонние неизменяемые коллекции (например, Scala), которые могут вызывать существующий код Java, поэтому они оставляют возможность открывать неизменяемые коллекции наименее разрушительным образом.

Карл Билефельдт
источник
Хорошо, это имеет смысл. Тем не менее, похоже, что он подходит к проблеме с неправильной стороны. Почему бы не начать с определения неизменяемых интерфейсов коллекции, а затем определить изменяемые интерфейсы для реализаций изменяемой коллекции?
MirroredFate