Наследование Java дает возможность одному классу наследовать свойства другого класса. Также называется расширением класса.
Когда один класс наследуется от другого класса, эти два класса принимают определенные роли. Подкласс расширяет суперкласс. Или подкласс наследует от суперкласса. Подкласс — это специализация суперкласса, а суперкласс — это обобщение одного или нескольких подклассов.
- Как метод повторного использования кода
- Классовые иерархии
- Основы
- Что унаследовано?
- Единичное наследование
- Объявление
- Приведение типов
- Upcasting и Downcasting
- Переопределяющие методы
- Аннотация @override
- Вызов методов суперкласса
- Пример инструкции
- Как наследуются
- Поля
- Конструкторы
- Вложенные классы
- Финальные классы
- Абстрактные классы
Как метод повторного использования кода
Наследование может быть эффективным методом для обмена кодом между классами, которые имеют некоторые общие черты, но позволяя классам иметь некоторые части, которые отличаются.
Вот диаграмма, иллюстрирующая класс с именем Vehicle, который имеет два подкласса, называемые Car и Truck.

Класс Vehicle является суперклассом легковых и грузовых автомобилей. Автомобиль и Грузовик — подклассы Автомобиля. Класс Vehicle может содержать те поля и методы, которые нужны всем транспортным средствам (например, номерной знак, владелец и т. д.), Тогда как Car и Truck могут содержать поля и методы, специфичные для легковых и грузовых автомобилей.
Примечание. Некоторые люди утверждают, что наследование — это способ классификации ваших классов в зависимости от того, чем они являются. Автомобиль — это Автомобиль. Грузовик — транспортное средство. Однако на практике это не то, как вы определяете, какие суперклассы и подклассы должны иметь ваше приложение. Обычно это определяется тем, как вам нужно работать с ними в приложении.
Например, вам нужно ссылаться на объекты Car и Truck как объекты Vehicle? Вам нужно обрабатывать объекты Car и Truck одинаково? Тогда имеет смысл иметь общий суперкласс Vehicle для двух классов. Если вы никогда не обрабатываете объекты Car и Truck одним и тем же способом, нет смысла иметь для них общий суперкласс, кроме, возможно, совместного использования кода между ними (чтобы избежать написания дублирующего кода).
Классовые иерархии
Суперклассы и подклассы образуют структуру наследования. На вершине иерархии классов суперклассы. В нижней части иерархии — подклассы. Иерархия может иметь несколько уровней, то есть несколько уровней суперклассов и подклассов. Подкласс сам может быть суперклассом других подклассов и т. д.
Основы
Когда класс наследует от суперкласса, он наследует части методов и полей суперкласса. Подкласс также может переопределять (переопределять) унаследованные методы. Поля не могут быть переопределены, но могут быть «затенены» в подклассах. Как все это работает, рассказывается далее в этом тексте.
Что унаследовано?
Когда подкласс расширяет суперкласс в Java, он наследует все защищенные и открытые поля и методы, которые становятся его частью, как если бы он объявил их сам. Защищенные и открытые поля можно вызывать и ссылаться так же, как на методы, объявленные непосредственно в нем.
Поля и методы с модификаторами доступа по умолчанию (пакет) могут быть доступны для подклассов, только если он находится в том же пакете, что и суперкласс. На частные поля и методы суперкласса никогда нельзя ссылаться непосредственно, только через методы, достижимые из подкласса (например, методы по умолчанию (пакет), защищенные и публичные).
Конструкторы не наследуются подклассами, но конструктор подкласса должен вызывать конструктор в суперклассе.
Единичное наследование
Механизм наследования позволяет наследовать класс только от одного суперкласса (единичное наследование). В некоторых языках программирования, таких как C ++, подкласс может наследоваться от нескольких суперклассов (множественное).
Так как множественный вариант может создать некоторые странные проблемы, например, суперклассы содержат методы с одинаковыми именами и параметрами, он был исключен в Java.
Объявление
Объявляется с использованием ключевого слова extends:
public class Vehicle {
protected String licensePlate = null;
public void setLicensePlate(String license) {
this.licensePlate = license;
}
}
public class Car extends Vehicle {
int numberOfSeats = 0;
public String getNumberOfSeats() {
return this.numberOfSeats;
}
}
Класс Car в этом примере расширяет класс Vehicle, то есть Car наследуется от Vehicle. Поскольку Car расширяет Vehicle, защищенное поле licensePlate из Vehicle наследуется Car. Когда licensePlate наследуется, оно становится доступным внутри экземпляра Car.
В поле licensePlate на самом деле не ссылаются из класса Car в приведенном выше коде, но можно, если мы захотим:
public class Car extends Vehicle {
int numberOfSeats = 0;
public String getNumberOfSeats() {
return this.numberOfSeats;
}
public String getLicensePlate() {
return this.licensePlate;
}
}
Ссылка происходит внутри метода getLicensePlate(). Во многих случаях имело бы смысл поместить этот метод в класс Vehicle, где находится поле licensePlate.
Приведение типов
Можно ссылаться на подкласс как на экземпляр одного из его суперклассов. Например, используя определения класса из примера в предыдущем разделе, можно ссылаться на экземпляр класса Car как на экземпляр класса Vehicle. Так как Car расширяет (наследует) Vehicle, он также называется Vehicle.
Вот пример кода Java:
Car car = new Car(); Vehicle vehicle = car;
- Сначала создается экземпляр автомобиля.
- Экземпляр Car присваивается переменной типа Vehicle.
- Теперь переменная Vehicle (ссылка) указывает на экземпляр Car. Это возможно, потому что Car наследуется от Vehicle.
Как видите, можно использовать экземпляр некоторого подкласса, как если бы он был экземпляром его суперкласса. Таким образом, вам не нужно точно знать, к какому подклассу относится объект. Например, вы можете рассматривать экземпляры Грузовика и Автомобиля как экземпляры Транспортного средства.
Процесс ссылки на объект класса как тип, отличный от самого класса, называется приведением типа. Вы бросаете объект из одного типа в другой.
Upcasting и Downcasting
Вы всегда можете привести объект подкласса к одному из его суперклассов, либо из типа суперкласса к типу подкласса, но только если объект действительно является экземпляром этого подкласса (или экземпляром подкласса этого подкласса). Таким образом, этот пример downcasting действителен:
Car car = new Car(); // upcast to Vehicle Vehicle vehicle = car; // downcast to car again Car car2 =(Car) vehicle;
Однако следующий приведенный ниже пример недопустим. Компилятор примет его, но во время выполнения, выдаст исключение ClassCastException.
Truck truck = new Truck(); // upcast to Vehicle Vehicle vehicle = truck; // downcast to car again Car car =(Car) vehicle;
Объект «Грузовик» может быть передан объекту «Автомобиль», но позже он не может быть передан объекту «Автомобиль». Это приведет к исключению ClassCastException.
Переопределяющие методы
В подклассе вы можете переопределить методы, определенные в суперклассе:
public class Vehicle {
String licensePlate = null;
public void setLicensePlate(String licensePlate) {
this.licensePlate = licensePlate;
}
}
public class Car extends Vehicle {
public void setLicensePlate(String license) {
this.licensePlate = license.toLowerCase();
}
}
Обратите внимание, как и класс Vehicle, и класс Car определяют метод setLicensePlate(). Теперь каждый раз, когда setLicensePlate() вызывается для объекта Car, вызывается метод, определенный в классе Car. Метод в суперклассе игнорируется.
Чтобы переопределить метод, сигнатура метода в подклассе должна быть такой же, как в суперклассе, иметь точно такое же имя, количество и тип параметров, последовательность их перечисления. В противном случае метод в подклассе будет считаться отдельным методом.
Невозможно переопределить закрытые методы из суперкласса. Если суперкласс вызывает внутренний метод из другого, он будет продолжать вызывать его из суперкласса, даже при создании частного метода в подклассе с той же сигнатурой.
Аннотация @override
Если вы переопределяете метод в подклассе, и метод внезапно удаляется или переименовывается или его сигнатура изменяется в суперклассе, метод в подклассе больше не переопределяет метод в суперклассе. Было бы хорошо, если бы компилятор мог сказать вам, что переопределяемый метод больше не переопределяет метод в суперклассе, верно?
Для этого и нужна аннотация @ override. Вы размещаете ее над методом, который переопределяет метод в суперклассе:
public class Car extends Vehicle {
@Override
public void setLicensePlate(String license) {
this.licensePlate = license.toLowerCase();
}
}
Вызов методов суперкласса
Если вы переопределяете метод в подклассе, но по-прежнему должны вызывать метод, определенный в суперклассе, используйте ссылку super, например:
public class Car extends Vehicle {
public void setLicensePlate(String license) {
super.setLicensePlate(license);
}
}
В приведенном выше примере кода метод setLicensePlate() в классе Car вызывает метод setLicensePlate() в классе Vehicle.
Вы можете вызывать реализации суперкласса из любого метода в подклассе, как описано выше. Он не должен быть из самого переопределенного метода. Например, вы могли бы также вызвать super.setLicensePlate() из метода в классе Car с именем updateLicensePlate(), который не переопределяет метод setLicensePlate().
Пример инструкции
Java содержит инструкцию с именем instanceof. Она может определить, является ли данный объект экземпляром некоторого класса:
Car car = new Car(); boolean isCar = car instanceof Car;
После выполнения этого кода переменная isCar будет содержать значение true.
Инструкция instanceof также может использоваться для определения того, является ли объект экземпляром суперкласса своего класса. Вот пример, который проверяет, является ли объект Car экземпляром Vehicle:
Car car = new Car(); boolean isVehicle = car instanceof Vehicle;
Предполагая, что класс Car расширяет (наследует от) класс Vehicle, переменная isVehicle будет содержать значение true после выполнения этого кода. Объект Car также является объектом Vehicle, поскольку Car является подклассом Vehicle.
Как видите, инструкция instanceof может использоваться для изучения иерархии наследования. Тип переменной, используемый с ней, не влияет на ее результат. Посмотрите на этот пример:
Car car = new Car(); Vehicle vehicle = car; boolean isCar = vehicle instanceof Car;
Несмотря на то, что переменная транспортного средства имеет тип Vehicle, объект, на который она в конечном итоге указывает в этом примере, является объектом Car. Поэтому экземпляр транспортного средства автомобиля будет оценен как истинный.
Вот тот же пример, но с использованием объекта Truck вместо объекта Car:
Truck truck = new Truck(); Vehicle vehicle = truck; boolean isCar = vehicle instanceof Car;
После выполнения этого кода isCar будет содержать значение false. Объект Truck не является объектом Car.
Как наследуются
Поля
Как упоминалось ранее, в Java поля не могут быть переопределены в подклассе. Если вы определяете поле в подклассе с тем же именем, что и в суперклассе, поле в подклассе будет скрывать поле в суперклассе. Если подкласс попытается получить доступ к полю, он получит доступ к полю в подклассе.
Однако, если подкласс вызывает метод в суперклассе, который обращается к полю с тем же именем, что и в подклассе, это поле в суперклассе, к которому осуществляется доступ.
Вот пример, который иллюстрирует, как поля в подклассах скрывают поля в суперклассах:
public class Vehicle {
String licensePlate = null;
public void setLicensePlate(String licensePlate) {
this.licensePlate = licensePlate;
}
public String getLicensePlate() {
return licensePlate;
}
}
public class Car extends Vehicle {
protected String licensePlate = null;
@Override
public void setLicensePlate(String license) {
super.setLicensePlate(license);
}
@Override
public String getLicensePlate() {
return super.getLicensePlate();
}
public void updateLicensePlate(String license){
this.licensePlate = license;
}
}
Обратите внимание, как для обоих классов определено поле licensePlate.
И класс Vehicle, и класс Car имеют методы setLicensePlate() и getLicensePlate(). Методы в классе Car вызывают соответствующие методы в классе Vehicle. В результате оба набора методов получают доступ к полю licensePlate в классе Vehicle.
Однако метод updateLicensePlate() в классе Car напрямую обращается к полю licensePlate. Таким образом, он получает доступ к полю licensePlate класса Car. Следовательно, вы не получите тот же результат, если вызовете setLicensePlate(), как при вызове метода updateLicense().
Посмотрите на следующие строки кода:
Car car = new Car();
car.setLicensePlate("123");
car.updateLicensePlate("abc");
System.out.println("license plate: "
+ car.getLicensePlate());
Этот код распечатает текст 123.
Метод updateLicensePlate() устанавливает значение номерного знака в поле licensePlate в классе Car. Однако метод getLicensePlate() возвращает значение поля licensePlate в классе Vehicle. Следовательно, значение 123, которое устанавливается как значение для поля licensePlate в классе Vehicle с помощью метода setLicensePlate(), является тем, что выводится на печать.
Конструкторы
Механизм наследования не включает конструкторы. Другими словами, конструкторы суперкласса не наследуются подклассами. Подклассы могут по-прежнему вызывать конструкторы в суперклассе, используя конструкцию super().
Фактически, конструктор подкласса должен вызывать один из конструкторов в суперклассе как самое первое действие внутри своего тела. Вот как это выглядит:
public class Vehicle {
public Vehicle() {
}
}
public class Car extends Vehicle{
public Car() {
super();
//здесь выступают другие инициализации
}
}
Обратите внимание на вызов super() внутри конструктора Car. Этот вызов super() выполняет конструктор в классе Vehicle.
Возможно, вы видели классы, где конструкторы подкласса, похоже, не вызывали конструкторы в суперклассе. Возможно, у суперкласса даже не было конструктора. Тем не менее, конструкторы подкласса все еще называют конструкторы суперкласса в этом случае. Вы просто не могли этого увидеть.
Если класс не имеет какого-либо явного конструктора, компилятор вставляет неявный без аргументов. Таким образом, класс всегда имеет конструктор.
Поэтому следующая версия транспортного средства эквивалентна версии, показанной чуть выше:
public class Vehicle {
}
Если конструктор явно не вызывает конструктор в суперклассе, компилятор вставляет неявный вызов конструктора no-arg в суперклассе. Это означает, что следующая версия класса Car фактически эквивалентна версии, показанной ранее:
public class Car extends Vehicle{
public Car() {
}
}
Фактически, поскольку конструктор теперь пуст, мы могли бы опустить его, и компилятор вставил бы его и неявный вызов конструктора no-arg в суперклассе. Вот как тогда будут выглядеть два класса:
public class Vehicle {
}
public class Car extends Vehicle{
}
Даже если в этих двух классах не объявлено ни одного конструктора, они оба получают конструктор без аргументов, который в классе Car будет вызывать конструктор без аргументов в классе Vehicle.
Если бы класс Vehicle не имел конструктора без аргументов, но имел другой, который принимает параметры, компилятор жаловался бы. Класс Car затем должен был бы объявить конструктор, а внутри него вызвать конструктор в классе Vehicle.
Вложенные классы
Те же правила наследования применяются к вложенным классам. Если объявлены закрытыми, не наследуются. Вложенные классы с модификатором доступа по умолчанию (пакет) доступны только для подклассов, если подкласс находится в том же пакете, что и суперкласс. С модификатором защищенного или открытого доступа всегда наследуются подклассами.
Вот пример:
class MyClass {
class MyNestedClass {
}
}
public class MySubclass extends MyClass {
public static void main(String[] args) {
MySubclass subclass = new MySubclass();
MyNestedClass nested = subclass.new MyNestedClass();
}
}
Обратите внимание, как можно создать экземпляр вложенного класса MyNestedClass, который определен в суперклассе(MyClass) посредством ссылки на подкласс(MySubclass).
Финальные классы
Класс может быть объявлен окончательным(final):
public final class MyClass {
}
Последний класс не может быть продлен. Другими словами, вы не можете наследовать от финального класса.
Абстрактные классы
Класс может быть объявлен абстрактным, который не содержит полную реализацию того, что должен делать. Таким образом, это не может быть реализовано. Другими словами, вы не можете создавать объекты абстрактного класса.
Они предназначены для расширения / создания полной реализации. Таким образом, вполне возможно расширить абстрактный класс. Правила наследования такие же для них, как и для неабстрактных классов.
