Retomando o Polimorfismo no Polígono

Reveja a seguir o exemplo de polimorfismo e amarração dinâmica em C++. Estude este exemplo e tente escrevê-lo em Java na célula abaixo.

Algumas coisas importantes:

  • Em linguagens OO modernas como o Java os ponteiros estão sempre presentes implicitamente.
  • Em linguagens OO modernas como o Java a amarração é sempre dinâmica, portanto, ela não precisa ser declarada explicitamente, a exemplo de como é feito com o virtual em C++.
#include <iostream>

class Poligono {
    private:
       int altura;
       int largura;
    public:
       Poligono(int altura, int largura) {
           this->altura = altura;
           this->largura = largura;
       }

       int getAltura() {
           return altura;
       }

       int getLargura() {
           return largura;
       }

       virtual float getArea() {
           return 0;
       }
};

class TrianguloRetangulo : public Poligono {
    public:
       TrianguloRetangulo(int altura, int largura) : Poligono(altura, largura) {
       }
       float getArea() {
           return getAltura() * getLargura() / 2;
       }
};

class Retangulo : public Poligono {
    public:
       Retangulo(int altura, int largura) : Poligono(altura, largura) {
       }
       float getArea() {
           return getAltura() * getLargura();
       }
};

Poligono *tr = new TrianguloRetangulo(6, 10);
Poligono *rt = new Retangulo(6, 10);

std::cout << "Área do triangulo retângulo: " << tr->getArea() << std::endl;
std::cout << "Área do retângulo: " << rt->getArea() << std::endl;

Exercício

Escreva o exemplo acima em Java.


In [ ]:

Resolução

Você deve ter construído algo como na solução a seguir.


In [1]:
class Poligono {
    private int altura;
    private int largura;

    
    public Poligono(int altura, int largura) {
        this.altura = altura;
        this.largura = largura;
    }
    
    int getAltura() {
        return altura;
    }
    
    int getLargura() {
        return largura;
    }
    
    float getArea() {
        return 0;
    }
}

class TrianguloRetangulo extends Poligono {
    public TrianguloRetangulo(int altura, int largura) {
        super(altura, largura);
    }
    
    float getArea() {
        return getAltura() * getLargura() / 2;
    }
};

class Retangulo extends Poligono {
    public Retangulo(int altura, int largura) {
        super(altura, largura);
    }
    
    float getArea() {
        return getAltura() * getLargura();
    }
};

Poligono tr = new TrianguloRetangulo(6, 10);
Poligono rt = new Retangulo(6, 10);

System.out.println("Área do triangulo retângulo: " + tr.getArea());
System.out.println("Área do retângulo: " + rt.getArea());


Área do triangulo retângulo: 30.0
Área do retângulo: 60.0

toString() no Garoto Zumbi

Veja abaixo como a classe GarotoZumbi foi modificada. O método mostra() que mostrava o zumbi no console foi substituído pelo método toString() que retorna uma String com o desenho do zumbi (mas não o imprime). Com essa nova versão, você precisa receber e imprimir no console da seguinte maneira:

System.out.println(garoto.toString());

Os outros métodos também deixaram de imprimir o desenho do zumbi e agora você precisa fazê-lo explicitamente.


In [2]:
public class GarotoZumbi
{
    protected int idade;
    protected String estado;
    protected String nome;
    
    public GarotoZumbi(int idade, String estado, String nome) {
        this.idade = idade;
        this.estado = estado;
        this.nome = nome;
    }
    
    public String toString(boolean imprimeNome) {
        String resultado = "";
        
        // cabeleira
        if (idade >= 2)
            resultado += "  *\n";
        
        // corpo com olhos
        if (estado.equalsIgnoreCase("acordado"))
            resultado += " o*o\n";
        else
            resultado += " -*-\n";
        
        // barba
        if (idade >= 3)
            resultado += "*****\n";
        
        if (imprimeNome) {
            resultado += nome + "\n";
            resultado += "\n";
        }
        
        return resultado;
    }
    
    public String toString() {
        return toString(true);
    }
    
    public void cresce() {
        if (idade < 3)
            idade++;
    }
    
    public void acorda() {
        estado = "acordado";
    }
    
    public void dorme() {
        estado = "dormindo";
    }
}

GarotoZumbi garoto = new GarotoZumbi(1, "acordado", "Asdrubal");
System.out.println(garoto.toString());
garoto.dorme();
System.out.println(garoto.toString());
garoto.cresce();
System.out.println(garoto.toString());


 o*o
Asdrubal


 -*-
Asdrubal


  *
 -*-
Asdrubal


Método toString() de Object

O nome do método toString() não foi escolhido por acaso. Ele é um método que está defininido na classe especial Object, que é aquela classe de onde descencem implicitamente todas as classes. Quando você não sobrescreve o método toString() ele retorna uma String o endereço do objeto na memória.

