Когда использовать: метод интерфейса Java 8+ по умолчанию, а не абстрактный метод

540

Java 8 допускает реализацию методов по умолчанию в интерфейсах, называемых Методами по умолчанию .

Я запутался между тем, когда я бы использовал этот вид interface default methodвместо abstract classabstract method(s)).

Так когда же следует использовать интерфейс с методами по умолчанию и когда следует использовать абстрактный класс (с абстрактным методом)? Являются ли абстрактные классы полезными в этом сценарии?

Нарендра Патхай
источник
38
Может быть, вы все еще не можете иметь поля, закрытые методы и т. Д. В интерфейсах, в то время как вы можете использовать абстрактный класс?
SP00m
2
Раньше я размышлял на эту тему, теперь мне ясно. Спасибо @Narendra Pathai. Я хотел бы добавить ссылку на другую ветку, заданную вами по той же теме, так как оба они были моими сомнениями. stackoverflow.com/questions/19998309/…
Ашутош Ранджан
Вы можете найти хороший пост по этому вопросу здесь: blog.codefx.org/java/everything-about-default-methods
Фабри Паутассо
Вы все еще можете иногда кодировать базовый класс как интерфейс, даже если базовый класс имеет состояние. Просто интерфейс должен определять сеттеры и геттеры для состояния, а конкретные классы должны реализовывать их и определять поле. Одним из ограничений является то, что в абстрактном классе свойство bean-компонента может быть частным или защищенным. В интерфейсах есть только публичные методы. Поэтому одна из причин, по которой вы бы использовали абстрактный базовый класс, - это если у ваших классов есть свойство, которое должно быть приватным или защищенным.
DaBlick
@DaBlick Не могли бы вы решить проблему состояния в интерфейсе через HashMap. Пример: если вам нужен класс Foo, который содержит int a, b, String c. и вы хотите, чтобы они имели состояние, создайте HashMap </ * имя объекта Foo * / String, / * карта полей * / Hashmap </ * имя конкретного поля * / String, / * значение поля * / Object >> map , Когда вы хотите "создать экземпляр" теоретического класса Foo, у вас есть метод instantiate (String nameOfFoo), который выполняет map.put (nameOfFoo, fields), где fields - это HashMap <String, Object> fields.put ("a", new INT ( "5")); fields.put ("b", new int ("6")); fields.put ("c", "blah"));
Джордж Ксавье

Ответы:

307

Абстрагировать классы можно гораздо больше, чем реализации методов по умолчанию (например, закрытое состояние), но в Java 8, когда у вас есть выбор, вы должны использовать метод defender (aka. default) В интерфейсе.

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

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

Первоначальной мотивацией для введения defaultметодов в Java 8 было желание расширить интерфейсы Collections Framework с помощью лямбда-ориентированных методов, не нарушая каких-либо существующих реализаций. Хотя это больше относится к авторам публичных библиотек, эта функция может оказаться полезной и в вашем проекте. У вас есть одно централизованное место, где можно добавить новые удобства, и вам не нужно полагаться на то, как выглядит остальная часть иерархии типов.

Марко Топольник
источник
34
По этой причине следующая вещь, которую они добавят, это объявления методов по умолчанию. Я до сих пор не уверен в этом, мне кажется, что эта функция больше похожа на хак, который подвергается всем за злоупотребления.
пантера
3
Единственное использование абстрактных классов в эпоху Java 8, которое я вижу, - это определение не финальных полей. В интерфейсах поля по умолчанию являются окончательными, поэтому их нельзя изменить после назначения.
Anuroop
7
@Anuroop Не только по умолчанию - это единственный вариант. Интерфейсы не могут объявлять состояние экземпляра, поэтому абстрактные классы здесь, чтобы остаться.
Марко Топольник
2
@PhilipRego Абстрактные методы не вызывают ничего, потому что у них нет реализации. Реализованные методы в классе могут получить доступ к состоянию класса (переменные экземпляра). Интерфейсы не могут объявить их, поэтому методы по умолчанию не могут получить к ним доступ. Они должны полагаться на класс, предоставляющий реализованный метод, который обращается к состоянию.
Марко Топольник
2
Марко Топольник, твой ответ мертв. Но я бы порекомендовал обновить ваш ответ. Вы можете добавить, что прелесть методов по умолчанию в том, что, если интерфейс добавляет новые методы по умолчанию, ваша предыдущая реализация этого интерфейса не сломается. Это не было правдой до Java 8.
hfontanez
125

Есть несколько технических отличий. Абстрактные классы все еще могут делать больше по сравнению с интерфейсами Java 8:

  1. Абстрактный класс может иметь конструктор.
  2. Абстрактные классы более структурированы и могут содержать состояние.

Концептуально, основным назначением методов защитника является обратная совместимость после введения новых функций (как лямбда-функций) в Java 8.

Вадим Васильев
источник
20
Этот ответ на самом деле правильный и имеет смысл, особенно «Концептуально, главная цель методов защитника - обратная совместимость»
Trying
1
@ Неизвестно, эта страница дает больше информации: docs.oracle.com/javase/tutorial/java/IandI/defaultmethods.html
Берни,
@UnKnown, в основном это позволяет вам добавлять методы к интерфейсу и классы, которые реализуют этот интерфейс, автоматически получают эту функциональность.
LegendLength
3
Более тонкий пункт о пункте нет. 2 выше о "может держать состояние это". Абстрактные классы могут содержать состояние, которое может быть изменено позже. Интерфейсы также могут содержать состояние, но как только состояние назначено после создания экземпляра, состояние не может быть изменено.
Anuroop
3
@Anuroop Я бы не описывал public static finalполя интерфейса как «состояние». Эта staticчасть означает, что вы вообще не связаны с конкретным экземпляром. Они присваиваются при создании экземпляра класса , который отличается от создания экземпляра .
Джеронимо
65

Это описано в этой статье . Подумайте о forEachколлекциях.

List<?> list = 
list.forEach(…);

ForEach еще не объявлен java.util.Listни java.util.Collectionинтерфейсом. Одним из очевидных решений было бы просто добавить новый метод в существующий интерфейс и предоставить реализацию, где это требуется в JDK. Однако после публикации невозможно добавить методы в интерфейс, не нарушая существующую реализацию.

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

Masudul
источник
1
«невозможно добавить методы в интерфейс, не нарушая существующую реализацию» - не так ли?
Андрей Чащев
26
@AndreyChaschev Если вы добавите новый метод в интерфейс, то все разработчики должны реализовать этот новый метод. Следовательно, это нарушает существующие реализации.
Марко Топольник
4
@MarkoTopolnik спасибо, пропустил это. Просто упомянуть, что есть способ частично избежать этого - путем представления этого метода в абстрактной реализации по умолчанию. Для этого примера это будет AbstractList::forEachбросать UnsupportedOperationException.
Андрей Чащев
3
@AndreyChaschev Да, это был старый способ (кхм ... это текущий путь :), с недостатком, который ограничивает реализацию одним наследованием от предоставленной абстрактной реализации.
Марко Топольник
Я не сломаюсь, если так случится, что раньше все реализации включали этот метод. Что маловероятно, но возможно.
Джордж Ксавье
19

Эти два совершенно разные:

Методы по умолчанию - добавить внешнюю функциональность к существующим классам без изменения их состояния.

И абстрактные классы являются нормальным типом наследования, они являются обычными классами, которые предназначены для расширения.

Андрей Чащев
источник
18

Как описано в этой статье,

Абстрактные классы против интерфейсов в Java 8

После введения метода по умолчанию кажется, что интерфейсы и абстрактные классы совпадают. Тем не менее, они все еще отличаются концепцией в Java 8.

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

Суфиян Гори
источник
Я считаю, что в абстрактном классе есть конструктор, который можно определить в отличие от интерфейса. В Java 8 также они оба отличаются друг от друга из-за этого.
Хемант Пила
1
почему абстрактный класс имеет конструктор, если его нельзя создать?
Джордж Ксавье
Мы можем вызвать super () из дочернего класса, который вызовет конструктор абстрактного класса. Это влияет на состояние абстрактного класса.
Суджай Мохан
14

Что касается вашего запроса

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

Java документация обеспечивает идеальный ответ.

Абстрактные классы по сравнению с интерфейсами:

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

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

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

Варианты использования для каждого из них были объяснены в посте SE ниже:

В чем разница между интерфейсом и абстрактным классом?

Являются ли абстрактные классы полезными в этом сценарии?

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

Равиндра Бабу
источник
Теперь интерфейсы также имеют частные методы howtodoinjava.com/java9/java9-private-interface-methods
valijon
13

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

  1. Методы по умолчанию положили конец классическому шаблону интерфейса и сопутствующему классу, который реализует большинство или все методы в этом интерфейсе. Пример есть Collection and AbstractCollection. Теперь мы должны реализовать методы в самом интерфейсе, чтобы обеспечить функциональность по умолчанию. Классы, которые реализуют интерфейс, могут переопределять методы или наследовать реализацию по умолчанию.
  2. Другое важное использование методов по умолчанию interface evolution. Предположим, у меня был класс Ball как:

    public class Ball implements Collection { ... }

Теперь в Java 8 появилась новая функция потоков. Мы можем получить поток, используя streamметод, добавленный в интерфейс. Если бы streamне было метода по умолчанию, все реализации Collectionинтерфейса были бы повреждены, поскольку они не реализовывали бы этот новый метод. Добавление метода не по умолчанию для интерфейса не является source-compatible.

Но предположим, что мы не перекомпилируем класс и используем старый файл jar, который содержит этот класс Ball. Класс будет нормально загружаться без этого отсутствующего метода, экземпляры могут быть созданы, и кажется, что все работает нормально. НО, если программа вызывает streamметод по экземпляру, Ballмы получим AbstractMethodError. Таким образом, создание метода по умолчанию решило обе проблемы.

