Необов’язкові методи в інтерфейсі Java


120

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

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


До яких методів ви звертаєтесь? Я не можу знайти його у JavaDoc чи вихідному коді
dcpomero


Відповіді:


232

Тут, мабуть, існує велика плутанина у відповідях.

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

Важливо усвідомити, що існують на зразок двох рівнів відповідності інтерфейсу:

  1. Що може перевірити мова Java. Це майже просто зводиться до: чи є якась реалізація для кожного з методів?

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

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

При розробці API колекцій Джошуа Блох вирішив, що замість того, щоб мати дуже дрібнозернисті інтерфейси, щоб розрізняти різні варіанти колекцій (наприклад: читабельний, доступний для запису, випадковий доступ тощо), він матиме лише дуже грубому наборі інтерфейсів, насамперед Collection, List, Setі Map, а потім документувати певні операції , як « по бажанню». Це дозволило уникнути комбінаторного вибуху, який виник у результаті дрібнозернистих інтерфейсів. З питань FAQ щодо дизайну API API API :

Щоб проілюструвати проблему в деталях, припустимо, ви хочете додати поняття модифікованості до Ієрархії. Вам потрібні чотири нові інтерфейси: ModifiableCollection, ModifiableSet, ModifiableList та ModifiableMap. Те, що раніше була простою ієрархією, зараз є безладним гетерархією. Крім того, вам потрібен новий інтерфейс Iterator для використання з незмінними колекціями, який не містить операції видалення. Тепер ви можете покінчити з UnsupportedOperationException? На жаль ні.

Розглянемо масиви. Вони реалізують більшість операцій зі списком, але не видаляють та додають. Вони є списками "фіксованого розміру". Якщо ви хочете зафіксувати це поняття в ієрархії, вам потрібно додати два нових інтерфейси: VariableSizeList та VariableSizeMap. Вам не потрібно додавати VariableSizeCollection та VariableSizeSet, оскільки вони були б ідентичними ModifiableCollection та ModifiableSet, але ви можете все-таки додати їх для послідовності. Крім того, вам потрібен новий різновид ListIterator, який не підтримує операції додавання та видалення, а також разом із немодифікованим списком. Зараз ми маємо до десяти чи дванадцяти інтерфейсів плюс два нових інтерфейсу Iterator замість наших первісних чотирьох. Ми зробили? Немає.

Розгляньте журнали (наприклад, журнали помилок, журнали аудиту та журнали для об'єктів даних, що підлягають відновленню). Вони є природними послідовностями для додавання, які підтримують усі операції списку, крім видалення та встановлення (заміни). Для них потрібен новий основний інтерфейс та новий ітератор.

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

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

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

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

Зауважте, що оскільки UnsupportedOperationExceptionце a, RuntimeExceptionви можете викинути його з будь-якої реалізації методу, що стосується компілятора. Наприклад, ви можете викинути це з реалізації Collection.size(). Однак така реалізація порушила б договір, оскільки в документації Collection.size()не сказано, що це дозволено.

Убік: однак, підхід, використовуваний API колекцій Java, дещо суперечливий (можливо, зараз менше, ніж коли він був вперше представлений). В ідеальному світі інтерфейси не мали б додаткових операцій, а натомість використовуються тонкозернисті інтерфейси. Проблема полягає в тому, що Java не підтримує ані структурних типів, ані типів перетину, через що спроба зробити «правильний шлях» стає надзвичайно громіздкою у випадку з колекціями.


30
+1 для There are no exceptions to this rule. Цікаво, чому ця відповідь не позначена як прийнята. Інші - це добре, але ви дали більше, ніж достатньо.
xyz

9
"Мова Java вимагає, щоб кожен метод інтерфейсу був реалізований кожною реалізацією цього інтерфейсу. Період. З цього правила немає винятків." За винятком ... коли є. :-) Інтерфейси Java 8 можуть визначати реалізацію методу за замовчуванням. Таким чином, у Java 8 ... НЕ ПРАВИЛЬНО, що кожен метод в інтерфейсі повинен бути ВПРОВАДЖЕНО кожною реалізацією інтерфейсу, принаймні, не в тому сенсі, що ви повинні код виконання у класі conrete.
DaBlick

