Дополнительные методы в интерфейсе Java


120

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

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


Какие методы вы имеете в виду? Я не могу найти его в JavaDoc или в исходном коде
dcpomero


Ответы:


233

Кажется, здесь очень много путаницы в ответах.

Язык Java требует, чтобы каждый метод в интерфейсе реализовывался каждой реализацией этого интерфейса. Период. Из этого правила нет исключений. Сказать «Коллекции - исключение» предполагает очень нечеткое понимание того, что здесь действительно происходит.

Важно понимать, что существует два уровня соответствия интерфейсу:

  1. Что может проверить язык Java. Это в значительной степени сводится к следующему: есть ли какая-то реализация для каждого из методов?

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

    Хорошо написанные интерфейсы будут включать документацию, точно объясняющую, что ожидается от реализации. Ваш компилятор не может проверить это за вас. Вам нужно читать документы и делать то, что они говорят. Если вы не сделаете то, что сказано в контракте, то у вас будет реализация интерфейса в том, что касается компилятора , но это будет дефектная / недействительная реализация.

При разработке API коллекций Джошуа Блох решил, что вместо очень детализированных интерфейсов для различения различных вариантов коллекций (например, читаемых, доступных для записи, произвольного доступа и т. Д.) У него будет только очень грубый набор интерфейсов, в первую очередь Collection, List, Setи Map, а затем документировать определенные операции , как « по желанию». Это было сделано, чтобы избежать комбинаторного взрыва, который может произойти из-за мелкозернистых интерфейсов. Из FAQ по дизайну Java Collections API :

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

Рассмотрим массивы. Они реализуют большинство операций со списком, но не удаляют и не добавляют. Это списки «фиксированного размера». Если вы хотите зафиксировать это понятие в иерархии, вам нужно добавить два новых интерфейса: VariableSizeList и VariableSizeMap. Вам не нужно добавлять VariableSizeCollection и VariableSizeSet, потому что они будут идентичны ModifiableCollection и ModifiableSet, но вы можете добавить их в любом случае для единообразия. Кроме того, вам понадобится новая разновидность ListIterator, которая не поддерживает операции добавления и удаления, чтобы работать с неизменяемым List. Теперь у нас есть десять или двенадцать интерфейсов, плюс два новых интерфейса Iterator вместо наших четырех. Мы все? Нет.

Учитывайте журналы (например, журналы ошибок, журналы аудита и журналы для восстанавливаемых объектов данных). Это естественные последовательности только для добавления, которые поддерживают все операции со списком, кроме удаления и установки (замены). Им нужен новый основной интерфейс и новый итератор.

А как насчет неизменяемых коллекций в отличие от неизменяемых? (т.е. Коллекции, которые не могут быть изменены клиентом И никогда не изменятся по какой-либо другой причине). Многие утверждают, что это наиболее важное различие, поскольку оно позволяет нескольким потокам одновременно обращаться к коллекции без необходимости синхронизации. Для добавления этой поддержки в иерархию типов требуется еще четыре интерфейса.

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

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

Когда методы в API коллекций задокументированы как «необязательные операции», это не означает, что вы можете просто оставить реализацию метода в реализации, и не означает, что вы можете использовать пустое тело метода (например, многие из им нужно вернуть результат). Скорее, это означает, что допустимый вариант реализации (тот, который по-прежнему соответствует контракту) - это бросить файл UnsupportedOperationException.

Обратите внимание, что, поскольку UnsupportedOperationExceptionэто a, RuntimeExceptionвы можете выбросить его из любой реализации метода, если это касается компилятора. Например, вы можете выбросить его из реализации Collection.size(). Однако такая реализация нарушила бы контракт, поскольку в документации Collection.size()не сказано, что это разрешено.

В сторону: подход, используемый Java Collections API, несколько противоречив (однако, вероятно, меньше, чем когда он был впервые представлен). В идеальном мире интерфейсы не имели бы дополнительных операций, вместо этого использовались бы мелкозернистые интерфейсы. Проблема в том, что 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 теперь у вас есть возможность иметь реализации, которые не закодированы в ЛЮБОМ суперклассе или подклассе. Это (ИМХО) открывает новый мир шаблонов проектирования, в том числе некоторые из них, которые могут быть уместными в тех случаях, когда запрет на множественное наследование мог вызвать некоторые проблемы. Я думаю, что это приведет к появлению нового набора очень полезных шаблонов проектирования. \
DaBlick

3
@AndrewS, потому что в Java 8 removeбыла предоставлена ​​реализация по умолчанию. Если вы не реализуете его, ваш класс получит реализацию по умолчанию. Два других упомянутых вами метода не имеют реализации по умолчанию.
Лоуренс Гонсалвес,

