Наследование в Java
В случае наследования в Java для указания родительского класса используется ключевое слово extends, которое следует после имени дочернего, т. е. производного, класса. После extends пишется родительский класс. Другими словами, "дочерний класс расширяет родительский".
В Java нет множественного наследования от классов, но есть множественное наследование интерфейсов.
В Java есть особенность, связанная с наследованием конструкторов и их вызовом. В конструкторе дочернего класса, перед тем как будут выполнены выражения тела, неявно вызывается дефолтный (без параметров) конструктор предка.
Причем конструктор без параметров обязательно должен быть в родительском классе, если в дочернем предполагаются только конструкторы с аргументами, а родительские конструкторы не вызываются явно. В этом случае, если в родительском классе будут только конструкторы с аргументами, то в дочернем будет возникать ошибка.
Файл Main.java:
public class Main { public static void main(String[] args) { Child child = new Child(10); System.out.println(child.a); System.out.println(child.b); } }
Файл Parent.java:
class Parent { int a; Parent(int a) { this.a = 1; } }
Файл Child.java:
class Child extends Parent { int b; Child(int b) { this.b = b; } }
В приведенном выше примере ошибка возникает на уровне конструктора класса Child. Перед тем как выполнится выражение this.b = b, будет неявно вызван конструктор Parent без параметров. Однако такого конструктора в родительском классе нет.
Если мы закомментируем или удалим конструктор Parent,
class Parent { int a; // Parent(int a) { // this.a = 1; // } }
то ошибка в дочернем классе исчезнет. Дело в том, что в Java любой класс есть потомок базового класса Object, у которого всегда имеется пустой дефолтный конструктор. В данном случае, двигаясь по дереву наследования к корню, из Child будет вызываться конструктор базового для всех класса.
Почему же он не вызывался, когда в Parent был определен конструктор с параметрами? В Java, как только вы определяете в классе любой конструктор, дефолтный родительского уже не наследуется напрямую, а начинает неявно вызываться из собственных конструкторов.
Заметим, что у объектов Child поле this.a существует, даже если ему не было присвоено значение. В данном случае значение будет нулевым.
Обычным вариантом недопущения подобных ошибок является объявление в Parent конструктора без аргументов. Он может быть пустым, или содержать какой-либо код:
import java.util.Random; class Parent { int a; Parent() { Random random = new Random(); this.a = random.nextInt(10); } }
Другой распространенный вариант – когда в дочернем классе явно вызывается конструктор родительского класса с помощью ключевого слова super. Причем вызывать всегда надо первой строчкой тела конструктора.
Класс Main:
public class Main { public static void main(String[] args) { Child child = new Child(10, 20); System.out.println(child.a); System.out.println(child.b); } }
Класс Parent:
class Parent { int a; Parent(int a) { this.a = a; } }
Класс Child:
class Child extends Parent { int b; Child(int a, int b) { super(a); this.b = b; } }
Обычные методы наследуются и переопределяются стандартно для объектно-ориентированного программирования. При этом в Java наследовать и переопределять можно только нестатические методы. Таким образом, если в родительском классе объявлен статический метод, то дочерние классы его не наследуют.
В Java объекты дочернего класса можно присваивать переменным родительского. Подобное называется расширением типа. При этом в случае вызова переопределенных методов будут вызываться методы дочернего класса, а не родительского. Однако набор допустимых для вызова методов определяется родительским классом, то есть типом переменной.
Также возможно сужение типа, когда объект, связанный с переменной родительского типа, присваивается переменной дочернего, то есть своего типа. В этих случаях, чтобы избежать ошибок, следует проверять тип объекта. Например, пусть класс B производный от класса A. Тогда:
A var = new B(); if (var instanceof B) { B var1 = (B) var; }
Если в методе дочернего класса требуется использовать возможности родительского, то обращение ко второму происходит через слово super, после которого пишется необходимый метод.
public class ParentMeth { public static void main(String[] args) { ChildMeth ch = new ChildMeth(); ch.PrintStr(); } public void PrintStr() { System.out.println("Parent class"); } } class ChildMeth extends ParentMeth { public void PrintStr() { super.PrintStr(); System.out.println("Child class"); } }
Результат:
Parent class Child class