1
@DaBlick Коли я сказав, що "реалізується кожною реалізацією", я не мав на увазі, що реалізація зазначеного методу повинна знаходитися в джерелі класу реалізації. Ще до Java 8 можна успадкувати реалізацію методу інтерфейсу, навіть від класу, який не реалізує згаданий інтерфейс. наприклад: створити, Fooщо не реалізується Runnableз публічним методом void run(). Тепер створіть клас, Barякий extends Fooі implements Runnableбез накиду run. Він все ще реалізує метод, хоча й опосередковано. Так само реалізація методу за замовчуванням все ще є реалізацією.
Лоранс Гонсальвес

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

3
@AndrewS, тому що в Java 8 removeбуло реалізовано за замовчуванням. Якщо ви не реалізуєте його, то ваш клас отримує реалізацію за замовчуванням. Інші два способи, які ви згадуєте, не мають типових реалізацій.
Лоранс Гонсальвес

27

Для того, щоб скласти впроваджувальний (не абстрактний) клас для інтерфейсу - всі методи повинні бути реалізовані.

Однак якщо ми думаємо про метод, що його реалізація є простим викидом виключення як "не реалізований" (як деякі методи в Collectionінтерфейсі), то Collectionінтерфейс є винятком у цьому випадку, а не звичайним. Зазвичай клас реалізації повинен (і буде) реалізувати всі методи.

"Необов'язковий" в колекції означає, що клас реалізації не повинен "реалізовувати" (відповідно до вищевикладеної термінології), а він просто кине NotSupportedException).

Хороший приклад - add()метод для незмінних колекцій - бетон просто реалізує метод, який не робить нічого, крім киданняNotSupportedException

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


Оновлення:

Станом на java 8 було введено метод за замовчуванням .

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

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


Замість того, щоб "не заплутатися", я думаю, що це більше "саме так".

@pst: Я вважаю , що дизайнери думали, реалізуючи це, в першу чергу, але я не можу це точно знати. Я думаю, що будь-який інший підхід просто створить безлад, але знову ж таки - може бути неправильним. Суть, яку я намагався показати тут, полягає в тому, що цей приклад - це виняток, а не звичайний - і хоча він іноді може бути корисним - для загального випадку - цього слід уникати, якщо це можливо.
Аміт

12
"не потрібно його реалізувати (ймовірно, це просто створить метод, який кидає ...)". Це є реалізацією способу.
Маркіз Лорнський

1
Оскільки це, на жаль, прийнято відповідь, я б запропонував переписати його. " Зазвичай , клас, що впроваджує, повинен (і буде) реалізовувати всі методи" вводить в оману, як уже вказував EJP
Альберто

2
. «Факультативний» в засобах збору , що клас реалізації не повинні реалізувати його »- це звичайна брехня К." Чи не повинні реалізувати "ви маєте в виду що - то інше.
djechlin

19

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

interface Foo {
  void doSomething();
  void doSomethingElse();
}

class MyClass implements Foo {
  public void doSomething() {
     /* All of my code goes here */
  }

  public void doSomethingElse() {
    // I leave this unimplemented
  }
}

Тепер я залишився без виконання doSomethingElse(), залишивши його вільним для моїх підкласів. Це необов’язково.

class SubClass extends MyClass {
    @Override
    public void doSomethingElse() {
      // Here's my implementation. 
    }
}

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


Я міг би поцілувати тебе, мій друг.
Мікро

16

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

Деякі реалізації колекції мають обмеження щодо елементів, які вони можуть містити. Наприклад, деякі реалізації забороняють нульові елементи, а деякі мають обмеження на типи їх елементів. Спроба додати непридатний елемент викидає неперевірений виняток, як правило, NullPointerException або ClassCastException. Спроба запитувати наявність непридатного елемента може викинути виняток, або він може просто повернути помилкове; деякі реалізації проявлятимуть першу поведінку, а деякі демонструють другу. Більш загально, спроба операції над непридатним елементом, завершення якого не призвело б до вставки непридатного елемента в колекцію, може призвести до винятку або може досягти успіху за вибором реалізації. Такі винятки позначаються як "необов'язкові"


