Atenção: esse artigo tem um vídeo complementar. Clique e assista!

De que se trata o artigo:

Apresenta conceitos mais avançados em relação aos que tratamos na primeira parte. Polimorfismo, por exemplo, é um conceito chave para criarmos aplicações cujo código é mais fácil de ler, manter, e a escalabilidade uma realidade possível. É recomendado aos desenvolvedores que já possuem um conhecimento básico na programação orientada a objetos e desejam aprofundar seus estudos, tratando temas mais avançados, focando também àqueles que se preparam para o SCJP – Certificação para Programador Java.

Em que situação o tema é útil:

É útil quando o programador busca criar aplicações modulares, onde as classes oferecem flexibilidade aos desenvolvedores. O conhecimento aqui exposto também pode ser usado como base para se criar aplicações escaláveis.

Resumo DevMan:

Neste artigo, serão apresentados os conceitos de sobrescrita, sobrecarga e polimorfismo. Principalmente, a sobrescrita e o polimorfismo são mecanismos cuja utilização é fundamental para tornar as aplicações escaláveis. Mas antes de tudo, tais conceitos somam-se àqueles já estudados na primeira parte da matéria no sentido de formar a base de conhecimento para o desenvolvedor criar aplicações modulares. Com isso, uma vez criada uma classe, ela pode tanto funcionar independente do restante do programa, quanto ser reutilizada em outras aplicações.

Na primeira parte desta série comentamos que a reutilização é uma peça fundamental para o aumento da produtividade e melhoria da qualidade dos produtos de software. Finalizamos o artigo falando do relacionamento É-UM, também chamado de generalização/especialização, e mais comumente de herança. Vimos no artigo Modelagem de Software com UML – Parte 4, publicado na Easy Java Magazine 10, que reuso é o processo de utilizar um módulo – uma classe, por exemplo – várias vezes em uma aplicação ou em aplicações diferentes. Desta forma, podemos afirmar que herança é um conceito que possibilita o reuso. Estudaremos nesta parte da série que, assim como herança, o polimorfismo é outro conceito que dá suporte à reutilização.

No entanto, para que o polimorfismo seja utilizado, é necessário que os métodos definidos nas subclasses tenham exatamente a mesma assinatura dos métodos declarados na superclasse. Esse mecanismo da orientação a objetos é chamado de sobrescrita, ou overriding, e será estudado em seguida.

Após entendermos a sobrescrita, estaremos preparados para iniciar o estudo sobre polimorfismo, que é o tópico final desta parte do curso. Nesta seção aprenderemos a implementar polimorfismo tornando as aplicações mais facilmente extensíveis.

Mas, antes de apresentarmos polimorfismo, outro conceito que precisamos entender é a sobrecarga de métodos, ou overloading. Além da importância que a sobrecarga oferece de tornar os métodos mais flexíveis, é fundamental que fique muito clara a diferença entre sobrescrita e sobrecarga.

Sobrescrita

Se uma classe herda um método de uma superclasse, então este método poderá ser sobrescrito na subclasse. Ou seja, podemos escrever uma nova implementação para um método já implementado na superclasse. A vantagem disso é tornar possível a definição de um comportamento que seja específico de determinada subclasse – ou subtipo.

Para exemplificar este recurso, iniciamos apresentando a classe Empregado na Listagem 1. Na Listagem 2, mostramos um exemplo da classe Gerente que estende a classe Empregado, e sobrescreve o método calculaSalario(). No exemplo dado, a sobrescrita se torna necessária porque os cálculos dos salários de Empregado e do seu subtipo, Gerente, são diferentes.

Listagem 1. Código da classe Empregado.

public class Empregado {
    private int matricula;
    private String nome;
    private long cpf;
    private String cargo;
    private double salario;
    private double gratificacao;
   
    public Empregado() {
    }
   
    public Empregado(int matricula, String nome) {
      this.matricula = matricula;
      this.nome = nome;
    }
   
    public Empregado(int matricula, String nome, long cpf, String cargo, 
                     double salario, double gratificacao) {
      this.matricula = matricula;
      this.nome = nome;
      this.cpf = cpf;
      this.cargo = cargo;
      this.salario = salario;
      this.gratificacao = gratificacao;
    }
   
    // método sobrescrito
    public double calculaSalario() {
      return this.getSalario() + this.getGratificacao();
    }
   
    public int getMatricula() {
      return matricula;
    }
   
    public void setMatricula(int matricula) {
      this.matricula = matricula;
    }
   
    public String getNome() {
      return nome;
    }
   
    public void setNome(String nome) {
      this.nome = nome;
    }
   
    public long getCpf() {
      return cpf;
    }
   
