Материал предоставлен https://it.rfei.ru

Ключевые слова this() и super()

Вызов конструктора this()

Конструкторы нельзя унаследовать или переопределить. Они могут быть перегружены, но только в том же самом классе. Так как конструктор имеет всегда такое же имя и класс, список параметров каждого конструктора должен отличаться от других, если в классе определяется несколько конструкторов. В примере 6.6 в классе Light определено три перегруженных конструктора. В конструкторе не по умолчанию в строке (3) используется ссылка this для доступа к полям, перекрытым параметрами. В методе main() из строки (4) вызывается соответствующий конструктор, в зависимости от аргументов которые задаются при его вызове, что иллюстрируется выводом программы

Пример 6.6 Перегрузка конструктора

class Light {

    private int     noOfWatts;

    private boolean indicator;

    private String  location;

    // Конструкторы
    Light() {                                      // (1) Явный конструктор по умолчанию
        noOfWatts = 0;
        indicator = false;
        location  = "X";
        System.out.println("Returning from default constructor no. 1.");
    }
    Light(int watts, boolean onOffState) {                      // (2) Не по умолчанию
        noOfWatts = watts;
        indicator = onOffState;
        location  = "X";
        System.out.println("Returning from non-default constructor no. 2.");
    }
    Light(int noOfWatts, boolean indicator, String location) {  // (3) Не по умолчанию
        this.noOfWatts = noOfWatts;
        this.indicator = indicator;
        this.location  = location;
        System.out.println("Returning from non-default constructor no. 3.");
    }
}

public class DemoConstructorCall {
    public static void main(String[] args) {                    // (4)
        System.out.println("Creating Light object no. 1.");
        Light light1 = new Light();
        System.out.println("Creating Light object no. 2.");
        Light light2 = new Light(250, true);
        System.out.println("Creating Light object no. 3.");
        Light light3 = new Light(250, true, "attic");
    }
}

Вывод программы:

Creating Light object no. 1.
Returning from default constructor no. 1.
Creating Light object no. 2.
Returning from non-default constructor no. 2.
Creating Light object no. 3.
Returning from non-default constructor no. 3.

В примере 6.7 показано использование конструктора this(), который позволяет реализовать локальное связывание конструкторов в классе во время создания экземпляров. Первые два конструктора в строках (1) и (2) из примера 6.6 переписаны с использованием вызова конструктора this(). Конструктор this() может считаться локально перегруженным, поскольку его параметры (следовательно, и сигнатура) могут варьироваться, что показано в теле конструкторов в строках (1) и (2). Вызов this() запускает конструктор с соответствующим списком параметров. В методе main() из (4) вызов определенного конструктора происходит в зависимости от аргументов, которые задаются при его запуске, когда создается каждый из трех объектов Light. Вызов конструктора по умолчанию для создания объекта Light приводит к тому, что второй и третий конструкторы также выполняются. Это подтверждается выводом программы. В нашем случае вывод показывает, что сначала выполняется третий конструктор, за ним второй и, наконец конструктор по умолчанию, вызванный первым. Имея в виду определения конструкторов, отметим, что они запускаются в обратном порядке; т.е. вызов конструктора по умолчанию немедленно приводит к вызову второго конструктора благодаря this(0, false) его вызов приводит к немедленному вызову третьего благодаря this(watt, ind, "X"), т.е. выполнение происходит в порядке, обратном вызову. Таким же образом вызов второго конструктора для создания экземпляра класса Light приводит к тому, что так же выполняется и третий конструктор.

Java требует от вас, чтобы любой вызов this() обязательно был первым оператором в конструкторе. За вызовом this() может следовать любой важный код. Это ограничение вводится из-за того, что Java обрабатывает вызов конструктора суперкласса на этапе создания объекта подкласса. Этот механизм объясняется в следующем подразделе.

Пример 6.7. Вызов конструктора this()

class Light {
    // Поля
    private int     noOfWatts;
    private boolean indicator;
    private String  location;

        Light() {                              // (1) Явный конструктор по умолчанию
        this(0, false);
        System.out.println("Returning from default constructor no. 1.");
    }
    Light(int watt, boolean ind) {         // (2) Не по умолчанию
        this(watt, ind, "X");
        System.out.println("Returning from non-default constructor no. 2.");
    }
    Light(int noOfWatts, boolean indicator, String location) {   // (3) Не по умолчанию
        this.noOfWatts = noOfWatts;
        this.indicator = indicator;
        this.location  = location;
        System.out.println("Returning from non-default constructor no. 3.");
    }
}

