TAD (Tipo abstrato de Dados) em POO

Neste módulo veremos:

Primeiro vamos recordar que, como ocorre em outras linguagens de programação, em Java um vetor é um excelente meio de armazenamento de valores na memória RAM pois seus elementos tem tempo de acesso constante, ou seja, o tempo de acesso é o mesmo para qualquer elemento de um vetor. O mesmo vale para estruturas similares multidimensionais.

No entanto, os arranjos apresentam uma dificuldade, uma vez alocado um espaço para ele este espaço não se altera, ou seja, em tempo de execução de um programa não podemos aumentar ou diminuir o tamanho de uma estrutura uniforme escalar, que é o caso das matrizes e dos vetores.

Bem, mas bem antes de entrar neste mérito da alocação dinâmica de espaço vamos recordar uma aplicação de arranjos para escrever o TAD pilha.

TAD em Java: Pilha

Abaixo encontramos a listagem de uma primeira e simples implementação do TAD Pilha em Java.
Esta é uma pilha de caracteres, que tem um elemento chamado topo que aponta para um elemento acima do último elemento da pilha. Diferentemente de outras versões este topo é iniciado com o zero pois Java não permite que índices de vetores ultrapassem os limites do mesmo.

Todo array em Java (e como um array é um objeto) deve ser ser alocado dinâmicamente através do operador new. Quando se aloca um vetor cada elemento recebe um valor padrão que é zero para as variáveis numéricas, false para variáveis lógicas (boolean) e null para as referências.

Vamos estudar esta primeira versão de um TAD pilha.

/** 
 * TAD Pilhav01
 * @author EESR
 * @version fev 2006
 */
public class Pilhav01 {
   private int topo;
   private char[] dados;

   public Pilhav01(int tam) {
      topo = 0;
      dados = new char[tam];
   }

   public boolean vazia()  {
      return (topo == 0);
   }

   public boolean cheia()  {
      return (topo  == dados.length);
   }
  
   public void push(char c) {
      dados[topo++] = c;
   }

   public char pop() {
      return dados[--topo];
   }
}

Note que o TAD Pilha é conhecido somente pelas partes públicas, basicamente pelos métodos já que a variável topo e o arranjo de dados em si, dados, são declarados como private, ou melhor, são de acesso restrito ao próprio objeto, não podem ser manipulados pelos clientes da classe Pilhav01.

/**
 * Testa TAD Pilha
 * @author EESR
 * @version fev 2006
 */

public class RunPilhav01 {

    public static void main(String[] args) {
    Pilhav01 s;
    char ch;

    s =  new Pilhav01(10); // 10 caracteres

    for (int i=40; i<46; i++)
	{
	  ch=(char)i;
	  s.push(ch);
	}
    System.out.println("encheu!");
    while (!s.vazia())
      System.out.println(s.pop());
    System.out.println("fim");
    }
}
Exercício

Compilem e executem o código acima. Leiam atentamente o código e procurem sanar as eventuais dúvidas, principalmente as dúvidas quanto ao paradigma de orientação a objetos.

Executando-se o código RunPilhav01.java acima temos a saída listada a seguir:

