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


12

Переглядаючи рамки колекцій Java, я помітив, що досить багато інтерфейсів мають коментар (optional operation). Ці методи дозволяють реалізовувати класи, UnsupportedOperationExceptionякщо вони просто не хочуть реалізувати цей метод.

Прикладом цього є addAllметод у Set Interface.

Тепер, як зазначено в цій серії питань, інтерфейси є визначальним договором того, що використання може очікувати.

Інтерфейси важливі, оскільки вони відокремлюють те, що робить клас від того, як це робить. Договір, що визначає, на що клієнт може розраховувати, залишає розробника вільним для його реалізації будь-яким способом, який він обрав, доки він підтримує контракт.

і

Інтерфейс - це опис дій, які може виконувати об’єкт ... наприклад, коли ви перемикаєте перемикач світла, світло вимикається, вам не байдуже, як це відбувається. В об'єктно-орієнтованому програмуванні інтерфейс - це опис усіх функцій, які повинен мати об'єкт, щоб бути "X".

і

Я думаю, що підхід на основі інтерфейсу значно приємніший. Потім ви зможете добре знущатися зі своїх залежностей, і все в основному менш щільно пов'язане.

У чому сенс інтерфейсу?

Що таке інтерфейси?

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

Враховуючи, що мета інтерфейсів полягає у визначенні контракту та узалежненні ваших залежностей, чи не маючи якихось методів нанести певну UnsupportedOperationExceptionпоразку меті? Це означає, що я більше не можу передаватись Setта просто користуватися addAll. Швидше, я повинен знати, яку реалізацію Setя пройшов, тому я можу знати, чи можу я використовувати, addAllчи ні. Мені це здається зовсім нікчемним.

Тож у чому сенс UnsupportedOperationException? Це просто заповнення застарілого коду, і їм потрібно очистити свої інтерфейси? Або це має більш чуттєве призначення, яке мені не вистачає?


Я не знаю, який саме JRE ви використовуєте, але моя версія Oracle 8 не визначається addAllв HashSet. Він відкладає до типової реалізації, в AbstractCollectionяку більшість точно не кидається UnsupportedOperationException.

@Snowman Ви маєте рацію. Я прочитав документи. Я відредагую своє запитання.
MirroredFate

1
Мені подобається запускати Eclipse і дивитися на вихідний код, підстрибуючи навколо посилань та визначень коду, щоб переконатися, що я маю це правильно. Поки JRE пов'язаний з src.zipцим, він працює чудово. Це допомагає точно знати, який код інколи працює JRE, а не відкладати на JavaDoc, що може бути трохи багатослівним.

Відповіді:


12

Подивіться на наступні інтерфейси:

Усі ці інтерфейси оголошують способи мутування як необов'язкові. Це неявно підтверджує той факт, що клас Collections здатний повертати реалізацію тих інтерфейсів, які незмінні: тобто ці необов'язкові операції з мутації гарантовано завершені. Однак відповідно до договору в JavaDoc всі реалізації цих інтерфейсів повинні дозволяти операціям читання. Сюди входять "звичайні" реалізації, такі як HashSetта LinkedList, а також незмінні обгортки в Collections.

Контраст з інтерфейсами черги:

Цей інтерфейс не вказує жодних необов'язкових операцій: черга, за визначенням, призначена для пропонування та опитування елементів FIFO. Незмінна черга настільки ж корисна, як і машина без коліс.


