Classes e objetos na prática

Aula de Laboratório

Objetivos:

1. Revisão


No capítulo anterior, vimos que os objetos são criados (instanciados) a partir de modelos, denominados classes.

Comentamos a necessidade de um método construtor, importante para a inicialização dos objetos.

Também vimos que um programa Java deve obrigatoriamente apresentar um método main em alguma classe. Esse método é responsável pela execução do programa em si, ou seja, a criação de objetos e sua posterior utilização.

Criamos a classe Contador, responsável pelo armazenamento e manipulação de um contador. A partir da classe Contador, foi sugerida a criação de uma classe Relógio, que deveria armazenar e manipular horas, minutos e segundos.

A listagem abaixo sugere uma implementação da classe Relógio.

import java.io.*;
class Relogio
{

   private int hora,minuto,segundo;


   // Construtor
   public Relogio(int h,int m,int s)
   {
      hora = h;
      minuto = m;
      segundo = s;
   }

   // Incrementa um segundo
   public void incrementa()
   {
     if(++segundo>59)
     {
        segundo = 0;
        if(++minuto>59)
        {
          minuto = 0;
          if(++hora>23)
            hora = 0;
        }
     }
   }

   // Decrementa um segundo
   public void decrementa()
   {
     if(--segundo<0)
     {
        segundo = 59;
        if(--minuto<0)
        {
          minuto = 59;
          if(--hora<0)
            hora = 23;
        }
     }
   }

   // Informa a hora atual
   public void informa()
   {
      System.out.println("Hora atual: "+hora+":"+minuto+":"+segundo);
   }

   public static void main(String args[])
   {
      Relogio r1;
      r1 = new Relogio(14,15,10);
      r1.informa();
      r1.incrementa();
      r1.informa();
      for(int c=0;c<20;++c) r1.decrementa();
      r1.informa();
   }
}

 

Exercício:

  • Qual é a saída na tela do programa acima ? Tente descobrir sem digitar o programa!

 

2. Experimentando a criação de objetos no BlueJ


A grande vantagem de um ambiente de aprendizado como o BlueJ é que ele permite o instanciamento de qualquer objeto, independentemente da presença ou não do método main.

Para verificar isso, crie um novo projeto e digite o programa anterior no BlueJ. A seguir, compile a classe Relogio e clique com o botão direito sobre a caixa que a representa para apresentar o menu:

Devemos agora prestar um pouco mais de atenção no que aparece no menu:

Ao selecionarmos a primeira opção, o BlueJ apresenta a seguinte tela, onde cada campo deverá ser preenchido com os valores numéricos desejados para hora, minuto e segundo. O primeiro campo é o nome da instância, ou seja, o nome de variável desejado. O BlueJ sugere um nome genérico, baseado no nome da classe e em quantas instâncias existem até agora. Portanto, a primeira instância será relogio_1, a segunda será relogio_2 e assim por diante.

Observe que imediatamente será criado o objeto relogio_1 e este deverá estar visível na área inferior da janela:

Se clicarmos com o botão direito sobre o objeto recém criado, serão apresentados todos os métodos public que este oferece:

Experimente selecionar algumas vezes os métodos decrementa() e incrementa(). Depois selecione o método informa() e verifique que os valores foram atualizados corretamente. Note que o método informa() irá causar o surgimento de uma nova janela, a BlueJ terminal window, pois foi utilizado o método println(), que exige a presença de um console (terminal de texto):

Note que a presença do método informa() não é essencial dentro do BlueJ, uma vez que existe a opção Inspect: esta opção exibe em uma janela separada todos os atributos do objeto selecionado:

3. Melhorando a classe Relogio: Sobrecarga


Como pudemos constatar, a classe Relogio funciona muito bem: armazena corretamente as horas e permite o incremento e decremento de tempo. Mas seria bastante interessante se fosse possível inicializá-la sem especificar a hora completa, não ?
Exemplo: queremos acertar o relógio para 14 horas, 0 minutos e 0 segundos: new Relogio(14,0,0)
Não parece um desperdício sempre utilizar uma chamada tão longa ?

A linguagem Java oferece um recurso muito interessante para resolver este problema: a sobrecarga de métodos (method overloading). Dizemos que um método está sobrecarregado quando existem várias cópias deste método, cada uma com um conjunto de parâmetros diferentes.

Como exemplo, iremos sobrecarregar o construtor da classe Relogio, fazendo com que o relógio possa ser inicializado de três maneiras diferentes:

import java.io.*;
class Relogio
{
   private int hora,minuto,segundo;

   // Construtores
   public Relogio(int h,int m,int s)
   {
      hora = h;
      minuto = m;
      segundo = s;
   }

   public Relogio(int h,int m)
   {
      hora = h;
      minuto = m;
      segundo = 0;
   }

   public Relogio(int h)
   {
      hora = h;
      minuto = 0;
      segundo = 0;
   }
   ...

Se olharmos para esse código com atenção, fica evidente que há uma maneira melhor de inicializar as variáveis. Por exemplo, podemos criar um método acertaHora(h,m,s) e chamá-lo de dentro dos construtores.

A criação deste método ainda tem a vantagem adicional de tornar possível um novo acerto de hora no futuro (após a criação do objeto):

import java.io.*;
class Relogio
{
   private int hora,minuto,segundo;

   // Construtores
   public Relogio(int h,int m,int s)
   {
      acertaHora(h,m,s);
   }

   public Relogio(int h,int m)
   {
      acertaHora(h,m,0);
   }

   public Relogio(int h)
   {
      acertaHora(h,0,0);
   }

