Для чего нужен абстрактный класс в java
Абстрактный класс в Java
Привет! Это статья про абстрактный класс в Java, а также об абстрактных методах. Это не самая простая тема, потому что она, ну. слегка абстрактная 🙂 Поэтому читайте внимательно!
Что такое абстрактный класс
Вы наверняка уже знакомы с понятием класса. Если нет, почитайте эту статью:
Обычно мы создаем классы для того, чтобы пользоваться их объектами. Например, мы создаем объекты типа String, или Scanner, или FileWriter для дальнейшей работы.
Тем не менее, объекты классов нам нужны не всегда. Иногда нам просто нужен суперкласс (т.е. класс, который мы будем наследовать), в котором будут задаваться какие-то общие для наследников методы и характеристики.
Представим, что у нас есть три типа животных:
1. Cобака (класс Dog)
2. Кошка (класс Cat)
3. Корова (класс Cow)
По навыкам, допустим, Ваш персонаж может:
3. Делать двойне сальто
Это все навыки: навык «красться», навык «бежать». Но навыка «навык» не существует. Верно?
По оружию, допустим, в игре есть такие опции:
Как выглядит абстрактный класс
Создать абстрактный класс очень просто. Просто перед словом «class» нужно дописать «abstract«:
Давайте посмотрим на примере. Создадим класс MyClass (пока без «abstract«):
Абстрактные классы и методы
Продолжаем тему наследования и на этом занятии поговорим об абстрактных классах. Абстрактные классы есть во многих языках программирования, в том числе и в Java. Они позволяют описывать поля, методы, но не требуют их конкретизации и реализации. Например, мы описываем класс автомобиль (Car), но пока незнаем как будут реализованы его методы. В этом случае можно объявить его как абстрактный и просто прописать переменные и методы без конкретного наполнения:
Смотрите, мы здесь определили одно поле model и три абстрактных метода: go, stop и draw. Абстракция в данном случае означает, что мы знаем что хотим от автомобиля, но пока незнаем как это будем делать. Своего рода, это некий набросок – абстракция, причем, абстракция на уровне класса и методов.
В действительности, если класс содержит хотя бы один абстрактный метод, то он должен быть объявлен как абстрактный. И, если мы попытаемся убрать ключевое слово abstract в определении класса, то возникнет ошибка. С другой стороны, если бы данный класс не содержал бы ни одного абстрактного метода, например, вот так:
То ключевое слово abstract можно как использовать, так и не использовать. Тогда в чем отличие этого абстрактного класса Car от такого же, но не абстрактного? В действительности, только одним: для абстрактных классов нельзя создавать экземпляры, то есть, вот такая строчка приведет к ошибке:
Но, если убрать слово abstract, то ошибки уже не будет. Конечно, это скорее искусственный пример, показывающий отличие в определении абстрактного и не абстрактного классов. В реальности, абстрактные классы, как правило, содержат абстрактные методы, как мы это сделали вначале. Но наряду с абстрактными методами можно записывать и обычные, например, так:
Здесь используется сеттер для задания поля model.
Ну, хорошо, мы можем объявлять абстрактные классы и методы, но зачем они нужны, если нельзя ни создавать объекты такого класса, ни вызывать такие методы? Все верно. И такие классы создаются исключительно для дальнейшего наследования и конкретизации их работы уже в дочерних классах. А абстракция позволяет сразу описать необходимые интерфейсы (то есть, методы), через которые в дальнейшем будут вызываться соответствующие переопределенные методы дочерних классов. Это часто очень удобно при реализации больших проектов, когда в целом определены информационные потоки через абстрактные классы и интерфейсы, а далее, создаются производные классы для их конкретного наполнения, реализации.
Итак, давайте тоже наполним конкретикой наш абстрактный класс Car. Для этого определим дочерний класс и назовем его, например, ToyotaCorolla. Если написать вот такие строчки:
то интегрированная среда нам сразу укажет на ошибку. Дело в том, что в нашем абстрактном классе определены три виртуальных метода, поэтому мы обязаны их здесь переопределить:
Теперь никаких ошибок не будет и мы можем создать экземпляр этого класса:
Или, используя обобщенный тип ссылок на абстрактный класс:
И, далее, мы можем вызывать методы go, stop и draw, определенные в дочернем классе:
Давайте для примера добавим еще один дочерний класс ToyotaCamry:
Определим массив обобщенных ссылок в функции main:
Присвоим им экземпляры дочерних классов:
И вызовем общие методы базового класса Car, реализованные в дочерних классах:
Видите, благодаря тому, что в базовом классе прописаны виртуальные методы go, stop и draw, мы имеем возможность вызывать их, используя единый интерфейс – ссылки на экземпляры базового класса Car. Это еще один пример полиморфизма в ООП.
Но здесь у вас может возникнуть вопрос: зачем городить огород, придумывать абстрактный класс Car, когда все то же самое можно было сделать и с обычным классом Car. Да, все верно, например, можно прописать базовый класс в виде:
И все тоже работало бы. Но в такой реализации есть принципиальные отличия от предыдущей (с абстрактным классом). Во-первых, здесь нам приходится прописывать пустые реализации у методов и такие методы можно уже не переопределять в дочерних классах. Абстрактные же методы обязаны быть определены в производных классах и это, с одной стороны, гарантирует их требуемое наполнение, а с другой, помогает избежать ошибок, если программист забудет их определить.
Вторым важным моментом отличия абстрактных классов от обычных – невозможность создания их экземпляров. Это позволяет дополнительно защитить программу от ее нежелательного использования, когда класс должен браться исключительно в качестве базового и не разрешать создавать свои экземпляры.
Вот эти моменты обусловили появление абстрактных классов и методов в языке Java.
Подвиг 1. Объявите абстрактный класс Geom для представления геометрических фигур с полями: width, color для определения толщины и цвета линии, а также с абстрактным методом draw() для рисования конкретного графического примитива. Затем, запишите дочерние классы Line, Rect, Ellipse для представления линий, прямоугольников и эллипсов. Определите в них поля для хранения координат этих фигур и метод draw() для их рисования. Создайте обобщенные ссылки Geom на объекты дочерних классов и вызовите у них метод draw().
Подвиг 2. Объявите абстрактный класс Recipes (рецепты) с полями: название, тип (вегетарианский/обычный). И абстрактными методами: showIngredients (показать ингредиенты), showRecipe (показать рецепт). Описать несколько дочерних классов: Salad (для салатов), Pizza (для пицц), Porridge (для каш). В каждом дочернем классе определить поле для списка ингредиентов (в виде строки) и описания самого рецепта (в виде строки). А также реализовать абстрактные методы базового класса Recipes. Создать несколько экземпляров дочерних классов и через общий интерфейс (в виде ссылок типа Recipes) вызвать методы showRecipe и showIngredients.
Видео по теме
#11 Концепция объектно-ориентированного программирования (ООП)
Абстрактный класс в Java
В продолжения статьи об классах и методах в java сегодня затронем тему абстрактного класса:
Абстрактный класс — это класс, в объявлении которого есть ключевое слово abstract. Его отличие от обычного класса в том, что нельзя создать объект или экземпляр данного класса. Все остальное в абстрактном классе остается таким как и в обычном. У него есть методы. Только абстрактный класс может иметь абстрактные методы — у которых нет реализации, только объявление. Это означает, что абстрактный метод должен быть реализован в классе-наследнике. Для того чтобы полностью понимать картину абстрактный классов и методов нужно владеть таким понятием как ООП. Ведь абстрактные классы чаще всего используются при реализации наследования.
Теперь когда мы немного познакомились с теорией, предлагаю перейти к практике:
Выше реализован пример абстрактного класса. Как уже говорилось: чтобы класс стал абстрактным нужно добавить к его объявлению ключевое слово abstract.
Абстрактный класс может содержать как обычные, так и абстрактные методы. Давайте реализуем и те, и другие.
public abstract class AbstractClassExample <
public abstract void abstractMethodExample ( ) ;
Класс, который унаследуется от примера выше должен обязательно реализовать абстрактный метод abstractMethodExample. Реализовывать обычный метод в классе-наследнике не обязательно. В случае, если наследник не реализует обычный метод абстрактного метода — выполняется реализация родителя.
public abstract class AbstractClassExample <
public abstract void abstractMethodExample ( ) ;
class AbstractClassRealization extends AbstractClassExample <
class Main <
public static void main ( String [ ] args ) <
AbstractClassRealization abstractClassRealization = new AbstractClassRealization ( ) ;
abstractClassRealization. abstractMethodExample ( ) ;
abstractClassRealization. classicMethodExample ( ) ;
>
>
Результатом выполнения кода будет:
В случае, если в наследнике переопределен обычный метод, будет выполняться его реализация.
public abstract class AbstractClassExample <
public abstract void abstractMethodExample ( ) ;
class AbstractClassRealization extends AbstractClassExample <
class Main <
public static void main ( String [ ] args ) <
AbstractClassRealization abstractClassRealization = new AbstractClassRealization ( ) ;
abstractClassRealization. abstractMethodExample ( ) ;
abstractClassRealization. classicMethodExample ( ) ;
>
>
В примере выше сработает реализация переопределенного метода в наследнике.
В самом начале мы говорили, что создать экземпляр абстрактного класса нельзя. Это верно. Но, можно создать переменную этого класса. Внимательно посмотрите на пример ниже.
public abstract class AbstractClassExample <
public abstract void abstractMethodExample ( ) ;
class AbstractClassRealization extends AbstractClassExample <
class Main <
public static void main ( String [ ] args ) <
AbstractClassRealization abstractClassRealization = new AbstractClassRealization ( ) ;
abstractClassRealization. abstractMethodExample ( ) ;
abstractClassRealization. classicMethodExample ( ) ;
//мы создали переменную с типом абстрактного класса
AbstractClassExample abstractClassExample = new AbstractClassRealization ( ) ;
//но сработает всеравно метод класса наследника
abstractClassExample. classicMethodExample ( ) ;
>
>
Результатом выполнения кода будет:
Данный или похожий пример очень часто можно увидеть в различных тестах. Здесь важно понимать, что будет выполняться метод экземпляра (new AbstractClassRealization()), а не метод типа (AbstractClassExample).
Еще очень часто на собеседованиях можно услышать вопрос: может ли обычный класс иметь абстрактный метод? Ответ: не может. Абстрактный класс может иметь абстрактный и обычный метод, но обычный класс не может иметь абстрактный метод.
Это все, что хотелось рассказать об абстрактных классах в Java. С большим познанием основ ооп и принципов построения систем, Вы сможете умело управлять различными видами классов. И, однажды, абстрактный класс сможет выручить в сложных ситуациях программирования и проектирования систем.
Доброго времени суток!
Тема весьма сложная для начинающего, потому возник вопрос, для чего все-таки нужны абстрактные классы, наследование — это понятно, но зачем тут нужны абстрактные классы, покажите на примере, где это является необходимостью
Здравствуйте. На первый взгляд: абстрактный класс можно заменить на интерфейс и это действительно так. НО! В абстрактном классе могут быть уже реализованные методы. Тогда как в интерфейсе идет только объявление методов. Сейчас, когда уже есть java11 в интерфейсе может быть один реализованный метод. В абстрактном классе же можно объявить и реализовать сколько угодно методов. Подкреплю теорию примером. Допустим у вас есть класс Animal в котором объявлены методы voice(), walk(), name(). Допустим первые 2 метода вы решили сделать абстрактными, а последний реализовать. Когда класс будет наследовать Animal он должен будет обязательно реализовать методы voice(), walk() так как они абстрактные, но метод name() ему реализовывать не обязательно. Допустим в данном методе будет выводиться строка «Я животное». Ваш товарищ по команде будет наследовать ваш абстрактный класс Animal и ему обязательно подсветит, что нужно реализовать методы voice(), walk(), а метод name() он будет реализовывать уже по усмотрению. Он может как переопределить такой метод, так и оставить реализацию родителя.
Разумеется, пример очень банальный, но на реальных и сложных проектах абстрактный класс часто приходит на выручку:)
Абстрактные классы Java и методы
Это класс, который не может быть создан, то есть вы не можете создавать новые экземпляры абстрактного класса. Целью является функционирование в качестве базы для подклассов.
Объявление
Нужно добавить ключевое слово abstract в объявление класса:
Теперь вы не можете создавать экземпляры MyAbstractClass. Таким образом, следующий код больше не действителен:
Если вы попытаетесь скомпилировать код выше, компилятор сгенерирует ошибку, сказав, что вы не можете создать экземпляр MyAbstractClass, потому что это абстрактный класс.
Абстрактные методы
Вы объявляете их, добавляя ключевое слово abstract перед объявлением метода:
Информация об абстрактных методах:
Вот пример подкласса:
Обратите внимание, как MySubClass должен реализовывать абстрактный метод abstractMethod() из своего абстрактного суперкласса MyAbstractClass.
Единственный раз, когда подкласс абстрактного класса не вынужден реализовывать все абстрактные методы своего суперкласса – когда также является абстрактным.
Цель – функционировать как базовые классы, которые могут быть расширены подклассами для создания полной реализации. Например, представьте, что определенный процесс требует 3 шага:
Если шаги до и после действия всегда одинаковы, трехэтапный процесс может быть реализован в абстрактном суперклассе с помощью следующего кода:
Обратите внимание, как метод action() является абстрактным. Подклассы MyAbstractProcess теперь могут расширять MyAbstractProcess и просто переопределять метод action().
Когда вызывается метод process() подкласса, выполняется полный процесс, включая stepBefore() и stepAfter() абстрактного суперкласса и метод action() подкласса.
Конечно, MyAbstractProcess не должен был быть абстрактным, чтобы функционировать как базовый класс. Метод action() также не должен быть абстрактным. Вы могли бы просто использовать обычный класс. Однако, создав метод для реализации абстрактного, а значит и класса, вы четко дадите понять его пользователям, что он не должен использоваться как есть. Вместо этого его следует использовать в качестве базового класса для подкласса, а абстрактный метод должен быть реализован в подклассе.
В приведенном выше примере не было реализации по умолчанию для метода action(). В некоторых случаях ваш суперкласс может ее иметь. Тогда вы можете не делать метод абстрактным. Вы все равно можете сделать суперкласс абстрактным, даже если он не содержит абстрактных методов.
Вот более конкретный пример, который открывает URL-адрес, обрабатывает его и впоследствии закрывает соединение с URL-адресом.
Обратите внимание, что processURLData() является абстрактным методом, а URLProcessorBase – абстрактным классом. Подклассы URLProcessorBase должны реализовывать processURLData(), потому он абстрактный.
Подклассы URLProcessorBase могут обрабатывать данные, загруженные с URL-адресов, не беспокоясь об открытии и закрытии сетевого подключения к URL-адресу. Это делается с помощью URLProcessorBase. Подклассам нужно только беспокоиться об обработке данных из InputStream, переданных методу processURLData(). Это облегчает реализацию классов, обрабатывающих данные из URL.
Вот пример подкласса:
Обратите внимание, как подкласс реализует только метод processURLData(), и ничего более. Остальной код унаследован от суперкласса URLProcessorBase.
Вот пример того, как использовать класс URLProcessorImpl:
Вызывается метод process(), который реализован в суперклассе URLProcessorBase. Этот метод, в свою очередь, вызывает processURLData() в классе URLProcessorImpl.
Шаблона проектирования Template
Пример, который я показал вам выше с классом URLProcessorBase, на самом деле является примером шаблона проектирования Template. Он обеспечивает частичную реализацию некоторого процесса, который подклассы могут выполнять при расширении базового класса Template.
Абстрактные классы
1. Общий базовый класс
Сегодня просто день интересных тем. Помните ситуацию, когда мы ввели базовый класс ChessItem для упрощения всех классов шахматных фигур? Надеюсь, что да 🙂
Введение базового класса ChessItem помогает очень упростить код: не нужно вызвать методы каждого класса отдельно, можно легко хранить все объекты в одной коллекции и т.п.
Именно. Более того, создавать объекты типа ChessItem не имеет смысла. Это не фигура из шахмат, а всего лишь абстракция — класс, который мы сделали для удобства. Так работает абстракция из ООП : мы вынесли важные (общие для всех фигур) данные и методы в базовый класс, а их различия оставили в их классах.
2. Абстрактные классы
Вот три вещи, которые стоит знать об абстрактных классах.
Метод без реализации
Абстрактный класс
Запрет на создание объектов
Создавать объекты абстрактного класса нельзя. Такой код просто не скомпилируется.
Код | Описание |
---|---|
Этот код не скомпилируется | |
А так можно |
Наследование от абстрактного класса
Если вы наследовали свой класс от абстрактного класса, нужно переопределить все унаследованные абстрактные методы — написать для них реализацию. Иначе такой класс тоже придется объявить абстрактным.
Если в классе есть хотя бы один нереализованный метод, объявленный прямо в нем или унаследованный от класса-родителя, класс считается абстрактным.
И зачем это все нужно? Зачем нужны абстрактные классы? Разве нельзя вместо них использовать обычные? А вместо абстрактных методов просто писать две скобочки в качестве тела метода – <>?
То же и с абстрактным классом. Тот, кто написал этот класс, не хочет, чтобы создавались его объекты. Наоборот, он рассчитывает на то, что от его абстрактного класса будут наследоваться и переопределяться абстрактные методы.
Преимущество этого подхода проявляется в больших проектах. Чем больше классов, тем чётче приходится очерчивать их роли. Вы увидите преимущество этого подхода, и уже в ближайшем будущем. Все через это проходят.