    public void setCpf(long cpf) {
      this.cpf = cpf;
    }
   
    public String getCargo() {
      return cargo;
    }
   
    public void setCargo(String cargo) {
      this.cargo = cargo;
    }
   
    public double getSalario() {
      return salario;
    }
   
    public void setSalario(double salario) {
      this.salario = salario;
    }
   
    public double getGratificacao() {
      return gratificacao;
    }
   
    public void setGratificacao(double gratificacao) {
      this.gratificacao = gratificacao;
    }
  }

Listagem 2. Código da classe Gerente com sobrescrição de método.

public class Gerente extends Empregado {
    private double adicionalCargo;
   
    public Gerente() {
    }
   
    public Gerente(int matricula, String nome, double adicionalCargo) {
      super(matricula, nome);
      this.adicionalCargo = adicionalCargo;
    }
   
    // método sobrescritor
    @Override
    public double calculaSalario() {
      return this.getSalario() + this.getGratificacao() + this.adicionalCargo;
    }
    
  }

Para um melhor entendimento, no restante do texto, usaremos o termo sobrescrito para o método da superclasse que será sobrescrito no subtipo, e para o método que o sobrescreve, será usado o termo sobrescritor.

A partir dessa redefinição, quando você chamar calculaSalario() para um objeto Gerente, ele sempre executará o método desta classe, e não mais o da superclasse. Entretanto, às vezes é necessário invocar o método sobrescrito dentro do sobrescritor, principalmente para que não haja repetição desnecessária de código.

Observe no exemplo que o método sobrescritor calculaSalario() – na classe Gerente – implementa também o mesmo código do método sobrescrito – a soma this.getSalario() + this.getGratificacao(). Assim, este é um caso em que o método sobrescrito pode ser chamado na subclasse. Portanto, podemos modificar a subclasse Gerente de maneira que seu método calculaSalario() seja implementado como mostra a Listagem 3.

Listagem 3. Chamada do método sobrescrito no método sobrescritor.

  @Override
  public double calculaSalario() {
    return super.calculaSalario() + this.adicionalCargo;
  }

Vimos na primeira parte desta série, que super é usado quando se deseja fazer uma referência à superclasse, a exemplo do que fizemos quando precisamos invocar, na classe filha, um construtor da classe pai. Desta forma, para chamar um método do supertipo, usa-se a palavra-chave super seguida de ponto e o nome do método que precisamos chamar. Por este motivo, a soma this.getSalario() + this.getGratificacao() foi substituída pela chamada super.calculaSalario() na Listagem 3.

Para que a sobrescrita de métodos seja implementada corretamente é necessário observar alguns cuidados e regras, descritos a seguir:

• A lista de argumentos do método que sobrescreve deve ser exatamente a mesma do método sobrescrito;

• O tipo de retorno do método sobrescritor deve ser o mesmo, ou de um subtipo do tipo de retorno declarado no método original. Por exemplo, se o método sobrescrito retornar Empregado, então o método sobrescritor pode retornar Gerente;

• O nível de acesso deve ser igual ou menos restritivo que o do método sobrescrito. Se, por exemplo, o método sobrescrito é protected, então o sobrescritor deve ser protected ou public;

• Métodos marcados como final ou static não podem ser sobrescritos;

• Apenas métodos que são herdados pela subclasse podem ser sobrescritos. Portanto, métodos private não podem ser sobrescritos;

• O método sobrescritor não pode lançar exceções verificadas novas ou mais abrangentes que as declaradas pelo método sobrescrito. Caso um método declare FileNotFoundException, ele não pode ser sobrescrito por um método que declare Exception, por exemplo. Voltaremos a falar de exceções mais adiante;

• Qualquer exceção não-verificada pode ser lançada pelo método sobrescritor, mesmo que o método sobrescrito não declare a exceção.

Após termos estudado como os métodos de uma superclasse podem ser sobrescritos nas classes filhas, é o momento de entender o que ocorre quando esses métodos são chamados. Para isso, vamos analisar o código mostrado na Listagem 4.

Listagem 4. Usando métodos sobrescritos.

public class Programa {
    public static void main(String[] args) {
      Empregado e = new Empregado(122, "José Ferreira");
      e.setSalario(650.00);
      Gerente g = new Gerente(123, "João da Silva",120.0);
      g.setSalario(1000.00);
      // declaração válida, pois g É-UM empregado
      Empregado emp = g;
      System.out.println(e.calculaSalario());           // exibe 660.0
      System.out.println(g.calculaSalario());           // exibe 1120.0
      System.out.println(emp.calculaSalario());  // exibe o que?
    }
  } ... 

Quer ler esse conteúdo completo? Tenha acesso completo