public class DemoThisCall {
    public static void main(String[] args) {                     // (4)
        System.out.println("Creating Light object no. 1.");
        Light light1 = new Light();                              // (5)
        System.out.println("Creating Light object no. 2.");
        Light light2 = new Light(250, true);                     // (6)
        System.out.println("Creating Light object no. 3.");
        Light light3 = new Light(250, true, "attic");            // (7)
    }
}

Вывод программы:

Creating Light object no. 1.
Returning from non-default constructor no. 3.
Returning from non-default constructor no. 2.
Returning from default constructor no. 1.
Creating Light object no. 2.
Returning from non-default constructor no. 3.
Returning from non-default constructor no. 2.
Creating Light object no. 3.
Returning from non-default constructor no. 3.
class TubeLight extends Light {

    private int tubeLength;
    private int colorNo;

    TubeLight(int tubeLength, int colorNo) {
        this(tubeLength, colorNo, 100, true, "Unknown");
        System.out.println(
                "Returning from non-default constructor no. 1 in class TubeLight");
    }

    TubeLight(int tubeLength, int colorNo, int noOfWatts,
              boolean indicator, String location) {             // (6) не по умолчанию
        super(noOfWatts, indicator, location);                  // (7)
        this.tubeLength = tubeLength;
        this.colorNo    = colorNo;
        System.out.println(
                "Returning from non-default constructor no. 2 in class TubeLight");
    }
}

public class Chaining {
    public static void main(String[] args) {
        System.out.println("Creating a TubeLight object.");
        TubeLight tubeLightRef = new TubeLight(20, 5);          // (8)
    }
}

Вывод программы:

Creating a TubeLight object.
Returning from non-default constructor no. 3.
Returning from non-default constructor no. 2 in class TubeLight
Returning from non-default constructor no. 1 in class TubeLight

Конструкция super() имеет такие же ограничения, как и конструкция this(): если она используется, то вызов super() должен быть первым оператором в конструкторе, и он может использоваться только в конструкторе. Это влечет за собой то, что вызовы this() и super() не могут встретиться в одном и том же конструкторе. Конструкция this() используется для связывания конструкторов в одном и том же классе, и самый последний конструктор в этой последовательности может вызвать конструктор суперкласса с помощью конструкции super(). Итак, конструкция this() приводит к формированию цепочки конструкторов в одном и том же классе, a super() приводит к связыванию конструкторов подкласса с конструкторами суперкласса. Использование цепочки гарантирует, что все конструкторы суперкласса будут вызваны, начиная с конструктора класса, объект которого порождается, и далее вверх до вершины по иерархии наследования, т.е. всегда до класса Object. Обратите внимание, что тело конструкторов выполняется в порядке обратном порядку их вызова, так как super() может быть только первым оператором в конструкторе. Это гарантирует, что конструктор из класса Object выполняется первым, а за ним конструкторы других классов вниз по иерархии наследования вплоть до класса, от которого порождается объект. Это называется формированием цепочки конструкторов (constructor chaining) (подкласс — суперкласс). Вывод примера 6.8 четко демонстрирует такое поведение, когда создается объект класса TubeLight.

Если конструктор в конце последовательности, формируемой this() (последовательности вообще-то может и не быть, если вызова this() не происходит), не содержит явного вызова super(), то неявно будет добавлен вызов super() (без параметров) для вызова конструктора по умолчанию суперкласса. Другими словами, если конструктор не содержит ни this(), ни super() в своей первой строке, то вызов super(), запускающий конструктор по умолчанию суперкласса, будет добавлен. Код:

class A {
    public A() {}
    // ...
}
class B extends A {
    // нет конструкторов
    // ...
}

class A {
    public A() { super(); }      // (1)
    // ...
}
class B extends A {
    public B() { super(); }      // (2)
    // ...
}

в котором в код добавлены конструкторы по умолчанию, вызывающие конструктор по умолчанию суперкласса.

