Це ще одна з тих проблем дизайну мови, яка здається "очевидно хорошою ідеєю", поки ви не почнете копати і не зрозумієте, що це насправді погана ідея.
У цій пошті багато питань щодо цієї теми (та й для інших предметів). Декілька дизайнерських сил зібралися, щоб привести нас до сучасного дизайну:
- Бажання зберегти модель спадкування простою;
- Той факт, що переглядаючи очевидні приклади (наприклад, перетворюючись
AbstractList
на інтерфейс), ти розумієш, що успадкування рівних / hashCode / toString сильно пов'язане з єдиним успадкуванням і станом, а інтерфейси множиться у спадок і без стану;
- Це потенційно відкрило двері для деяких дивних поведінок.
Ви вже торкнулися мети "тримай просто"; правила успадкування та вирішення конфліктів розроблені як дуже прості (класи перемагають інтерфейси, похідні інтерфейси перемагають над суперінтерфейсами, а будь-які інші конфлікти вирішуються класом-реалізатором.) Звичайно, ці правила можна змінити, щоб зробити виняток, але Я думаю, що ви побачите, коли почнете тягнути за цією струною, що додаткова складність не така мала, як ви могли б подумати.
Звичайно, є певна ступінь вигоди, яка б виправдовувала більшу складність, але в цьому випадку її немає. Методи, про які ми тут говоримо, - це рівні, hashCode та toString. Ці методи по суті стосуються стану об'єкта, і саме клас, який належить державі, а не інтерфейсу, знаходиться в найкращому становищі для визначення того, що означає рівність для цього класу (особливо, оскільки договір на рівність є досить сильним; див. Ефективний Java для деяких дивних наслідків); Автори інтерфейсу просто надто віддалені.
Легко витягнути AbstractList
приклад; було б чудово, якби ми могли позбутися AbstractList
і вкласти поведінку в List
інтерфейс. Але після того, як ви вийдете за межі цього очевидного прикладу, не знайдеться багато інших хороших прикладів. У корені, AbstractList
розрахований на одинакове успадкування. Але інтерфейси повинні бути розроблені для багаторазового успадкування.
Далі, уявіть, що ви пишете цей клас:
class Foo implements com.libraryA.Bar, com.libraryB.Moo {
// Implementation of Foo, that does NOT override equals
}
В Foo
письменник дивиться на супертіпа, не бачить реалізації рівних, і приходять до висновку , що , щоб отримати рівність посилань, все , що йому потрібно зробити , це наслідувати одно від Object
. Потім, наступного тижня, сервіс бібліотеки для Bar "з користю" додає equals
реалізацію за замовчуванням . Ой! Тепер семантика Foo
була порушена інтерфейсом в іншій області технічного обслуговування, "корисно", додавши за замовчуванням загальний метод.
За замовчуванням повинні бути дефолти. Додавання за замовчуванням інтерфейсу, де його не було (ніде в ієрархії), не повинно впливати на семантику конкретних класів реалізації. Але якби параметри за замовчуванням могли "перекрити" методи "Об'єкт", це не було б правдою.
Отже, хоча це здається нешкідливою особливістю, воно насправді є досить шкідливим: воно додає багато складності для невеликої поступової експресивності, і це робить занадто легким для навмисних, нешкідливих на вигляд змін, щоб окремо складені інтерфейси підірвати. передбачувана семантика занять із реалізації.