Как в терминологии java принято называть родительский класс
Перейти к содержимому

Как в терминологии java принято называть родительский класс

  • автор:

Inheritance

In the preceding lessons, you have seen inheritance mentioned several times. In the Java language, classes can be derived from other classes, thereby inheriting fields and methods from those classes.

Definitions: A class that is derived from another class is called a subclass (also a derived class, extended class, or child class). The class from which the subclass is derived is called a superclass (also a base class or a parent class).

Excepting Object , which has no superclass, every class has one and only one direct superclass (single inheritance). In the absence of any other explicit superclass, every class is implicitly a subclass of Object .

Classes can be derived from classes that are derived from classes that are derived from classes, and so on, and ultimately derived from the topmost class, Object . Such a class is said to be descended from all the classes in the inheritance chain stretching back to Object .

The idea of inheritance is simple but powerful: When you want to create a new class and there is already a class that includes some of the code that you want, you can derive your new class from the existing class. In doing this, you can reuse the fields and methods of the existing class without having to write (and debug!) them yourself.

A subclass inherits all the members (fields, methods, and nested classes) from its superclass. Constructors are not members, so they are not inherited by subclasses, but the constructor of the superclass can be invoked from the subclass.

The Java Platform Class Hierarchy

The Object class, defined in the java.lang package, defines and implements behavior common to all classes—including the ones that you write. In the Java platform, many classes derive directly from Object , other classes derive from some of those classes, and so on, forming a hierarchy of classes.

All Classes in the Java Platform are Descendants of Object

At the top of the hierarchy, Object is the most general of all classes. Classes near the bottom of the hierarchy provide more specialized behavior.

An Example of Inheritance

Here is the sample code for a possible implementation of a Bicycle class that was presented in the Classes and Objects lesson:

A class declaration for a MountainBike class that is a subclass of Bicycle might look like this:

MountainBike inherits all the fields and methods of Bicycle and adds the field seatHeight and a method to set it. Except for the constructor, it is as if you had written a new MountainBike class entirely from scratch, with four fields and five methods. However, you didn't have to do all the work. This would be especially valuable if the methods in the Bicycle class were complex and had taken substantial time to debug.

What You Can Do in a Subclass

A subclass inherits all of the public and protected members of its parent, no matter what package the subclass is in. If the subclass is in the same package as its parent, it also inherits the package-private members of the parent. You can use the inherited members as is, replace them, hide them, or supplement them with new members:

  • The inherited fields can be used directly, just like any other fields.
  • You can declare a field in the subclass with the same name as the one in the superclass, thus hiding it (not recommended).
  • You can declare new fields in the subclass that are not in the superclass.
  • The inherited methods can be used directly as they are.
  • You can write a new instance method in the subclass that has the same signature as the one in the superclass, thus overriding it.
  • You can write a new static method in the subclass that has the same signature as the one in the superclass, thus hiding it.
  • You can declare new methods in the subclass that are not in the superclass.
  • You can write a subclass constructor that invokes the constructor of the superclass, either implicitly or by using the keyword super .

The following sections in this lesson will expand on these topics.

Private Members in a Superclass

A subclass does not inherit the private members of its parent class. However, if the superclass has public or protected methods for accessing its private fields, these can also be used by the subclass.

A nested class has access to all the private members of its enclosing class—both fields and methods. Therefore, a public or protected nested class inherited by a subclass has indirect access to all of the private members of the superclass.

Casting Objects

We have seen that an object is of the data type of the class from which it was instantiated. For example, if we write

then myBike is of type MountainBike .

MountainBike is descended from Bicycle and Object . Therefore, a MountainBike is a Bicycle and is also an Object , and it can be used wherever Bicycle or Object objects are called for.

The reverse is not necessarily true: a Bicycle may be a MountainBike , but it isn't necessarily. Similarly, an Object may be a Bicycle or a MountainBike , but it isn't necessarily.

Casting shows the use of an object of one type in place of another type, among the objects permitted by inheritance and implementations. For example, if we write

