Apostila de Java, capítulo 07 - Herança, reescrita (override) e polimorfismo

Neste tutorial de Java de nosso curso, iremos resolver as questões propostas na apostila de Java da Caelum.
Especificamente, o capítulo 7, que trata de assuntos como Herança e Polimorfismo, conceitos muito importantes e essenciais sobre Orientação à Objetos.

Deseja trabalhar com Java? Clique aqui e obtenha seu certificado para entrar no Mercado de Trabalho!

Apostila de Java da Caelum

Nesse artigo de nosso curso iremos comentar e resolver os exercícios propostos no capítulo 7, sobre Herança, Polimorfismo e reescrita da apostila FJ-11: Java e Orientação a Objetos, da Caelum.que é disponibilizada gratuitamente por eles e nos autorizaram utilizar seu material.

Clique aqui para saber mais sobre a empresa Caelum e suas apostilas gratuitas.

O material da apostila é uma excelente fonte de estudos sobre diversos assuntos que já abordamos aqui no Java Progressivo, dentre eles:
Herança - O que é, para que serve e como usar
Polimorfismo - O que é, para que serve e como usar
Métodos e atributos do tipo protected
Override (reescrita de métodos)

Na seção "7.6 Um pouco mais..." é sugerido uma pesquisa e leitura sobre a Composição, assunto que já foi abordado em nossa Apostila Java Progressivo:
Composição - Trocando informações entre objetos



Enunciados do capítulo 7 da apostila

7.7    Exercícios: Herança e Polimorfismo


Questão 01:

Vamos criar uma classe Conta, que possua um saldo os métodos para pegar saldo, depositar e sacar.
   a) Crie a classe Conta:
      public class Conta {
      }
   b) Adicione o atributo saldo
      public class Conta {
           private double saldo;
      }
   c) Crie os métodos getSaldo(), deposita(double) e saca(double)
      public class Conta {
           private double saldo;
           public void deposita(double valor) {
                this.saldo += valor;
           }
            public void saca(double valor) {
                 this.saldo -= valor;
            }
            public double getSaldo() {
                 return this.saldo;
            }
      }

Questão 02:

Adicione um método na classe Conta, que atualiza essa conta de acordo com uma taxa percentual fornecida.
   class Conta {
        private double saldo;
          // outros métodos aqui também ...
        void atualiza(double taxa) {
              this.saldo += this.saldo * taxa;
        }
   }

Questão 03:

Crie duas subclasses da classe Conta: ContaCorrente e ContaPoupanca. Ambas terão o método atualiza reescrito: A ContaCorrente deve atualizar-se com o dobro da taxa e a ContaPoupanca deve atualizar-se com o triplo da taxa.
Além disso, a ContaCorrente deve reescrever o método deposita, a fim de retirar uma taxa bancária de dez centavos de cada depósito.

• Crie as classes ContaCorrente e ContaPoupanca. Ambas são filhas da classe Conta:
         public class ContaCorrente extends Conta {
         }
         public class ContaPoupanca extends Conta {
         }

• Reescreva o método atualiza na classe ContaCorrente, seguindo o enunciado:
         public class ContaCorrente extends Conta {
               public void atualiza(double taxa) {
                   this.saldo += this.saldo * taxa * 2;
               }
         }

Repare que, para acessar o atributo saldo herdado da classe Conta, você vai precisar trocar o modificador de visibilidade de saldo para protected.

• Reescreva o método atualiza na classe ContaPoupanca, seguindo o enunciado:
         public class ContaPoupanca extends Conta {
              public void atualiza(double taxa) {
                  this.saldo += this.saldo * taxa * 3;
              }
         }

• Na classe ContaCorrente, reescreva o método deposita para descontar a taxa bancária de dez centavos:
         public class ContaCorrente extends Conta {
              public void atualiza(double taxa) {
                  this.saldo += this.saldo * taxa * 2;
              }
              public void deposita(double valor) {
                  this.saldo += valor - 0.10;
              }
         }


Questão 04:

Crie uma classe com método main e instancie essas classes, atualize-as e veja o resultado. Algo como:
   public class TestaContas {
        public static void main(String[] args) {
             Conta c = new Conta();
             ContaCorrente cc = new ContaCorrente();
             ContaPoupanca cp = new ContaPoupanca();
             c.deposita(1000);
             cc.deposita(1000);
             cp.deposita(1000);
             c.atualiza(0.01);
             cc.atualiza(0.01);
             cp.atualiza(0.01);
             System.out.println(c.getSaldo());
             System.out.println(cc.getSaldo());
             System.out.println(cp.getSaldo());
        }
   }
   Após imprimir o saldo (getSaldo()) de cada uma das contas, o que acontece?

Questão 05: 

O que você acha de rodar o código anterior da seguinte maneira:
   Conta c = new Conta();
   Conta cc = new ContaCorrente();
   Conta cp = new ContaPoupanca();

Compila? Roda? O que muda? Qual é a utilidade disso? Realmente, essa não é a maneira mais útil do polimorfismo - veremos o seu real poder no próximo exercício. Porém existe uma utilidade de declararmos uma variável de um tipo menos específico do que o objeto realmente é.
É extremamente importante perceber que não importa como nos referimos a um objeto, o método que será invocado é sempre o mesmo! A JVM vai descobrir em tempo de execução qual deve ser invocado, dependendo de que tipo é aquele objeto, não importando como nos referimos a ele.

Questão 06 (opcional): 

Vamos criar uma classe que seja responsável por fazer a atualização de todas as contas bancárias e gerar um relatório com o saldo anterior e saldo novo de cada uma das contas.
Além disso, conforme atualiza as contas, o banco quer saber quanto do dinheiro do banco foi atualizado até o momento. Por isso, precisamos ir guardando o saldoTotal e adicionar um getter à classe.

   public class AtualizadorDeContas {
        private double saldoTotal = 0;
        private double selic;
        public AtualizadorDeContas(double selic) {
            this.selic = selic;
        }
        public  void roda(Conta c) {
            //  aqui você imprime o saldo anterior, atualiza a conta,
            //  e depois imprime o saldo final
            //  lembrando de somar o saldo final ao atributo saldoTotal
        }
        // outros métodos, colocar o getter para saldoTotal!
   }

Questão 07 (opcional): 

No método main, vamos criar algumas contas e rodá-las:
    public class TestaAtualizadorDeContas {
         public static void main(String[] args) {
              Conta c = new Conta();
              Conta cc = new ContaCorrente();
              Conta cp = new ContaPoupanca();
              c.deposita(1000);
              cc.deposita(1000);
              cp.deposita(1000);
              AtualizadorDeContas adc = new AtualizadorDeContas(0.01);
              adc.roda(c);
              adc.roda(cc);
              adc.roda(cp);
              System.out.println("Saldo Total: " + adc.getSaldoTotal());
         }
    }

Questão 08 (Opcional): 

Use a palavra chave super nos métodos atualiza reescritos, para não ter de refazer o trabalho.

Questão 09 (Opcional): 

Se você precisasse criar uma classe ContaInvestimento, e seu método atualiza fosse complicadíssimo, você precisaria alterar a classe AtualizadorDeContas?

Questão 10 (Opcional, Trabalhoso): 

Crie uma classe Banco que possui um array de Conta. Repare que num array de Conta você pode colocar tanto ContaCorrente quanto ContaPoupanca. Crie um método public void adiciona(Conta c), um método public Conta pegaConta(int x) e outro public int pegaTotalDeContas(), muito similar a relação anterior de Empresa-Funcionario.

Faça com que seu método main crie diversas contas, insira-as no Banco e depois, com um for, percorra todas as contas do Banco para passá-las como argumento para o AtualizadorDeContas.


Solução comentada das questões do capítulo 7 da apostila de Java

Questões 01, 02, 03 e 04

A solução destas 4 primeiras questões é bem óbvia e fácil de se fazer, basta seguir o que é dito nos enunciados da apostila.
Como resultado, temos o seguinte código:
Código Java:
Classe Cap7ApostilaCaelum.java
public class Cap7ApostilaCaelum {
	
	public static void main(String[] args) {
		Conta programador = new Conta();
		ContaCorrente programadorCC = new ContaCorrente();
		ContaPoupanca programadorCP = new ContaPoupanca();
		
		//Recebendo o salário e atualizando a conta
		programador.deposita(5000);
		programadorCC.deposita(5000);
		programadorCP.deposita(5000);
		
		programador.atualiza(0.01);
		programadorCC.atualiza(0.01);
		programadorCP.atualiza(0.01);
		
		//Exibindo informações
		System.out.printf("Saldo da Conta Corrente: %.2f\n",programadorCC.getSaldo());
		System.out.printf("Saldo da Poupança:  %.2f\n", programadorCP.getSaldo());
		
		//Tirando 5 reais do café em cada conta
		programador.saca(5);
		programadorCC.saca(5);
		programadorCP.saca(5);
		
		programador.atualiza(0);
		programadorCC.atualiza(0);
		programadorCP.atualiza(0);
		
		//Exibindo informações
		System.out.println("\nDepois do cafezinho: ");
		System.out.printf("Saldo da Conta Corrente: %.2f\n",programadorCC.getSaldo());
		System.out.printf("Saldo da Poupança:  %.2f\n", programadorCP.getSaldo());
	}

}



Classe: Conta.java
public class Conta {
	protected double saldo;
	
	public void deposita(double valor) {
	    this.saldo += valor;
	}
	
	public void saca(double valor) {
	    this.saldo -= valor;
	}
	
	public double getSaldo() {
	    return this.saldo;
	}
	
	void atualiza(double taxa) {
	    this.saldo += this.saldo * taxa;
	}

}



Classe: ContaCorrente.java
public class ContaCorrente extends Conta{
	
	public void atualiza(double taxa) {
	    this.saldo += this.saldo * taxa * 2;
	}
	
	public void deposita(double valor) {
	    this.saldo += valor - 0.10;
	}
}




Classe: ContaPoupanca.java
public class ContaPoupanca extends Conta{
	
	public void atualiza(double taxa) {
	    this.saldo += this.saldo * taxa * 3;
	}


}


Questão 05:

Não há o menor problema, em termos de sintaxe, compilação ou lógica de programação.
Criamos 3 tipos de contas:
Conta programador;
Conta programadorCC;
Conta programadorCP;

Em seguida vamos inicializar estes objetos e fazê-los receberem instâncias específicas de cada classe, o que ocorre naturalmente, pois ContaCorrente é Conta também, e ContaPoupança é Conta.
programador = new Conta();
programadorCC = new ContaCorrente();
programadorCP = new ContaPoupanca();

Questão 06 e 07:

Essas duas também são bem simples, pois basicamente pedem para criar uma classe, dois métodos e testar o funcionamento dessa classe. E um deles é um simples getter do atributo 'saldoTotal', que vai armazenar todo o dinheiro existente no banco, que é o somatório do dinheiro das contas.

Ele pede que o método roda() mostre o valor do saldo anterior (um print no método getSaldo() do objeto recebido), que atualize (usando o método atualiza(taxa) do objeto, onde a taxa está armazenada na variável 'selic') e seguida exiba o novo valor armazenado na conta.
Este novo valor deve ser somado à variável 'saldoTotal'.

Assim, nossa classe AtualizadorDeConta fica:
Código Java:
Classe: AtualizadorDeContas.java
public class AtualizadorDeContas {
	private double saldoTotal = 0;
	private double selic;
	
	public AtualizadorDeContas(double selic) {
	    this.selic = selic;
	}
	
	public void roda(Conta c) {
		System.out.printf("Antes da atualização: %.2f\n", c.getSaldo());
		c.atualiza(this.selic);
		System.out.printf("Depois da atualização: %.2f\n", c.getSaldo());
		this.saldoTotal += c.getSaldo();
	}

	public double getSaldoTotal() {
		return saldoTotal;
	}

}


Questão 08:

Nesta questão da apostila é pedido para usar o 'super', que se refere a uma classe mãe (superclasse).
Note que o método atualiza é:
this.saldo += this.saldo * taxa * valor;

Onde valor=2 na classe ContaCorrente e igual a 3 na classe ContaPoupanca.
Já na classe super, a Conta, não existe esse valor.

Mas podemos, mesmo assim, usar o método atualiza() da classe super nos métodos das classes filhas(subclasses), para isso basta passar para o super.atualiza() um 'valor' diferente, em vez de passar 'taxa', passar: taxa*valor

Assim, na classe ContaCorrente:
public void atualiza(double taxa) {
		super.atualiza(taxa*2);
    }

E na classe ContaPoupanca:
public void atualiza(double taxa) {
		super.atualiza(taxa*3);
    }

Questão 09:

Não. Uma coisa que este capítulo da apostila de Java da Caelum deixou bem claro foi a utilidade da herança para criar super classes bem gerais, e fazendo as alterações somente em locais específicos.

Nesse caso, não precisamos alterar a classe mais geral, que é a AtualizadorDeContas.
Como é uma situação específica, fazemos um override (reescrita) do método atualiza() da classe ContaInvestimento.

Questão 10:

Como a própria apostila da Caelum diz, essa é realmente mais trabalhosa, mas é bem interessante pois criamos um 'esboço' de um banco. Bom, vamos lá.

Primeiro criamos a classe "Banco" e ela possui 3 atributos:
Um vetor de contas, ou seja, um vetor de objetos da classe "Conta"
Um inteiro 'capacidade' que vai armazenar a capacidade do banco, ou seja, o número máximo de contas que armazena.
Um inteiro 'numContas' que contém o número atual de contas armazenadas em nossa banco.

A 'capacidade' é decidida pelo usuário no momento da criação do objeto do tipo "Banco".
Esse número é necessário para definir o tamanho do array de contas bancárias.
Portanto, é isso que fazemos no único construtor desse classe: contas = new Banco(capacidade);

Depois vamos usar 3 métodos.
Um deles é bem simples, pois é nada mais que um getter, é o pegaTotalDeContas() que simplesmente retorna o número de contas que já foram adicionadas ao banco, ou seja, retorna o atributo inteiro 'numContas'.

A adição dessas contas ao banco é feita pelo método 'adiciona()' que recebe uma referência do objeto do tipo "Conta", que pode ter sido declarado por qualquer uma das classes de conta ("Conta", "ContaCorrente" ou "ContaPoupanca").
Ao receber o objeto, precisamos antes checar se tem espaço no banco, bastando checar se o número de contas atual (atributo numContas) é menor que a capacidade de contas (atributo capacidade).
Se for menor, colocamos na posição 'numContas' do array o objeto recebido, e incrementamos essa variável, afinal de contas agora temos uma conta a mais.
E caso esteja cheio, retornamos a referência null.

E por fim, o último método retorna qualquer conta que esteja armazenada em nossa banco.
Para isso, o usuário deve escolher um número entre 0 e (capacidade-1), que nosso aplicativo Java vai retornar a referida conta. Pronto, classe Conta concluída.

Agora vamos testá-la na main(), afinal, não basta criar, tem que mostrar que está funcionando.
Vamos aproveitar as 3 contas que temos: programador, programadorCC e programadorCP, bem como o depósito de R$ 5000,00 que fizemos em cada uma dessas contas anteriormente, nas outras questões desse capítulo da apostila.

Em seguida criamos nosso Banco, o 'banco', com capacidade para 3 contas, e um objeto do tipo "AtualizadorDeContas", o 'adc', para manusear todos esses objetos, assim como fizemos antes nessa apostila.
Depois adicionamos essas 3 contas ao nosso Banco 'banco', através do método 'adiciona()'.

Agora vamos percorrer todas contas existentes nesse banco na 0 até a ... ?
Ué, até a banco.pegaTotalDeContas(), afinal este método retorna o número de contas armazenadas no banco.
Feito isso, mostramo o número de cada funcionário, e pegamos cada funcionário através do método: banco.pegaConta()
Pegamos o salário através do método 'getSaldo()' de cada conta, ficando: banco.pegaConta(i).getSaldo()
Também, a cada iteração, temos que adicionar cada conta dessa em nosso AtualizadorDeContas, o 'adc': adc.roda(banco.pegaConta(i)).

Concluído este laço for, simplesmente mostramos o saldo total, que foi atualizado pelo 'adc'.
Veja como ficou nosso código da classe que contém a main() e da classe "Banco" (as demais ficaram como estavam):

Classe: Cap7ApostilaCaelum.java

public class Cap7ApostilaCaelum { public static void main(String[] args) { Conta programador = new Conta(); Conta programadorCC = new ContaCorrente(); Conta programadorCP = new ContaPoupanca(); //Recebendo o salário programador.deposita(5000); programadorCC.deposita(5000); programadorCP.deposita(5000); //Objeto que vai atualizar as contas do Banco AtualizadorDeContas adc = new AtualizadorDeContas(0.01); //Questão 10 System.out.println("\nCriando um banco com 3 contas"); Banco banco = new Banco(3); banco.adiciona(programador); banco.adiciona(programadorCC); banco.adiciona(programadorCP); for(int i=0 ; i < banco.pegaTotalDeContas() ; i++){ System.out.println("\nFuncionario de numero: " + (i+1)); System.out.printf("Saldo: %.2f\n", banco.pegaConta(i).getSaldo()); adc.roda(banco.pegaConta(i)); } System.out.printf("\nSaldo total: %.2f\n", adc.getSaldoTotal()); } }

Classe: Banco.java

public class Banco { private Conta[] contas; private int capacidade, numContas=0; public Banco(int capacidade){ this.capacidade = capacidade; contas = new Conta[this.capacidade]; } public void adiciona(Conta c){ if(numContas < capacidade){ contas[numContas] = c; numContas++; }else{ System.out.println("Número de contas no limite"); } } public Conta pegaConta(int x){ if(x < capacidade){ return contas[x]; }else{ System.out.println("Essa conta não existe"); return null; } } public int pegaTotalDeContas(){ return this.numContas; } }

Um comentário:

Pedro Doncatto Schinestzki disse...

Excelente explicação. Com esse exercício consegui ver aplicações na prática sobre muita coisa que já aprendemos.

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.