Abstract Methods and Classes
An abstract class is a class that is declared abstract —it may or may not include abstract methods. Abstract classes cannot be instantiated, but they can be subclassed.
An abstract method is a method that is declared without an implementation (without braces, and followed by a semicolon), like this:
If a class includes abstract methods, then the class itself must be declared abstract , as in:
When an abstract class is subclassed, the subclass usually provides implementations for all of the abstract methods in its parent class. However, if it does not, then the subclass must also be declared abstract .
Abstract Classes Compared to Interfaces
Abstract classes are similar to interfaces. You cannot instantiate them, and they may contain a mix of methods declared with or without an implementation. However, with abstract classes, you can declare fields that are not static and final, and define public, protected, and private concrete methods. With interfaces, all fields are automatically public, static, and final, and all methods that you declare or define (as default methods) are public. In addition, you can extend only one class, whether or not it is abstract, whereas you can implement any number of interfaces.
Which should you use, abstract classes or interfaces?
- Consider using abstract classes if any of these statements apply to your situation:
- You want to share code among several closely related classes.
- You expect that classes that extend your abstract class have many common methods or fields, or require access modifiers other than public (such as protected and private).
- You want to declare non-static or non-final fields. This enables you to define methods that can access and modify the state of the object to which they belong.
- You expect that unrelated classes would implement your interface. For example, the interfaces Comparable and Cloneable are implemented by many unrelated classes.
- You want to specify the behavior of a particular data type, but not concerned about who implements its behavior.
- You want to take advantage of multiple inheritance of type.
An example of an abstract class in the JDK is AbstractMap , which is part of the Collections Framework. Its subclasses (which include HashMap , TreeMap , and ConcurrentHashMap ) share many methods (including get , put , isEmpty , containsKey , and containsValue ) that AbstractMap defines.
An example of a class in the JDK that implements several interfaces is HashMap , which implements the interfaces Serializable , Cloneable , and Map<K, V> . By reading this list of interfaces, you can infer that an instance of HashMap (regardless of the developer or company who implemented the class) can be cloned, is serializable (which means that it can be converted into a byte stream; see the section Serializable Objects), and has the functionality of a map. In addition, the Map<K, V> interface has been enhanced with many default methods such as merge and forEach that older classes that have implemented this interface do not have to define.
Note that many software libraries use both abstract classes and interfaces; the HashMap class implements several interfaces and also extends the abstract class AbstractMap .
An Abstract Class Example
In an object-oriented drawing application, you can draw circles, rectangles, lines, Bezier curves, and many other graphic objects. These objects all have certain states (for example: position, orientation, line color, fill color) and behaviors (for example: moveTo, rotate, resize, draw) in common. Some of these states and behaviors are the same for all graphic objects (for example: position, fill color, and moveTo). Others require different implementations (for example, resize or draw). All GraphicObject s must be able to draw or resize themselves; they just differ in how they do it. This is a perfect situation for an abstract superclass. You can take advantage of the similarities and declare all the graphic objects to inherit from the same abstract parent object (for example, GraphicObject ) as shown in the following figure.
Classes Rectangle, Line, Bezier, and Circle Inherit from GraphicObject
First, you declare an abstract class, GraphicObject , to provide member variables and methods that are wholly shared by all subclasses, such as the current position and the moveTo method. GraphicObject also declares abstract methods for methods, such as draw or resize , that need to be implemented by all subclasses but must be implemented in different ways. The GraphicObject class can look something like this:
Each nonabstract subclass of GraphicObject , such as Circle and Rectangle , must provide implementations for the draw and resize methods:
When an Abstract Class Implements an Interface
In the section on Interfaces , it was noted that a class that implements an interface must implement all of the interface's methods. It is possible, however, to define a class that does not implement all of the interface’s methods, provided that the class is declared to be abstract . For example,
In this case, class X must be abstract because it does not fully implement Y , but class XX does, in fact, implement Y .
Что такое абстрактный класс в java
Кроме обычных классов в Java есть абстрактные классы . Абстрактный класс похож на обычный класс. В абстрактном классе также можно определить поля и методы, но в то же время нельзя создать объект или экземпляр абстрактного класса. Абстрактные классы призваны предоставлять базовый функционал для классов-наследников. А производные классы уже реализуют этот функционал.
При определении абстрактных классов используется ключевое слово abstract :
Но главное отличие состоит в том, что мы не можем использовать конструктор абстрактного класса для создания его объекта. Например, следующим образом:
Кроме обычных методов абстрактный класс может содержать абстрактные методы . Такие методы определяются с помощью ключевого слова abstract и не имеют никакой реализации:
Производный класс обязан переопределить и реализовать все абстрактные методы, которые имеются в базовом абстрактном классе. Также следует учитывать, что если класс имеет хотя бы один абстрактный метод, то данный класс должен быть определен как абстрактный.
Зачем нужны абстрактные классы? Допустим, мы делаем программу для обслуживания банковских операций и определяем в ней три класса: Person, который описывает человека, Employee, который описывает банковского служащего, и класс Client, который представляет клиента банка. Очевидно, что классы Employee и Client будут производными от класса Person, так как оба класса имеют некоторые общие поля и методы. И так как все объекты будут представлять либо сотрудника, либо клиента банка, то напрямую мы от класса Person создавать объекты не будем. Поэтому имеет смысл сделать его абстрактным.
Другим хрестоматийным примером является система геометрических фигур. В реальности не существует геометрической фигуры как таковой. Есть круг, прямоугольник, квадрат, но просто фигуры нет. Однако же и круг, и прямоугольник имеют что-то общее и являются фигурами:
Абстрактные классы и интерфейсы в Java
Абстрактные классы и интерфейсы встречаются повсюду как в Java-приложениях, так и в самом Java Development Kit (JDK). Каждый из них служит своей цели:
Интерфейс — это контракт, который должен быть реализован конкретным классом.
Абстрактный класс похож на обычный, но отличается тем, что может содержать абстрактные методы — методы без реализации, и нельзя создать экземпляр абстрактного класса.
Многие разработчики не видят разницы между интерфейсами и абстрактными классами, но на самом деле между ними есть весьма существенное различие.
Интерфейсы
Интерфейс — это контракт, который реализуется в некотором классе. У интерфейса не может быть состояния, поэтому в нем нельзя использовать изменяемые поля экземпляра. В интерфейсе могут быть только неизменяемые final-поля.
Когда использовать интерфейсы
Интерфейсы очень полезны для уменьшения связанности (coupling) кода и реализации полиморфизма. Для примера давайте взглянем на интерфейс List из JDK:
Как вы, вероятно, заметили, код весьма краток и лаконичен. Здесь мы видим сигнатуры методов, которые будут реализованы в конкретном классе, реализующем этот интерфейс.
Контракт интерфейса List реализуется классами ArrayList , Vector , LinkedList и другими.
При использовании полиморфизма тип переменной объявляем как List , и присваиваем ей любую из доступных реализаций. Например:
В этом случае в каждом классе присутствует своя реализация методов. И это отличный пример использования интерфейсов. Если вы заметили, что ряд ваших классов содержит одинаковые методы, но с разными реализациями, то стоит использовать интерфейс.
Переопределение метода интерфейса
Помните, что интерфейс — это контракт, который должен быть реализован конкретным классом. Методы интерфейса неявно абстрактны и обязаны быть реализованы в классе, реализующем этот интерфейс.
Рассмотрим следующий пример:
Результат будет следующий:
Обратите внимание еще раз, что методы интерфейса неявно абстрактны и их не нужно явно объявлять как abstract.
Неизменяемые переменные
Еще одно правило, которое следует помнить, заключается в том, что интерфейс может содержать только неизменяемые переменные. Следующий код вполне рабочий:
Обратите внимание, что обе переменные неявно final и static . Это означает, что они являются константами, не зависят от экземпляра и не могут быть изменены.
При попытке изменить поля в интерфейсе Challenger , например, следующим образом:
будет ошибка компиляции:
Default-методы
После появления в Java 8 методов по умолчанию, некоторые разработчики решили, что интерфейсы стали абстрактными классами. Однако это не так, поскольку у интерфейсов не может быть состояния.
У методов по умолчанию может быть реализация, а у абстрактных методов — нет. Методы по умолчанию — результат появления лямбда-выражений и Stream API, но использовать их нужно с осторожностью.
В качестве примера default-метода из JDK можно привести метод forEach() из интерфейса Iterable . Вместо копирования кода этого метода во все реализации Iterable , мы можем переиспользовать метод forEach :
Любая реализация Iterable может использовать метод forEach() без необходимости реализации этого нового метода.
Давайте рассмотрим пример с методом по умолчанию:
Важно отметить, что у default-метода должна быть реализация и default-метод не может быть статическим.
Абстрактные классы
У абстрактных классов может быть состояние в виде изменяемых полей экземпляра. Например:
Абстрактные методы в абстрактных классах
Аналогично интерфейсам в абстрактных классах могут быть абстрактные методы. Абстрактный метод — это метод без тела (без реализации). Но в отличие от интерфейсов, абстрактные методы в абстрактных классах должны быть явно объявлены как абстрактные.
Попытка объявить метод без реализации и без ключевого слова 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).
Могут быть любые методы.
Задачка
Давайте изучим основные различия между интерфейсами и абстрактными классами с помощью небольшой задачки. Вы также можете посмотреть данный материал в формате видео (англ.).
В приведенном ниже коде объявлены интерфейс, абстрактный класс и используются лямбда-выражения.
Как вы думаете, какой будет вывод, когда мы запустим этот код? Выберите один из следующих вариантов:
Вариант 1
Вариант 2
Вариант 3
Вариант 5
Разбор задачи
Эта задачка демонстрирует понятия об интерфейсах, абстрактных методах и о некоторых других вещах. Давайте разберем код строка за строкой.
В первой строке main() присутствует лямбда-выражение для интерфейса Zombie. Обратите внимание, что в этой лямбде мы инкрементируем статическое поле. Здесь также можно было использовать поле экземпляра, но не локальную переменную, объявленную вне лямбда-выражения. То есть код компилируется без ошибок. Также обратите внимание, что это лямбда-выражение еще не выполняется, оно только объявлено, и поле nemesisRaids не будет увеличено.
Далее мы выводим значение поля nemesisRaids , которое еще не увеличено. Следовательно, вывод будет:
Еще один интересный момент заключается в том, что мы используем анонимный внутренний класс. Мы создаем не экземпляр абстрактного класса Nemesis , но экземпляр анонимного класса, расширяющего Nemesis . Также обратите внимание, что первый конкретный класс в иерархии наследования всегда будет обязан реализовать абстрактные методы.
В интерфейсе Zombie есть поле с типом интерфейса Zombie , объявленное с помощью лямбда-выражения. Поэтому, когда мы вызываем метод Zombie.zombie.shoot() , получим следующий вывод:
В следующей строке вызывается лямбда-выражение, которое мы создали в начале. Следовательно, переменная nemesisRaids будет увеличена. Однако, поскольку мы используем оператор постинкремента, она будет увеличена только после этого выражения. Следующий вывод будет:
Далее вызовем метод shoot для nemesis , который изменяет поле экземпляра shoots на 23. Обратите внимание, что как раз здесь мы видим основную разницу между интерфейсом и абстрактным классом.
Наконец, мы выводим значение nemesis.shoots и nemesisRaids .
Правильный ответ — вариант 3:
Материал подготовлен в преддверии старта специализации Java-разработчик.
Недавно в рамках специализации прошел открытый урок, на котором мы обсудили алгоритм бинарного поиска, разобрались, почему он быстрее линейного. А также познакомились с понятием «О-большое». Делимся записью этого урока.
Абстрактные классы в Джаве
Джава – распространенный и достаточно легкий язык программирования. Приложив должные усилия, освоить его сможет любой.
Для того, чтобы писать на Java качественный софт, требуется изучить его базовые принципы, инструменты и функции. Немаловажную роль здесь играют объекты и функции. А еще – абстрактные классы Java. Именно о них пойдет речь далее.
Термины – что пригодится для понимания
Перед тем, как начинать изучать выбранное направление, стоит запомнить несколько ключевых понятий. Они пригодятся всем программистам, желающим писать софт на языках с объектно-ориентированными концепциями:
- Алгоритмы – инструкции, принципы и правила, которые необходимы для решения поставленной перед разработчиком задачи.
- API – интерфейс прикладного программирования. Процедуры и правила, а также протоколы, помогающие ПО взаимодействовать друг с другом и разнообразными службами.
- Аргумент – передаваемое в функцию или команду значение.
- Переменная – единица хранения информации. Именованная ячейка памяти.
- Объект – связанные между собой переменные, структурные данные и константы. Они обрабатываются и выбираются совместно.
- Константа – значение, которое по ходу выполнения кода никогда не меняется.
- Массив – список или группа схожих типов значений, прошедших предварительную группировку. Множество электронных материалов.
- Ключевое слово – слово, зарезервированное языком программирования. Необходимо для обозначения операций, команд, функций.
- Указатель – переменная, содержащая адрес места в памяти.
Также пользователю предстоит выяснить, что собой представляет метод и абстракция. Все это пригодится при попытках рассмотреть наглядный пример упомянутого ранее вида классов.
Несколько слов о методах
Метод – это блок кода, который имеет имя. Он объявляется внутри заданного класса. Содержит законченную последовательность операций, необходимых для решения поставленной изначально задачи. Соответствующий блок допускается использовать многократно.
Метод – функция, указывающая на то, что умеет делать имеющийся класс. В Java выступает членом classes. Обладает различными модификаторами доступа:
- Public – публичный. Методы и поля видимы другим классам в коде в пределах пакета или из внешних пакетов.
- Protected – «частный».
- Private – приватный. Методы и поля доступны в пределах заданного класса.
- По умолчанию. Применяется, когда модификатор отсутствует вовсе.
Теперь стоит рассмотреть абстрактные классы и методы более подробно.
Понятие класса
Class в Java – это шаблон или описание имеющегося в коде объекта. Object – это экземпляр класса. Пример – аналогия с человеком. У каждого есть две руки, ноги, глаза, туловище и так далее. Есть некий шаблон – это класс. Реально существующий человек – объект.
Задается класс при помощи ключевого слова class. Далее пишется его название и в фигурные скобках – описание. А именно – указываются поля и method.
Абстракции
Абстракция – качество общения с идеями, а не некоторыми событиями. Пример – электронная почта. Тут все скрыто от обычного пользователя: протоколы, а также детали происходящего во время отправки и получения сообщений. Но для того, чтобы написать и отослать e-mail, клиент может просто напечатать его содержимое. Далее – щелкнуть по кнопке, отвечающей за реализацию соответствующей функции.
Говоря об абстрактных классах в Джаве, нужно понимать, что собой представляет абстракция. Это – процедура скрытия деталей реализации от юзера. В конечном итоге пользователь увидит только итоговый функционал. Говоря простым языком – предоставляется информация о том, что делает объект, а не как он справляется с задачей.
Абстрактный класс
Класс, который содержит в себе ключевое слово abstract носит название абстрактного. Здесь программист должен запомнить следующее:
- abstract class может содержать абстрактные методы Java – без тела (public void get(););
- если обычный класс имеет хотя бы один единственный абстрактный метод, этот class объявляется абстрактным;
- реализовать абстрактный класс нельзя;
- для использования рассматриваемого элемента кода требуется наследовать его из другого класса – для обеспечения реализации методов абстрактного типа в его пределах.
Если применяется наследование абстрактного класса, требуется обеспечить полную реализацию всех соответствующих методов в нем.
Наглядный пример
Вот код, который продемонстрирует рассматриваемый компонент:
Здесь можно увидеть класс Employee – он такой же, как обычный класс в Джаве. Он абстрактный, но имеет три поля, а также 7 методов и один конструктор.
Можно создать new экземпляр класса Employee:
Но при компиляции система выдаст ошибку.
Наследование
Вот код, который поможет рассмотреть наглядный пример наследования:
- Не получится создать экземпляр класса Employee.
- Можно создать экземпляр Salary. Его используют для получения доступа к трем полям и методам Employee.
После обработки кода на экране появится следующий результат:
Абстрактные методы
Пример абстрактного класса Java был рассмотрен выше. Чтобы class содержал конкретный метод, но фактическая реализация осуществлялась за счет дочерних классов, можно объявить в «родителе» абстрактный method.
Здесь стоит запомнить, что:
- для объявления используется слово abstract;
- ключевое слово ставится перед именем метода при его объявлении;
- абстрактный метод имеет сигнатуру method, но не имеет тела;
- вместо фигурных скобок ставится точка с запятой в конце.
Специализированные дистанционные компьютерные курсы помогут лучше разобраться в том, что такое public abstract void, extends, classes и не только.