O método toString() é chamado implicitamente toda vez que você pede para imprimir um objeto sem indicar o método, portanto, no exemplo a seguir:

Poligono tr = new TrianguloRetangulo(6, 10);
System.out.println(tr);
Poligono rt = new Retangulo(6, 10);
System.out.println(rt);

Ele imprime umas sequências estranhas que são a identificação única do objeto na memória.

Entretanto, se você sobrescreve o método toString(), através do polimorfismo, o novo método será chamado implicitamente quando você pede para imprimir o objeto. Por exemplo:

GarotoZumbi garoto = new GarotoZumbi(1, "acordado", "Asdrubal");
System.out.println(garoto);
garoto.dorme();
System.out.println(garoto);
garoto.cresce();
System.out.println(garoto);

Imprime o desenho do GarotoZumbi.


In [3]:
Poligono tr = new TrianguloRetangulo(6, 10);
System.out.println(tr);
Poligono rt = new Retangulo(6, 10);
System.out.println(rt);

System.out.println("===============================");

GarotoZumbi garoto = new GarotoZumbi(1, "acordado", "Asdrubal");
System.out.println(garoto);
garoto.dorme();
System.out.println(garoto);
garoto.cresce();
System.out.println(garoto);


REPL.$JShell$19$TrianguloRetangulo@40200267
REPL.$JShell$21$Retangulo@b6c4c12
===============================
 o*o
Asdrubal


 -*-
Asdrubal


  *
 -*-
Asdrubal


Exercício

Considere os herdeiros Monstro e Perfeito abaixo implementados à moda antiga com o método mostra() (eles não funcionarão agora se você tentar executá-los porque a superclasse -- GarotoZumbi -- não tem mais o mostra()).

Adapte ambos para que passem a ter o método toString() que retorna o desenho na forma de String, substituindo o mostra().


In [7]:
public class Monstro extends GarotoZumbi
{
    public Monstro(int idade, String estado, String nome) {
        super(idade, estado, nome);
    }

    public String toString(boolean imprimeNome) {
        String resultado = super.toString(false);

        if (idade >= 4)
            resultado += "#####\n";
        if (idade >= 5)
            resultado += "/   \\\n";

        if (imprimeNome) {
            resultado += nome + "\n";
            resultado += "\n";
        }
        
        return resultado;
    }

    public void cresce() {
        if (idade < 5)
            idade++;
    }
}

Monstro monst = new Monstro(1, "acordado", "Bonerges");
System.out.println(monst);
monst.cresce();
System.out.println(monst);
monst.cresce();
System.out.println(monst);
monst.cresce();
System.out.println(monst);
monst.cresce();
System.out.println(monst);
monst.cresce();
System.out.println(monst);
monst.dorme();
System.out.println(monst);


 o*o
Bonerges


  *
 o*o
Bonerges


  *
 o*o
*****
Bonerges


  *
 o*o
*****
#####
Bonerges


  *
 o*o
*****
#####
/   \
Bonerges


  *
 o*o
*****
#####
/   \
Bonerges


  *
 -*-
*****
#####
/   \
Bonerges



In [10]:
public class Perfeito extends GarotoZumbi
{
    private String situacao = "normal";
    
    public Perfeito(int idade, String estado, String nome) {
        super(idade, estado, nome);
    }

    public Perfeito(int idade, String estado, String nome, String situacao) {
        super(idade, estado, nome);
        this.situacao = situacao;
    }

    public String toString() {
        String resultado = "+-----+\n";

        if (idade >= 2)
            resultado += "|     |\n";

        if (estado.equals("dormindo")) {
            resultado += " - -\n";
            resultado += "|  _  |\n";
        }
        else if (situacao.equals("normal")) {
            resultado += "| o-o |\n";
            resultado += "| ___ |\n";
        }
        else if (situacao.equals("milionario")) {
            resultado += "| $-$ |\n";
            resultado += "| ___ |\n";
        }
        else if (situacao.equals("doido")) {
            resultado += "| @-@ |\n";
            resultado += "|  ~  |\n";
        }

        if (idade < 3)
            resultado += " \\___/\n";
        else {
            resultado += "|     |\n";
            resultado += "\\_____/\n";
        }

        resultado += nome + "\n\n";
        
        return resultado;
    }

    public void mudaSituacao() {
        String s[] = {"normal",
                      "milionario",
                      "doido" };

        int p;
        for (p = 0; p < s.length && !situacao.equals(s[p]); p++)
            /* nada */ ;

        p = (p + 1) % 3;
        situacao = s[p];
    }
}

Perfeito perf = new Perfeito(1, "acordado", "Zandor");
System.out.println(perf);
perf.cresce();
System.out.println(perf);
perf.cresce();
System.out.println(perf);
perf.mudaSituacao();
System.out.println(perf);
perf.mudaSituacao();
System.out.println(perf);


+-----+
| o-o |
| ___ |
 \___/
Zandor


+-----+
|     |
| o-o |
| ___ |
 \___/
