- Разница между абстрактным классом и интерфейсом в Java
- Абстрактный класс
- Интерфейс
- Абстрактный класс против интерфейса
- Когда использовать Абстрактный класс и интерфейс:
- Введение стандартных и статических методов в Java 8
- Расширение абстрактных классов с помощью абстрактных классов в Java
- Аннотация класс определение проблемы
- Корень проблемы
- Решение
- навынос
Разница между абстрактным классом и интерфейсом в Java
Некоторые из популярных вопросов интервью: «В чем разница между абстрактным классом и интерфейсом», «Когда вы будете использовать абстрактный класс и когда вы будете использовать интерфейс». Итак, в этой статье мы пройдем эту тему.
Прежде чем перейти к различиям между ними, давайте рассмотрим их введение.
Абстрактный класс
Абстрактные классы создаются для захвата общих характеристик подклассов. Его нельзя создать, его подклассы можно использовать только как суперкласс. Абстрактные классы используются для создания шаблона для его подклассов вниз по иерархии.
Давайте возьмем пример класса JDK GenericServlet:
Когда HttpServlet расширяет универсальный сервлет, он обеспечивает реализацию метода service ():
Интерфейс
Интерфейс – это коллекция абстрактных методов. Класс реализует интерфейс, тем самым наследуя абстрактные методы интерфейса. Так что это своего рода подписание контракта, вы соглашаетесь, что если вы реализуете этот интерфейс, то вам придется использовать его методы. Это просто шаблон, он ничего не может сделать сам.
Давайте возьмем пример внешнего интерфейса:
Когда вы реализуете этот интерфейс, вы должны реализовать два вышеупомянутых метода:
Абстрактный класс против интерфейса
параметр | Абстрактный класс | Интерфейс |
Реализация метода по умолчанию | Может иметь реализацию метода по умолчанию | Интерфейсы – это чистая абстракция. Не может быть реализации вообще. |
Реализация | Подклассы используют ключевое слово extends для расширения абстрактного класса, и они должны обеспечивать реализацию всех объявленных методов в абстрактном классе, если только подкласс не является также абстрактным классом. | подклассы используют ключевое слово Implements для реализации интерфейсов и должны обеспечивать реализацию для всех методов, объявленных в интерфейсе. |
Конструктор | Абстрактный класс может иметь конструктор | Интерфейс не может иметь конструктор |
Отличается от обычного Java-класса | Абстрактные классы почти такие же, как классы Java, за исключением того, что вы не можете создать их экземпляр. | Интерфейсы совсем другого типа |
Модификатор доступа | Методы абстрактного класса могут иметь публичный, защищенный, приватный и модификатор по умолчанию | Методы интерфейса по умолчанию общедоступны. вы не можете использовать любой другой модификатор доступа с ним |
Основной () метод | Абстрактные классы могут иметь метод main, поэтому мы можем его запустить | Интерфейс не имеет основного метода, поэтому мы не можем его запустить. |
Множественное наследование | Абстрактный класс может расширять еще один класс и может реализовывать один или несколько интерфейсов. | Интерфейс может распространяться только на один или несколько интерфейсов. |
скорость | Это быстрее, чем интерфейс | Интерфейс несколько медленнее, так как поиск метода в классе занимает некоторое время |
Добавление нового метода | Если вы добавляете новый метод в абстрактный класс, вы можете обеспечить его реализацию по умолчанию. Таким образом, вам не нужно менять свой текущий код | Если вы добавляете новый метод в интерфейс, вы должны изменить классы, которые реализуют этот интерфейс |
Когда использовать Абстрактный класс и интерфейс:
- Если у вас много методов и вы хотите использовать реализацию по умолчанию для некоторых из них, используйте абстрактный класс
- Если вы хотите реализовать множественное наследование, вы должны использовать интерфейс. Поскольку Java не поддерживает множественное наследование, подкласс не может расширять более одного класса, но вы можете реализовать несколько интерфейсов, чтобы вы могли использовать интерфейс для этого.
- Если ваш базовый контракт продолжает изменяться, то вы должны использовать абстрактный класс, как если бы вы продолжали изменять свой базовый контракт и использовать интерфейс, тогда вам придется изменить все классы, которые реализуют этот интерфейс.
Введение стандартных и статических методов в Java 8
Oracle попытался преодолеть разрыв между абстрактным классом и интерфейсом, введя в интерфейс концепцию стандартных и статических методов. Так что теперь мы можем обеспечить реализацию метода по умолчанию в интерфейсе и не будем применять класс для его реализации. Я расскажу об этой теме в моем следующем посте.
Расширение абстрактных классов с помощью абстрактных классов в Java
Когда я создавал абстрактный класс Java :: Geci AbstractFieldsGenerator и AbstractFilteredFieldsGenerator я столкнулся с не слишком сложной проблемой проектирования. Я хотел бы подчеркнуть, что эта проблема и дизайн могут показаться очевидными для некоторых из вас, но во время моего недавнего разговора с младшим разработчиком (мой сын, в частности Михай, который также просматривает мои статьи, потому что его английский намного лучше моего), я понял, что эта тема еще может иметь значение.
Так или иначе. У меня были эти два класса, поля и генератор отфильтрованных полей. Второй класс расширяет первый
добавляя дополнительную функциональность и в то же время он должен обеспечивать ту же сигнатуру для конкретной реализации. Что это означает?
Эти генераторы помогают генерировать код для определенного класса, используя отражение. Поэтому входная информация, с которой они работают, является объектом Class . Класс генератора полей имеет абстрактный метод process() , который вызывается для каждого поля. Он вызывается из реализованного метода, который зацикливается на полях и выполняет вызов отдельно для каждого. Когда конкретный класс extends AbstractFieldsGenerator и, таким образом, реализует этот абстрактный метод, он будет вызван. Когда тот же конкретный класс изменяется так, что он extends AbstractFilteredFieldsGenerator тогда конкретный метод будет вызываться только для отфильтрованного метода. Я хотел дизайн, чтобы ЕДИНСТВЕННОЕ изменение, которое было необходимо в конкретном классе, это изменение имени.
Аннотация класс определение проблемы
Та же проблема описана более абстрактно: есть два абстрактных класса A и F так что F extends A а F обеспечивает некоторую дополнительную функциональность. Оба объявляют абстрактный метод m() который должен реализовать конкретный класс. Когда конкретное объявление класса C изменено с C extends A на C extends F то вызов метода m() должен измениться, но в классе C не должно быть никаких других изменений. Метод m() вызывается из метода p() определенного в классе A Как спроектировать F ?
Расширение A может быть сделано двумя существенно разными способами:
- F переопределяет m() делая его конкретным реализующим дополнительные функции в m() и вызывает новый абстрактный метод, скажем, mx()
- F переопределяет метод p() версией, которая обеспечивает дополнительную функциональность (фильтрация в приведенном выше примере) и вызывает все еще абстрактный метод m()
Первый подход не удовлетворяет требованию, чтобы подпись, которая должна быть реализована конкретным классом C оставалась неизменной. Второй подход выбрасывает уже реализованную функциональность A в мусор и переопределяет его немного по-другому. На практике это возможно, но это определенно будет программирование копирования / вставки. Это проблематично, позвольте мне не объяснять почему.
Корень проблемы
В инженерном деле, когда мы сталкиваемся с подобной проблемой, обычно это означает, что проблема или структура недостаточно хорошо описаны, и решение находится где-то в совершенно другой области. Другими словами, есть некоторые предположения, движущие наше мышление, которые являются ложными. В этом случае проблема заключается в том, что мы предполагаем, что абстрактные классы предоставляют ОДНО расширение «API» для их расширения. Обратите внимание, что API – это не только то, что вы можете вызывать. В случае абстрактного класса API – это то, что вы реализуете, когда расширяете абстрактный класс. Подобно тому, как библиотеки могут предоставлять разные API для разных способов использования (HTTP-клиент Java 9 может send() а также sendAsync() ) абстрактные (а на самом деле также не абстрактные) классы также могут предоставлять различные способы расширения для разных целей.
Нет способа кодирования F достигающего нашей цели дизайна, без изменения A Нам нужна версия A которая предоставляет другой API для создания конкретной реализации и другую, не обязательно дизъюнктивную / ортогональную, для создания еще абстрактного расширения.
Разница между API в этом случае заключается в том, что конкретная реализация стремится быть в конце цепочки вызовов, в то время как абстрактное расширение хочет подключиться к последнему, но одному элементу цепочки. Реализация A должна предоставлять API для подключения к последнему, но одному элементу цепочки вызовов. Это уже решение.
Решение
Мы реализуем метод ma() в классе F и хотим, чтобы p() вызывал наш ma() вместо прямого вызова m() . Изменяя A мы можем это сделать. Мы определяем ma() в A и вызываем ma() из p() . Версия ma() реализованная в A должна вызывать m() без лишних слов, чтобы предоставить исходный «API» для конкретных реализаций A Реализация ma() в F содержит дополнительную функциональность (фильтрация в примере) и затем вызывает m() . Таким образом, любой конкретный класс может расширять A или F и может реализовывать m() с точно такой же сигнатурой. Мы также избегали копирования / вставки, за исключением того, что вызов m() является кодом, который одинаков в двух версиях ma() .
Если мы хотим, чтобы класс F расширялся более абстрактными классами, то реализация F::ma должна напрямую вызывать не m() а новую mf() которая вызывает m() . Таким образом, новый абстрактный класс может переопределить mf() снова предоставив новую функциональность, и вызвать абстрактный m() .
навынос
- Программирование абстрактных классов является сложным, и иногда трудно получить четкое представление о том, кто кого вызывает и какую реализацию. Вы можете преодолеть эту проблему, если поймете, что это может быть сложным вопросом. Документируйте, визуализируйте, обсуждайте любой способ, который может вам помочь.
- Когда вы не можете решить проблему (в примере, как кодировать F ), вы должны бросить вызов среде (класс A мы безоговорочно предполагали неизменным, сформулировал вопрос: «Как реализовать F ?»).
- Избегайте копирования / вставки программирования. (Pasta содержит много CH и делает ваш код жирным, артерии забиваются и, наконец, сердце вашего приложения перестанет биться.)
- Хотя это и не подробно описано в этой статье, имейте в виду, что чем глубже иерархия абстракции, тем сложнее получить четкий обзор того, кто кому звонит (см. Также пункт 1).
- Найдите пример демонстрационного приложения по адресу https://github.com/verhas/abstractchain
- Найдите оригинальное, чуть более сложное приложение с таким шаблоном на https://github.com/verhas/javageci
Опубликовано на Java Code Geeks с разрешения Питера Верхаса, партнера нашей программы JCG . Смотрите оригинальную статью здесь: Расширение абстрактных классов с помощью абстрактных классов в Java
Мнения, высказанные участниками Java Code Geeks, являются их собственными.