Интерфейсы как тип абстрактного класса c
Перейти к содержимому

Интерфейсы как тип абстрактного класса c

  • автор:

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

Абстрактные классы и интерфейсы встречаются повсюду как в Java-приложениях, так и в самом Java Development Kit (JDK). Каждый из них служит своей цели:

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

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

Интерфейсы

Интерфейс — это контракт, который реализуется в некотором классе. У интерфейса не может быть состояния, поэтому в нем нельзя использовать изменяемые поля экземпляра. В интерфейсе могут быть только неизменяемые final-поля.

Когда использовать интерфейсы

Интерфейсы очень полезны для уменьшения связанности (coupling) кода и реализации полиморфизма. Для примера давайте взглянем на интерфейс List из JDK:

public interface List extends Collection

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

Контракт интерфейса List реализуется классами ArrayList , Vector , LinkedList и другими.

При использовании полиморфизма тип переменной объявляем как List , и присваиваем ей любую из доступных реализаций. Например:

List list = new ArrayList(); System.out.println(list.getClass()); List list = new LinkedList(); System.out.println(list.getClass());
class java.util.ArrayList class java.util.LinkedList

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

Переопределение метода интерфейса

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

Рассмотрим следующий пример:

public class OverridingDemo < public static void main(String[] args) < Challenger challenger = new JavaChallenger(); challenger.doChallenge(); >> interface Challenger < void doChallenge(); >class JavaChallenger implements Challenger < @Override public void doChallenge() < System.out.println("Challenge done!"); >>

Результат будет следующий:

Challenge done!

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

Неизменяемые переменные

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

public interface Challenger

Обратите внимание, что обе переменные неявно final и static . Это означает, что они являются константами, не зависят от экземпляра и не могут быть изменены.

При попытке изменить поля в интерфейсе Challenger , например, следующим образом:

Challenger.number = 8; Challenger.name = "Another Challenger";

будет ошибка компиляции:

Cannot assign a value to final variable 'number' Cannot assign a value to final variable 'name'

Default-методы

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

У методов по умолчанию может быть реализация, а у абстрактных методов — нет. Методы по умолчанию — результат появления лямбда-выражений и Stream API, но использовать их нужно с осторожностью.

В качестве примера default-метода из JDK можно привести метод forEach() из интерфейса Iterable . Вместо копирования кода этого метода во все реализации Iterable , мы можем переиспользовать метод forEach :

default void forEach(Consumer action) < // Code implementation here…

Любая реализация Iterable может использовать метод forEach() без необходимости реализации этого нового метода.

Давайте рассмотрим пример с методом по умолчанию:

public class DefaultMethodExample < public static void main(String[] args) < Challenger challenger = new JavaChallenger(); challenger.doChallenge(); >> class JavaChallenger implements Challenger < >interface Challenger < default void doChallenge() < System.out.println("Challenger doing a challenge!"); >>
Challenger doing a challenge!

Важно отметить, что у default-метода должна быть реализация и default-метод не может быть статическим.

Абстрактные классы

У абстрактных классов может быть состояние в виде изменяемых полей экземпляра. Например:

public abstract class AbstractClassMutation < private String name = "challenger"; public static void main(String[] args) < AbstractClassMutation abstractClassMutation = new AbstractClassImpl(); abstractClassMutation.name = "mutated challenger"; System.out.println(abstractClassMutation.name); >> class AbstractClassImpl extends AbstractClassMutation
mutated challenger

Абстрактные методы в абстрактных классах

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

public abstract class AbstractMethods

Попытка объявить метод без реализации и без ключевого слова abstract , например, следующим образом:

public abstract class AbstractMethods

приведет к ошибке компиляции:

Missing method body, or declare abstract

Когда использовать абстрактные классы

Рекомендуется использовать абстрактный класс, когда вам нужно изменяемое состояние. В качестве примера можно привести класс AbstractList из Java Collections Framework, который использует состояние.

Если хранить состояние класса не нужно, обычно лучше использовать интерфейс.

Хороший пример использования абстрактных классов — паттерн "шаблонный метод" (template method). Шаблонный метод манипулирует переменными экземпляра (полями) внутри конкретных методов.

Различия между абстрактными классами и интерфейсами

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

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

Еще одно различие состоит в том, что интерфейс может быть реализован классом или расширен другим интерфейсом, а класс может быть только расширен.

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

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

Таблица 1. Сравнение интерфейсов и абстрактных классов

Интерфейсы

Абстрактные классы

Могут содержать только final static поля. Интерфейс никогда не может изменять свое состояние.

Могут быть любые поля, в том числе статические, изменяемые и неизменяемые.

Класс может реализовывать несколько интерфейсов.

Класс может расширять только один абстрактный класс.

Может быть реализован с помощью ключевого слова implements.

Может расширять другой интерфейс с помощью extends.

Может быть только расширен с помощью extends.

Можно использовать только static final поля. Параметры и локальные переменные в методах.

Могут быть изменяемые поля экземпляра. Параметры и локальные переменные в методах.

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

Абстрактные классы с одним абстрактным методом не могут использоваться в лямбда-выражениях.

