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.
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"); } }
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
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?
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"); } }
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!
Para codificar TAD é preciso entender duas construções de programas:
Dois conceitos que pautaremos a seguir:
É 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 { ...
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ó!
(evandro at usp ponto br)