Наследование в Java

1. Что такое наследование?

У нас есть следующий класс, описывающий коробку:

public class Box6 {
    double width;
    double height;
    double depth;

    Box6(double w, double h, double d) {
        width = w;
        height = h;
        depth = d;
    }

    public Box6() {
    }

    double getVolume() {
        return width * height * depth;
    }
}

И вдруг заказчик решает добавить цветную коробку - ColorBox и тяжелую коробку - HeavyBox. Цветная коробка будет отличаться от обычной только цветом, а тяжелая - весом. Получается, что в класс ColorBox мы должны добавить те же переменные width, height и depth, конструкторы и методы, которые существуют в классе Box. Дублирование кода в программировании не приветствуется, поэтому для таких случаев придуман такой механизм как наследование.

Используя наследование Java, можно создать класс, который определяет характеристики, общие для набора связанных элементов - Box. Затем этот общий класс может наследоваться другими, более специализированными классами ColorBox и HeavyBox, каждый из которых будет добавлять свои особые характеристики.

В терминологии Jаvа наследуемый класс называется суперклассом, а наследующий класс – подклассом. Подкласс наследует все члены, определенные в суперклассе, добавляя к ним собственные, особые элементы. Набор классов, связанных отношением наследования, называют иерархией, что изображается таким образом:

Пример наследования фото

Общая форма объявления класса, который наследуется от суперкласса:

class имяПодкласса extends имяСуперКласса {
   // тело класса
}

Для каждого создаваемого подкласса можно указать только один суперкласс. Класс не может стать суперклассом для самого себя.

В объявлении класса ColorBox используется ключевое слово extends для указания того, что ColorBox является наследником Box6ColorBox содержит все переменные и методы класса Box6 несмотря на то, что в самом классе ColorBox они не указаны.

public class ColorBox extends Box6 {
    String color; 

    public ColorBox(int width, int height, int depth, String color) {
        this.width = width;
        this.height = height;
        this.depth = depth;
        this.color = color; 
    }

    public ColorBox() {
    }
}

Один класс может содержать несколько наследников - класс HeavyBox тоже расширяет Box6:

public class HeavyBox extends Box6 {
    int weight; 

    public HeavyBox() {
    }

    public HeavyBox(int width, int height, int depth, int weight) {
        this.width = width;
        this.height = height;
        this.depth = depth;
        this.weight = weight;
    }
}

В следующем классе создаем объекты всех трех типов и подсчитываем объем для каждой коробки.

public class DifferentBoxDemo1 {
    public static void main(String[] args) {
        Box6 box = new Box6(15, 10, 25);
        HeavyBox heavyBox = new HeavyBox(15, 10, 20, 5);
        ColorBox colorBox = new ColorBox(25, 12, 20, "красный");

        System.out.println("Объём коробки: " + box.getVolume());
        System.out.println("Объём коробки: " + heavyBox.getVolume()
                + " Вес коробки: " + heavyBox.weight);
        System.out.println("Объём коробки: " + colorBox.getVolume()
                + " Цвет коробки: " + colorBox.color);
    }
}

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

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

public class A {
    public int value1;
    private int value2;
}

Из класса B, который является наследником класса A, невозможно напрямую обратиться к private переменной класса A. Доступ к ним можно получить через геттер методы:

public class B extends A {
    public int sum() {
        // return value1 + value2;
        return value1 + getValue2();
    }
}

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

Ссылочной переменной суперкласса может быть присвоена ссылка на любой его подкласс.

Например, переменная heavyBox объявлена как Box, но она указывает на объект типа HeavyBox:

Box heavyBox = new HeavyBox(15, 10, 20, 5);
Box colorBox = new ColorBox(25, 12, 20, "красный"); 

Обратное не верно! Нельзя написать так: HeavyBox heavyBox = new Box(15, 10, 20).

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

Для каждого объекта мы можем узнать его ширину, но при попытке обратиться к переменной color объекта redBox, возникнет ошибка компиляции. В чем причина такого поведения? Переменная color объявлена в классе ColorBox с уровнем доступа по умолчанию, класс DifferentBoxDemo2 находится в том же пакете, то есть переменная color должна быть доступна. Дело в том, что доступные члены класса определяются типом ссылочной переменной, а не типом объекта, на который она ссылается. То есть если переменная объявлена типа Box6, нам доступны только члены объявленные в классе Box6 (weight, height, depth), и неважно на какой объект она ссылается. А вот для переменной blueBox мы можем узнать цвет, так как переменная объявлена как ColorBox:

public class DifferentBoxDemo2 {
    public static void main(String[] args) {
        Box6 box = new Box6(15, 10, 25);
        Box6 heavyBox = new HeavyBox(15, 10, 20, 5);
        Box6 redBox = new ColorBox(25, 12, 20, "красный");

        ColorBox blueBox = new ColorBox(25, 12, 20, "голубой");

        System.out.println("Ширина коробки: " + box.width);
        System.out.println("Ширина тяжелой коробки: " + heavyBox.width);
        System.out.println("Ширина красной коробки: " + redBox.width);
        System.out.println("Ширина голубой коробки: " + blueBox.width);

        System.out.println("Цвет голубой коробки: " + blueBox.color);
        //System.out.println("Цвет красной коробки: " + redBox.color);
        //System.out.println("Вес тяжелой коробки: " + heavyBox.weight);
    }
}

4. Создание многоуровневой иерархии

Можно строить иерархии, состоящие из любого количества уровней наследования. Например, добавим класс Shipment, расширяющий HeavyBox:

Создание многоуровневой иерархии фото

public class Shipment extends HeavyBox1 {
    public double cost;

    public Shipment(int width, int height, int depth, int weight, double cost) {
        super(width, height, depth, weight);
        this.cost = cost;
    }

    public Shipment(double cost) {
        this.cost = cost;
    }
}

Класс Shipment содержит не только переменную cost, объявленную в самом классе, но и переменные класса HeavyBox и Box6:

public class DemoShipment {
    public static void main(String[] args) {
        Shipment shipment = new Shipment(2, 3, 4, 5, 5.3);

        System.out.println(shipment.cost);
        System.out.println(shipment.depth);
        System.out.println(shipment.height);
        System.out.println(shipment.weight);
        System.out.println(shipment.width);
    }
}

5. Порядок вызова конструкторов в многоуровневой иерархии

В иерархии классов конструкторы выполняются в порядке наследования, начиная с суперкласса и кончая подклассом. Рассмотрим следующую иерархию классов:

public class E {
    public E() {
        System.out.println("В конструкторе E");
    }
}
public class F extends E {
    public F() {
        System.out.println("В конструкторе F");
    }
}
public class G extends F {
    public G() {
        System.out.println("В конструкторе G");
    }
}

При создании объекта класса Gсначала закончит свое выполнение конструктор класса E, потом F и в конце G:

public class CallingConstructors {
    public static void main(String[] args) {
        G g = new G();
    }
}

Результат выполнения кода:

В конструкторе E
В конструкторе F
В конструкторе G
Read also:
Comments