then obj is both an Object and a MountainBike (until such time as obj is assigned another object that is not a MountainBike ). This is called implicit casting.

If, on the other hand, we write

we would get a compile-time error because obj is not known to the compiler to be a MountainBike . However, we can tell the compiler that we promise to assign a MountainBike to obj by explicit casting:

This cast inserts a runtime check that obj is assigned a MountainBike so that the compiler can safely assume that obj is a MountainBike . If obj is not a MountainBike at runtime, an exception will be thrown.

Here the instanceof operator verifies that obj refers to a MountainBike so that we can make the cast with knowledge that there will be no runtime exception thrown.

Родительские и дочерние классы в Java

Java поддерживает наследование, концепцию ООП, когда один класс получает члены (методы и поля) другого.

Вы можете наследовать члены одного класса от другого, используйте ключевое слово extends как:

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

Ниже приведен пример, демонстрирующий наследование. Здесь у нас есть два класса, а именно Sample и MyClass. Где Sample является родительским классом, а класс с именем MyClass является дочерним классом в Java.

Пример

Средняя оценка 0 / 5. Количество голосов: 0

Спасибо, помогите другим — напишите комментарий, добавьте информации к статье.

Или поделись статьей

Видим, что вы не нашли ответ на свой вопрос.

Помогите улучшить статью.

Напишите комментарий, что можно добавить к статье, какой информации не хватает.

Кофе-брейк #95. Как решить проблему множественного наследования в Java

Кофе-брейк #95. Как решить проблему множественного наследования в Java - 1

Источник: FreeCodeCamp Java — один из самых популярных объектно-ориентированных языков программирования, используемых сегодня. Поскольку он не зависит от платформы, вы можете найти Java-приложения на всех типах устройств и в каждой операционной системе. А поскольку Java относительно легко выучить, это один из языков, который осваивают многие программисты. Важная особенность Java, с которой вы должны быть знакомы, — это наследование классов. Наследование позволяет оптимизировать код, облегчая повторное использование классов. Когда вы можете повторно использовать код, который уже был протестирован и отлажен, жизненный цикл разработки программного обеспечения становится короче и менее затратным. Хотя теоретически это простая концепция, кодирование отношений наследования требует внимания к деталям. Особенно в отношении множественного наследования, когда один дочерний класс наследует свойства от нескольких родительских классов. Java отвергает множественные отношения наследования, потому что они создают неоднозначность, но есть несколько способов добиться многих из тех же эффектов, если вы знаете, что делать. В этой статье мы рассмотрим проблемы с множественным наследованием и обсудим альтернативные варианты кодирования на Java.

Терминология наследования

  • Классы — это фундаментальная структура шаблона в объектно-ориентированных языках программирования. Класс определяет общие свойства для группы объектов.
  • Родительский класс : также известный как базовые классы или суперклассы. Родительский класс — это расширяемый класс, который предоставляет функции дочернему классу. Он допускает возможность повторного использования. Определения и функции родительского класса повторно используются при создании дочерних классов.
  • Дочерний класс : более обобщенно называемый подклассом, дочерний класс наследует функции от другого класса. Дочерние классы — это расширенные или производные классы.
  • Наследование : отношения между родительским и дочерним классами.

Типы наследования ООП

  • Одноуровневое наследование : когда дочерний класс наследует функции от единственного родительского класса.
  • Многоуровневое наследование : это многоуровневая форма одноуровневого наследования. При многоуровневом наследовании дочерний класс также может выступать в качестве родительского класса для других дочерних классов. Отношения между каждым уровнем линейны — никакие ветви не выходят выше, чем при множественном наследовании. В этом случае конечный дочерний класс имеет функции со всех уровней выше.
  • Иерархическое наследование : противоположность множественного наследования. В иерархическом наследовании единственный родительский класс имеет более одного дочернего класса. Таким образом, вместо того, чтобы иметь ветви над ним, он разветвляется внизу.
  • Гибридное наследование : как следует из названия, гибридное наследование представляет собой комбинацию других типов наследования.
  • Множественное наследование : при множественном наследовании дочерний класс имеет более одного родительского класса. Хотя Java и JavaScript не поддерживают множественное наследование, такие языки ООП, как C ++, поддерживают.
  • Многопутевое наследование : гибрид множественного, многоуровневого и иерархического наследования, при многопутевом наследовании дочерний класс наследует свои характеристики и функции от родительского класса и нескольких дочерних классов родительского класса. Поскольку многопутевое наследование основано на множественном наследовании, Java не поддерживает его использование.

Почему Java не поддерживает множественное наследование

Кофе-брейк #95. Как решить проблему множественного наследования в Java - 3

Основная проблема множественного наследования заключается в том, что оно может создавать неоднозначности в дочерних классах. В обзорном техническом документе 1995 года ведущий дизайнер Java Джеймс Гослинг заявил, что проблемы с множественным наследованием были одной из причин создания Java. Сложности, присущие множественному наследованию, наиболее отчетливо видны в проблеме алмаза. В задаче “ромб” родительский класс A имеет два различных дочерних класса B и C; то есть дочерние классы B и C расширяют класс A. Теперь мы создаем новый дочерний класс D, который расширяет как класс B, так и класс C. Обратите внимание, что у нас есть множественное наследование (D расширяет B и C), иерархическое наследование (B и C расширяют A) и многоуровневое наследование (D расширяет A, B и C). В проблеме ромба дочерние классы B и C наследуют метод от родительского класса A. И B, и C переопределяют унаследованный метод. Но новые методы в B и C противоречат друг другу. Окончательный дочерний класс D наследует два независимых и конфликтующих метода от своих нескольких родителей B и C. Неясно, какой метод класса D следует использовать, поэтому возникает двусмысленность. Другие языки программирования ООП реализуют различные методы решения неоднозначности множественного наследования.

Pro Java

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

Чтобы еще раз лучше отложилось в голове:

  • Суперкласс – это родительский класс
  • Подкласс – это класс наследник родительского класса

Объявление класса-наследника

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

E0001

В данном примере объявляется что класс Derived является наследником класса Example.

Теперь рассмотрим простой пример. Создадим родительский класс Robot и унаследуем от него класс робота-уборщика – RobotCleaner.

E0002

E0003

Как видите в классе наследнике нет ни каких методов и полей.

E0004

В классе с методом main() мы создаем по экземпляру классов Robot и RobotCleaner, устанавливаем имя для объекта rc и затем выводим информацию о наших роботах. И хотя в классе наследнике нет ни каких методов и полей, мы все же можем обращаться к ним, поскольку они унаследованы.

Вывод у программы следующий:

E0005

Доступ к членам и наследование

В нашем примере поле name объявлено с модификатором private, а методы с модификатором protected. Именно по этому мы могли использовать методы, вот если попробуем получить доступ к унаследованным полям на прямую, в обход методов, то компилятор выдаст нам ошибку:

E0006

Хотя подкласс включает в себя все члены своего суперкласса, он не может получать доступ к тем членам суперкласса, которые объявлены как private .

Чтобы исправить эту ситуацию можно объявить поле name в родительском классе Robot как protected и тогда мы сможем к нему обращаться из классов наследников.

Давайте сделаем это…

E0007

После этих изменений ошибка исчезнет и наш класс RobotCleaner откомпилируется.

Надеюсь вы заметили что в класс RobotCleaner мы добавили метод printName(), то есть мы расширили (extends) функционал в классе наследнике.

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

Иерархия классов

У каждого определяемого вами класса есть родительский класс. Если вы не указали родительский класс в операторе extends, то его родительским классом будет класс java.lang.Object . Класс Object уникален по двум причинам:

  • Это единственный класс в Java, у которого нет родителя.
  • Все классы Java наследуют методы класса Object.

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

E0008

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

Конструкторы подклассов

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

super(список_аргументов);

Список_аргументов определяет любые аргументы, требуемые конструктору в суперклассе, он может быть пустым для вызова конструктора суперкласса по умолчанию. Оператор super всегда должен быть первым выполняемым внутри конструктора подкласса .

E0009

Чтобы все стало понятнее, попрактикуемся. Я добавил в класс Robot конструктор по умолчанию (на примере слева). Теперь вывод у программы следующий:

E0010

Мы видим что конструктор по умолчанию был вызван два раза. Одни раз при создании объекта rb, второй – при создании объекта rc.

Здесь, пока, мы не использовали ключевое слово super, так как я хотел показать цепочку вызовов конструкторов. Именно на это и хочу обратить внимание, что Java сама подставила вызов конструктора суперкласса.

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

E0011

E0012

E0013

Как видно из трех отрывков кода наших классов, если мы уберем конструктор по умолчанию в классе Robot и добавим другой, с каким-либо параметром, а в классах RobotCleaner и RobotShow появятся ошибки компиляции. Обратите внимание на то, что в классе RobotCleaner нет вообще ни каких конструкторов. Java подставила туда вызов конструктора по умолчанию суперкласса, но поскольку он не определен в суперклассе Robot, то получилась ошибка компиляции.

Все это означает, что вызовы конструкторов объединяются в цепочку; при каждом создании объекта вызывается последовательность конструкторов: конструктор подкласса, конструктор родительского класса и далее вверх по иерархии классов до конструктора класса Object. Так как конструктор родительского класса всегда вызывается первым оператором конструктора подкласса, то операторы конструктора класса Object всегда выполняются первыми. Затем выполняются операторы конструктора подкласса и далее вниз по иерархии классов вплоть до конструктора класса, объект которого создается. Здесь есть важное следствие: когда вызван конструктор, он может положиться на то, что поля родительского класса уже проинициализированы.

Если конструктор не вызывает конструктор родительского класса, то Java делает это неявно . А если класс объявлен без конструктора ? как в нашем случае в классе RobotCleaner? В этом случае Java неявно добавляет конструктор по умолчанию . Конструктор по умолчанию не делает ничего, кроме вызова родительского конструктора по умолчанию. В нашем примере это привело к ошибке компиляции, так как у родительского класса Robot не был определен конструктор по умолчанию.

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

E0014

Теперь приведем наших роботов в более-менее рабочий вид. Класс Robot я оставил как есть, то есть без конструктора по умолчанию, но с конструктором принимающим строку. А вот класс RobotCleaner я изменил добавив конструктор по умолчанию, который вызывает конструктор этого же класса с параметром принимающим строку и вызывающим конструктор суперкласса, который так же принимает строку. Кажется немного замысловато, но вообще все достаточно просто. Так же пришлось изменить строку создающую объект rb в классе RobotShow. Теперь она имеет вид:

Robot rb = new Robot ( «NoNaMe» ) ;

Так пришлось сделать, поскольку в классе Robot у нас нет конструктора по умолчанию.

E0015

Теперь у нас все работает. Пример вывода программы представлен слева. Рабочий пример можно посмотреть в коммите Примеры наследования. Вызовы super и this.

Затенение полей родительского класса

В нашем классе RobotCleaner мы можем определить свое поле с именем name. В таком случае говорят что поле подкласса затеняет (shadows) или скрывает поле родительского класса. Как же мы тогда можем сослаться на поле name родительского класса Robot? Для этого существует специальный синтаксис, использующий ключевое слово super:

super.член_класса

Где член_класса может быть методом либо переменной экземпляра.

E0016

Чтобы лучше понять, попрактикуемся. Я изменил класс RobotCleaner как на примере слева. Другие классы я не менял. Теперь вывод у программы следующий:

E0017

Унаследованный метод setName() установил значение унаследованного от супер класса Robot поля name в объекте rc , а поле name класса RobotCleaner осталось не тронутым.

Теперь изменим классы RobotShow, Robot и RobotCleaner, так чтобы конструктор класса RobotCleaner устанавливал значение поля name для класса RobotCleaner и оставим другой метод этого класса без изменений. В классе Robot расскоментируем конструктор по умолчанию. А в классе RobotShow создадим объект rc при помощи конструктора по умолчанию.

Таким образом мы сможем изменить значение поля name в классе RobotCleaner.

E0019

E0018

Вывод у программы сейчас следующий:

E0021

E0020

Как видно из вывода поле name класса RobotCleaner получило значение «Cleaner».

Первую из последних двух строчек выводит первая строка в методе printName() класса RobotCleaner, воторя – соответственно выводит вторую.

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

System . out . println ((( Robot ) this ) . name ) ;

Вывод у программы не изменится. Мы просто поменяли метод обращения к полю name суперкласса.

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

Для примера возьмем три класса: А, В и С. Класс В является потомком класса А, а класс С потоком класса В. В каждом классе есть поле x. А так же есть методы выводящие значение поля x для каждого класса. И еще в классе А есть метод printX(), который выводит значение поля х для класса А.

E00023

В классе А есть два метода которые выводят значение поля х для класса А. Причем оба этих метода наследуются потомками этого класса. Но чтобы не было путаницы в потомках используются свои методы для вывода значения поля х этих классов.

E0024

Класс В использует свой метод printB() для вывода своего поля х, которое затеняет поле х, класса А.

Так же в этом методе выводится значение поля х из класса А.

E0025

Как видно из кода класса С, мы можем обратиться к полю х класса А, который не является прямым родителем класса С через приведение типов (строка 10).

Вы не можете ссылаться на затененное поле x в родителе родителя с помощью вызова super.super.x. Это неправильный синтаксис.

Благодаря приведению классов можно ссылаться на поля вышестоящих родителей, если они открыты для доступа. Пример этого приведен в строках 25-27 класса АВС.

До настоящего времени мы обсуждали поля экземпляров. Поля класса (static) также могут быть затенены. Но в этом нет особого смысла.

Вывод у данной программы следующий:

Первые две строки выводятся командами в строках 12 и 13 класса АВС.

Вторые три строки выводятся командами в строках 15 и 16.

Третьи четыре строки выводятся командами в строках 18 и 19.

Затем вывод делают строки 21 – 23, ну это мы уже проходили. Это простой доступ к полям экземпляров.

Ну и на последок, в строках 25 – 27 мы видим доступ к полям родителей через приведение классов. Синтаксис может показаться немного запутанным, но на самом деле он логичный и простой.

На что следует обратить особенное внимание в этом примере так это на метод printX() в классе А, и на его вызовы на экземплярах классов В и С. Не смотря на затенение поля х в этих классах, метод printX() все время выводит поле х класса А. Это происходит потому, что методы родительского класса А могут работать только с полями своего же класса, так как ни чего не знают о полях в классах потомках.

Подобно полям, могут «затенятся» и методы, но это уже называется перегрузкой (override) методов, что является основой полиморфизма о котором мы скоро поговорим.

Чтобы еще чуть лучше усвоить как работает сокрытие полей, в класс АВС можно добавить еще три строчки:

b . printA () ;
c . printA () ;
c . printB () ;

Которые будут выводить следующее:

Класс А
Класс А
Класс B
Из В Класс А

То есть метод каждого класса выводит только сове собственное поле x.

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

Переменная суперкласса может ссылаться на объект подкласса

Ссылочной переменной суперкласса может быть присвоена ссылка на любой объект подкласса, производного от данного суперкласса. Этот аспект наследования будет весьма полезен во множестве ситуаций. Особенно когда познакомимся с полиморфизмом. И сразу лучше покажем на примере. Добавим в наш класс АВС еще несколько строк:

A ab ;
ab = new B () ;
ab . printA () ;
//ab.printB(); // ОШИБКА!
B bc = new C () ;
bc . printA () ;
bc . printB () ;
// bc.printc(); // ОШИБКА!

Данный код сгенерирует следующий вывод:

Класс А
Класс А
Класс B
Из В Класс А

Как видите пара строк в коде закомментирована, поскольку они ошибочны и не скомпилируются. Это происходит потому, что хотя, допустим, ссылочная переменная ab и содержит ссылку на объект класса B, но она может обратиться только к тем членам класса о которых известно классу А, поскольку является ссылочной переменной этого класса.

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

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

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

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