Однією загальною ідеєю, яка виникає неодноразово, є наявність ієрархії успадкування, яка має як змінні, так і незмінні об'єкти. Однак у всіх цих є вади. Складність замулює води, фактично не вирішуючи проблеми.

  • Гіпотетичний Setможе мати операції зчитування, а підінтерфейс MutableSetможе мати операції запису. Лісков каже нам, що MutableSetпотім можна передавати все, що потрібно Set. Спочатку це звучить нормально, але розгляньте метод, який очікує, що базовий набір не буде змінено під час зчитування: можливо, два потоки можуть використовувати один і той же набір і порушувати інваріант множини, що не змінюється. Це може спричинити проблеми, наприклад, якщо метод зчитує елемент із набору двічі, і він там вперше, але не вдруге.

  • Setне може мати прямих реалізацій, натомість мати MutableSetі ImmutableSetяк підінтерфейси, які потім використовуються для реалізації класів. Це те саме питання, що і вище: в якийсь момент ієрархії інтерфейс має конфліктні інваріанти. Один каже, що "цей набір повинен бути змінним", а другий говорить, що "цей набір не може змінитися".

  • Можливі дві абсолютно окремі ієрархії для змінних та незмінних структур даних. Це додає тонни додаткової складності, тому що виходить дуже незначний виграш. Це також має специфічну слабкість методів, які не переймаються мутабельністю (наприклад, я просто хочу повторити список), тепер повинні підтримувати два окремих інтерфейси. Оскільки Java введена статично, це означає додаткові методи обробки обох ієрархій інтерфейсу.

  • Ми можемо мати єдиний інтерфейс і дозволити реалізації реалізувати винятки, якщо метод не застосовується до нього. Це шлях, який пройшла Ява, і це має найбільш сенс. Кількість інтерфейсів зведено до мінімуму, і інваріантів мутаційності немає, оскільки документований інтерфейс не дає жодних гарантій щодо змінності . Якщо потрібен інваріант незмінності, використовуйте обгортки Collections. Якщо методу не потрібно змінювати колекцію, просто не змінюйте її. Недолік методу не може гарантувати, що колекція не зміниться в іншому потоці, якщо вона надається колекцією ззовні, але це все одно викликає занепокоєння методом виклику (або його методом виклику).


Пов’язане читання: Чому Java 8 не включає незмінні колекції?


1
Але якщо методи необов’язкові, яке місце вони займають в інтерфейсі? Чи не повинен бути, наприклад, окремий інтерфейс, що містить необов'язкові методи MutableCollection?
MirroredFate

Ні. Не існує жодного значущого способу мати змінні та незмінні предмети в одній ієрархії. Нещодавно виникло запитання, в якому була гарна діаграма, яка показувала складність та пояснення того, чому це погана ідея, але це видалено. Можливо, хтось інший знає питання, яке допоможе пояснити це, я нічого не можу знайти. Але я оновлю свою відповідь, щоб трохи пояснити.

Це свого роду широке твердження про незмінні черги. Я один раз пару днів назад , щоб вирішити цю проблему .
Карл Білефельдт

@Snowman Але це, здається, виявляє ті змінні та незмінні предмети як протилежні один одному. Я думаю, що незмінний об'єкт насправді є лише об'єктами, у яких відсутня можливість мутувати. Чесно кажучи, те, що зараз є складнішим і заплутаним, адже ви повинні з'ясувати, що таке змінна реалізація, а що ні. Мені здається, єдиною різницею між розміщенням усіх методів в одному інтерфейсі, на відміну від розбиття змінних методів, є одна чіткість.
MirroredFate

@MirroredFate прочитав мою останню редакцію.

2

Це в основному YAGNI. Усі конкретні колекції в стандартній бібліотеці є змінними, реалізуючи або успадковуючи необов'язкові операції. Їм не цікаво незмінних колекцій загального призначення, а також переважна більшість розробників Java. Вони не збираються створювати цілу ієрархію інтерфейсів лише для незмінних колекцій, а потім не включати жодних реалізацій.

З іншого боку, є декілька значень спеціального призначення або "віртуальних" колекцій, які можуть бути дуже корисними як незмінні, такі як порожній набір і nCopies . Також існують сторонні незмінні колекції (наприклад, Scala's), які, можливо, захочуть викликати існуючий код Java, тому вони залишили можливість для незмінних колекцій відкритими найменш руйнівним способом.


Гаразд, це має певний сенс. Але все ще здається, що він підходить до проблеми з неправильного напрямку. Чому б не почати з визначення незмінних інтерфейсів колекції, а потім не визначити змінні інтерфейси для реалізації змінних колекцій?
MirroredFate
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.