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.
- Por que construtores não são herdados e como chamar o da superclasse com
super(…). - O erro
constructor … cannot be applied to given typese 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:
| Forma | Onde | O que faz |
|---|---|---|
super(…) | Dentro de um construtor | Chama um construtor da superclasse, para montar a camada de baixo |
super.metodo(…) | Dentro de um método | Chama 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
Cachorrorecebe tudo o que é preciso para montar as duas camadas. - super(nome);
- Chama o construtor
Animal(String nome), passando o nome. Quem cuida do nome é oAnimal; o cachorro só repassa. - this.raca = raca;
- Depois, o
Cachorrocuida 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:
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
Animalnã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:
new Cachorro()chama o construtor deCachorro.- A primeira coisa que ele faz é o
super()invisível: chamaMamifero(). - O de
Mamiferotambém começa comsuper(): chamaAnimal(). - O de
Animalchama o deObject(que não faz nada) e depois imprime a linha 1. - Volta para
Mamifero, que imprime a linha 2; volta paraCachorro, 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.
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:
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 anterior | Java 25+ |
|---|---|---|
| Validar parâmetros, fazer contas com eles | Erro | Pode |
| Dar valor a um atributo da própria classe | Erro | Pode |
Ler um atributo, usar this, chamar um método do objeto | Erro | Erro (cannot reference … before supertype constructor has been called) |
Chamar super(…) duas vezes, ou super(…) e this(…) | Erro | Erro |
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 doAnimal. - super.descrever()
- Chama a versão do
Animal, que devolve"Animal chamado Rex". - + ", da raça " + raca
- O que o
Cachorroacrescenta.
$ 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
$ 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.
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.
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
| Escrita | Onde | O que faz |
|---|---|---|
super(a, b); | Construtor | Chama o construtor da superclasse que recebe esses argumentos |
super(); | Construtor | Chama o construtor sem parâmetros (o compilador escreve sozinho se você não chamar nenhum) |
this(a, b); | Construtor | Chama outro construtor da mesma classe (que por sua vez chama o super) |
super.metodo() | Método de instância | Chama a versão da superclasse do método |
super.atributo | Método de instância | O 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:
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
superfala 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(…)nemthis(…), o compilador põesuper();. 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.