Чому колекції Java реалізовувались за допомогою «необов’язкових методів» в інтерфейсі?


69

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

Прочитавши велику частину чудової книги "Ефективна Java" Джошуа Блоха і пізніше дізнавшись, що він може бути відповідальним за ці рішення, не здавалося, що це відповідає принципам, викладеним у цій книзі. Я б подумав, що оголошення двох інтерфейсів: Collection та MutableCollection, що розширює Collection за допомогою "необов'язкових" методів, призвело б до набагато більш доступного клієнтського коду.

Там відмінний огляд питань тут .

Чи була вагома причина, чому замість реалізації двох інтерфейсів були обрані необов'язкові методи?


ІМО, немає вагомих причин. STL C ++ був створений Степановим на початку 80-х. Хоча зручність використання обмежена незграбним синтаксисом шаблонів C ++, це парагон послідовності та зручності використання порівняно з класами колекції Java.
Кевін Клайн

Був C ++ STL 'перенос' на Java. Але це було в 5 разів більше за кількість класів. Зважаючи на це, я не вважаю, що більший є більш послідовним та корисним. docs.oracle.com/javase/6/docs/technotes/guides/collections/…
m3th0dman

@ m3th0dman На Java це набагато більше, тому що Java не має нічого потужного за шаблонами C ++.
Кевін Клайн

Може бути , це просто якийсь - то дивний стиль , який я розробив, але мій код , як правило , щоб розглядати всі колекції , як «тільки для читання», (точніше, тільки те , що ви вважаєте , чи на яких ви ітерацію) , за винятком для кількох методів , які фактично створюють колекцію. Мабуть, хороша практика в будь-якому випадку (особливо для одночасності). І необов'язкові методи, на які так багато скаржаться, ніколи не були для мене справжньою проблемою. Ні коли-небудь кошмари Generics з "супер" і "продовженнями" ніколи не були проблемою. Просто цікаво, чи застосовують інші цю загальну практику?
user949300

Відповіді:


28

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


6
Усього цього можна було б уникнути, якби у Java було таке constключове слово, як C ++.
Етьєн де Мартель

@Etienne: Кращими будуть метакласи типу Python. Тоді ви могли б програмно побудувати комбінаторне число інтерфейсів. Проблема з сопзЬ є те , що вона дає вам тільки один або два виміри: vector<int>, const vector<int>, vector<const int>, const vector<const int>. До сих пір так добре, але потім спробувати реалізувати графіки, і ви хочете, щоб постійна структуру графа, але вузол атрибутів змінним і т.д.
Neil G

2
@Etienne, ще одна причина, щоб підняти "Learn Scala" в моєму списку справ!
glenviewjeff

2
Я не розумію, чому вони не створили canметод, який би перевіряв, чи можлива операція? Це дозволило б підтримувати інтерфейс простим і швидким.
Мехрдад

3
@Etienne de Martel Дурниця. Як би це допомогло у комбінаторному вибуху?
Том Хотін - тайклін

10

Мені це звучить, як Interface Segregation Principleтоді не так добре досліджений, як зараз; такий спосіб виконання дій (тобто ваш інтерфейс включає всі можливі операції, і у вас є "вироджені" методи, які викидають винятки для тих, які вам не потрібні) були популярними до того, як SOLID та ISP стали фактичним стандартом коду якості.


2
Чому потік? Хтось не любить провайдер?
Уейн Моліна

Варто також зазначити, що в структурах, що підтримують дисперсію, є ВЕЛИЧЕЗНА перевага перед відокремленням тих аспектів інтерфейсу, які можуть бути коваріантними чи противаріантними, ніж ті, які принципово інваріантні. Навіть відсутня така підтримка, в рамках, які не використовують стирання типу, було б важливо в розділенні аспектів інтерфейсу, які не залежать від типу (наприклад, дозволяють отримати Countколекцію, не турбуючись про те, які типи елементів вони містять), але в структурах на основі стирання типу Java, це не така проблема.
supercat

4

Хоча деякі люди можуть ненавидіти "необов'язкові методи", вони в багатьох випадках можуть пропонувати кращу семантику, ніж високосегреговані інтерфейси. Крім усього іншого, вони передбачають можливості того, що об’єкт може набути здібностей або характеристик протягом свого життя, або що об'єкт (особливо об'єкт обгортки) може не знати, коли будується, про які саме здібності він повинен повідомляти.

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

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

Зауважте, що існування "необов'язкових здібностей" може в деяких випадках дозволяти зведеним колекціям реалізовувати певні функції способами, які були набагато ефективнішими, ніж це було б можливо, якби здібності визначалися існуванням реалізацій. Наприклад, припустимо, що concatenateметод був використаний для формування складної колекції з двох інших, першим з яких став ArrayList з 1 000 000 елементів, а останній - колекцією з двадцяти елементів, яку можна було повторити лише з самого початку. Якщо складена колекція запитала елемент 1000,013-й (індекс 1000,012), вона могла запитати ArrayList, скільки предметів у ній міститься (тобто, 1 000 000), відняти це з запитуваного індексу (вихід 12), прочитати та пропустити дванадцять елементів з другого колекція, а потім поверніть наступний елемент.

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