Если в классе нет конструктора по умолчанию (т.е. только конструкторы с параметрами), то его подклассы не могут надеяться на неявный вызов super(). Это вызовет ошибку на этапе компиляции. В таком случае подкласс должен явно вызывать конструктор суперкласса, используя конструкцию super() с правильными аргументами.

class NeonLight extends TubeLight {
    // Field
    String sign;

    NeonLight() {                               // (1)
        super(10, 2, 100, true, "Roof-top");    // (2) не может быть закомментировано
        sign = "All will be revealed!";
    }
    // ...
}

Предыдущее описание подкласса NeonLight объявляет в строке (1) конструктор. Вызов в строке (2) конструктора суперкласса TubeLight не может быть опущен. Если его опустить, добавление вызова super() (без параметров) в этот конструктор не будет соответствовать конструктору по умолчанию суперкласса TubeLight, так как такого конструктора в суперклассе нет. Суперкласс TubeLight содержит только конструкторы не по умолчанию. Класс NeonLight не откомпилируется, пока в строку (2) будет вставлен явно вызов super() (с допустимыми параметрами).

Если суперкласс содержит только конструкторы не по умолчанию (т.е. в нет конструктора по умолчанию), то это может иметь нежелательные последствия для класса. Подкласс, который рассчитывает на неявное добавление конструктор умолчанию, не будет откомпилирован, потому что будет реализована попытка вызова несуществующего конструктора по умолчанию суперкласса. Любой конструктор по умолчанию подкласса должен явно содержать вызов super(), с соответствую параметрами, для вызова предопределенного конструктора суперкласса, так как конструктор в подклассе не может надеяться на неявный вызов super(), приводящий к вызову конструктора по умолчанию суперкласса.

Вызов конструктора super()

Конструкция super() используется в конструкторе подкласса для вызова конструктора непосредственного суперкласса. Это позволяет подклассу, когда создается его объект, влиять на инициализацию своего унаследованного состояния. Вызов super() в конструкторе подкласса приведет к выполнению необходимого конструктора суперкласса, выбор которого основывается на сигнатуре вызова. Поскольку имя суперкласса известно в подклассе, вызываемый конструктор суперкласса определяется по списку параметров сигнатуры.

Конструктор в подклассе может обратиться напрямую к унаследованным членам (т.е. по их простым именам). Ключевое слово super также может использоваться в конструкторе подкласса для доступа к унаследованным членам через свой суперкласс. Может возникнуть желание использовать ключевое слово super в конструкторе для задания первоначальных значений унаследованным полям. Однако конструкция super() предлагает лучшее решение — воспользуйтесь конструкторами суперкласса для инициализации унаследованного состояния.

В примере 6.8 конструктор не по умолчанию из строки (3) класса Light вызывает super() (без параметров) в строке (4). Хотя конструктор не является строго необходимым, поскольку компилятор его добавит, как объясняется ниже, он включен в демонстрационных целях. Конструктор не по умолчанию из строки (6) класса TubeLight содержит вызов super() (с тремя параметрами) в (7). Такой вызов super() соответствует вызову конструктора не по умолчанию суперкласса Light из (3). Это очевидно из вывода программы.

Пример 6.8. Вызов конструктора super()

class Light {

    private int     noOfWatts;
    private boolean indicator;
    private String  location;

    Light() {                              // (1) Явный конструктор по умолчанию
        this(0, false);
        System.out.println(
            "Returning from default constructor no. 1 in class Light");
    }
    Light(int watt, boolean ind) {                              // (2) не по умолчанию
        this(watt, ind, "X");
        System.out.println(
            "Returning from non-default constructor no. 2 in class Light");
    }
    Light(int noOfWatts, boolean indicator, String location) {  // (3) не по умолчанию
        super();                                                // (4)
        this.noOfWatts = noOfWatts;
        this.indicator = indicator;
        this.location  = location;
        System.out.println(
            "Returning from non-default constructor no. 3 in class Light");
    }
}
class TubeLight extends Light {
    // Instance variables
    private int tubeLength;
    private int colorNo;

    TubeLight(int tubeLength, int colorNo) {                    // (5) не по умолчанию
        this(tubeLength, colorNo, 100, true, "Unknown");
        System.out.println(
            "Returning from non-default constructor no. 1 in class TubeLight");
    }
}
Переопределение и сокрытие методовИнтерфейсы