Garoto Zumbi Plus

Faça uma revisão na especificação do Garoto Zumbi que está na parte anterior.

Reveja abaixo a resolução do problema:


In [ ]:
public class GarotoZumbi
{
    int idade;
    String estado;
    String nome;
    
    public GarotoZumbi(int pIdade, String pEstado, String pNome) {
        idade = pIdade;
        estado = pEstado;
        nome = pNome;
    }
    
    public void mostra() {
        // cabeleira
        if (idade >= 2)
            System.out.println("  *");
        
        // corpo com olhos
        if (estado.equalsIgnoreCase("acordado"))
            System.out.println(" o*o");
        else
            System.out.println(" -*-");
        
        // barba
        if (idade >= 3)
            System.out.println("*****");
        
        System.out.println(nome);
        
        System.out.println();
    }
    
    public void cresce() {
        if (idade < 3)
            idade++;
        mostra();
    }
    
    public void acorda() {
        estado = "acordado";
        mostra();
    }
    
    public void dorme() {
        estado = "dormindo";
        mostra();
    }
}

GarotoZumbi garoto = new GarotoZumbi(1, "acordado", "Asdrubal");
garoto.mostra();
garoto.dorme();
garoto.cresce();

Auto-referência

Um objeto pode fazer referência à própria instância usando o this. Este termo indica uma referência ao próprio objeto dentro dele mesmo.

No construtor da versão acima foi usado o nome do parâmetro diferente do atributo para que não haja confusão.

public GarotoZumbi(int pIdade, String pEstado, String pNome) {
    idade = pIdade;
    estado = pEstado;
    nome = pNome;
}

Entretanto, é possível (e comum) o uso do mesmo nome (parâmetros e atributos), com a diferenciação sendo feita pelo this. Veja como o construtor é modificado:

public GarotoZumbi(int idade, String estado, String nome) {
    this.idade = idade;
    this.estado = estado;
    this.nome = nome;
}

Quando não há indicação do escopo da variável, o Java dá sempre prioridade ao escopo mais interno. Portanto, quando indicamos apenas idade ele entende que é o parâmetro (escopo mais interno). Já this.idade indica o atributo especificado explicitamente.

Veja o código completo a seguir:


In [ ]:
public class GarotoZumbi
{
    int idade;
    String estado;
    String nome;
    
    public GarotoZumbi(int idade, String estado, String nome) {
        this.idade = idade;
        this.estado = estado;
        this.nome = nome;
    }
    
    public void mostra() {
        // cabeleira
        if (idade >= 2)
            System.out.println("  *");
        
        // corpo com olhos
        if (estado.equalsIgnoreCase("acordado"))
            System.out.println(" o*o");
        else
            System.out.println(" -*-");
        
        // barba
        if (idade >= 3)
            System.out.println("*****");
        
        System.out.println(nome);
        
        System.out.println();
    }
    
    public void cresce() {
        if (idade < 3)
            idade++;
        mostra();
    }
    
    public void acorda() {
        estado = "acordado";
        mostra();
    }
    
    public void dorme() {
        estado = "dormindo";
        mostra();
    }
}

GarotoZumbi garoto = new GarotoZumbi(1, "acordado", "Asdrubal");
garoto.mostra();
garoto.dorme();
garoto.cresce();

Encapsulamento e Controle de Acesso

Como será visto posteriormente, as classes elas controlam o acesso aos seus atributos e métodos. Isso permite que a classe decida o que é publicado.

O controle de acesso é feito por modificadores como private (visível apenas dentro da classe) e public (visível fora da classe).

As boas práticas da Orientação a Objetos indicam que atributos devem ser privados e métodos publicos, como na implementação a seguir.


In [ ]:
public class GarotoZumbi
{
    private int idade;
    private String estado;
    private String nome;
    
    public GarotoZumbi(int idade, String estado, String nome) {
        this.idade = idade;
        this.estado = estado;
        this.nome = nome;
    }
    
    public void mostra() {
        // cabeleira
        if (idade >= 2)
            System.out.println("  *");
        
        // corpo com olhos
        if (estado.equalsIgnoreCase("acordado"))
            System.out.println(" o*o");
        else
            System.out.println(" -*-");
        
        // barba
        if (idade >= 3)
            System.out.println("*****");
        
        System.out.println(nome);
        
        System.out.println();
    }
    
    public void cresce() {
        if (idade < 3)
            idade++;
        mostra();
    }
    
    public void acorda() {
        estado = "acordado";
        mostra();
    }
    
    public void dorme() {
        estado = "dormindo";
        mostra();
    }
}

GarotoZumbi garoto = new GarotoZumbi(1, "acordado", "Asdrubal");
garoto.mostra();
garoto.dorme();
garoto.cresce();

Acessando Atributos

Isso cria um problema de como acessar e modificar atributos externamente. Por exemplo, suponha que você queira apenas consultar a idade do Garoto Zumbi ou modificar seu nome, o acesso externo aos atributos causará erro, como é ilustrado a seguir.


In [ ]:
System.out.println(garoto.idade);
garoto.nome = "Asdrubal Silva";

Exercício

Crie um mecanismo para a classe GarotoZumbi que lhe permita acesso aos atributos. Deve ser possível:

  • consultar o atributo idade (mas não modificá-lo);
  • consultar e modificar o atributo nome.

Consultar não significa mostrar o atributo no console, significa que um programa externo ou outro objeto devem ser capazes de ter acesso ao valor do atributo. O programa externo ou objeto decidem o que fazer com o valor.

O mecanismo que você vai criar tem que manter todos os atributos como private.

Para este e os próximos exercícios que solicitarem modificação na classe, você deve copiar o códgo da classe que vai mudá-lo na célula destino e acrescentar ou modificar o que quiser.

Resolução

Métodos get e set são usadas para expor os atributos indiretamente. Esta exposição é chamada de propriedade.

  • get (leitura da propriedade) -> get seguido do nome do atributo - retorna o valor;
public int getIdade() {
    return idade;
}

public String getNome() {
    return nome;
}
  • set (modificação da propriedade) -> set seguido do nome do atributo - recebe argumento com novo valor.
public void setNome(String nome) {
    this.nome = nome;
}

Para propriedades somente de leitura o método set não é definido.


In [4]:
public class GarotoZumbi
{
    private int idade;
    private String estado;
    private String nome;
    
    public GarotoZumbi(int idade, String estado, String nome) {
        this.idade = idade;
        this.estado = estado;
        this.nome = nome;
    }
    
    public int getIdade() {
        return idade;
    }
    
    public String getNome() {
        return nome;
    }
    
    public void setNome(String nome) {
        this.nome = nome;
    }
    
    public void mostra() {
        // cabeleira
        if (idade >= 2)
            System.out.println("  *");
        
        // corpo com olhos
        if (estado.equalsIgnoreCase("acordado"))
            System.out.println(" o*o");
        else
            System.out.println(" -*-");
        
        // barba
        if (idade >= 3)
            System.out.println("*****");
        
        System.out.println(nome);
        
        System.out.println();
    }
    
    public void cresce() {
        if (idade < 3)
            idade++;
        mostra();
    }
    
    public void acorda() {
        estado = "acordado";
        mostra();
    }
    
    public void dorme() {
        estado = "dormindo";
        mostra();
    }
}

GarotoZumbi garoto = new GarotoZumbi(1, "acordado", "Asdrubal");
garoto.cresce();

System.out.println("Nome do Zumbi:  " + garoto.getNome());
System.out.println("Idade do Zumbi: " + garoto.getIdade());

garoto.setNome("Quincas");
System.out.println("Novo nome: " + garoto.getNome());


  *
 o*o
Asdrubal

Nome do Zumbi:  Asdrubal
Idade do Zumbi: 2
Novo nome: Quincas

Sobrecarga de Métodos

É possível se criar mais de um método com o mesmo nome para uma classe, entretanto cada um dos métodos tem que ter uma assinatura diferente. A assinatura é definida pela sequência de tipos dos parâmetros (independentemente do nome desses parâmetros).

Por exemplo, considere que eu quero criar duas versões para o método cresce. A primeira versão é igual a já existente cresce() sem parâmetros; a segunda recebe como parâmetro quantos anos eu quero que o Garoto Zumbi cresça: cresce(int anos). Estas duas assinaturas são consideradas diferentes:

  • cresce() - sem parâmetros
  • cresce(int anos) - com um parâmetro inteiro

Para cada uma das assinaturas você pode criar uma implementação diferente:

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

public void cresce(int anos) {
    if (idade + anos <= 3)
        idade += anos;
    mostra();
}

Criar dois métodos como mesmo nome e assinaturas diferentes se chama sobrecarga de métodos. O Java saberá quem chamar de acordo com o tipo dos parâmetros (se chamar sem parâmetros aciona o primeiro e com um inteiro como parâmetro aciona o segundo).

Note que o nome do parâmetro nunca importa na assinatura, somente o tipo. Portanto, você não pode criar duas assinaturas assim:

  • cresce(int anos) - com um parâmetro inteiro
  • cresce(int meses) - com um parâmetro inteiro

Mesmo tendo nomes diferentes eles têm o mesmo tipo, portanto são a mesma assinatura e o Java não aceitará.


In [ ]:
public class GarotoZumbi
{
    private int idade;
    private String estado;
    private String nome;
    
    public GarotoZumbi(int idade, String estado, String nome) {
        this.idade = idade;
        this.estado = estado;
        this.nome = nome;
    }
    
    public void mostra() {
        // cabeleira
        if (idade >= 2)
            System.out.println("  *");
        
        // corpo com olhos
        if (estado.equalsIgnoreCase("acordado"))
            System.out.println(" o*o");
        else
            System.out.println(" -*-");
        
        // barba
        if (idade >= 3)
            System.out.println("*****");
        
        // calcas
        if (idade >= 4)
            System.out.println("#####");
        
        // pernas
        if (idade >= 5)
            System.out.println("/   \\");

        System.out.println(nome);
        
        System.out.println();
    }
    
    public void cresce() {
        if (idade < 5)
            idade++;
        mostra();
    }

    public void cresce(int anos) {
        if (idade + anos <= 3)
            idade += anos;
        mostra();
    }
    
    public void acorda() {
        estado = "acordado";
        mostra();
    }
    
    public void dorme() {
        estado = "dormindo";
        mostra();
    }
}

GarotoZumbi garoto = new GarotoZumbi(1, "acordado", "Asdrubal");
garoto.mostra();
garoto.cresce(2);
garoto.cresce();

Chamando a si mesmo

Um método pode chamar a si mesmo (recursão) ou a outro método no objeto. Você pode explorar a ideia com a sobrecarga de métodos da seguinte maneira:

public void cresce() {
    cresce(1);
}

public void cresce(int anos) {
    if (idade + anos <= 3)
        idade += anos;
    mostra();
}

In [ ]:
public class GarotoZumbi
{
    private int idade;
    private String estado;
    private String nome;
    
    public GarotoZumbi(int idade, String estado, String nome) {
        this.idade = idade;
        this.estado = estado;
        this.nome = nome;
    }
    
    public void mostra() {
        // cabeleira
        if (idade >= 2)
            System.out.println("  *");
        
        // corpo com olhos
        if (estado.equalsIgnoreCase("acordado"))
            System.out.println(" o*o");
        else
            System.out.println(" -*-");
        
        // barba
        if (idade >= 3)
            System.out.println("*****");
        
        // calcas
        if (idade >= 4)
            System.out.println("#####");
        
        // pernas
        if (idade >= 5)
            System.out.println("/   \\");

        System.out.println(nome);
        
        System.out.println();
    }
    
    public void cresce() {
        cresce(1);
    }

    public void cresce(int anos) {
        if (idade + anos <= 3)
            idade += anos;
        mostra();
    }
    
    public void acorda() {
        estado = "acordado";
        mostra();
    }
    
    public void dorme() {
        estado = "dormindo";
        mostra();
    }
}

GarotoZumbi garoto = new GarotoZumbi(1, "acordado", "Asdrubal");
garoto.mostra();
garoto.cresce(2);
garoto.cresce();

Retomando o empréstimo

Vamos realizar um exercício que retoma a questão do empréstimo. Reveja a implementação a seguir, que já foi modificada com alguns conceitos que introduzimos nesta parte.


In [4]:
class Emprestimo {
    private float s;
    private int   n;
    private float j;
    private int   corrente;
    private float p;

    public Emprestimo(float s, int n, float j) {
        this.s = s;
        this.n = n;
        this.j = j;
        corrente = 1;
        this.p = s;
    }

    public float proximaParcela() {
        float retorno = p;
        corrente++;
        if (corrente <= n)
            p = p + (p * (j/100));
        else
            p = 0;
        return retorno;
    }
}

// codigo principal

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

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


Emprestimo 1: parcela 1 eh 200.0
Emprestimo 2: parcela 1 eh 500.0
Emprestimo 1: parcela 2 eh 202.0
Emprestimo 2: parcela 2 eh 510.0
Emprestimo 1: parcela 3 eh 204.02
Emprestimo 2: parcela 3 eh 520.2
Emprestimo 1: parcela 4 eh 206.06021
Emprestimo 2: parcela 4 eh 530.604
Emprestimo 1: parcela 5 eh 208.12082
Emprestimo 2: parcela 5 eh 541.21606
Emprestimo 2: parcela 6 eh 552.0404
Emprestimo 2: parcela 7 eh 563.08124

Exercício

Modifique a classe empréstimo acrescentando dois métodos chamados parcela com sobrecarga. Eles devem se comportar da seguinte maneira:

  • parcela sem informar a parcela - retorna o valor da parcela corrente (sem deslocar a parcela corrente para a próxima parcela);
  • parcela informando o número da parcela - retorna o cálculo da parcela informada (retorna zero se a parcela informada não existir).

Ambos os métodos são apenas de consulta, portanto, eles não modificam a parcela corrente que continuará a mesma.

Resolução

Na execução, ambos os métodos foram usados em paralelo para testes. Os números de ponto flutuante podem apresentar problemas de precisão, como será discutido em sala.


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

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

    public Emprestimo(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;
    }

    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

Emprestimo emprestimo1 = new Emprestimo(200, 5, 1),
           emprestimo2 = new Emprestimo(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