Zandor


+-----+
|     |
| o-o |
| ___ |
|     |
\_____/
Zandor


+-----+
|     |
| $-$ |
| ___ |
|     |
\_____/
Zandor


+-----+
|     |
| @-@ |
|  ~  |
|     |
\_____/
Zandor


Exercício

Dada a classe Servicos, a seguir, crie nela um método estático chamado imprimeZumbi. Esse método deve receber como parâmetro uma referência para um objeto da classe GarotoZumbi (considere a nova versão com toString()), ou um de seus herdeiros, e deve imprimi-lo.

Utilize uma solução que explore o polimorfismo, sem o uso de condicional.


In [14]:
public class Servicos {
    public static void imprimeZumbi(GarotoZumbi garoto) {
        System.out.println(garoto);
    }
}

Monstro monst = new Monstro(2, "dormindo", "Bonerges");
Servicos.imprimeZumbi(monst);
Perfeito perf = new Perfeito(1, "acordado", "Zandor");
Servicos.imprimeZumbi(perf);


  *
 -*-
Bonerges


+-----+
| o-o |
| ___ |
 \___/
Zandor


Exercício

Dada a classe EmprestimoComposto desenvolvida anteriormente em sala de aula.

Crie uma classe Emprestimo que generalize qualquer emprestimo e EmprestimoComposto passa a ser herdeira da mesma. Transfira para a superclasse o máximo de atributos e métodos, de tal modo que possam ser reusados pelos herdeiros.

Crie uma classe EmprestimoSimples herdeira de Emprestimo que também disponha dos métodos parcela() e proximaParcela().


In [1]:
import java.lang.Math;

class EmprestimoComposto {
    private float s;
    private int   n;
    private float j;
    private int   corrente;
    private float p,
                  proxima;

    public EmprestimoComposto(float s, int n, float j) {
        this.s = s;
        this.n = n;
        this.j = j;
        corrente = 1;
        this.p = -1;  // antes da primeira parcela
        this.proxima = s;
    }

    float getS() {
        return s;
    }

    int getN() {
        return n;
    }
    
    float getJ() {
        return j;
    }

    public float proximaParcela() {
        p = proxima;
        corrente++;
        if (corrente <= n)
            proxima += (proxima * (j/100));
        else
            proxima = 0;
        return p;
    }
    
    public float parcela() {
        return p;
    }
    
    public float parcela(int numero) {
        float resultado = 0;
        if (numero <= n)
            resultado = s * (float)Math.pow(1 + j/100, numero-1);
        return resultado;
    }
}

// codigo principal

EmprestimoComposto emprestimo1 = new EmprestimoComposto(200, 5, 1),
                   emprestimo2 = new EmprestimoComposto(500, 7, 2);

int i = 1;
emprestimo1.proximaParcela();
emprestimo2.proximaParcela();
while (emprestimo1.parcela() > 0 || emprestimo2.parcela() > 0) {
    if (emprestimo1.parcela() > 0) {
        System.out.println("Emprestimo 1: parcela " + i + " eh " + emprestimo1.parcela());
        System.out.println("              parcela " + i + " eh " + emprestimo1.parcela(i));
    }
    if (emprestimo2.parcela() > 0) {
        System.out.println("Emprestimo 2: parcela " + i + " eh " + emprestimo2.parcela());
        System.out.println("              parcela " + i + " eh " + emprestimo2.parcela(i));
    }
    emprestimo1.proximaParcela();
    emprestimo2.proximaParcela();
    i++;
}


Emprestimo 1: parcela 1 eh 200.0
              parcela 1 eh 200.0
Emprestimo 2: parcela 1 eh 500.0
              parcela 1 eh 500.0
Emprestimo 1: parcela 2 eh 202.0
              parcela 2 eh 202.0
Emprestimo 2: parcela 2 eh 510.0
              parcela 2 eh 510.0
Emprestimo 1: parcela 3 eh 204.02
              parcela 3 eh 204.02
Emprestimo 2: parcela 3 eh 520.2
              parcela 3 eh 520.19995
Emprestimo 1: parcela 4 eh 206.06021
              parcela 4 eh 206.0602
Emprestimo 2: parcela 4 eh 530.604
              parcela 4 eh 530.60394
Emprestimo 1: parcela 5 eh 208.12082
              parcela 5 eh 208.1208
Emprestimo 2: parcela 5 eh 541.21606
              parcela 5 eh 541.216
Emprestimo 2: parcela 6 eh 552.0404
              parcela 6 eh 552.04034
Emprestimo 2: parcela 7 eh 563.08124
              parcela 7 eh 563.0811

In [ ]:

Exercício

Escreva um programa que defina um vetor de Emprestimos que pode conter tanto instâncias de EmprestimoSimples quanto EmprestimoComposto. O programa deve percorrer o vetor e imprimir a próxima parcela de cada financiamento.


In [ ]: