Herança

Em Java, é possível herdar atributos e métodos de uma classe para outra. O mecanismo de
herança envolve dois tipos de classes;

  • subclasse (filho) – a classe que herda de outra classe
  • superclasse (pai) – a classe herdada

Uma das grandes vantagens do mecanismo de herança é a reutilização de código.

Implementação da herança em Java

O mecanismo de herança entre duas classe em Java é implementado usando a palavra-reservada extends:

class Veiculo{ 
   protected String marca = "Ford";        
   
   public void buzina() {                    
      System.out.println("Tuut, tuut!"); 
   } 
} 
class Carro extends Veiculo { 
   private String modelo = "Mustang";    
}

class Main {
   public static void main(String[] args) {  
      Carro carro = new Carro(); 

      carro.buzina(); 
      System.out.println(carro.marca + " " + carro.modelo); 
   } 
}

No exemplo acima a classe Carro (subclasse) herda os atributos (marca) e métodos (buzina) da classe Veiculo (superclasse).

A relação de herança entre estas duas classes é representada pelo diagrama abaixo.

Existe uma relação É entre a subclasse Carro e a superclasse Veiculo.

Por exemplo, se fizermos
Carro c = new Carro();
O objeto c é do tipo Carro mas também é do tipo Veiculo, por herança.

É por isso que podemos criar uma variável do tipo Veiculo mas que aloja um objeto de Carro:
Veiculo v = new Carro();

Nesta instrução torna-se evidente a relação É de Carro para Veiculo. Embora a variável v seja Veiculo a instância que nela é criada é uma instância de Carro.

No entanto, a relação É só é verdadeira de Carro para Veiculo e não ao contrário.
Isso é mais evidente no exemplo abaixo:

Podemos afirmar que Carro é Veiculo mas não que Veiculo é Carro, uma vez que pode ser Carro, Mota ou Camiao.

Conversão entre objetos de classes diferentes

Podemos converter objetos (cast) de classes diferentes desde que haja uma relação de herança entre eles.

Por exemplo, podemos converter um objeto do tipo Carro num objeto do tipo Veiculo porque Carro É Veiculo. :

Carro carro = new Carro();
Veiculo veiculo = (Veiculo) carro;

Já se fizermos o cast de Veiculo para Carro, tal pode originar uma exceção porque Veiculo pode não ser Carro mas sim Mota, por exemplo:

//A conversão faz-se sem problemas porque o objeto na variável veiculo é                           //do tipo Carro:
Veiculo veiculo = new Carro();
Carro = (Carro) veiculo; 

//Aqui será originada uma exceção ClassCastException porque o objeto na variável veiculo é //Mota e não Carro:
Veiculo veiculo = new Mota();
Carro = (Carro) veiculo;

Herança multipla

Herança múltipla é a capacidade de uma classe ser subclasse de várias classes diretamente (ver diagrama abaixo). Tal não é permitido em Java.

No entanto, uma classe pode ser subclasse de uma classe que, por sua vez, é subclasse de outra classe e assim sucessivamente:

No diagrama acima, as classes Carro, Mota e Camiao herdam os atributos e métodos não privados das classes Veiculo e Object.

A classe Object

A classe Object é a superclasse raiz de todas as classes em Java. Isso significa que todas as classes em Java, diretamente ou indiretamente, herdam da classe Object. Ela fornece métodos básicos que são comuns a todos os objetos em Java, como equals(), hashCode(), toString(), clone(), e outros.

A classe Object é importante porque:

  • Raiz da Hierarquia de Classes: Como todas as classes em Java herdam da classe Object, ela define o comportamento fundamental e comum que todos os objetos Java devem ter.
  • Polimorfismo: Permite o uso de polimorfismo, onde um método pode receber um argumento do tipo Object e, portanto, pode aceitar qualquer objeto.
  • Métodos Comuns: Fornece métodos que são úteis para manipular e gerir objetos de maneira genérica.

A palavra-reservada super

A palavra reservada super pode ser usada numa subclasse para aceder aos membros da superclasse, sejam eles variáveis, métodos ou construtores.

Consideremos as seguintes classes, numa relação de herança:

public class Veiculo { 
 
    String matricula; 
     
    public Veiculo(String matricula) { 
        this.matricula = matricula; 
    } 
       
    public void acelera(){ 
        System.out.println("Veiculo acelera"); 
    } 
     
    public void trava(){ 
        System.out.println("Trava"); 
    } 
} 
 
public class Carro extends Veiculo{ 
     
    public Carro(String matricula){ 
        super(matricula); //acede ao construtor da superclasse 
        showMatricula(); 
    } 
     
    private void showMatricula(){ 
        System.out.println(super.matricula); //acede a uma variável da superclasse 
    } 
     
    public void acelera(){ 
        super.acelera(); //acede a um método da superclasse 
    }     
} 

Como se pode ver no exemplo acima, a palavra reservada super permite-nos aceder, a partir de uma subclasse, a variáveis, métodos e construtores da superclasse.

Override (sobreposição) de métodos e polimorfismo de herança

A sobreposição (override) de um método ocorre quando esse método está presente na superclasse e na subclasse, com a mesma assinatura.
O método da subclasse sobrepõe-se ao método da superclasse.

Exemplo:

public class Veiculo { 
   public void acelera(){ 
      System.out.println("Veiculo Acelera"); 
   } 
   public void trava(){ 
      System.out.println("Trava"); 
   } 
}

public class Carro extends Veiculo{ 
   public void acelera(){ 
      System.out.println("Carro Acelera"); 
   }     
} 

Neste exemplo, o método public void acelera() existe na superclasse e na subclasse. Embora ele seja herdado pela subclasse, o método public void acelera() da subclasse sobrepõe-se ao da subclasse:

Carro c = new Carro(); 
c.acelera(); 

Veiculo v = new Carro(); 
v.acelera();

Nas duas situações acima, uma vez que a instâncias dos objetos é Carro, embora os seus tipos sejam Carro e Veiculo respetivamente, há sobreposição do método acelera() e o resultado final nos dois casos é “Carro acelera”.

Podemos designar esta situação de polimorfismo de herança.