Herança de construtores e Override

Agora que você já sabe o que é herança, sua importância, quando usar e viu vários exemplos práticos do mundo real, vamos mostrar como fazer uma classe herdar as características de outra.

Nesse tutorial de Java ensinaremos também a relação dos construtores com as superclasses e subclasses, e como usar o tão importante @Override.

Sintaxe de declaração da Herança

Se quisermos declarar uma classe "Filha" que herda os atributos do pai, simplesmente usamos a palavra "extends" na hora de criar a classe:

public class Filha extends Pai{
}


Herança: o construtor da Subclasse sempre invoca o construtor da Superclasse

Um fato importante: os construtores são algo único a cada classe, portanto não são herdados.

Porém, é possível invocar os construtores de uma superclasse através da subclasse.
Vale lembrar que, para uma aplicação funcionar corretamente, algumas variáveis devem ser iniciadas. Algumas dessas variáveis são iniciadas em uma superclasse, portanto, sempre que o método construtor de uma subclasse roda, roda também o construtor de sua superclasse.

Usando um dos exemplos de nosso tutorial passado sobre herança, na classe "AlunoMedio", que tem algumas disciplinas (como Física e Química), que não existem na classe dos alunos do ensino médio, ela foi herdada da classe "Aluno".
Porém, no construtor da superclasse "Aluno" é que são inicializadas informações de todos os alunos, como nome e número de matrícula.
Portanto, a primeira coisa que construtor da "AlunoMedio" faz é rodar o construtor da "Aluno" - sua superclasse - pois informações e ações importantes ocorrem no construtor da "Aluno".

Resumindo: a primeira coisa que um construtor faz é rodar o construtor de sua superclasse, pois ações importantes podem estar acontecendo lá - como inicialização de variáveis que poderão ser usadas na subclasse.

Vamos ver isso na prática, criando duas classes: a Pai e a Filha, e óbvio, fazendo a Filha herdar a classe Pai.

--->Heranca.java
public class Heranca {

    public static void main(String[] args) {
        new Filha();

    }

}


--->Pai.java
public class Pai {

    public Pai(){
        System.out.println("Método construtor da classe Pai");
    }
}


--->Filha.java
public class Filha extends Pai {

    public Filha(){
        System.out.println("Método construtor da classe Filha");
    }
}


O resultado disso é:
Método construtor da classe Pai
Método construtor da classe Filha

Isso mostra que a primeira coisa que um construtor de uma subclasse faz é invocar o construtor da superclasse. Só depois é que esse construtor da classe Filha faz suas ações.

O exemplo a seguir mostra como a inicialização de variáveis realmente ocorre na superclasse antes de ocorrer na subclasse.

Definimos o nome do pai na classe "Pai" e fazemos a classe "Filha" herdar a pai.
Note que classe "Filha" usamos a variável 'nomePai', mas não declaramos ela na classe Filha.
Porém podemos usar esse atributo pois ele pertence a classe Pai.
Viu que esse valor já vem inicializado? Ou seja, o construtor da classe Pai realmente ocorreu antes da classe Filha.


Heranca.java
public class Heranca {

    public static void main(String[] args) {
        Filha filha = new Filha("Mariazinha");

    }

}


Pai.java
public class Pai {

    public String nomePai;
    
    public Pai(){
        this.nomePai = "Neil";
    }

}


Filha.java
public class Filha extends Pai {

    private String nomeFilha;
    
    public Filha(String nomeFilha){
        this.nomeFilha = nomeFilha;
        
        System.out.println("O nome da filha é '" + this.nomeFilha +
                   "' e o do pai é '" + nomePai + "'.");
    }

}


Caso o 'nomePai' fosse private, você poderia ter criado um método público na classe "Pai" que retorna o nome do pai, e usar esse método na classe "Filha" sem problema algum.


Herança: quando a superclasse não tem método construtor

Ok, a chamada da superclasse sempre ocorre. Mas pode ocorrer de duas formas: ou você claramente invoca ela ou ela ocorre sozinha.
Quando ela ocorre sozinha, o método construtor chamado é o padrão, que não recebe argumentos.
Note que sua superclasse pode não conter nenhum construtor. Mesmo assim ele é invocado e nada ocorre, pois o Java sempre cria um construtor vazio, que não faz nada, para as classes que são definidas sem construtor.