27

Чтобы скомпилировать реализующий (не абстрактный) класс для интерфейса - все методы должны быть реализованы.

Однако , если мы думаем о методе, реализация которого представляет собой простую выдачу исключения, как о «нереализованном» (как некоторые методы в Collectionинтерфейсе), тогда Collectionинтерфейс является исключением в этом случае, а не обычным случаем. Обычно реализующий класс должен (и будет) реализовывать все методы.

«Необязательный» в коллекции означает, что реализующий класс не должен «реализовывать» (в соответствии с приведенной выше терминологией) его, и он просто выбрасывает NotSupportedException).

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

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


Обновить:

Начиная с java 8, был введен метод по умолчанию .

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

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


Вместо того, чтобы «не испортить», я думаю, это скорее «так оно и есть».

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

12
"не обязательно реализовывать это (вероятно, он просто создаст метод, который выбрасывает ...)". Это является реализацией способа.
Маркиз Лорн

1
Поскольку, к сожалению, это был принятый ответ, я предлагаю переписать его. « Обычно реализующий класс должен (и будет) реализовывать все методы» вводит в заблуждение, как уже указывал EJP .
Альберто

2
«Необязательный» в коллекции означает, что реализующий класс не должен его реализовывать »- это просто ложь. Под« не обязательно реализовывать »вы имеете в виду что-то еще.
Джеклин

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. 
    }
}

Однако, если вы говорите об интерфейсах Collection, как говорили другие, они являются исключением. Если некоторые методы остаются нереализованными и вы вызываете их, они могут вызывать UnsupportedOperationExceptionисключения.


Я мог бы поцеловать тебя, мой друг.
Micro

16

Необязательные методы в интерфейсе Collection означают, что реализации метода разрешено генерировать исключение, но оно должно быть реализовано в любом случае. Как указано в документации :

Некоторые реализации коллекций имеют ограничения на элементы, которые они могут содержать. Например, некоторые реализации запрещают нулевые элементы, а некоторые имеют ограничения на типы их элементов. Попытка добавить неподходящий элемент вызывает непроверенное исключение, обычно NullPointerException или ClassCastException. Попытка запросить наличие неподходящего элемента может вызвать исключение или просто вернуть false; некоторые реализации будут демонстрировать первое поведение, а некоторые - второе. В более общем смысле, попытка операции с неподходящим элементом, завершение которой не приведет к вставке неприемлемого элемента в коллекцию, может вызвать исключение или может быть успешной по выбору реализации. Такие исключения помечены как «необязательные».


Я так и не понял, что javadocs подразумевает под опцией. Я полагаю, они имели в виду то, что вы сказали. Но большинство методов не являются обязательными по этому стандарту new Runnable ( ) { @ Override public void run ( ) { throw new UnsupportedOperationException ( ) ; } }:;
emory

Похоже, это не относится к необязательным методам , скорее, это, например, 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. Если автор пишет класс, который не позволяет добавлять новые элементы в реализацию List, реализация не add()является обязательной, поскольку поведение по умолчанию - именно то, что необходимо.

В этом случае "третье утверждение" выше по-прежнему остается в силе, потому что метод был реализован в самом интерфейсе.

3. Метод, возвращающий Optionalрезультат.

Последний новый вид необязательного метода - это просто метод, который возвращает Optional. OptionalКласс обеспечивает явно более объектно-ориентированный способ борьбы с nullрезультатами.

В свободном стиле программирования, который обычно наблюдается при кодировании с использованием нового Java Streams API, нулевой результат в любой момент вызывает сбой программы с исключением 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!");
    }
}

Что теперь произойдет, если вы, например, реализуете это в классе под названием «Test», компилятор будет прекрасно справляться со следующим:

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!

Возможны все комбинации. Все, что имеет значение «default», может быть реализовано, но не должно быть реализовано, однако все, что без него, должно быть реализовано.

Надеюсь, что теперь я отвечу не совсем неправильно.

Всем хорошего дня!

[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

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


0

Учебник Oracle по коллекциям Java:

Чтобы поддерживать управляемость количества основных интерфейсов коллекции, платформа Java не предоставляет отдельные интерфейсы для каждого варианта каждого типа коллекции. (Такие варианты могут включать неизменяемый, фиксированный размер и только добавление.) Вместо этого операции модификации в каждом интерфейсе считаются необязательными - данная реализация может выбрать не поддерживать все операции. Если вызывается неподдерживаемая операция, коллекция выдает исключение UnsupportedOperationException . Реализации несут ответственность за документирование поддерживаемых ими дополнительных операций. Все универсальные реализации платформы Java поддерживают все необязательные операции.

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.