Зачем конструктор абстрактному классу

Обновлено: 27.04.2024

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

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

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

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

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

Если ваш класс не объявляет конструктор, javac сделает для вас конструктор no-arg, do-nothing. Затем, когда ваш подкласс инициализирован, он вызовет сгенерированный конструктор no-op, и жизнь хороша.

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

конструкторы для абстрактных классов используются подклассами (вызываются из конструкторов подклассов с помощью super(params) ).

вы должны сделать эти конструкторы protected чтобы внести ясность.

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

использование может быть для инициализации общих атрибутов ie.

де-дублирование общих знаний / поведения.

Е. Г. автомобиля: все автомобили будут построены из тела и четырех колес и двигателя. Таким образом, Вы делаете эту часть конструкции в конструкторе для абстрактного класса автомобиля, вызывая такие функции, как Body (), Wheel(int x), Engine(). Каждый конкретный класс автомобилей будет иметь свою собственную реализацию Body (), Wheel() и Engine () - но все они будут делать те же шаги, чтобы построить автомобиль из них, поэтому нет необходимости дублировать эти шаги в каждом из этих классов. В этом случае вы реализуете это общее поведение в предке.

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

Вопрос: зачем создавать явный конструктор для АБК, если его объект создать нельзя (при наличии чистой виртуальной функции конечно) и вопрос второй: что за странный вызов конструктора АБК в конструкторе производного класса? Спасибо

  • Вопрос задан более трёх лет назад
  • 373 просмотра

Простой 3 комментария

myjcom

myjcom

и т.д. у него есть закрытые члены, доступ к которым имеет только сам класс

5ada4764457d2837325143.jpg

Абстрактные классы делят на интерфейсы и частично реализованные. Грань между ними такова:
• Интерфейс не имеет данных.
• У интерфейса все неабстрактные виртуальные методы представляют собой или эталонное поведение, или самую частую реализацию. В обоих случаях, если что, их надо не расширять, а переписывать с нуля.

Так вот, для интерфейсов таких конструкторов, разумеется, не нужно.

Например, между абстрактным потоком и файлом Win32 может быть такая иерархия: Stream → HandleStream → File. Stream — интерфейс, даже если там есть что-то типа


HandleStream содержит уже данные (дескриптор Win32), и это уже частично реализованный класс, который крутится вокруг этого дескриптора: в деструкторе вызов CloseHandle, конструктор может принимать дескриптор, полученный каким-то «левым» образом.

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

т.е. вызов конструктора в списке инициализаторов это нечто иное как изменение базового объекта в иерархии наследования, просто вызывая конструктор абстрактного класса мы меняем поведение вызывающего объекта?(производного от абстрактного)

sddvxd, Нет, мы даём недореализованному классу корректно инициализировать те куцые данные, которыми он распоряжается.

sddvxd, Если HandleStream распоряжается дескриптором Win32 — пусть он будет инициализирован тем, что надо.

Mercury13, а, нет, абстрактный класс для того и нужен чтобы создавать неполную картинку для основы строительства

Mercury13, сейчас тщательно перечитал ваш ответ и понял вот что:
- Для абстрактных интерфейсных классов не нужно делать вызов конструктора и производном классе из-за того, что в нем нет данных, а только функции
- Вызов конструктора родителя делается для изменения данных такового, в случае с File проверяем доступность дескриптора вызовом конструктора HandleStream

sddvxd, Недореализованный класс создаёт некий кусок функциональности, оставляя какие-то места конкретным потомкам.
Второй пример, из Delphi: TComponent реализует постановку на форму, загрузку и сохранение. TGraphicControl даёт компоненту графический вид, TWinControl — дескриптор окна Win32, TCustomControl — ещё кучу функций, позволяющих написать свой компонент с нуля.

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

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

Верно. Только «интерфейсный класс» не говорят, и в данном случае интерфейс — это как «интерфейс RS-232», то есть стандартный метод взаимодействия. Никаким пользовательским интерфейсом тут и не пахнет.

Вызов конструктора родителя делается для изменения данных такового, в случае с File проверяем доступность дескриптора вызовом конструктора HandleStream


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


Эталонная реализация — функция remainder из ответа.

Реализация, верная для большинства потомков,— ну, например.

Например, не могут позиционировать порты, каналы, архивы (последние теоретически могут, но долго, сложно и редко нужно).

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