Не может быть конструктора.

Может содержать конструктор.

Могут быть абстрактные методы.

Могут быть default и static методы (c Java 8).

Могут быть private методы с реализацией (с Java 9).

Могут быть любые методы.

Задачка

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

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

public class AbstractResidentEvilInterfaceChallenge < static int nemesisRaids = 0; public static void main(String[] args) < Zombie zombie = () ->System.out.println("Graw. " + nemesisRaids++); System.out.println("Nemesis raids: " + nemesisRaids); Nemesis nemesis = new Nemesis() < public void shoot() < shoots = 23; >>; Zombie.zombie.shoot(); zombie.shoot(); nemesis.shoot(); System.out.println("Nemesis shoots: " + nemesis.shoots + " and raids: " + nemesisRaids); > > interface Zombie < Zombie zombie = () ->System.out.println("Stars. "); void shoot(); > abstract class Nemesis implements Zombie

Как вы думаете, какой будет вывод, когда мы запустим этот код? Выберите один из следующих вариантов:

Вариант 1

 Compilation error at line 4

Вариант 2

 Graw. 0 Nemesis raids: 23 Stars. Nemesis shoots: 23 and raids:1

Вариант 3

 Nemesis raids: 0 Stars. Graw. 0 Nemesis shoots: 23 and raids: 1
 Nemesis raids: 0 Stars. Graw. 1 Nemesis shoots: 23 and raids:1

Вариант 5

 Compilation error at line 6

Разбор задачи

Эта задачка демонстрирует понятия об интерфейсах, абстрактных методах и о некоторых других вещах. Давайте разберем код строка за строкой.

В первой строке main() присутствует лямбда-выражение для интерфейса Zombie. Обратите внимание, что в этой лямбде мы инкрементируем статическое поле. Здесь также можно было использовать поле экземпляра, но не локальную переменную, объявленную вне лямбда-выражения. То есть код компилируется без ошибок. Также обратите внимание, что это лямбда-выражение еще не выполняется, оно только объявлено, и поле nemesisRaids не будет увеличено.

Далее мы выводим значение поля nemesisRaids , которое еще не увеличено. Следовательно, вывод будет:

Nemesis raids: 0

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

В интерфейсе Zombie есть поле с типом интерфейса Zombie , объявленное с помощью лямбда-выражения. Поэтому, когда мы вызываем метод Zombie.zombie.shoot() , получим следующий вывод:

Stars. 

В следующей строке вызывается лямбда-выражение, которое мы создали в начале. Следовательно, переменная nemesisRaids будет увеличена. Однако, поскольку мы используем оператор постинкремента, она будет увеличена только после этого выражения. Следующий вывод будет:

Graw. 0

Далее вызовем метод shoot для nemesis , который изменяет поле экземпляра shoots на 23. Обратите внимание, что как раз здесь мы видим основную разницу между интерфейсом и абстрактным классом.

Наконец, мы выводим значение nemesis.shoots и nemesisRaids .

Nemesis shoots: 23 and raids: 1

Правильный ответ — вариант 3:

 Nemesis raids: 0 Stars. Graw. 0 Nemesis shoots: 23 and raids: 1

Материал подготовлен в преддверии старта специализации Java-разработчик.

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

  • java
  • абстрактные классы
  • интерфейсы
  • бинарный поиск
  • Блог компании OTUS
  • Программирование
  • Java

Абстрактные классы и интерфейсы

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

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

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

Один класс может использовать несколько интерфейсов. Этим объясняется их популярность в Java, так как здесь отсутствием множественное наследование классов.

В Java, чтобы объявить класс абстрактным, надо в заголовке прописать слово abstract. Также обычно должен быть хотя бы один абстрактный метод. Рассмотрим пример:

public class AbstrTest  public static void main(String[] args)  UsualClass a = new UsualClass(); a.strPrint(); a.intPrint(); > > abstract class AbstrClass  abstract void strPrint(); void intPrint()  System.out.println(1); > > class UsualClass extends AbstrClass  void strPrint()  System.out.println("hi"); > >

В данном случае

  • Нельзя создавать объекты от класса AbstrClass, так как в его заголовке есть слово abstract. (Однако переменную такого типа можно было бы создать.)
  • Нельзя опустить реализацию метода strPrint() в классе UsualClass, поскольку он наследник абстрактного класса, в котором указанный метод объявлен абстрактным.
  • Абстрактные методы не имеют тел.
  • Если бы класс AbstrClass не содержал абстрактный strPrint(), или метод был бы не абстрактным, то в UsualClass можно было бы не переопределять данный метод. Таким образом, объявляя абстрактные методы, мы заставляем дочерние классы придерживаться определенного стандарта.
  • Абстрактный класс может не иметь абстрактных методов. Отличие такого класса от обычного родительского только в том, что от него нельзя создавать объекты.

При определении интерфейсов вместо class пишется ключевое слово interface. Если есть родительские интерфейсы, они также как в случае классов перечисляются после слова extends. У интерфейсов не может быть родительских классов.

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

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

Если класс является наследником как другого класса, так интерфейса, в его заголовке сначала пишется extends имя_класса, затем implements имя_интерфейса.

. interface InterFace  String getStr(); > class UsualClass extends AbstrClass implements InterFace  void strPrint()  System.out.println("hi"); > public String getStr()  return "HI"; > >

Если не указать слово public в реализации метода getStr(), будет ошибка.

Зачем нужны абстрактные методы, будь они в абстрактных классах или интерфейсах, если вся их реализация ложится на плечи дочерних классов? Если вы создаете группу порожденных от сестринских классов объектов, то можете присваивать их переменным типа абстрактного класса или интерфейса и обрабатывать всю группу, например, в одном цикле. У всей группы будут одни и те же методы, хотя реализация будет зависеть от типа объекта.

import java.util.ArrayList; public class ListObjects  public static void main(String[] args)  ArrayListAnimal> house = new ArrayList<>(); house.add(new Cat()); house.add(new Dog()); house.add(new Dog()); for (Animal animal : house)  animal.voice(); > > > abstract class Animal  abstract void voice(); > class Cat extends Animal  void voice()  System.out.println("Meow"); > > class Dog extends Animal  void voice()  System.out.println("Woof"); > >

Приведенная программа один раз мяукнет и два раза гавкнет, так как для каждого животного будет вызвана его реализация метода. Это также пример полиморфизма.

В случае наследования интерфейса было бы так:

. interface Animal  void voice(); > class Cat implements Animal  public void voice()  System.out.println("Meow"); > > class Dog implements Animal  public void voice()  System.out.println("Woof"); > >

В Java над переопределяемыми методами принято писать аннотацию @Override. Так при взгляде на класс сразу понятно, что метод не определяется, а переопределяется.

В последних версиях языка Java в интерфейсах можно писать реализацию методов. Перед такими методами добавляется ключевое слово default:

interface Animal  void voice(); default void drink()  System.out.println("I drink!"); > >

X Скрыть Наверх

Программирование на Java. Курс

C ++ - Интерфейсы (абстрактные классы)

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

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

Класс делается абстрактным, объявляя хотя бы одну из своих функций чистой виртуальной функцией. Чистая виртуальная функция задается путем размещения в объявлении «= 0» следующим образом:

class Box < public: // pure virtual function virtual double getVolume() = 0; private: double length; // Length of a box double breadth; // Breadth of a box double height; // Height of a box >;

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

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

Классы, которые могут использоваться для создания объектов, называются конкретными классами .

Пример абстрактного класса

Рассмотрим следующий пример, когда родительский класс предоставляет интерфейс базовому классу для реализации функции getArea () -

#include using namespace std; // Base class class Shape < public: // pure virtual function providing interface framework. virtual int getArea() = 0; void setWidth(int w) < width = w; >void setHeight(int h) < height = h; >protected: int width; int height; >; // Derived classes class Rectangle: public Shape < public: int getArea() < return (width * height); >>; class Triangle: public Shape < public: int getArea() < return (width * height)/2; >>; int main(void) < Rectangle Rect; Triangle Tri; Rect.setWidth(5); Rect.setHeight(7); // Print the area of the object. cout 

Когда приведенный выше код компилируется и выполняется, он производит следующий результат:

Total Rectangle area: 35 Total Triangle area: 17

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

Стратегия проектирования

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

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

Эта архитектура также позволяет легко добавлять новые приложения в систему даже после того, как система была определена.

Интерфейсы в C ++ (Абстрактные классы)

Интерфейс описывает поведение или возможности класса C ++ без привязки к конкретной реализации этого класса.

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

Класс становится абстрактным, объявляя по крайней мере одну из его функций чисто виртуальной функцией. Чистая виртуальная функция указывается путем помещения “= 0” в ее объявление следующим образом:

class Box < public: // pure virtual function virtual double getVolume() = 0; private: double length; // Length of a box double breadth; // Breadth of a box double height; // Height of a box >;

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

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

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

Пример абстрактного класса

Рассмотрим следующий пример, где родительский класс предоставляет интерфейс к базовому классу для реализации функции с именем getArea ()

#include using namespace std; // Base class class Shape  public: // pure virtual function providing interface framework. virtual int getArea() = 0; void setWidth(int w)  width = w; > void setHeight(int h)  height = h; > protected: int width; int height; >; // Derived classes class Rectangle: public Shape  public: int getArea()  return (width * height); > >; class Triangle: public Shape  public: int getArea()  return (width * height)/2; > >; int main(void)  Rectangle Rect; Triangle Tri; Rect.setWidth(5); Rect.setHeight(7); // Print the area of the object. cout  <"Total Rectangle area: "  <Rect.getArea() < endl; Tri.setWidth(5); Tri.setHeight(7); // Print the area of the object. cout  <"Total Triangle area: "  <Tri.getArea() < endl; return 0; >

Когда приведенный выше код компилируется и выполняется, он дает следующий результат –

Total Rectangle area: 35 Total Triangle area: 17

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

Разработка стратегии

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

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

Эта архитектура также позволяет легко добавлять новые приложения в систему даже после ее определения.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *