Интерфейс с методами по умолчанию против абстрактного класса в Java 8

Поскольку Java 8 допускает реализацию методов по умолчанию в интерфейсе, называемом Методами по умолчанию, кажется, что возникает путаница между тем, когда я буду использовать abstract class,

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

17 ответов

Решение

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

  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, Поэтому создание метода по умолчанию решило обе проблемы.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

В заключение отмечу, что:

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Почему мы должны использовать абстрактный класс:

  1. Абстрактный класс может иметь абстрактные и неабстрактные методы.
  2. Абстрактный класс не поддерживает множественное наследование.
  3. Абстрактный класс может иметь конечные, не финальные, статические и нестатические переменные.
  4. Абстрактный класс может иметь статические методы, метод main и конструктор.
  5. Абстрактный класс может обеспечить реализацию интерфейса.
  6. Ключевое слово abstract используется для объявления абстрактного класса.

Почему мы должны использовать интерфейс:

  1. Интерфейс - это самый абстрактный уровень дизайна вашего приложения.
  2. Интерфейс поддерживает множественное наследование.
  3. Интерфейс создан только для описания подписей подклассов
  4. Интерфейс имеет только статические и конечные переменные.
  5. Используйте метод по умолчанию только для расширения некоторых интерфейсов, например, для внешней библиотеки. Но в основном это не очень хорошая практика:)

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Скажем, какой-то владелец бизнеса хочет сотрудничать с Amazon и Walmart, тогда определенные здесь интерфейсы будут WalmartPartner а также AmazonPartner определит конкретные бизнес-правила и абстрактный класс BusinessSetup получит открытие бизнеса в конкретном регионе.

      // Interfaces
 
public interface WalmartPartner {
    public static boolean signUpForWalmartBusinessAccount(String BusinessId){
        System.out.println("Setting up Walmart Business Partner");
        return true;
    }
    public default  void  getWalmartDeals(){
        System.out.println("Default walmart deal executed !");
    }
    public abstract void setupShopifyForWalmart();
    public abstract  void setupWalmartProducts();

public interface AmazonPartner {
    public static boolean signUpAsAmazonServicePartner(String BusinessId){
        System.out.println("Setting up Amazon Business Partner");
        return true;
    }
    public default  void  paymentPlatformSetup(){
        System.out.println(" Amazon default payment platform is setup");
    }
    public abstract void setupPrimeMemberDealsByRegion();
    public abstract  void setupPrimeDeals();
}

 // Abstract class 

public abstract class BusinessSetup {
    String businessId ;
    public BusinessSetup(String businessId){
        this.businessId = businessId;
        System.out.println("1. Initial Business setup for BusienssID: "+this.businessId+" is Complete");
    }
    public final boolean getBusinessRegisteredInRegion(String region){
        System.out.println("2. Business got registered in "+region+ "!");
        return true;
    }
    public abstract void setupCustomerPlatform(String customerId);
    public abstract void setupVendorPlatform(String vendorId);

}

// Concrete Class 
public class WalMartPartnerImpl extends BusinessSetup implements WalmartPartner {
    public WalMartPartnerImpl(String businessId) {
        super(businessId);
    }
    @Override
    public void setupCustomerPlatform(String customerId) {
    }

    @Override
    public void setupVendorPlatform(String vendorId) {
    }

    @Override
    public void setupShopifyForWalmart() {
    }

    @Override
    public void setupWalmartProducts() {
    }
    public static void main(String args[]){
        WalMartPartnerImpl walMartPartner = new WalMartPartnerImpl("wal8989");
        walMartPartner.getBusinessRegisteredInRegion("california");
        walMartPartner.getWalmartDeals();
        walMartPartner.setupCustomerPlatform("wal8989");

    }
}

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

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

Другие вопросы по тегам