Então se você não quer que o método construtor da superclasse interfira na subclasse, é só não criar nenhum construtor na superclasse, que nada ocorrerá.
Veja o exemplo a seguir, em que apenas definimos a variável 'nomePai' na classe "Pai". Ela não tem nenhum construtor.
Então, ao ser invocado o construtor da "Pai" na "Filha", nada ocorre na "Filha".

Heranca.java
public class Heranca {

    public static void main(String[] args) {
        Filha filha = new Filha("Mariazinha");

    }

}


Pai.java
public class Pai {

    public String nomePai;

}


Filha.java
public class Filha extends Pai {

    private String nomeFilha;
    
    public Filha(String nomeFilha){
        this.nomeFilha = nomeFilha;
        
        System.out.println("O nome da filha é '" + this.nomeFilha +
                   "' e o do pai é '" + nomePai + "'.");
    }

}


O que é e como fazer Override em Java


Suponha que na classe Pai tenha o método nome(), que mostra uma String na tela, com o nome da pessoa:

public void nome(){
System.out.println("O nome do pai é '" + this.nomePai + "'.");
}


Se você usar esse método na classe Filha verá o nome do pai, correto?
Claro, pois a classe filha herda os métodos também da classe pai também.

Mas esse método retorna o nome da pessoa da classe. Não faz sentido ver o nome do pai, quando invocamos esse método na classe filha.
Para isso, vamos criar um método próprio da classe "Filha", que retorne o nome dela, e não o do pai.
Ficará assim:

public void nome(){
System.out.println("O nome da filha é '" + this.nomeFilha + "'.");
}

Porém vamos usar o mesmo nome nesse método, 'nome()'.
E agora? Ao chamar esse método, o que Java vai mostrar? O método da classe Pai ou da classe Filha?

Não vamos nos estressar com o que ele vai mostrar, pois vamos usar um Override, ou seja, vamos sobrescrever um método.
O método original, é claro que é o da classe "Pai".
Porém, quando criarmos uma classe filha e invocarmos o método 'nome()' queremos que apareça o nome da filha, e não o do pai. Então queremos que o método chamado seja o método da subclasse e não o método da superclasse.

Para dizer isso ao Java escrevemos "@Override" antes do método da subclasse.
Então, nosso programa fica assim:


Heranca.java
public class Heranca {

    public static void main(String[] args) {
        Filha filha = new Filha("Mariazinha");
        Pai pai = new Pai();
        
        filha.nome();
        pai.nome();
    }

}


Pai.java
public class Pai {

    public String nomePai;
    
    public Pai(){
        this.nomePai="Neil";
    }
    
    public void nome(){
        System.out.println("O nome do pai é '" + nomePai + "'.");
    }
}


Filha.java
public class Filha extends Pai {

    private String nomeFilha;
    
    public Filha(String nomeFilha){
        this.nomeFilha = nomeFilha;
    }
    
    @Override
    public void nome(){
        System.out.println("O nome da filha é '" + this.nomeFilha + "'.");
    }
}


Pronto. Com o @Override, o Java vai mostrar o nome da filha, caso o objeto seja do tipo Filha.
E vai mostrar o nome do pai caso o objeto seja apenas do tipo Pai.

super: Chamando o construtor da Superclasse

Muitas vezes, em nossas aplicações Java, criamos um objeto que é subclasse de outra.
Porém passamos 'direto' pela superclasse e vamos usar diretamente o objeto da subclasse.

Mas acontece que a subclasse depende das variáveis inicializadas na superclasse.
E como vai ser inicializada essas variáveis, já que não criamos um objeto da superclasse, e sim da subclasse?

A resposta é: usando a keyword super.
Com a super, nós chamamos o construtor da superclasse.
Por exemplo, vamos supor que queiramos criar somente um objeto da classe "Filha". Ou seja, não queremos criar a "Pai". Somente a filha!
Mas a filha usa o nome do pai. Então vamos chamar o construtor da Pai no construtor da Filha, passando o argumento para ele (que é o nome do pai).

Nosso código ficará assim:

Heranca.java
public class Heranca {

    public static void main(String[] args) {
        Filha filha = new Filha("Mariazinha", "Neil");
        
        filha.nome();
    }

}

Pai.java
public class Pai {