   // Método para acertar o horário
   public void acertaHora(int h,int m,int s)
   {
      hora = h;
      minuto = m;
      segundo = s;
   } 

Para verificar isso, altere a classe de acordo com o código acima. Não esqueça de antes remover o objeto já criado, para evitar problemas... Compile a classe. Agora, se clicarmos novamente com o botão direito sobre ela, serão apresentadas todas as novas opções de construtores. Experimente com alguns valores e verifique que aqueles não informados são inicializados corretamente com 0.

Exercícios:

  • Experimente criar alguns relógios e alterar a hora após a criação (com o método informaHora()
  • Observe que não há nenhuma consistência nos dados de entrada. Isso pode ser um problema: experimente criar um relógio com hora=80, minuto=200, segundo=-20
  • Altere a classe para consistir os dados de entrada no construtor, isto é, se for informado um valor errado, troque por 0. De uma forma geral, um construtor sempre deve consistir a entrada
  • Utilizando sobrecarga, altere os métodos incrementa() e decrementa() para que possam também receber um valor numérico, relativo à quantidade de segundos que deverão incrementar/decrementar (no máximo 60)

4. Métodos de classe e de instância


A grande maioria das linguagens orientadas a objetos permite a declaração de dois tipos de métodos: de classe e de instância. Métodos de instância são aqueles que estão disponíveis quando existe uma instância, pois manipulam os atributos contidos nesta. Então, todos os métodos que escrevemos até agora são de instância.

Mas imagine que queremos estender a classe Relogio, permitindo que ela seja capaz de calcular e retornar a diferença entre duas horas:

class Relogio
{
   ...
   // Método para calcular a diferença em segundos entre duas horas
   public int difHora(int h1,int m1,int s1,int h2,int m2,int s2)
   {
      int dif;
      // Calculos
      ...
      return dif;
   } 

Olhando com atenção, notamos que esse método não utiliza nenhum atributo da instância (hora, minuto ou segundo). Então, teoricamente ele poderia ser utilizado independentemente da existência ou não de instâncias, certo ? Para tanto, declara-se o método como static, indicando que é um método de classe, ou seja, não depende de nenhum atributo de instância:

class Relogio
{
   ...
   // Método para calcular a diferença em segundos entre duas horas
   public static int difHora(int h1,int m1,int s1,int h2,int m2,int s2)
   {
      int dif;
      // Calculos (invente alguma coisa)
      ...
      return dif;
   } 

Ao compilarmos novamente a classe (você evidentemente já digitou o código, certo ?) e clicarmos sobre a caixa, verificamos que o método difHora(...) está disponível juntamente com os demais métodos, ou seja, pode ser utilizado mesmo sem que exista uma instância:

Para utilizar o método de classe em qualquer parte de um programa Java, utilize a sintaxe abaixo, ou seja, NomeDaClasse.metodo(..)

   ...
   public static void main(String args)
   {
      ...
      int dif;
      dif = Relogio.difHora(15,10,5,20,10,2);
      System.out.println("A diferença entre as duas horas é: "+dif);
      ...
   } 

Se você prestar atenção, verá que já usava um método de classe sem saber: System.out.println(...) é uma chamada para o método println(...), dentro do objeto "out", presente na classe System (funções gerais do sistema).

 

5. Variáveis de classe e de instância


Todas as variáveis que usamos até agora são variáveis de instância, ou seja, existe uma cópia de cada variável em cada instância que criamos. Cada uma pode armazenar um valor diferente, dependendo da instância. Porém, assim como existem métodos de classe, também existem variáveis de classe. Para que serviria uma variável de classe ?

Ei, não seria ótimo se cada instância da classe Relogio soubesse quantas outras existem ?

A melhor maneira de fazer isso é declarar uma variável de classe (static) para armazenar a quantidade de instâncias já criadas.

E como fazer isso ?

Simples, inicializamos esta variável com zero (na declaração) e no construtor incrementamos esse valor. Como é uma variável de classe, quando a utilizarmos em diversas instâncias, na verdade estaremos atualizando a mesma variável:

class Relogio
{
   private int hora, minuto, segundo;
   private static int total = 0; // qtd. de relogios

   // Construtores
   public Relogio(int h,int m,int s)
   {
      total++;
      acertaHora(h,m,s);
   }
   ... // idem para os demais construtores

Para acreditar em mim, crie uma porção de relógios e inspecione o conteúdo das variáveis de qualquer um deles. Você deverá ver algo como:

Repare que a variável total aparece separada, pois é um static field, ou seja, uma variável de classe. Variáveis de classe também são comumente utilizadas para armazenar constantes úteis ao programa inteiro, por exemplo, armazenar a quantidade de horas em um dia:

class Relogio
{
   private int hora, minuto, segundo;
   private static int total = 0; // qtd. de relogios
   public static final int HORAS_DIA = 24;
   ...

Lembre-se que constantes são declaradas com o modificador final.

Se queremos que uma constante possa ser utilizada por outras classes (como no exemplo acima), a declaramos como public.
Caso contrário, só será utilizável nesta classe.

Exercício:

  • Inclua um método na classe para retornar a quantidade de relógios que já foram criados. Esse método pode ou deve ser static ? Quais as implicações disso ?

 

Não OUSE deixar o laboratório se hover alguma dúvida com relação a:

  • utilização do BlueJ para instanciar/inspecionar/executar métodos em objetos
  • utilização da sobrecarga de métodos e construtores
  • utilização de variáveis e métodos de classe

    Você foi avisado(a). Caso contrário, as consequências já são conhecidas...