akhil_mittal
источник
9

Методы по умолчанию в интерфейсе Java обеспечивают эволюцию интерфейса .

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

Статический метод уникален для класса. Метод по умолчанию уникален для экземпляра класса.

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

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

Подробнее по теме здесь .

kiriloff
источник
7

Хотя это старый вопрос, позвольте мне также внести свой вклад в него.

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

    Интерфейс: Внутри интерфейса каждая переменная всегда общедоступна, и, наконец, мы не можем объявить переменные экземпляра.

  2. абстрактный класс: абстрактный класс может говорить о состоянии объекта

    Интерфейс: Интерфейс никогда не может говорить о состоянии объекта

  3. абстрактный класс: внутри абстрактного класса мы можем объявить конструкторы

    Интерфейс: Внутри интерфейса мы не можем объявлять конструкторы, так как целью
    конструкторов является инициализация переменных экземпляра. Так зачем же нужен конструктор, если у нас не может быть переменных экземпляра в интерфейсах ?

  4. абстрактный класс: внутри абстрактного класса мы можем объявить экземпляры и статические блоки

    Интерфейс: Интерфейсы не могут иметь экземпляров и статических блоков.

  5. абстрактный класс: абстрактный класс не может ссылаться на лямбда-выражение

    Интерфейсы: интерфейсы с одним абстрактным методом могут ссылаться на лямбда-выражения

  6. абстрактный класс : внутри абстрактного класса мы можем переопределить методы OBJECT CLASS

    Интерфейсы: Мы не можем переопределить методы OBJECT CLASS внутри интерфейсов.

Я закончу на примечании, что:

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

Умар Тахир
источник
5

Правило Remi Forax : Вы не проектируете классы Abstract. Вы разрабатываете свое приложение с интерфейсами . Какой бы ни была версия Java, каким бы ни был язык. Она опирается на I принцип сегрегации nterface в SOL I D принципов.

Позже вы можете использовать абстрактные классы для факторизации кода. Теперь с Java 8 вы можете сделать это прямо в интерфейсе. Это средство, не более.

Николас Зозол
источник
2

когда следует использовать интерфейс с методами по умолчанию и когда следует использовать абстрактный класс?

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

Факты и ограничения:

1-может быть объявлено только внутри интерфейса, а не внутри класса или абстрактного класса.

2-должны обеспечить тело

3. Предполагается, что он не является абстрактным, как другие обычные методы, используемые в интерфейсе.

Ахмад Сани
источник
1

В Java 8 интерфейс выглядит как абстрактный класс, хотя они могут иметь некоторые отличия, такие как:

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

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

Маниш Сахни
источник
Я согласен с # 2, но для # 1, вы не можете просто реализовать интерфейс и, таким образом, получить состояние через класс реализации?
Джордж Ксавье
0

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

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

Shubh
источник
0

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

Иначе, если вы добавите реализацию к интерфейсу, вы нарушите основной закон, почему интерфейсы были добавлены в первую очередь. Java - это язык с одним наследованием, в отличие от C ++, который допускает множественное наследование. Интерфейсы предоставляют преимущества типизации, которые поставляются с языком, который поддерживает множественное наследование, не создавая проблем, связанных с множественным наследованием.

В частности, Java допускает только одно наследование реализации, но допускает множественное наследование интерфейсов. Например, следующий допустимый код Java:

class MyObject extends String implements Runnable, Comparable { ... }

MyObject наследует только одну реализацию, но наследует три контракта.

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

Чтобы поддержать мою точку зрения, вот цитата Кена Арнольда и Джеймса Гослинга из книги «Язык программирования Java, 4-е издание» :

Одиночное наследование исключает некоторые полезные и правильные конструкции. Проблемы множественного наследования возникают из-за множественного наследования реализации, но во многих случаях множественное наследование используется для наследования ряда абстрактных контрактов и, возможно, одной конкретной реализации. Предоставление средств для наследования абстрактного контракта без наследования реализации позволяет использовать преимущества множественного наследования без проблем наследования множественной реализации. Наследование абстрактного договора называется наследованием интерфейса . Язык программирования Java поддерживает наследование интерфейса, позволяя вам объявлять interfaceтип

johnnyodonnell
источник
-1

Пожалуйста, подумайте сначала об открытом / закрытом принципе. Методы по умолчанию в интерфейсах нарушают его. Это плохая функция в Java. Это поощряет плохой дизайн, плохую архитектуру, низкое качество программного обеспечения. Я бы предложил полностью избегать использования методов по умолчанию.

Задайте себе несколько вопросов: почему вы не можете поместить свои методы в абстрактный класс? Вам понадобится более одного абстрактного класса? Затем подумайте, за что отвечает ваш класс. Вы уверены, что все методы, которые вы собираетесь использовать в одном классе, действительно выполняют одну и ту же цель? Может быть, вы будете различать несколько целей, а затем разделите свой класс на несколько классов, для каждой цели свой класс.

mentallurg
источник