Насколько я знаю, имея такую иерархию наследования - Stream → HandleStream → File создаем объект File - вызываются конструкторы сверху вниз. От базового к текущему, чтобы создать данные из родителей. Получается, если мы делаем такую манипуляцию: File::File(. ) : HandleStream(. ) мы вызываем конструктор прошлого родителя, а значит порядок вызова конструкторов будет такой:
Stream → HandleStream → File - не изменился, поскольку конструктор не вызывается, пока не выполнится код со списком инициализаторов, а если мы сделаем так: File::File(. ) : Stream(. ), то порядок поменяется на такой:
Stream → HandleStream → Stream → File - я правильно понимаю?


Не позволяет такого Си с крестами. Мы обязаны вызвать какой-то отцовский конструктор: хоть без параметров, но какой-то нужно.
Остальное верно.

1. Могут ли в языке Java у абстрактного класса быть конструкторы?

Да, в абстрактном классе в Java можно объявить и определить конструкторы. Поскольку создавать экземпляры абстрактных классов нельзя, вызвать такой конструктор можно только при формировании цепочки конструкторов, то есть при создании экземпляра конкретного класса-реализации. Но представьте, что интервьюер задаст затем вопрос: а какой смысл в конструкторе, если создать экземпляр абстрактного класса все равно нельзя? Дело в том, что его всё равно можно использовать для задания начальных значений общих переменных, объявленных в абстрактном классе и используемых различными реализациями. Даже если вы не объявили никакого конструктора, компилятор добавит в абстрактный класс конструктор по умолчанию без аргументов. Без него ваш подкласс не скомпилируется, поскольку первый оператор в любом конструкторе представляет собой неявный вызов super() – конструктора суперкласса по умолчанию в языке Java.

2. Могут ли абстрактные классы в языке Java реализовывать интерфейсы? Должны ли они реализовывать все методы?

Да, абстрактные классы могут реализовывать интерфейсы с помощью ключевого слова implements . Поскольку они абстрактные, то не обязаны реализовывать все методы. Наличие абстрактного базового класса и интерфейса для объявления типа является рекомендуемой практикой. Пример — интерфейс java.util.List и соответствующий абстрактный класс java.util.AbstractList . Поскольку AbstractList реализует все общие методы, то конкретные реализации (например, LinkedList и ArrayList ) не должны реализовать все методы, как в случае, если бы они реализовали интерфейс List напрямую. Это решение сочетает преимущество использования интерфейса для объявления типа и гибкость абстрактного класса для реализации всего общего поведения в одном месте. В книге Джошуа Блоха «Java. Эффективное программирование» есть отличная глава на тему использования интерфейсов и абстрактных классов в Java, для лучшего понимания имеет смысл её изучить.

3. Может ли абстрактный класс быть final?

Нет, не может. Ключевое слово final означает, что класс на вершине иерархии, и у него не может быть наследников. А абстрактный класс без наследников — это сферический конь в вакууме, так как нельзя создать экземпляр abstract class . Таким образом, если класс одновременно abstract и final , то у него нет наследников и нельзя создать его экземпляр. Компилятор Java выдаст ошибку, если сделать класс одновременно abstract и final .

4. Могут ли у абстрактного класса в языке Java быть статические методы?

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

5. Можно ли создать экземпляр абстрактного класса?

Нет, этого делать нельзя. Суть абстрактного класса заключается в том, что он не завершён, и его нужно завершить в классах-наследниках. То есть этот класс не готов к использованию. В нём, например, может отсутствовать реализация каких-то методов. Раз класс не готов к использованию, то нельзя создавать его объект. А вот экземпляры наследников абстрактного класса создавать можно. Компилятор Java выдаст ошибку, если программа попытается создать экземпляр абстрактного класса.

6. Обязательно ли в абстрактном классе должны быть абстрактные методы?

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

7. Каковы различия между абстрактным классом и интерфейсом в Java?

  • Интерфейс описывает только поведение (методы) объекта, а вот состояний (полей) у него нет (кроме public static final ), в то время как у абстрактного класса они могут быть.
  • Абстрактный класс наследуется (extends), а интерфейс — реализуется (implements). Мы можем наследовать только один класс, а реализовать интерфейсов — сколько угодно. Интерфейс может наследовать (extends) другой интерфейс/интерфейсы.
  • Абстрактные классы используются, когда есть отношение "is-a", то есть класс-наследник расширяет базовый абстрактный класс, а интерфейсы могут быть реализованы разными классами, вовсе не связанными друг с другом.

8. Когда имеет смысл предпочесть абстрактный класс интерфейсу и наоборот?

  • Вы хотите поделиться кодом между несколькими тесно связанными классами.
  • Вы ожидаете, что классы, которые расширяют ваш абстрактный класс, имеют много общих методов или полей, или требуют других модификаторов доступа, кроме public (например, protected и private ).
  • Вы хотите объявить нестатические или не-final поля. Это позволяет вам определять методы, которые могут получить доступ и изменить состояние объекта, которому они принадлежат.
  • Вы ожидаете, что несвязанные классы будут реализовывать ваш интерфейс. Например, интерфейсы Comparable и Cloneable реализуются многими несвязанными классами.
  • Вы хотите определить поведение конкретного типа данных, но вам не важно, кто его реализует.
  • Вы хотите использовать множественное наследование типа.

