Наследование в 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

Создано