Наследование. Переопределение методов и полиморфизм. Абстрактные методы и классы. Аннотация @Override

В первом уроке этой главы мы рассмотрели абстракции Phone и MobilePhone и конкретные их реализации — Nokia3310 и Nexus5. В этом уроке мы также будем использовать эти классы, но разберём больше технических подробностей.

Возьмём класс из первого урока:

public abstract class Phone {
    public abstract void dial(String number);
}

Класс Phone абстрактен — это значит, что выражение new Phone() недопустимо: нельзя создать экземпляр абстрактного телефона, с конвейеров заводов сходят лишь конкретные модели. У него есть абстрактный метод dial(String number) — это значит, что любой экземпляр телефона умеет звонить; создавая конкретный класс телефона (как Nokia3310, Nexus5), программист обязан определить метод dial(String). (Разрабатывая новую модель телефона инженер обязан предусмотреть возможность звонить.)

Следующий класс:

public abstract class MobilePhone extends Phone {
    
    private String serialNumber;
    private String imei;
    
    private int batteryVoltage;
    public String getDisplayVoltage() {
        return batteryVoltage + " мВ";
    }
    
}

Класс MobilePhone также абстрактен; он наследует класс Phone — это значит, что любой мобильный телефон является телефоном и по нему можно звонить, — то есть он поддерживает метод dial(String).

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

Переопределение методов

Мы уже определяли методы dial(String) у классов Nokia3310 и Nexus5. У их общего предка, класса Phone, присутствует этот метод, поэтому технически определение этого метода является переопределением: подкласс (наследник) определяет своё поведение вместо поведения предка (суперкласса).

Далее — класс Nokia3310 из первого урока.

public class Nokia3310 extends MobilePhone {
    @Override
    public void dial(String number) {
        System.out.println("Передаём номер «" + number + 
                "» финскому GSM-модулю, сделанному в Венгрии.");
    }
}

Аннотация @Override гарантирует, что метод будет переопределён. Например, если по ошибке создать метод call(String) (другое название) или dial(long number) (другой набор аргументов), такой метод не переопределит метод dial(String). Но если на нём будет аннотация @Override, компилятор и среда разработки сообщат об ошибке в этом месте:

Override негодует

Неабстрактные методы также можно переопределять. Например:

public class PirateNokia3310 extends Nokia3310 {
    @Override
    public String getDisplayVoltage() {
        if (batteryVoltage > 3900) {
            return batteryVoltage + " мВ морского спокойствия!";
        } else {
            return "всего " + batteryVoltage + " мВ, разрази меня гром!";
        }
    }
}

Таким образом, для «пиратской» Nokia3310 вызов метода getDsplayVoltage будет возвращать напряжение на аккумуляторе в характерной, пиратской форме.

Класс имеет доступ к методам суперкласса.

public class PirateNokia3310 extends Nokia3310 {
    @Override
    public String getDisplayVoltage() {
        // это переопределение бесполезно: 
        // метод ведёт себя так же, как метод суперкласса
        return super.getDisplayVoltage();
    }
}

Можно вызвать метод суперкласса и воспользоваться его результатом:

public class PirateNokia3310 extends Nokia3310 {
    @Override
    public String getDisplayVoltage() {
        if (batteryVoltage > 3900) {
            // 4121 мВ морского спокойствия!
            return super.getDisplayVoltage() + " морского спокойствия!";
        } else {
            // всего 3866 мВ, разрази меня гром!
            return "всего " + super.getDisplayVoltage() + ", разрази меня гром!";
        }
    }
}
Комментарии к уроку

Сообщить об ошибке