Java super

Na aula de Herança, a subclasse recebia tudo da superclasse sem esforço. Mas há dois momentos em que ela precisa falar com a superclasse: na hora de nascer (o construtor da mãe tem que rodar) e quando quer usar a versão da mãe de um método que ela mesma trocou. Para os dois casos existe a palavra super.

Você vai aprender
  • Por que construtores não são herdados e como chamar o da superclasse com super(…).
  • O erro constructor … cannot be applied to given types e como resolvê-lo.
  • A ordem em que os construtores rodam (com uma demonstração).
  • O super() que o compilador escreve sozinho.
  • Onde o super(…) pode ficar: a regra antiga ("primeira linha") e a do Java 25.
  • Como chamar a versão da superclasse de um método com super.metodo().

A ideia: "super" = a parte de cima

Um objeto Cachorro é, ao mesmo tempo, um Animal. Pense nele como um bolo de duas camadas: embaixo, a camada Animal (o nome); em cima, a camada Cachorro (a raça). A palavra super é o jeito de a camada de cima se referir à de baixo, a da superclasse. Ela aparece de duas formas:

FormaOndeO que faz
super(…)Dentro de um construtorChama um construtor da superclasse, para montar a camada de baixo
super.metodo(…)Dentro de um métodoChama a versão da superclasse de um método

Ela é prima do this: this(…) chama outro construtor da mesma classe, this.x fala do objeto atual. super faz o mesmo, olhando um degrau acima.


super(…): chamando o construtor da superclasse

A Ana deu um construtor ao Animal, para que todo animal nasça com nome. Agora o Cachorro precisa de um construtor que receba nome e raça:

public class Main {
    public static void main(String[] args) {
        Cachorro rex = new Cachorro("Rex", "vira-lata");
        System.out.println(rex.nome + " é um " + rex.raca);
    }
}

class Animal {
    String nome;

    Animal(String nome) {
        this.nome = nome;
    }
}

class Cachorro extends Animal {
    String raca;

    Cachorro(String nome, String raca) {
        super(nome);
        this.raca = raca;
    }
}
Cachorro(String nome, String raca)
O construtor do Cachorro recebe tudo o que é preciso para montar as duas camadas.
super(nome);
Chama o construtor Animal(String nome), passando o nome. Quem cuida do nome é o Animal; o cachorro só repassa.
this.raca = raca;
Depois, o Cachorro cuida da parte dele.
$ java Main.java
Rex é um vira-lata

Por que não basta escrever this.nome = nome?

Os construtores não são herdados. O Animal disse "para criar um animal, me dê um nome". Todo cachorro é um animal; então, ao criar um cachorro, alguém tem que chamar o construtor do Animal com um nome. Se você não chamar, o compilador tenta chamar Animal() sem argumentos, que não existe:

Este código tem um erro de propósito
public class Main {
    public static void main(String[] args) {
        Cachorro rex = new Cachorro("Rex", "vira-lata");
    }
}

class Animal {
    String nome;

    Animal(String nome) {
        this.nome = nome;
    }
}

class Cachorro extends Animal {
    String raca;