9. Что такое абстрактный метод в языке Java?

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

Абстрактные классы в Java на конкретных примерах - 1

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

Почему классы называют «абстрактными»

Ты наверняка помнишь, что такое «абстракция» — мы это уже проходили :) Если вдруг подзабыл — не страшно, вспомним: это принцип ООП, согласно которому при проектировании классов и создании объектов необходимо выделять только главные свойства сущности, и отбрасывать второстепенные. Например, если будем проектировать класс SchoolTeacher — школьный учитель — вряд ли понадобится характеристика «рост». Действительно: для преподавателя эта характеристика не важна. Но вот если мы будем создавать в программе класс BasketballPlayer — игрок в баскетбол — рост станет одной из главных характеристик. Так вот, абстрактный класс — это максимально абстрактная, о-о-о-чень приблизительная «заготовка» для группы будущих классов. Эту заготовку нельзя использовать в готовом виде — слишком «сырая». Но она описывает некое общее состояние и поведение, которым будут обладать будущие классы — наследники абстрактного класса.

Примеры абстрактных классов Java

Почему в Java нет множественного наследования классов

Абстрактные классы в Java на конкретных примерах - 2

Мы уже говорили, что в Java нет множественного наследования, но так толком и не разобрались почему. Давай попробуем сделать это сейчас. Дело в том, что если бы в Java было множественное наследование, дочерние классы не могли бы определиться, какое именно поведение им выбрать. Допустим, у нас есть два класса — Toster и NuclearBomb : Как видишь, у обоих есть метод on() . В случае с тостером он запускает приготовление тоста, а в случае с ядерной бомбой — устраивает взрыв. Ой :/ А теперь представь, что ты решил (уж не знаю, с чего вдруг!) создать что-то среднее между ними. И вот он твой класс — MysteriousDevice ! Этот код, разумеется, нерабочий, и мы приводим его просто в качестве примера «а как оно могло бы быть»: Давай посмотрим, что у нас получилось. Загадочное устройство происходит одновременно и от Тостера, и от Ядерной Бомбы. У обоих есть метод on() , и в результате непонятно, какой из методов on() должен срабатывать у объекта MysteriousDevice , если мы его вызовем. Объект никак не сможет этого понять. Ну и в качестве вишенки на торте: у Ядерной Бомбы нет метода off() , так что если мы не угадали, отключить устройство будет нельзя. Именно из-за такой «непонятки», когда объекту неясно, какое поведение он должен выбрать, создатели Java отказались от множественного наследования. Впрочем, ты помнишь, что классы Java реализуют множество интерфейсов. Кстати, ты уже встречался в учебе как минимум с одним абстрактным классом! Хотя, может, и не заметил этого :) Это твой старый знакомый — класс Calendar . Он абстрактный, и у него есть несколько наследников. Одним из них — GregorianCalendar . Ты уже пользовался им в уроках о датах :) Вроде бы все понятно, остался только один момент: в чем все-таки принципиальная разница между абстрактными классами и интерфейсами? Зачем в Java добавили и то, и другое, а не ограничились чем-то одним? Этого ведь вполне могло хватить. Об этом поговорим в следующей лекции! До встречи:)

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

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

Так вот, все работает хорошо, вот только есть сомнение, что как-то не по фен-шую эти конструкторы сделаны как оптимизировать?

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

А что вы подразумеваете под оптимизацией? Что именно, по вашему мнению, нуждается в улучшении? Какой параметр?

@andreycha Спасибо за ответ! Выбор сделан в пользу абстрактного класса, а не интерфейса, чтобы не дублировать в наследниках N строк с объявлением полей, их геттерами и сеттерами. Классы полностью идентичны по своей сути, разница только в логике ключевого метода

2 ответа 2

По фэн шую, конструктор абстрактного класса должен быть protected , то есть:

На сколько я понял, у вас примерно следующая ситуация:

Основная причина для создания новых классов - разное поведение метода. Если вы вынесете этот метод в функциональный интерфейс, то все равно вам придется объявлять два класса с транзитными конструкторами (которые вызывают super).

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

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

Другой вариант - не делать несколько классов, а использовать изначальный класс, который абстрактный, т.е. сделать его не абстрактным, а ему в качестве параметра передавать реализацию того самого метода.

Читайте также: