W3docs

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

Обращение к родительскому классу в Java через super — вызов конструкторов и переопределённых методов.

super — это аналог this для родительского класса. Если this означает «текущий объект», то super — «текущий объект, рассматриваемый как экземпляр суперкласса». Ключевое слово super имеет смысл только внутри класса, который расширяет другой, и используется в трёх местах:

  1. super(args) внутри конструктора — вызов конструктора родителя.
  2. super.method(args) внутри метода экземпляра — вызов родительской версии переопределённого метода.
  3. super.field внутри метода экземпляра — чтение поля родителя, перекрытого подклассом.

Первый вариант встречается намного чаще всего.

super(...) — вызов конструктора родителя

Каждый конструктор подкласса должен начинаться с вызова какого-либо конструктора родителя. Если вы не пишете его явно, Java автоматически вставляет super(); в качестве первой строки — вызов конструктора родителя без аргументов:

public class Animal {
  String name;
  public Animal() { name = "(unnamed)"; }     // no-arg
}

public class Cat extends Animal {
  public Cat() {
    // super();  ← inserted by the compiler
  }
}

Когда у родителя нет конструктора без аргументов, необходимо явно вызвать подходящий:

public class Animal {
  String name;
  public Animal(String name) { this.name = name; }   // no no-arg constructor
}

public class Cat extends Animal {
  public Cat(String name) {
    super(name);              // required — there is no Animal()
  }
}
Внимание

Самая распространённая ошибка при наследовании — забыть об этом. Когда у родителя нет конструктора без аргументов, компилятор всё равно пытается вставить super(); в любой конструктор подкласса, который не вызывает super(...) явно, — и завершается с ошибкой "there is no default constructor available in Animal". Решение — добавить явный вызов super(...), соответствующий одному из существующих конструкторов родителя.

Правила для super(...):

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

Почему существует правило «первого оператора»?

JVM обязана полностью инициализировать родительскую часть объекта до того, как выполнится какой-либо код подкласса. Это включает инициализацию final-полей, объявленных в родителе. Если бы код подкласса выполнялся первым, он обращался бы к этим полям до того, как им были присвоены значения.

super.method(args) — вызов родительской версии метода

Внутри метода экземпляра super.method(...) вызывает родительскую версию method, даже если текущий класс её переопределил:

public class Animal {
  String speak() { return "(some noise)"; }
}

public class Cat extends Animal {
  @Override
  String speak() {
    return super.speak() + " — actually, meow";
    //     ^^^^^^^^^^^^^ calls Animal.speak(), not Cat.speak()
  }
}

new Cat().speak();    // "(some noise) — actually, meow"

Без super. обычный вызов speak() внутри Cat.speak обратился бы к самому себе и ушёл в бесконечную рекурсию.

Классический паттерн — расширять поведение родителя, а не заменять его:

@Override
void onSave() {
  super.onSave();      // run parent's save logic first
  // then add subclass-specific behavior
}

super.method(...) не может подняться на два уровня вверх — в Java не существует super.super.method(). Если ваш класс расширяет класс, который расширяет другой, вы можете вызвать метод непосредственного родителя, но не прародителя.

super.field — доступ к перекрытому полю

Если подкласс объявляет поле с тем же именем, что и поле родителя, поле подкласса скрывает (shadows) родительское:

public class A {
  String label = "A's label";
}
public class B extends A {
  String label = "B's label";

  void show() {
    System.out.println(label);          // B's label
    System.out.println(super.label);    // A's label
    System.out.println(this.label);     // B's label
  }
}

Это почти всегда плохой дизайн — иметь два поля с одинаковым именем в паре родитель–потомок запутывает код, — однако язык предоставляет super.field для разрешения неоднозначности. Чище всего переименовать одно из полей.

Обратите внимание: поля не являются полиморфными. В отличие от переопределённых методов, обращение к полю определяется во время компиляции на основе объявленного типа ссылки. Подробнее об этом рассказывается в главе о полиморфизме.

super и static

super, как и this, существует только в контексте экземпляра. Нельзя использовать super в static-методе или инициализаторе — там нет текущего объекта, а значит, нет и его родительского представления. Для вызова статического метода родителя следует указывать имя класса родителя напрямую:

public class A { static void hi() { System.out.println("hi"); } }
public class B extends A {
  static void demo() {
    A.hi();             // ok — call by class name
    // super.hi();      // ERROR — super in a static context
  }
}

Подкласс также может объявить static-метод с тем же именем, что и static-метод родителя, — это называется сокрытием метода (method hiding), а не переопределением, и подчиняется правилам времени компиляции. Кратко это рассматривается в главе о переопределении методов.

Цепочки вызовов super

Каждый вызов super(...) активирует следующий уровень цепочки наследования. Трассировка выполнения для глубоко вложенного подкласса:

new SiameseCat("Lulu")
  → SiameseCat(String)
      → super("Lulu")        — Cat(String)
          → super("Lulu")    — Animal(String)
              → super()      — Object()
              → Animal body runs
          → Cat body runs
      → SiameseCat body runs

Инициализация всегда идёт от самого дальнего предка вниз к текущему классу. К моменту, когда выполняется тело SiameseCat, все поля родителей полностью инициализированы.

Пример

java— editable, runs on the server

Что дальше

super — это мост к родительскому классу, но главная идея, которую он открывает, — полиморфизм: возможность вызвать метод через ссылку родительского типа и получить вызов нужной реализации подкласса в момент выполнения. Это тема следующей главы. Продолжайте с полиморфизма в Java.

Практика

Практика
Внутри конструктора когда компилятор неявно вставляет super();?
Внутри конструктора когда компилятор неявно вставляет super();?
Was this page helpful?