    Cachorro(String nome, String raca) {
        this.nome = nome;
        this.raca = raca;
    }
}
$ javac Main.java
Main.java:18: error: constructor Animal in class Animal cannot be applied to given types;
    Cachorro(String nome, String raca) {
                                       ^
  required: String
  found:    no arguments
  reason: actual and formal argument lists differ in length
1 error

Leia devagar, linha por linha:

  • constructor Animal in class Animal cannot be applied to given types: "o construtor Animal não pode ser usado com os tipos dados".
  • required: String: ele exige uma String.
  • found: no arguments: "encontrado: nenhum argumento".
  • reason: actual and formal argument lists differ in length: "as listas de argumentos têm tamanhos diferentes".

Repare que o erro aponta a chave que abre o construtor do Cachorro, onde não há nenhuma chamada visível. É ali que o compilador colocou um super() invisível, como você vai ver a seguir. O conserto é chamar super(nome) você mesmo.

Missão: a classe Gato não compila. Rode e leia o erro. Depois, no construtor do Gato, troque a linha this.nome = nome; por uma chamada ao construtor do Animal com super. A saída deve ser Mimi tem 5 anos.

public class Main {
    public static void main(String[] args) {
        Gato mimi = new Gato("Mimi", 5);
        System.out.println(mimi.nome + " tem " + mimi.idade + " anos");
    }
}

class Animal {
    String nome;

    Animal(String nome) {
        this.nome = nome;
    }
}

class Gato extends Animal {
    int idade;

    Gato(String nome, int idade) {
        this.nome = nome;
        this.idade = idade;
    }
}
public class Main {
    public static void main(String[] args) {
        Gato mimi = new Gato("Mimi", 5);
        System.out.println(mimi.nome + " tem " + mimi.idade + " anos");
    }
}

class Animal {
    String nome;

    Animal(String nome) {
        this.nome = nome;
    }
}

class Gato extends Animal {
    int idade;

    Gato(String nome, int idade) {
        super(nome);
        this.idade = idade;
    }
}

O construtor da superclasse é chamado sozinho

Se todo objeto tem uma camada de cada classe da família, todos os construtores precisam rodar. Quem garante isso é o compilador: se um construtor não começa chamando super(…) ou this(…), ele acrescenta super(); (sem argumentos) por você. Para ver isso acontecendo, a Ana colocou um println em cada construtor de uma família de três gerações:

public class Main {
    public static void main(String[] args) {
        System.out.println("Criando o Rex...");
        Cachorro rex = new Cachorro();
        System.out.println("Pronto!");
    }
}

class Animal {
    Animal() {
        System.out.println("1. construtor de Animal");
    }
}

class Mamifero extends Animal {
    Mamifero() {
        System.out.println("2. construtor de Mamifero");
    }
}

class Cachorro extends Mamifero {
    Cachorro() {
        System.out.println("3. construtor de Cachorro");
    }
}
$ java Main.java
Criando o Rex...
1. construtor de Animal         « rodou primeiro, sem ninguém pedir
2. construtor de Mamifero
3. construtor de Cachorro       « o que o new pediu termina por último
Pronto!

Nenhum construtor escreveu super(), mas os três rodaram, de cima para baixo. O que aconteceu foi:

  1. new Cachorro() chama o construtor de Cachorro.
  2. A primeira coisa que ele faz é o super() invisível: chama Mamifero().
  3. O de Mamifero também começa com super(): chama Animal().
  4. O de Animal chama o de Object (que não faz nada) e depois imprime a linha 1.
  5. Volta para Mamifero, que imprime a linha 2; volta para Cachorro, que imprime a linha 3.

Faz sentido: como no bolo, a camada de baixo tem que estar pronta antes da de cima. Quando o construtor do Cachorro roda o código dele, o nome do Animal já está arrumado.

O construtor padrão também chama super()

Uma classe sem nenhum construtor ganha um vazio, o construtor padrão (veja Construtores). Ele também contém o super() invisível. Por isso, se o Animal só tiver Animal(String nome), até um class Gato extends Animal { } vazio dá o erro constructor Animal in class Animal cannot be applied to given types.


Onde o super(…) pode ficar

A regra clássica: primeira linha

Do Java 1.0 ao Java 24, a regra era rígida: super(…) (ou this(…)) tinha que ser a primeira instrução do construtor. Nada antes, nem um println. Quase todo código Java que você vai ler segue essa regra. Compilando como Java 21 (a opção --release 21 faz o javac se comportar como o de 21), dá para ver o erro:

Este código tem um erro de propósito (até o Java 24)
class Cachorro extends Animal {
    String raca;

    Cachorro(String nome, String raca) {
        this.raca = raca;
        super(nome);
    }
}
$ javac --release 21 Main.java
Main.java:20: error: flexible constructors is not supported in -source 21
        this.raca = raca;
        ^
  (use -source 25 or higher to enable flexible constructors)
1 error

O arquivo é o do começo da aula, com as duas linhas do construtor trocadas de lugar (por isso o erro fala da linha 20). A mensagem já denuncia a novidade: "construtores flexíveis não são suportados no Java 21; use 25 ou mais".

A regra do Java 25: o "prólogo"

Java 25+ O Java 25 trouxe os construtores flexíveis (flexible constructor bodies): agora pode haver código antes do super(…). Esse trecho se chama prólogo. O uso mais útil é validar os argumentos antes de montar o objeto:

class Cachorro extends Animal {
    String raca;

    Cachorro(String nome, String raca) {
        if (nome.isBlank()) {
            throw new IllegalArgumentException("nome vazio");
        }
        super(nome);
        this.raca = raca;
    }
}

Com o JDK 25 da Ana, isso compila e roda (Rex é um vira-lata). Mas o prólogo tem uma restrição importante: como a camada de baixo ainda não foi montada, ali não se pode usar o objeto. Nada de ler atributos ou chamar métodos dele:

$ javac Main.java
Main.java:17: error: cannot reference this before supertype constructor has been called
        System.out.println("Criando " + this.nome);
                                        ^
1 error
$ javac Main.java
Main.java:21: error: cannot reference apresentar() before supertype constructor has been called
        apresentar();
        ^
1 error

"Não é possível usar this (ou o método apresentar()) antes de o construtor da superclasse ter sido chamado." A única coisa permitida com o próprio objeto no prólogo é dar valor a atributos da própria classe (como o this.raca = raca; do exemplo anterior, que compila no Java 25).

Antes do super(…)…Java 24 ou anteriorJava 25+
Validar parâmetros, fazer contas com elesErroPode
Dar valor a um atributo da própria classeErroPode
Ler um atributo, usar this, chamar um método do objetoErroErro (cannot reference … before supertype constructor has been called)
Chamar super(…) duas vezes, ou super(…) e this(…)ErroErro

Sobre a última linha: um construtor só pode ter uma chamada explícita de construtor. Duas dão:

$ javac Main.java
Main.java:18: error: redundant explicit constructor invocation
        super(nome);
             ^
1 error

Redundant explicit constructor invocation = "chamada explícita de construtor redundante". Na dúvida, siga o estilo que funciona em todas as versões: super(…) na primeira linha.


super.metodo(): a versão da superclasse

A segunda forma de super aparece quando a subclasse sobrescreve um método, isto é, escreve a sua própria versão de um método que herdou. A próxima aula trata disso em detalhe; aqui basta a ideia. O Animal sabe se descrever. O Cachorro quer a mesma descrição, mais a raça. Em vez de copiar o código do Animal, ele chama a versão da mãe e acrescenta:

public class Main {
    public static void main(String[] args) {
        Animal bicho = new Animal("Totó");
        Cachorro rex = new Cachorro("Rex", "vira-lata");
        System.out.println(bicho.descrever());
        System.out.println(rex.descrever());
    }
}

class Animal {
    String nome;

    Animal(String nome) {
        this.nome = nome;
    }

    String descrever() {
        return "Animal chamado " + nome;
    }
}

class Cachorro extends Animal {
    String raca;

    Cachorro(String nome, String raca) {
        super(nome);
        this.raca = raca;
    }

    @Override
    String descrever() {
        return super.descrever() + ", da raça " + raca;
    }
}
@Override
Uma anotação que avisa "este método substitui um herdado". Ela é explicada em Sobrescrita.
String descrever()
A versão do Cachorro, com o mesmo nome e os mesmos parâmetros da do Animal.
super.descrever()
Chama a versão do Animal, que devolve "Animal chamado Rex".
+ ", da raça " + raca
O que o Cachorro acrescenta.
$ java Main.java
Animal chamado Totó                      « versão do Animal
Animal chamado Rex, da raça vira-lata    « versão do Cachorro, usando a do Animal

Se um dia a Ana mudar a descrição do Animal, a do Cachorro muda junto. Diferente do super(…), o super.metodo() pode aparecer em qualquer linha de qualquer método (não static) da subclasse.

O que acontece se esquecer o super.

Parece um detalhe, mas sem o super. o método chama a si mesmo: descrever() dentro de descrever() do Cachorro é o próprio descrever() do Cachorro, que chama de novo, e de novo… (é uma recursão sem fim). Trocando super.descrever() por descrever():

$ java Main.java 2>&1 | head -8
Animal chamado Totó
Exception in thread "main" java.lang.StackOverflowError
	at Cachorro.descrever(Main.java:32)
	at Cachorro.descrever(Main.java:32)
	at Cachorro.descrever(Main.java:32)
	at Cachorro.descrever(Main.java:32)
	at Cachorro.descrever(Main.java:32)
	at Cachorro.descrever(Main.java:32)
$ java Main.java 2>&1 | wc -l
1021

A saída tem 1.021 linhas, quase todas iguais (o head -8 mostra só as 8 primeiras; o wc -l conta). StackOverflowError = "erro de estouro da pilha": cada chamada ocupa um espaço na memória (a pilha), e chamadas sem fim a esgotam. A JVM corta o rastro depois de umas mil linhas. A linha Main.java:32 é a do return descrever() + …: quando o rastro repete a mesma linha, procure uma chamada que deveria ter super..


Quando dá errado

"constructor … in class … cannot be applied to given types"
$ javac Main.java
Main.java:18: error: constructor Animal in class Animal cannot be applied to given types;
    Cachorro(String nome, String raca) {
                                       ^
  required: String
  found:    no arguments
  reason: actual and formal argument lists differ in length
1 error

O que aconteceu: a superclasse não tem construtor sem parâmetros, e o construtor da subclasse não chamou super(…). O compilador pôs super() e não achou o construtor certo.

Como resolver: comece o construtor da subclasse com super(argumentos), com os tipos que o required: pede. Outra opção, se fizer sentido, é criar um construtor sem parâmetros na superclasse.

"cannot reference this before supertype constructor has been called"

O que aconteceu: no Java 25, o código antes do super(…) usou o objeto (leu um atributo, chamou um método).

Como resolver: mova essa linha para depois do super(…). Antes dele, use só os parâmetros.

"flexible constructors is not supported in -source …"

O que aconteceu: há código antes do super(…) e o projeto compila para Java 24 ou anterior (por --release, pelo Maven ou porque o JDK é antigo).

Como resolver: coloque o super(…) na primeira linha. Para validar antes, a forma clássica é um método static que valida e devolve o valor: super(validar(nome));.

StackOverflowError com a mesma linha repetida

O que aconteceu: um método sobrescrito chamou a si mesmo quando queria chamar a versão da superclasse.

Como resolver: acrescente super. na chamada: super.descrever().


Referência

EscritaOndeO que faz
super(a, b);ConstrutorChama o construtor da superclasse que recebe esses argumentos
super();ConstrutorChama o construtor sem parâmetros (o compilador escreve sozinho se você não chamar nenhum)
this(a, b);ConstrutorChama outro construtor da mesma classe (que por sua vez chama o super)
super.metodo()Método de instânciaChama a versão da superclasse do método
super.atributoMétodo de instânciaO atributo da superclasse (só útil se a subclasse tiver um atributo com o mesmo nome, o que é melhor evitar)

Exercícios

1. Repassar o nome

O construtor Gato(String nome) precisa passar o nome para Animal(String nome). Complete a primeira linha:

2. A versão da mãe

Dentro do toString() de Gerente, complete para usar o toString() de Funcionario:

return + " (gerente)";

3. A instrução invisível

Que instrução o compilador acrescenta no começo de um construtor que não chama nem super(…) nem this(…)?

4. C extends B e B extends A, cada um com um construtor que imprime a própria letra. O que new C() mostra?

5. No Java 25, o que não pode vir antes do super(nome) num construtor?

6. Um método sobrescrito chama descrever() em vez de super.descrever(). O que acontece?

Missão final: complete a classe Gerente. (1) No construtor, chame o construtor de Funcionario com super(nome, salario) antes de guardar o bônus. (2) No método descrever(), use super.descrever() e acrescente " - bônus " e o valor do bônus. A saída deve ser Bia - salário 7000.0 - bônus 2000.0.

public class Main {
    public static void main(String[] args) {
        Gerente bia = new Gerente("Bia", 7000, 2000);
        System.out.println(bia.descrever());
    }
}

class Funcionario {
    String nome;
    double salario;

    Funcionario(String nome, double salario) {
        this.nome = nome;
        this.salario = salario;
    }

    String descrever() {
        return nome + " - salário " + salario;
    }
}

class Gerente extends Funcionario {
    double bonus;

    Gerente(String nome, double salario, double bonus) {
        // 1. chame o construtor de Funcionario aqui
        this.bonus = bonus;
    }

    @Override
    String descrever() {
        return ""; // 2. use super.descrever() aqui
    }
}
public class Main {
    public static void main(String[] args) {
        Gerente bia = new Gerente("Bia", 7000, 2000);
        System.out.println(bia.descrever());
    }
}

class Funcionario {
    String nome;
    double salario;

    Funcionario(String nome, double salario) {
        this.nome = nome;
        this.salario = salario;
    }

    String descrever() {
        return nome + " - salário " + salario;
    }
}

class Gerente extends Funcionario {
    double bonus;

    Gerente(String nome, double salario, double bonus) {
        super(nome, salario);
        this.bonus = bonus;
    }

    @Override
    String descrever() {
        return super.descrever() + " - bônus " + bonus;
    }
}

Resumo

  • super fala com a superclasse: super(…) chama o construtor dela; super.metodo() chama a versão dela de um método.
  • Construtores não são herdados. Se a superclasse só tem construtor com parâmetros, a subclasse precisa chamar super(…); senão: constructor … cannot be applied to given types.
  • Sem super(…) nem this(…), o compilador põe super();. Os construtores rodam de cima para baixo: Object, Animal, Mamifero, Cachorro.
  • Até o Java 24, super(…) tinha que ser a primeira linha. No Java 25, pode haver um prólogo antes, desde que não use o objeto (cannot reference this before supertype constructor has been called).
  • Esquecer o super. num método sobrescrito faz o método chamar a si mesmo: StackOverflowError.