Ключевое слово super

Ключевое слово super фото
Author: Tatyana Milkina

1. Использование ключевого слова super

В сегодняшнем нашем уроке мы поговорим с вами о ключевом слове super в Java. Ключевое слово super очень похоже на ключевое слово this, которое мы с вами рассматривали в одном из предыдущих уроков. Ключевое слово super в Java используется тогда, когда подклассу требуется сослаться на его непосредственный супер класс.

У ключевого слова super имеются две общие формы:

  1. Для вызова конструктора супер класса. В этом случае после super мы используем круглые скобочки, в которых мы передаём список аргументов в наш суперкласс:
    super(списокАргументов);​
  2. И второй вариант используется для обращения к переменным либо методам суперкласса, чаще всего скрываемыми членами подкласса:
    suреr.member;​

2. Вызов конструкторов супер класса с помощью ключевого слова super

И давайте начнём рассмотрение с первого варианта.

Передача параметров в конструктор супер класса

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

В этом примере из конструктора класса HeavyBox вызываем конструктора класса Box с помощью super(), тем самым передавая необходимые значения:

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

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

    public Box() {
    }
}
public class HeavyBox extends Box {
    int weight;

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

    public HeavyBox() {
        this.weight = -1;
    }
}

Если в конструкторе наследника нет явного вызова super(), как например во втором конструкторе класса HeavyBox, JVM сама его подставляет первой строкой:

public HeavyBox() {
    super();
    this.weight = -1;
}

Из этого следует, что супер класс должен иметь конструктор без параметров, иначе возникнет ошибка компиляции.

Вызов конструкторов в многоуровневой иерархии

Когда метод super() вызывается из подкласса, вызывается конструктор его непосредственного супер класса. Это справедливо даже для многоуровневой иерархии. Давайте рассмотрим такой пример - допустим у нас в нашей иерархии есть три класса:

Иерархия классов

У нас есть SuperSuperClass, его расширяет SuperClass, и далее SomeClass расширяет SuperClass.

Давайте посмотрим на код этих классов:

public class SuperSuperClass {
   public SuperSuperClass() {
       System.out.println("В конструкторе SuperSuperClass");
   }
}
public class SuperClass extends SuperSuperClass {
   public SuperClass() {
       System.out.println("В конструкторе SuperClass");
   }
}
public class SomeClass extends SuperClass {
   public SomeClass() {
       System.out.println("В конструкторе SomeClass");
   }
}

Код в этих классов достаточно краток. В классе SuperSuperClass есть конструктор, который просто выводит на консоль то, что мы находимся в конструкторе этого класса. В классе SuperClass есть аналогичный конструктор, и в классе SomeClass опять же есть только конструктор, который выводит на консоль информацию о том, что мы находимся в конструкторе SomeClass.

И давайте теперь создадим объект класса SomeClass:

 SomeClass someClass = new SomeClass();

Теперь посмотрим в каком порядке у нас вызываются эти конструкторы:

В конструкторе SomeClass
В конструкторе SuperSuperClass
В конструкторе SuperClass

Несмотря на то, что мы создаём объект SomeClass, сначала у нас вызывается конструктор SuperSuperClass, потом конструктор SuperClass, и уже потом конструктор SomeClass. О чём это говорит? Это говорит о том, что сначала  должны быть выполнены действия по конструированию самого первого суперкласса. Потом идём вверх по иерархии - выполняется действия по созданию объекта SuperClass, и уже потом мы возвращаемся к объекту SomeClass. 

Использование super в Java 22, 23

До Java 22 вызов метода super,  должен быть всегда в первом операторе в конструкторе. То есть, если мы напишем следующим образом, то у нас будет ошибка компиляции:

public class SomeClass extends SuperClass {
    public SomeClass() {
        System.out.println("В конструкторе SomeClass");// ошибка компиляции до Java 22
        super();
    }
}

Опять же - почему так? Потому что сначала должен вызваться конструктор суперкласса.

Но Java 22 была добавлена возможность написания кода перед super() для того, чтобы облегчить подготовку, валидацию и передачу аргументов в конструктор. В Java 22 эта фича была добавлена в форме preview и называлась  Statements before Super. В Java 23 она была немножко изменена и переименована в Flexible Constructor Body, но всё равно это всё ещё preview. Preview означает то, что эта возможность ещё не вошла в язык Java, и для того чтобы её использовать Вы должны компилировать программу использовав специальный флаг enable preview.

То есть super() может быть теперь и второй строчкой. Но сразу нужно сказать, что не весь код можно писать перед super().

Выделяется пролог - это код до вызова super(). В предыдущем примере - это System.out.println. И эпилог - это код после вызова super() или же весь код в конструкторе.

В прологе МОЖНО выполнять любой код, который не обращается к создаваемому объекту:

  • инициализировать переменные классы

  • во внутреннем классе можно обращаться к членам внешнего (outer) класса 

В прологе НЕЛЬЗЯ: 

  • вызывать нестатические методы класса

  • создавать объекты  нестатических внутренних классов (nonstatic inner)

 Давайте более детально рассмотрим те случаи, когда в прологе мы можем писать какой-то код:

  • Для валидации аргументов, передаваемых в super()

  • Подготовка аргументов для конструктора суперкласса

  • Передача аргументов конструктору суперкласса

Первый вариант - это для валидации аргументов передаваемых в super().

Давайте вернёмся к классу HeavyBox:

 public HeavyBox(int width, int height, int depth, int weight) {
        if (width <= 0) {
            throw new IllegalArgumentException();
        }
        super(width, height, depth);
        this.weight = weight;
 }

Представьте, что перед вызовом конструктора нужно проверить, что переменная width больше нуля. Если ширина меньше либо равна нулю, то необходимо выбросить исключение - в этом случае нет смысла создавать объект.

Второй вариант, когда это может быть необходимо - подготовка аргументов для передачи в конструктор суперкласса. Допустим, необходимо удвоить переменные width, height и depth перед вызовом конструктора:

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

И третий вариант, где это может использоваться - передача аргументов конструктору суперкласса. Допустим, в классе Box есть конструктор, на вход которого передаются две переменные SomeClass:

public class Box {
   ...
   public Box(SomeClass s1, SomeClass s2) {
   }
   ...
}

И из HeavyBox вызывается этот конструктор. Если перед super() нельзя писать никакой код, то придётся создать два разных объекта: 

 public HeavyBox() {
     super(new SomeClass(), new SomeClass());
 }

Либо необходимо передавать эти объекты извне в HeavyBox. Но если есть возможность написать какой-то код перед super, то можно создать один объект и передавать его в качестве первого и второго аргумента:

 public HeavyBox1() {
      SomeClass s = new SomeClass();
      super(s, s);
}

3. Обращения к члену супер класса с помощью ключевого слова super

И теперь давайте перейдем ко второй форме использования ключевого слова super - для обращения к членам суперкласса (к методам либо к переменным).

С помощью ключевого слова super можно обратиться к члену супер класса из класса наследника. Чаще всего это можно сделать не используя super, но в этом примере рассмотрим случаи, когда без него не обойтись.

В классе С объявлена переменная i типа int. В его наследнике классе D, тоже объявлена переменная i, но типа String. (Сразу же предупредим - на практике не стоит так делать! Пример приводится с целью иллюстрирования применение ключевого слова super с переменными.) Из класса D мы можем напрямую обратиться только к переменной String i, которая перекрывает область видимости переменной int i. Для обращения же к int i, необходимо использовать слово super.

Похожая ситуация и с методами. В обоих классах определен метод print(). Если мы хотим из класса D вызвать метод print() класса С, используем слово super - super.print().

public class C {
    public int i;
    public void print() {
        System.out.println("C.i = " + i);
    }
}

public class D extends C {
    public String i;

    public D(String a, int b) {
        i = a;
        super.i = b;
    }

    public void print() {
        System.out.println("D.i = " + i);
        super.print();
    }
}


public class UseSuperExample {
    public static void main(String[] args) {
        D d = new D("someString", 2);
        d.print();
        System.out.println(d.i);
    }
}

Вывод на консоль:

D.i = someString
C.i = 2
someString

Презентацию с видео можно скачать на Patreon.

Курс 'Java для начинающих' на Udemy Курс 'Java для начинающих' на Udemy
Читайте также:
Комментарии