    public String nomePai;
    
    public Pai(String nomePai){
        this.nomePai=nomePai;
    }
    
    public void nome(){
        System.out.println("O nome do pai é '" + nomePai + "'.");
    }
}

Filha.java
public class Filha extends Pai {

    private String nomeFilha;
    
    public Filha(String nomeFilha, String nomePai){
        super(nomePai);
        this.nomeFilha = nomeFilha;
    }
    
    @Override
    public void nome(){
        System.out.println("O nome da filha é '" + this.nomeFilha + "', e do pai '"+nomePai+"'.");
    }
}

8 comentários:

Anônimo disse...

Legal :)

Anônimo disse...

Mais um ótimo artigo... ^^
Obrigado cara, está me ajudando bastante, já que um outro conteúdo que tenho não explica tão especificamente os assuntos.

emanuel douglas disse...

Dei mo valor a essa aula, so fica esperto que a explicação do keywords "super" ta meio sinistra de entender, tipo meio na nuvens.

Mathias C. M disse...

Lembrando que se você tiver que passar mais de um parâmetro para a classe Pai, você deve passar no mesmo super. Ex:

super(nomePai,idadePai....);

Anônimo disse...

Muito bom. Só lembrando que construtores não são métodos.

João Paulo disse...

Boa noite !

Uma dúvida...
E se no caso abaixo:


public class Heranca {

public static void main(String[] args) {
Filha filha = new Filha("Mariazinha", "Neil");
Pai pai = new Pai();

filha.nome();
pai.nome();
}

}


public class Pai {
public String nomePai;

public Pai(){

}

public Pai(String nomePai){
this.nomePai=nomePai;
}

public void nome(){
System.out.println("O nome do pai é '" + nomePai + "'.");
}
}


public class Filha extends Pai {
private String nomeFilha;

public Filha(String nomeFilha, String nomePai){
super(nomePai);
this.nomeFilha = nomeFilha;
}

@Override
public void nome(){
System.out.println("O nome da filha é '" + this.nomeFilha + "', e do pai '"+nomePai+"'.");
}
}


Por quê o resultado é esse abaixo??

run:
O nome da filha é 'Mariazinha', e do pai 'Neil'.
O nome do pai é 'null'.
CONSTRUÍDO COM SUCESSO (tempo total: 0 segundos)

Obrigado !
Excelente esse site !!!

Raul disse...

João Paulo, quando vc criou o objeto filha: Filha filha = new Filha("Mariazinha", "Neil"), vc passou dois argumentos como parâmetro, logo o construtor que recebe 2 parâmetros foi chamado, e dentro desse construtor, foi chamado o construtor da classe pai com um argumento: super(nomePai).

Já quando vc criou o objeto pai: Pai pai = new Pai(), vc n passou nenhum argumento, logo o construtor da classe pai, que é usado pra atribuir um valor a variável nome não foi chamado e esse campo continuou nulo.

Quando vc chamou os métodos nome() das duas classes:

filha.nome();
pai.nome();

foi chamado o método por cada objeto, e eles são independentes, logo o valor nome da classe pai é diferente do valor nome da classe filha.

Alex Amorim disse...

João Paulo ,você criou 2 construtores na Classe Pai : um vazio e o outro com parâmetro, se você perceber bem , na hora que você instanciou(Criou o objeto) a classe Pai no método main, você chamou o construtor vazio, por isso que a resposta no console foi null.

Exemplo :

Voce chamou esse aqui:
//construtor da classe pai vazio que é null
Pai pai = new Pai(null);
pai.nome();
Qual é a resposta ?

agora se chamar esse aqui:
// construtor da classe Pai com Parâmetro do tipo String
Pai pai = new Pai("Neil");
pai.nome();

Qual é a Resposta ?

Espero ter ajudado , abraços.

Dicas e Novidades de Java por e-mail

Sabe quanto custa um bom livro de java?
Entre R$ 100,00 e R$300,00

Sabe quanto custa um bom curso presencial de Java?
Entre R$ 1.500,00 até R$ 4.000,00

Sabe quanto custa estudar pelo Java Progressivo?
Absolutamente nada.

Porém, também precisamos de sua ajuda e apoio.
Para isso, basta curtir nossa Fan Page e clicar no botão G+ do Google.