encheu!
-
,
+
*
)
(
fim
Exercício

Obviamente que, se forçarmos para que a Pilha seja abastecida com mais caracteres do que o necessário teremos um erro de execução e este é um bom exercício para fazermos. Tente provocar este erro. Qual é a saída do programa?

Uma Pilha mais elegante

Vamos avançar nos conceitos da linguagem um pouco mais.
Para que precisamos de uma TAD que armazena um só tipo de dado?
Porque não usar os conceitos de OO para escrevermos um TAD Pilha genérico, ou seja, que possa receber informações das mais variadas?

Vejamos como na listagem a seguir:

/** 
 * TAD Pilhav02
 * @author EESR
 * @version fev 2006
 */
public class Pilhav02 {
   private int topo;
   private Object[] pilha;

   public Pilhav02(int capacidade) {
      topo = 0;
      pilha = new Object[capacidade];
   }

   public boolean vazia()  {
      return (topo == 0);
   }

   public boolean cheia()  {
      return (topo  == pilha.length);
   }
  
   public void push(Object obj) {
      pilha[topo++] = obj;
   }

   public Object pop() {
      return pilha[--topo];
   }
}

Temos agora um TAD pilha quase que perfeito.
Reparem que agora temos uma pilha formada por Object que pode ser lido como o tipo maior em Java, um tipo que engloba todos as concepções de objeto.

Dada a classe Pilhav02 acima esta pode receber quaisquer objetos. Vejamos um executor que testa estas características da nova pilha.

/**
 * Testa TAD Pilha
 * @author EESR
 * @version fev 2006
 */

public class RunPilhav02 {

    public static void main(String[] args) {
    Pilhav02 s;

    s =  new Pilhav02(10); // 10 caracteres

    s.push( new Integer(1234));
    s.push( new Float(5.678));
    s.push( new String("abc"));

    System.out.println("encheu!");
    while (!s.vazia())
      System.out.println(s.pop());
    System.out.println("fim");
    }
}
Exercício

Notem que nesta última listagem temos alguns empacotadores de tipos, os chamados wrappers, ou seja, construtores de objetos que pegam tipos primitivos da linguagem e geram objetos a partir destes tipos. Estudar na API de Java as classes de java.lang, o pacote que praticamente define a linguagem. Preste atenção nas classes Integer, Float e String.

Notem que até o momento, apesar das grandes considerações que tivemos quanto ao paradigma de OO, o conceito do TAD prendeu-se muito aos aspectos da linguagem. Por exemplo, nesta nossa implementação a pilha pode ficar cheia, um contracenso para um TAD que ganhou uma particularidade da implementação que não permite uma pilha maior que o tamanho do vetor declarado.

Do ponto de vista do usuário estas considerações são secundárias e deveriam ser evitadas, somente a funcionalidade do TAD deveria ser colocado. Isto é um trabalho para o que chamamos de Interface em Java. Vejamos!

Aperfeiçoando o código Java

Para codificar TAD é preciso entender duas construções de programas:

Dois conceitos que pautaremos a seguir:

Interface

É uma maneira de declarar o comportamento de uma classe, não exatamente como acontece internamente este comportamento. Para uma interface são declrados somente o nome do método e seus parâmetros. Esta especificação de parâmetros é feita pelos seus tipos.
Vejamos:

public interface mp3Player {
  public void play();
  public void stop();
}

Uma vez construída a interface passa-se a codificação do conteúdo dos métodos.
A utilização de interface é uma técnica de programação útil e que deve ser, sempre que possível, explorada.

Vejamos uma interface para a classe Pilha já descrita:

/**
 * Interface para o TAD Pilha
 * @author EESR
 * @version fev 2006
 */
public interface PilhaTAD {
    //metodos de acesso
    public boolean vazia();

    //metodos para atualizacao
    public void push(Object c);
    public Object pop();
}

É redundante declarar uma interface public como também os seus métodos, pois uma interface é um meio de comunicação entre os objetos que irão implemetá-la. De qualquer modo, está é uma primeira versão de nossa interface, podemos sempre melhorá-la.

Bem, implementando a interface agora podemos rescrever a classe Pilha que obedecerá a esta nova interface. A classe Pilha pouco muda então, só a primeira linha de sua declaração. Veja a porção de código a seguir:

public class Pilha implements PilhaTAD {
...

Exception

Uma exceção, ou melhor dizendo, uma exception é um evento que ocorre durante a execução de um programa e que, obviamente, altera o fluxo programado de execução das instruções.

Quando um erro num método este cria um objeto e o lança ao sistema de execução. Este objeto é o chamado exception object e inclui uma série de informações sobre o erro e o objeto emque foi originado.
A criação de um objeto de erro e a sua atribuição (o que já chamamos de lançamento) ao sistema executor é o que é chamado tecnicamente de throwing an exception. Existem vários tipos de exceções previstras, desde erros de entrada/saída, como é o caso, até erros de apontadores, erros de execução, entre outros. A própria linguagem possui mecanismos de detecção de erros. Estes erros (exceções) são derivados da classe Throwable, a superclasse de todos os erros e exceções em Java.

Uma vez lançada a exception o sistema executor irá procurar um objeto para trabalhar sobre este erro. Na realidade, o sistema executor procura numa pilha de chamadas de métodos para encontrar um exception handler.
É um exception handler que escreveremos agora para a classe do TAD pilha.

Veja a listagem a seguir. Reparem nas alterações ocorridas nos construtores e no método pop.

/** 
 * TAD Pilhav04
 * @author EESR
 * @version fev 2006
 */
public class Pilhav04 implements PilhaTAD {
   private int topo;
   private Object[] pilha;
   private String nome;

   public Pilhav04() {
	this("pilha", 10);
   }

   public Pilhav04(String lnome, int capacidade) {
       nome = lnome;
       topo = 0;
       pilha = new Object[capacidade];
   }

   public boolean vazia()  {
       return (topo == 0);
   }

   public boolean cheia()  {
       return (topo  == pilha.length);
   }
  
   public void push(Object obj) {
       pilha[topo++] = obj;
   }

   public Object pop() {
       if (topo == 0) 
	   throw new PilhaVaziaException(nome);
       else
	   return pilha[--topo];
   }
}

Esta classe funciona como qualquer outra classe em Java. A herança (extends RuntimeException ocorre de classes em java.lang, a classe que guarda as definições da linguagem e é carregada sempre.

/**
 * Definicao de excecao para tad pilha
 * escalar
 */
public class PilhaVaziaException 
       extends RuntimeException {
    public PilhaVaziaException() {
	this("pilha");
    }

    public PilhaVaziaException(String nome) {
	super(nome + " esta vazia");
    }
}

Vamos então escrever um código que provoque o lançamento de um objecto exception. Enchemos uma pilha com dez elementos e tentamos tirar um a mais, ou sejam onze, situação que solicitamos tirar um elemento de uma pilha já vazia.

/**
 * Testa TAD Pilha
 * @author EESR
 * @version fev 2006
 */

public class RunPilhav04 {

    public static void main(String[] args) {
    Pilhav04 s;

    s =  new Pilhav04("Pilha 1", 10); // 10 caracteres
    for (int i=10; i<20; i++)
	s.push( new Integer(i));
    System.out.println("encheu!");

    for (int i=10; i<=20; i++)
      System.out.println(s.pop());
    System.out.println("fim");
    }
}

A execução do código acima provoca a seguinte saída (editada):

encheu!
19
...(recortamos alguns valores aqui)
10
Exception in thread "main" PilhaVaziaException: Pilha 1 esta vazia
        at Pilhav04.pop(Pilhav04.java:35)
        at RunPilhav04.main(RunPilhav04.java:18)

Notem que o comportamento do objeto foi igual ao programado pelo exception, obviamente.

Este recurso é importantíssimo para recuperar falhas de programas, aliás este é o propósito de exception.

Por enquanto é só!

Última modificação feita em: 14 de agosto de 2006
Evandro Eduardo Seron Ruiz, PhD (evandro at usp ponto br)
www.imagcom.org/evandro/ibm1030