Я ніколи не розумів, що означає "javadocs". Я вважаю, що вони мали на увазі, як ви сказали. Але більшість методів НЕ є обов'язковими до цього стандарту: new Runnable ( ) { @ Override public void run ( ) { throw new UnsupportedOperationException ( ) ; } };
Еморі

Це, мабуть, не стосується необов'язкових методів , а, наприклад, add((T)null)може бути дійсним в одному випадку, але не в іншому. Тобто, це говорить про необов'язкові винятки / поведінку та аргументи ("обмеження елементів" ... "невідповідний елемент" ... "винятки, позначені як необов'язкові" і не стосується факультативних методів .

9

Для компіляції коду повинні бути реалізовані всі методи (окрім тих, що мають defaultреалізацію в Java 8+), однак реалізація не повинна робити нічого функціонально корисного. Зокрема, це:

  • Може бути порожнім (порожній метод.)
  • Може просто кинути UnsupportedOperationException(або подібне)

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


5

Насправді мене надихає SurfaceView.Callback2. Я думаю, що це офіційний шлях

public class Foo {
    public interface Callback {
        public void requiredMethod1();
        public void requiredMethod2();
    }

    public interface CallbackExtended extends Callback {
        public void optionalMethod1();
        public void optionalMethod2();
    }

    private Callback mCallback;
}

Якщо вашому класу не потрібно застосовувати додаткові методи, просто "реалізується зворотний виклик". Якщо вашому класу потрібно реалізувати необов'язкові методи, просто "реалізується CallbackExtended".

Вибачте за лайно англійська.


5

У Java 8 і пізніших версіях відповідь на це питання все ще справедлива, але тепер є більш нюансованою.

По-перше, ці твердження з прийнятої відповіді залишаються правильними:

  • інтерфейси призначені для конкретизації їх неявної поведінки у контракті (заява про правила поведінки, яким повинні керуватися класи, що реалізують, щоб вважати їх дійсними)
  • існує різниця між договором (правилами) та виконанням (програмне кодування правил)
  • методи, визначені в інтерфейсі, ОБОВ'ЯЗКОВО ВИНАГИ бути реалізовані (в певний момент)

Отже, який нюанс є новим у Java 8? Якщо говорити про "необов'язкові методи" , зараз підходить будь-яке з наступного:

1. Метод, реалізація якого є контрактним

У "третьому твердженні" йдеться про те, що абстрактні методи інтерфейсу повинні бути впроваджені завжди, і це залишається вірним у Java 8+. Однак, як і в Java Collections Framework, в контракті можна описати деякі абстрактні методи інтерфейсу як "необов'язкові".

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

public SomeReturnType optionalInterfaceMethodA(...) {
    throw new UnsupportedOperationException();
}

У Java 7 та більш ранніх версіях це був єдиний тип "необов'язкового методу", тобто метод, який, якщо він не був реалізований, кинув UnsupportedOperationException. Така поведінка обов'язково визначається договором інтерфейсу (наприклад, необов'язкові методи інтерфейсу Java Collections Framework).

2. Метод за замовчуванням, повторна реалізація якого не є обов'язковим

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

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

У цьому новому середовищі Java Collections Framework можна переписати як:

public interface List<E> {
    :
    :
    default public boolean add(E element) {
        throw new UnsupportedOperationException();
    }
    :
    :
}

Таким чином, "необов'язковий" метод add()має поведінку за замовчуванням закидання UnsupportedOperationException, якщо клас реалізації не надає своєї власної нової поведінки, що саме те, що ви хотіли б, щоб сталося і яке відповідає договору для List. Якщо автор пише клас, який не дозволяє додавати нові елементи до реалізації списку, реалізація не add()є обов'язковою, оскільки поведінка за замовчуванням саме те, що потрібно.

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

3. Метод, який повертає Optionalрезультат

Остаточний новий вид необов'язкового методу - це просто метод, який повертає Optional. OptionalКлас забезпечує явно більш об'єктно-орієнтована спосіб боротьби з nullрезультатами.

У плавному стилі програмування, такому, як це часто зустрічається при кодуванні з новим API Java Streams, нульовий результат у будь-якій точці спричиняє збій програми з NullPointerException. OptionalКлас забезпечує механізм для повернення результатів невизначеною коди клієнта таким чином , що дає можливість вільно стиль , не викликаючи клієнтський код аварію.


4

Якщо ми переглянемо код AbstractCollection.java у grepCode, який є класом предків для всіх реалізацій колекції, це допоможе нам зрозуміти значення необов'язкових методів. Ось код методу add (e) у класі AbstractCollection. метод add (e) необов’язковий відповідно до інтерфейсу колекції

public boolean  add(E e) {

        throw new UnsupportedOperationException();
    } 

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


4

Що ж, ця тема була присвячена ... так ... але подумайте, одна відповідь відсутня. Я говорю про "Методи за замовчуванням" інтерфейсів. Наприклад, давайте уявимо, що у вас буде клас для закриття чого-небудь (наприклад, деструктора чи чогось іншого). Скажімо, він повинен мати 3 методи. Назвемо їх "doFirst ()", "doLast ()" та "onClose ()".

Тому ми кажемо, що хочемо, щоб будь-який об’єкт цього типу принаймні реалізував "onClose ()", але інші необов'язкові.

Ви можете це зрозуміти, використовуючи інтерфейси "Методи за замовчуванням". Я знаю, це більшу частину часу заперечує причину інтерфейсу, але якщо ви розробляєте фреймворк, це може бути корисним.

Тож якщо ви хочете реалізувати це таким чином, виглядало б наступне

public interface Closer {
    default void doFirst() {
        System.out.print("first ... ");
    }
    void onClose();
    default void doLast() {
        System.out.println("and finally!");
    }
}

Що б зараз сталося, якби ви, наприклад, реалізували його в класі під назвою "Тест", компілятор був би абсолютно чудовим із наступним:

public class TestCloser implements Closer {
    @Override
    public void onClose() {
        System.out.print("closing ... ");
    }
}

з виходом:

first ... closing ... and finally!

або

public class TestCloser implements Closer {
    @Override
    public void onClose() {
        System.out.print("closing ... ");
    }

    @Override
    public void doLast() {
        System.out.println("done!");
    }
}

з виходом:

first ... closing ... done!

Можливі всі комбінації. Все, що має "за замовчуванням", може бути реалізовано, але не повинно, проте нічого без цього не повинно бути реалізовано.

Сподіваюсь, що я не зовсім помиляюся, що я зараз відповідаю.

Приємного дня всім!

[edit1]: Зверніть увагу: Це працює лише в Java 8.


Так, вибачте, я забув згадати це ... Слід редагувати зараз.
Торбен Кук

1

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

Тож замість інтерфейсу я використовував клас із порожньою реалізацією, такий як:

public class MyCallBack{
    public void didResponseCameBack(String response){}
}

І ви можете встановити змінну члена CallBack таким чином,

c.setCallBack(new MyCallBack() {
    public void didResponseCameBack(String response) {
        //your implementation here
    }
});

тоді називайте це так.

if(mMyCallBack != null) {
    mMyCallBack.didResponseCameBack(response);
}

Таким чином, вам не потрібно буде турбуватися про впровадження всіх методів за зворотній дзвінок, а лише переосмислити ті, які вам потрібні.


0

Хоча це не відповідає на питання ОП, варто зазначити, що в Java 8 додавання методів за замовчуванням до інтерфейсів насправді доцільно . defaultКлючове слово поміщається в підпису методу як інтерфейс буде приводити до класу , який має можливість перевизначити метод, але не вимагати його.


0

Підручник Java-колекцій Java Oracle:

Щоб зберегти кількість керованих інтерфейсів колекції керованими, платформа Java не забезпечує окремі інтерфейси для кожного варіанту кожного типу колекції. (Такі варіанти можуть включати незмінні, фіксованого розміру та лише додавання.) Натомість операції з модифікації у кожному інтерфейсі позначаються необов'язковими - дана реалізація може обрати не підтримку всіх операцій. Якщо викликається непідтримувана операція, колекція передає UnsupportedOperationException . Виконання несе відповідальність за документування того, яку з необов'язкових операцій вони підтримують. Усі загальноприйняті платформи Java підтримують усі необов'язкові операції.

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