@kevincline: Ви не згодні з цитованим формулюванням? Я б вважав, що дизайн засобів, за допомогою яких реалізація інтерфейсу може описати їх основні характеристики та здібності, є одним із найважливіших аспектів дизайну інтерфейсів, хоча він часто не приділяє великої уваги. Якщо в різних реалізаціях інтерфейсу будуть різні оптимальні способи виконання певних загальних завдань, для клієнтів, які піклуються про продуктивність, слід мати засоби для вибору найкращого підходу в сценаріях, де це має значення.
supercat

1
вибачте, неповний коментар. Я збирався сказати, що "" способи запитання про колекцію "..." переміщує те, що має бути перевірки часу компіляції на час виконання.
Кевін Клайн

@kevincline: У випадках, коли клієнтові необхідно мати певні здібності і не може жити без нього, перевірка часу складання може бути корисною. З іншого боку, існує багато ситуацій, коли колекції можуть мати здібності, що перевищують ті, які вони можуть бути гарантованими часом. У деяких випадках може мати сенс мати похідний інтерфейс, у контракті якого зазначено, що всі законні реалізації похідного інтерфейсу повинні підтримувати певні методи, необов'язкові в базовому інтерфейсі, але у випадках, коли код зможе щось обробляти, чи ні у неї є якась особливість ...
supercat

... але виграш від функції, яка існує, краще, щоб код запитав об'єкт, чи підтримує він функцію, ніж запитувати систему типів, чи обіцяє тип об'єкта підтримку функції. Якщо конкретний "специфічний" інтерфейс буде широко використовуватися, AsXXXв базовий інтерфейс може бути включений метод, який поверне об'єкт, на який він викликається, якщо він реалізує цей інтерфейс, поверне об'єкт обгортки, який підтримує цей інтерфейс, якщо можливо, або киньте виняток, якщо ні. Наприклад, ImmutableCollectionінтерфейс може знадобитися за контрактом ...
supercat

2
У вашій пропозиції є проблема: модель "запитай, тоді роби" має проблему одночасності, якщо можливості об'єкта можуть змінюватися протягом його життя. (Якщо вам все одно доведеться ризикувати винятком, ви можете також не турбуватися просити ...)
jhominal

-1

Я б віднесла це до оригінальних розробників, просто тоді не знаючи кращого. Ми пройшли довгий шлях в розробці ОО з 1998 року або близько того, коли вперше вийшли Java 2 і колекції. Те, що здається очевидним поганим дизайном, не було таким очевидним у перші дні ООП.

Але це, можливо, було зроблено для запобігання додаткового лиття. Якби це був другий інтерфейс, вам доведеться передати екземпляри своїх колекцій, щоб викликати ті необов'язкові методи, які теж некрасиві. Як зараз, ви зараз же можете зловити UnsupportedOperationException і виправити свій код. Але якби було два інтерфейси, вам доведеться використовувати instanceof і кастинг по всьому світу. Можливо, вони вважали це дійсним компромісом. Крім того, ще на початку Java за два дні екземпляри були сильно нахмурені через його низьку продуктивність, вони, можливо, намагалися запобігти надмірне використання.

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


7
Що кастинг? Якщо метод повертає вам a, Collectionа не a MutableCollection, це явна ознака того, що він не призначений для зміни. Я не знаю, чому комусь потрібно було б їх кидати. Наявність окремих інтерфейсів означає, що ви отримаєте такі помилки під час компіляції, а не виняток під час виконання. Чим раніше ви отримаєте помилку, тим краще.
Етьєн де Мартель

1
Оскільки це менш гнучко, одна з найбільших переваг бібліотеки колекцій полягає в тому, що ви можете повертати інтерфейси високого рівня скрізь і не турбуватися про фактичну реалізацію. Якщо ви використовуєте два інтерфейси, то тепер ви більш щільно з'єднані. У більшості випадків ви просто хочете повернути Список, а не ImmutableList, тому що ви зазвичай хочете залишити його для виклику класу для визначення.
Jberg

6
Якщо я даю вам колекцію лише для читання, це тому, що я не хочу, щоб вона була змінена. Якщо викинути виняток і покластися на документацію, то це дуже нагадує виправлення. У C ++ я б просто повернув constоб'єкт, негайно повідомивши користувачеві, що об'єкт неможливо змінити.
Етьєн де Мартель

7
1998 рік не був "першими днями дизайну ОО".
Quant_dev
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.