apostiladejavanapratica-120712200815-phpapp01

March 23, 2018 | Author: Rui F. S. Neto | Category: Pointer (Computer Programming), Class (Computer Programming), Programming Language, C++, Data Type


Comments



Description

UNIVERSIDADE FEDERAL DE VIÇOSADEPARTAMENTO DE INFORMÁTICA JAVA NA PRÁTICA Alcione de Paiva Oliveira Vinícius Valente Maciel 2002 J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 1 Sumário Capítulo I - Introdução ....................................... 6 CONVENÇÕES........................................................................................................................... 10 Capítulo II - Programação Orientada a Objetos .................. 11 CLASSES E OBJ ETOS E LINGUAGENS DE PROGRAMAÇÃO......................................................... 12 Ocultando de Informação................................................................................................... 16 Especialização e Herança.................................................................................................. 17 Sobrescrita, Sobrecarga e Polimorfismo............................................................................ 18 INTRODUÇÃO À DIAGRAMA DE CLASSES ................................................................................. 20 Diagrama de Classes.......................................................................................................... 20 Capítulo III - Introdução à Linguagem Java ..................... 27 PALAVRAS RESERVADAS......................................................................................................... 30 LITERAIS.................................................................................................................................. 31 SEPARADORES.......................................................................................................................... 34 TIPOS DE DADOS....................................................................................................................... 35 Tipos de dados simples....................................................................................................... 35 Tipos de dados compostos.................................................................................................. 37 CONVERSÃO DE TIPOS.............................................................................................................. 41 OPERADORES........................................................................................................................... 42 Expressões e Precedência entre Operadores ..................................................................... 50 COMENTÁRIOS......................................................................................................................... 51 BLOCOS E ESCOPO................................................................................................................... 52 ESTRUTURAS DE CONTROLE .................................................................................................... 53 Seleção ............................................................................................................................... 53 Repetição............................................................................................................................ 57 break e continue......................................................................................................... 60 ARGUMENTOS DA LINHA DE COMANDO.................................................................................... 61 ASSERT (ASSERTIVAS) ............................................................................................................. 63 Sintaxe e semântica ............................................................................................................ 63 Habilitando e Desabilitando Assertivas ............................................................................. 64 Capítulo IV Classes, Packages e Interfaces ................... 66 CLASSES .................................................................................................................................. 66 Construtores ....................................................................................................................... 68 Valor de Retorno ................................................................................................................ 68 OBJ ETOS.................................................................................................................................. 69 MODIFICADORES DE ACESSO.................................................................................................... 72 Outros Modificadores......................................................................................................... 74 REFERÊNCIAS COMPARTILHADAS............................................................................................ 78 COPIANDO OBJ ETOS................................................................................................................. 81 O objeto this ................................................................................................................... 81 PACKAGES............................................................................................................................... 83 Usando Packages ............................................................................................................... 83 Criando Packages .............................................................................................................. 84 O Mecanismo de Extensão ................................................................................................. 86 DERIVANDO CLASSES............................................................................................................... 88 super................................................................................................................................... 90 J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 2 A classe Object ............................................................................................................... 91 Sobrescrita e Polimorfismo ................................................................................................ 92 CLASSES E MÉTODOS ABSTRATOS........................................................................................... 95 INTERFACES............................................................................................................................. 96 CLASSES INTERNAS.................................................................................................................. 99 Classes Internas Anônimas............................................................................................... 101 CONVERSÃO........................................................................................................................... 103 EXCEÇÕES.............................................................................................................................. 103 A hierarquia de Exceções................................................................................................. 105 Capturando mais de uma exceção.................................................................................... 106 Lançando exceções........................................................................................................... 107 Comportamento do Sistema diante das Exceções ............................................................ 109 Criando suas próprias exceções....................................................................................... 110 A cláusula finally....................................................................................................... 111 DOCUMENTANDO O CÓDIGO................................................................................................... 112 Rótulos.............................................................................................................................. 113 HTML embutida ............................................................................................................... 115 AGENDA ELETRÔNICA VERSÃO CONSOLE 1.0........................................................................ 115 Capítulo V – Entrada e Saída (java.io) ...................... 122 ACESSO SEQUENCIAL............................................................................................................. 122 ACESSO DIRETO..................................................................................................................... 127 Capítulo VI – java.util ..................................... 131 LIDANDO COM COLEÇÕES...................................................................................................... 131 As Interfaces Iterator e Enumeration................................................................... 131 Vector ........................................................................................................................... 133 Stack.............................................................................................................................. 136 Hashtable .................................................................................................................... 138 MISCELÂNEA DE CLASSES DO PACOTE JAVA.UTIL................................................................. 141 Arrays ........................................................................................................................... 141 Date ................................................................................................................................ 144 Observable.................................................................................................................. 146 StringTokenizer...................................................................................................... 150 AGENDA ELETRÔNICA VERSÃO CONSOLE 2.0........................................................................ 152 Capítulo VII - Serialização e Persistência .................... 159 AGENDA ELETRÔNICA VERSÃO CONSOLE 2.1........................................................................ 161 Capítulo VIII – AWT (Abstract Window Toolkit) ............... 164 A HIERARQUIA DE COMPONENTES......................................................................................... 164 OLÁ MUNDO AWT................................................................................................................ 166 TRATAMENTO DE EVENTOS ................................................................................................... 167 Modelo de Eventos 1.1 ..................................................................................................... 167 Tratamento de Eventos com classes Internas................................................................... 171 EXEMPLO BÁSICO.................................................................................................................. 176 ACRESCENTANDO CORES....................................................................................................... 180 GERENCIANDO O LAYOUT...................................................................................................... 181 Exemplo com BorderLayout....................................................................................... 182 Exemplo com FlowLayout ........................................................................................... 183 Exemplo com CardLayout ........................................................................................... 184 Exemplo com GridLayout ........................................................................................... 186 J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 3 Exemplo com GridBagLayout .................................................................................... 187 UTILIZANDO LISTAS............................................................................................................... 189 TRABALHANDO COM MENUS E DIÁLOGOS............................................................................. 192 CAPTURANDO EVENTOS DO TECLADO................................................................................... 196 PRINCIPAIS CLASSES.............................................................................................................. 196 Color.............................................................................................................................. 196 Component .................................................................................................................... 198 Button ........................................................................................................................... 200 Label.............................................................................................................................. 201 List ................................................................................................................................ 202 TextField .................................................................................................................... 203 TextArea....................................................................................................................... 204 CONTAINERS.......................................................................................................................... 205 Panel.............................................................................................................................. 207 Frame.............................................................................................................................. 208 AGENDA ELETRÔNICA VERSÃO GRÁFICA 1.0......................................................................... 211 Capítulo IX - Applets ....................................... 217 DESCRIÇÃO DO CÓDIGO HTML ............................................................................................. 221 MÉTODOS DA CLASSE APPLET............................................................................................... 222 EXIBINDO UMA IMAGEM ........................................................................................................ 226 ÁUDIO.................................................................................................................................... 229 OBTENDO PARÂMETROS......................................................................................................... 230 EXECUTANDO UM APPLET COMO APLICAÇÃO........................................................................ 232 PREPARANDO APPLETS PARA PRODUÇÃO E ARQUIVOS J ARS................................................. 232 CRIANDO OS PRÓPRIOS ARQUIVOS MANIFEST......................................................................... 235 AGENDA ELETRÔNICA VERSÃO APPLET 1.0........................................................................... 236 Capítulo X JavaBean ......................................... 237 O QUE É UM J AVABEAN?....................................................................................................... 237 J AVABEANS E FERRAMENTAS RAD....................................................................................... 237 PROPRIEDADES ...................................................................................................................... 238 Simples ............................................................................................................................. 238 Indexada........................................................................................................................... 239 Ligada (Bound)................................................................................................................. 240 Restringidas(Constrained) ............................................................................................... 241 EVENTOS................................................................................................................................ 243 DESENVOLVIMENTO DO EXEMPLO......................................................................................... 244 TimerEventListener .......................................................................................................... 244 TimerEvent ....................................................................................................................... 245 TimerBean ........................................................................................................................ 245 INSTALANDO O BEANS DEVELOPMENT KIT (BDK)................................................................ 249 TESTANDO EXEMPLO NO BDK............................................................................................... 249 Capítulo XI - Concorrência .................................... 255 CRIANDO THREADS EM J AVA ................................................................................................. 257 Criando threads por meio da interface Runnable ........................................................ 259 A CLASSE THREAD................................................................................................................. 259 Hierarquia........................................................................................................................ 259 Construtores ..................................................................................................................... 259 Métodos ............................................................................................................................ 259 J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 4 Variáveis públicas ............................................................................................................ 259 CICLO DE VIDA DOS THREADS............................................................................................... 259 sleep(), yield(), join(), destroy(), stop(), suspend() e resume(). .............. 259 DAEMON THREADS................................................................................................................ 259 INFLUÊNCIA DO SISTEMA OPERACIONAL NO COMPORTAMENTO DOS THREADS..................... 259 Forma de escalonamento de threads................................................................................ 259 Relacionamento entre os níveis de prioridades definidas na linguagem Java e os níveis de prioridades definidas nos Sistemas Operacionais............................................................ 259 COMPARTILHAMENTO DE MEMÓRIA E SINCRONIZAÇÃO........................................................ 259 Atomicidade de Instruções e Sincronização do Acesso à Sessões Críticas ...................... 259 Comunicação entre Threads: wait() e notify() ................................................................. 259 Capítulo XII - Animação ....................................... 259 Capítulo XIII - Programação em rede ......................... 259 CONCEITOS SOBRE PROTOCOLOS USADOS NA INTERNET....................................................... 259 TCP................................................................................................................................... 259 UDP.................................................................................................................................. 259 IDENTIFICAÇÃO DE HOSTS (Número IP).................................................................... 259 Identificação de Processos (Portas)................................................................................. 259 PROGRAMAÇÃO EM REDE COM J AVA..................................................................................... 259 Comunicação Básica Entre Aplicações............................................................................ 259 Comunicação Sem Conexão (UDP) ................................................................................. 259 Comunicação por meio de URL ....................................................................................... 259 Capítulo XIV – Computação Distribuída (RMI) ................. 259 CRIANDO NOSSA AGENDA DISTRIBUÍDA................................................................................. 259 Implementar interface do objeto remoto .......................................................................... 259 Capítulo XV - Acesso a Banco de Dados ....................... 259 MODELOS DE ACESSO A SERVIDORES.................................................................................... 259 TIPOS DE DRIVERS J DBC....................................................................................................... 259 Obtendo os Drivers JDBC................................................................................................ 259 PREPARANDO UM BANCO DE DADOS..................................................................................... 259 Configurando o ODBC..................................................................................................... 259 EXEMPLO INICIAL .................................................................................................................. 259 Carregando o Driver........................................................................................................ 259 Estabelecendo a conexão ................................................................................................. 259 Criando e Executando Comandos .................................................................................... 259 RECUPERANDO VALORES....................................................................................................... 259 TRANSAÇÕES E NÍVEL DE ISOLAMENTO................................................................................. 259 Transação......................................................................................................................... 259 Níveis de isolamento......................................................................................................... 259 PREPARED STATEMENTS........................................................................................................ 259 PROCEDIMENTOS ARMAZENADOS (STORED PROCEDURES).................................................... 259 AGENDA ELETRÔNICA VERSÃO J DBC................................................................................... 259 Capítulo XVI Servlets e JSP ................................. 259 SERVLETS .............................................................................................................................. 259 Applets X Servlets............................................................................................................. 259 CGI X Servlets .................................................................................................................. 259 A API SERVLET..................................................................................................................... 259 Exemplo de Servlet ........................................................................................................... 259 COMPILANDO O SERVLET....................................................................................................... 259 J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 5 Instalando o Tomcat ......................................................................................................... 259 PREPARANDO PARA EXECUTAR O SERVLET............................................................................ 259 Compilando o Servlet ....................................................................................................... 259 Criando uma aplicação no Tomcat .................................................................................. 259 EXECUTANDO O SERVLET...................................................................................................... 259 Invocando diretamente pelo Navegador........................................................................... 259 Invocando em uma página HTML.................................................................................... 259 Diferenças entre as requisições GET e POST.................................................................. 259 CONCORRÊNCIA..................................................................................................................... 259 OBTENDO INFORMAÇÕES SOBRE A REQUISIÇÃO.................................................................... 259 LIDANDO COM FORMULÁRIOS................................................................................................ 259 LIDANDO COM COOKIES......................................................................................................... 259 LIDANDO COM SESSÕES......................................................................................................... 259 J SP......................................................................................................................................... 259 PHP X JSP ....................................................................................................................... 259 ASP X JSP ........................................................................................................................ 259 Primeiro exemplo em JSP ................................................................................................ 259 Executando o arquivo JSP................................................................................................ 259 Objetos implícitos............................................................................................................. 259 Tags JSP........................................................................................................................... 259 Comentários ..................................................................................................................... 259 Diretivas ........................................................................................................................... 259 Extraindo Valores de Formulários................................................................................... 259 Criando e Modificando Cookies....................................................................................... 259 Lidando com sessões ........................................................................................................ 259 O Uso de JavaBeans......................................................................................................... 259 REENCAMINHANDO OU REDIRECIONANDO REQUISIÇÕES....................................................... 259 UMA ARQUITETURA PARA COMÉRCIO ELETRÔNICO............................................................... 259 Tipos de aplicações na WEB............................................................................................ 259 Arquitetura MVC para a Web .......................................................................................... 259 Agenda Web: Um Exemplo de uma aplicação Web usando a arquitetura MVC.............. 259 Capítulo XVII Perguntas Frequentes .......................... 259 Bibliografia ................................................ 259 Links ....................................................... 259 Índice ...................................................... 259 J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 6 Capítulo I - Introdução J ava é uma linguagem de programação desenvolvida pela Sun Microsystems e lançada em versão beta em 1995. O seu desenvolvimento foi iniciado em 1991 pela equipe liderada por J ames Gosling visando o mercado de bens eletrônicos de consumo. Por isso foi projetada desde o início para ser independente de hardware, uma vez que as características dos equipamentos variam amplamente neste nicho de desenvolvimento. Outro objetivo estabelecido desde sua concepção foi o de ser uma linguagem segura. Segura tanto no sentido de evitar algumas falhas comuns que os programadores costumam cometer durante o desenvolvimento, como no sentido de evitar ataques externos. Isto é importante no mercado de bens eletrônicos de consumo por que ninguém gostaria de adquirir um produto que necessitasse desligar e religar para que voltasse a funcionar corretamente. Estas características despertaram o interesse para utilização de J ava em outro ambiente que também necessitava de uma linguagem com este perfil: a Internet. A Internet também é um ambiente constituído por equipamentos de diferentes arquiteturas e necessita muito de uma linguagem que permita a construção de aplicativos seguros. Muitas pessoas argumentarão que estas características podem ser encontradas em outras linguagens e portanto isto não explica o súbito sucesso da linguagem. Podemos arriscar alguns palpites apesar de este ser um terreno um pouco pantanoso para se aventurar, até por que as linguagens de programação tendem assumir um caráter quase religioso. Uma das razões que na nossa opinião favoreceram a rápida adoção da linguagem foi a sintaxe. J ava é sintaticamente muito semelhante à linguagem C/C++, apesar de existirem diferenças fundamentais na filosofia de implementação entre as duas linguagens. Isto facilitou a migração de uma legião imensa de programadores C/C++para a nova linguagem. Outra razão que não pode ser desprezada é o momento atual onde os desenvolvedores estão ansiosos para se libertarem de sistemas proprietários. Portanto, apesar de não serem novas as idéias embutidas na linguagem J ava, a reunião delas em uma só linguagem, juntamente com a facilidade migração dos programadores e o momento atual, contribuíram para o rápido sucesso da linguagem. Hoje, segundo a International Data Corp. (IDC), existem mais de 2 milhões de programadores J ava no mundo e a estimativa é que o número de desenvolvedores ultrapasse os 5 milhões em 2004. O número de programadores J ava deve ultrapassar o de programadores C++ainda este ano (2002), segundo a consultoria americana Evans Data. Os profissionais que dominam a linguagem J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 7 estão entre os mais bem pagos da área de Tecnologia da Informação (TI), com salários variando de 3 a 10 mil reais, podendo em alguns casos chegar à 16 mil reais, segundo a revista Info Exame (dezembro de 2001). A lista abaixo apresenta as principais características de J ava de modo que o leitor tenha uma visão geral da linguagem: • Orientação a objetos. J ava não é uma linguagem totalmente orientada a objetos como Smalltalk, onde tudo é objeto ou métodos de objetos. Por questões de eficiência foram mantidos alguns tipos primitivos e suas operações. No entanto, J ava possui um grau de orientação a objetos bem maior que C/C++, o que a torna bem mais harmoniosa e fácil de assimilar, uma vez que o programador tenha compreendido esta forma de desenvolvimento. • Compilação do código fonte para código de uma máquina virtual (Bytecodes). Esta característica visa tornar a linguagem independente de plataforma de Hardware e Sistema Operacional. Obviamente é necessário que exista um programa capaz de interpretar o código em Bytecodes para cada Sistema Operacional, denominado de Máquina Virtual. No entanto, nada impede que o código fonte seja traduzido diretamente para o código executável na máquina de destino. J á existem ambientes de desenvolvimento que apresentam este tipo de opção. Alternativamente, é possível projetar equipamentos que processem em hardware os Bytecodes. A Sun desenvolveu um processador que executa operações em Bytecodes, denominado de J avaChip. O diagrama abaixo ilustra as etapas envolvidas na execução de um código J ava. Figura I-1. Fases para execução de um programa fonte em Java • Ausência de manipulação explícita de ponteiros. Em linguagens como C/C++e Pascal existe o tipo ponteiro como tipo primitivo da linguagem. A especificação original de Pascal é restritiva no uso de ponteiros, permitindo que sejam usados apenas para referenciar memória obtida na área de alocação dinâmica (heap) e não permite que o programador examine o valor J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 8 da variável do tipo ponteiro, nem que realize operações aritméticas com ponteiros. J á a linguagem C/C++ permite que o valor armazenado na variável do tipo ponteiro faça referência a qualquer área de memória, inclusive à área estática e automática (pilha), além de permitir aritmética de ponteiros e o exame direto do valor armazenado. A manipulação do tipo ponteiro exige uma grande dose de atenção por parte do programador e mesmo programadores experientes frequentemente cometem erros no seu uso. Além disso, o uso de ponteiros é uma fonte de insegurança na linguagem, uma vez que permite que o usuário faça acesso a memória que pode pertencer a outros processos, abrindo a possibilidade para desenvolvimento de programas hostis ao sistema. A linguagem J ava não possui o tipo ponteiro. Isto não que dizer que não seja possível realizar alocação dinâmica de memória. Todo objeto criado é alocado na área de heap, mas o usuário não pode manipular a referência ao objeto explicitamente. • Recuperação automática de memória não utilizada (Coleta de Lixo – Garbage Collection). Nas linguagens onde existe alocação dinâmica de memória, o programador é responsável pela liberação de memória previamente obtida na área de alocação dinâmica e que não está sendo mais utilizada. Se houver falhas na execução desta responsabilidade ocorrerá o problema que é conhecido sob a denominação de “vazamento de memória”. Este problema faz com que a partir de certo ponto o programa não consiga obter memória para criação de novos objetos, apesar de existir área que não está sendo mais usada mas que não foi devolvida ao gerente de memória. Outro erro comum é a tentativa de acesso á áreas de memória já liberadas. Todos os programadores que trabalham com linguagens que permitem alocação dinâmica conhecem bem estes problemas e sabem o quanto é difícil implementar programas que não possuam estes tipos de erros. A maior parte dos erros que ocorrem no uso destas linguagens é devido a problemas na alocação/liberação de memória. Visando o desenvolvimento de aplicações robustas, livres deste tipo de falha, os projetistas de J ava incorporaram um procedimento de coleta automática de lixo à máquina virtual. Deste modo, os objetos que não estão sendo mais usados são identificados pelo procedimento, que libera a memória para ser utilizada na criação de novos objetos. • Segurança. As pessoas costumam dizer que J ava é uma linguagem segura. Mas o que é ser uma linguagem de programação segura? Segurança possui significados distintos para pessoas diferentes. No caso da linguagem J ava na J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 9 versão 1.0 segurança significa impedir que programas hostis que possam causar danos ao ambiente computacional, ou que busquem informações sigilosas em computadores remotos para uso não autorizado. Na versão 1.1 foi adicionada a capacidade de permitir a verificação da identidade dos programas (autenticação) e na versão 1.2 os dados que os programas enviam e recebem podem ser criptografados por meio do uso de um pacote adicional. Na versão 1.4 o pacote de criptografia J CE (J ava TM Cryptography Extension) foi incorporado ao J 2SDK. • Suporte à Concorrência. A construção de servidores, a criação de programas com interfaces gráficas, e programas semelhantes que tem em comum a necessidade de que o atendimento de uma solicitação não incapacite o sistema de responder a outras solicitações concorrentemente, demandam o uso de uma linguagem que facilite o desenvolvimento deste tipo de programa. As linguagens projetadas antes do surgimento destas necessidades, como C/C++, não previam facilidades para este tipo de programação, o que obrigou a incorporação destes recursos posteriormente, por meio de funções adicionais. Como a programação concorrente é uma forma de programação que difere bastante da programação sequencial convencional, a simples adição de novas funções para tentar adaptar a linguagem a esta forma de codificação, não cria um ajuste perfeito com a linguagem subjacente. Por outro lado, J ava foi projetada visando facilitar a programação concorrente. Isto faz com que a criação linhas de execução (threads) seja bem mais natural dos que nas linguagens tradicionais.Programação em rede. J ava possui em seu núcleo básico classes para comunicação em rede por meio dos protocolos pertencentes à pilha de protocolos TCP/IP. A pilha de protocolos TCP/IP é a utilizada pela Internet e tornou-se o padrão de fato para comunicação entre computadores em uma rede heterogênea. Isto torna J ava particularmente atrativa para o desenvolvimento de aplicações na Internet. Além disso J ava está incorpora um amplo de conjunto de soluções para computação distribuída, como CORBA (Common Object Request Broker Architecture), RMI (Remote Method Invocation) e Servlets/J SP (aplicações J ava que são executadas por servidores Web). Após o lançamento da versão beta da linguagem em 1995, a Sun tem liberado diversas evoluções da linguagem na forma de versões e releases de um conjunto de ferramentas denominado de J ava Development Kit (J DK) até a versão 1.2, quando se passou a denominar J ava 2 SDK (Standard Development Kit). Isto ocorreu porque outros kits de desenvolvimento com propósitos J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 10 específicos foram lançados, como o J 2EE (J ava 2 Enterprise Edition), voltado para aplicações distribuídas escaláveis e o J 2ME (J ava 2 Micro Edition), voltado para aplicações embutidas em dispositivos eletrônicos (Celulares, handheld, etc.). Durante a elaboração deste livro, a última versão estável do SDK era a de número 1.3.1 que pode ser obtida gratuitamente no site http://java.sun.com/. Convenções As seguintes convenções são usadas neste livro. 1. Fontes com larguras constantes são usadas em: • exemplos de código public class Ponto { private int x,y; } • nomes de métodos, classes e variáveis mencionadas no texto. 2. Fontes com larguras constantes em negrito são usadas dentro de exemplos de códigos para destacar palavras chave. 3. Fontes em itálico são usadas: • em termos estrangeiros; • na primeira vez que for usado um termo cujo significado não for conhecimento generalizado. J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 11 Capítulo II - Programação Orientada a Objetos O mundo pode ser visto como um conjunto de objetos que se relacionam. Por exemplo, uma pessoa, uma casa, uma cadeira da casa, etc. Os objetos não são necessariamente físicos. Podem possuir uma natureza abstrata, como um evento (uma partida de futebol) ou algo inexistente no mundo real (elefante cor- de-rosa). Na verdade, o conceito de objeto atua no nível lógico e não no real. Se iremos representar algo como objeto ou não depende apenas de uma decisão a nível lógico que pode facilitar a simulação de determinado aspecto da realidade. Os objetos se agrupam em classes, segundo propriedades ou atributos comuns. Por exemplo, a classe dos retângulos agrupa todas as formas geométricas com a propriedade de possuir quatro lados formando ângulos de 90 o . A relação entre um objeto e uma classe é de pertinência. Dizemos que um objeto pertence a uma classe ou, mais comumente, que é uma instância de uma classe. O Figura abaixo ilustra exemplos de classes. Figura II-1. Classe dos (a) retângulos e dos (b) dos triângulos. As classes podem ser relacionar com outra classe no sentido que uma classe pode conter outra. Por exemplo, a classe de retângulos está inserida em uma classe mais genérica, a classe dos polígonos. A classe mais genérica é denominada de Superclasse e as classes mais específicas são denominadas de J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 12 Subclasses. As subclasses herdam as propriedades das superclasses. No nosso exemplo, os polígonos possuem as propriedades de ter uma área, uma posição no plano, um número n de vértices e n-1 ângulos. Todas essas propriedades são herdadas tanto pela classe dos retângulos como pela classe do triângulos. Podemos desta forma organizar os objetos em uma hierarquia onde as classes mais específicas herdam as propriedades das classes mais genéricas. Figura II-2. Classe dos polígonos. Os objetos de uma classe possuem comportamentos que podem alterar o valor de suas propriedades. Por exemplo, um carro pode sofrer uma aceleração ou ser freado e com isso alterar a sua velocidade. Um objeto qualquer pode ser deslocado, alterando assim as suas coordenadas no espaço. Classes e Objetos e Linguagens de Programação As linguagens de programação são utilizadas para construir simulações de aspectos da realidade no computador. Quanto mais facilmente pudermos expressar os conceitos capturados da realidade, mais facilmente construiremos a simulação. Seguindo este raciocínio podemos concluir que as linguagens que possuem facilidades para representação de objetos permitem uma modelagem mais fácil dos conceitos do mundo real. No entanto, podemos utilizar uma linguagem de programação convencional para modelar as classes e objetos abstraídos da realidade. Por exemplo, podemos modelar a classe dos retângulos por meio de um registro (record) em Pascal. J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 13 type Classe_Retangulo = record X1,Y1,X2,Y2: integer; end; Exemplo II-1. Representação da Classe Retângulo em Pascal. No Exemplo II-1 o retângulo é definido por dois pontos, sendo X1 e Y1 o ponto superior esquerdo e X2 e Y2 o ponto inferior direito. Os objetos podem ser representados por meio de variáveis do tipo definido: type Classe_Retangulo = record X1,Y1,X2,Y2: integer; end; var Retangulo1 : Classe_Retangulo; Exemplo II-2. Criação de objetos em Pascal. procedure intRetangulo(XA,YA,XB,YB: integer; var R: Classe_Retangulo); begin R.X1 := XA; R.Y1 := YA; R.X2 := XB; R.Y2 := YB; end; procedure MudaPos(X,Y: integer; var R: Classe_Retangulo); begin R.X2 := X+(R.X2-R.X1); R.Y2 = Y+(R.Y2-R.Y1); R.X1 = X; R.Y1 = Y; end; Exemplo II-3. Definição das operações em Pascal. J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 14 As propriedades dos objetos podem ser manipuladas através de funções e procedimentos. Por exemplo, podemos ter uma operação para inicializar os valores da estrutura e outra para alterar a posição do retângulo. O Exemplo II-3 mostra essas duas operações. Contudo, existem algumas limitações das linguagens convencionais que as tornam inadequadas para a modelagem de objetos: • Não existem recursos para ocultar a estrutura de dados de procedimentos que não foram projetados para a sua manipulação. É muito importante que a linguagem forneça recursos para se implementar este tipo de isolamento. Se acessarmos uma estrutura apenas por meio dos procedimentos projetados para este fim, quando a estrutura for alterada apenas os procedimentos que manipulam a estrutura sofreriam modificações. No entanto, se não agirmos desta forma será necessário procurar em todo o programa os acessos diretos à estrutura. Claro que este comportamento pode ser adotado em qualquer linguagem, mas é mais seguro se a linguagem fornece meios para o programador forçar este tipo de comportamento. A capacidade de “esconder” a estrutura de dados de acessos diretos é chamada de ocultação de informação. • Não existem recursos para herança de propriedades entre classes e subclasses. Se precisarmos implementar uma estrutura que é uma especialização de outra já implementada, será preciso codificar novamente todas as propriedades, mesmo as comuns, e todos os procedimentos de acesso. Isto dificulta o reaproveitamento de código, o que, consequentemente aumenta o tempo de desenvolvimento e a possibilidade de erros. • Não existe uma forma de relacionar explicitamente as estruturas de dados com os procedimentos que as manipulam. O relacionamento entre os procedimentos que manipulam uma estrutura de dados e a estrutura é estabelecido implicitamente, por meio de alguma convenção definida pelo programador. É importante que a linguagem obrigue o programador relacionar explicitamente os procedimentos com a estrutura de dados, de modo que fique claro qual é a interface de acesso ao objeto. A adoção de uma linguagem programação orientada a objetos resolve todos esses problemas. Existem várias linguagens comerciais com esta característica: Smalltalk, Eiffel, C++, etc. Algumas com elementos não orientados a objetos como C++, outras puramente orientadas a objetos como Smalltalk, onde tudo é J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 15 objeto. J ava é uma linguagem orientada a objetos mais “pura” do que C++, fugindo desta orientação apenas em alguns pontos bem definidos, em nome da eficiência de execução. Em J ava podemos representar diretamente as classes e objetos. Por exemplo, a classe retângulo seria declarada da seguinte forma: class Retangulo { int X1,Y1,X2,Y2; public Retangulo(int XA, int YA, int XB, int YB) { X1 = XA; Y1 = YA; X2 = XB; Y2 = YB; } void MudaPos(int X, int Y) { X2 = X+(X2-X1); Y2 = Y+(Y2-Y1); X1 = X; Y1 = Y; }; } Exemplo II-4.Representação da Classe Retângulo em Java. No Exemplo II-4 mudamos o nome do procedimento iniRetangulo para Retangulo, que é o mesmo nome da classe. No momento não é importante entendermos a razão desta mudança, que será esclarecida no próximo capítulo. Note que os procedimentos são declarados dentro do corpo da classe, tornando explícito relacionamento entre a classe e os procedimento. As funções declaradas nas classes são chamadas de métodos e a partir de agora nos referenciaremos a eles como tal. Note também que diferentemente do exemplo em Pascal, não é preciso passar o objeto como parâmetro, uma vez que as variáveis que estão sendo modificadas pertencem ao objeto corrente, ao qual está associado o método. É como se para cada objeto de uma classe fossem criadas versões de todos os métodos da classe, de modo que cada método só opera sobre as variáveis do objeto a quem pertencem. Para declarar uma variável do tipo da classe basta preceder a variável com o nome da classe. Retangulo ret; J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 16 Até esse momento nenhum objeto foi criado. Para criar um objeto (instância) é usado o operador new. ret = new Retangulo(10,10,20,30); Note que o operador new é seguido de uma chamada ao método com o mesmo nome da classe. O métodos com esta característica são chamados de construtores e só podem ser invocados durante a criação de um objeto. Como veremos mais tarde, uma classe pode ter mais de um construtor. Após a criação do objeto é possível acessar os outros métodos do objeto através do operador “.”. Por exemplo, podemos mudar a posição do objeto por meio do método MudaPos. ret.MudaPos(40,40); Como já dissemos, não é preciso passar o objeto como argumento, já que é criada uma cópia do método para cada objeto. A grosso modo podemos dizer que cada instância da classe recebe uma cópia da variáveis e dos métodos da classe. Ocultando de Informação O projetista cuidadoso deve ocultar a representação interna da classe, permitindo o acesso aos atributos da classe via métodos predefinidos. Desta forma a representação interna fica isolada do restante do programa e fica mais fácil alterá-la sem que seja preciso alterar outras partes do código. A ocultação de informação é obtida por meio de qualificadores, como o private, que impede o acesso à variáveis via métodos definidos em outras classes. O nível de ocultação depende do qualificador utilizado. Todos os qualificadores serão abordados com detalhes no Capítulo IV. O exemplo II-5 mostra como impedir que as variáveis declaradas na classe Retangulo sejam acessadas diretamente. class Retangulo { private int X1,Y1,X2,Y2; public Retangulo(int XA, int YA, int XB, int YB) { X1 = XA; Y1 = YA; J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 17 X2 = XB; Y2 = YB; } void MudaPos(int X, int Y) { X2 = X+(X2-X1); Y2 = Y+(Y2-Y1); X1 = X; Y1 = Y; }; } Exemplo II-5. Ocultando informação em Java. Especialização e Herança Para criar uma subclasse de uma classe pré-existente utilizamos o operador extends. Por exemplo, podemos definir uma subclasse da classe Retangulo, chamada de RetanguloColorido, que possui, além das variáveis e métodos herdados da superclasse, uma variável para armazenar a cor do retângulo, juntamente com um método para alterar o valor. class RetanguloColorido extends Retangulo { private Color Cor; void AtribuiCor(Color C) { Cor = C; }; } Exemplo II-6. Declarando subclasses em Java. A princípio, subclasse pode acessar todos os métodos e variáveis da superclasse. No entanto, isto também pode ser alterado via qualificadores. J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 18 Sobrescrita, Sobrecarga e Polimorfismo Podemos definir mais de um método com o mesmo nome na mesma classe ou subclasses. Caso o método possua a mesma assinatura (número e tipos de argumentos e tipo de retorno) que outro método, então o método não pode pertencer à mesma classe do anterior. Se ambos os métodos estiverem na mesma linha hierárquica (classe/subclasse), dizemos que o método da subclasse sobrescreve o método da superclasse. O método que será executado dependerá da classe do objeto. class Empregado { protected float salario; public float getSalario() {return salario;} } class Vendedor extends Empregado { protected float comissao; public float getSalario() {return salario+comissao;} } Exemplo II-7. Sobrescrita do método getSalario(). No exemplo II-7 o método getSalario() da classe Vendedor sobrescreve o método do mesmo nome da classe Empregado. Se a assinatura do método for diferente de outro método com o mesmo nome definido anteriormente na mesma classe ou em outra classe da mesma linha hierárquica, então estamos realizando uma sobrecarga sobre o identificador do método. Quando for usado o identificador dentro do código de um programa o método invocado será determinado pela classe a que pertence o objeto do método e pelo número e tipos dos argumentos passados para o método. O termo sobrecarga advém do fato de um mesmo identificador denotar mais de método. class Empregado { protected float salario; public void aumento() {salario= salario*10.0;} public void aumento(float porcent) J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 19 { salario= salario*porcent; } } Exemplo II-8. Sobrecarga do método aumento(). No exemplo II-8 o identificador aumento pode referenciar dois métodos distintos. Um aumenta o salário em 10% e no outro o aumento depende do valor da porcentagem passado como parâmetro. Note que as assinaturas do métodos diferem entre si. Alguns autores chamam sobrecarga de polimorfismo, que é a habilidade de um determinado objeto se comportar ou ser visto de diferentes formas, quando na verdade a sobrecarga é um tipo particular de polimorfismo, chamado de polimorfismo ad hoc . Na sobrecarga um identificador representa vários métodos com computações distintas. Existe também o polimorfismo paramétrico, onde um método pode realizar a mesma computação sobre objetos de tipos distintos. Isso pode ser implementado em J ava definindo um método que recebe e retorna objetos da classe Object. Como a classe Object é a “mãe de todas as classes” o método pode operar da mesma forma independente da classe a qual o objeto realmente pertence, desde que a computação seja independente da classe. class Poli { public Object identidade(Object objeto) { return Object; } } Exemplo II-9. Polimorfismo paramétrico. No Exemplo II-9 o método identidade() retorna o objeto passado como parâmetro. Este método realiza a mesma computação, independentemente da classe do objeto. Obviamente é um exemplo muito simples e sem utilidade prática mas serve para ilustrar o conceito de polimorfismo paramétrico. J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 20 Introdução à Diagrama de Classes É possível registrar diretamente em uma linguagem de programação os objetos percebidos em uma determinada realidade. No entanto, é melhor utilizarmos uma notação gráfica intermediária para melhor visualizarmos os objetos e as relações entre objetos, e ir alterando esta representação até estarmos seguros que possuímos um entendimento razoável do problema, e ai sim, partirmos para a codificação da solução. Muitas vezes é preciso recorrer a mais de uma notação gráfica, de modo a expressar várias facetas da realidade que está sendo modelada. Neste livro, recorreremos ao uso de notação gráfica em alguns exemplos para ilustrarmos a arquitetura dos programas antes de apresentarmos o código. Acreditamos que desta forma o leitor compreenderá melhor os exemplos. A notação gráfica que adotamos mostra as relações estáticas entre classes de objetos. Ela faz parte do conjunto de notações da UML (Unified Modeling Language ou Linguagem de Modelagem Unificada) proposta por Grady Booch, J ames Rumbaugh e Ivar J acobson em 1995. A UML é um conjunto de notações que tem por objetivo modelar diversos aspectos de um sistema em diferentes níveis de abstração. Ou seja, pode ser utilizado para a captura de requisitos de um sistema assim como em projeto de programas. É voltada para análise e projeto de sistemas orientados a objetos. A área de análise e projeto orientados a objetos ainda não possui uma notação “vencedora” como existe para a análise e projeto estruturado. Contudo, a UML vem se popularizando rapidamente, e é encontrada com facilidade em textos de programação, sobretudo em se tratando de J ava. Portanto, um conhecimento sobre as principais notações que constituem a UML é importante para qualquer um que deseja ingressar na área de programação orientada a objetos. Este livro utiliza uma das linguagens, ou diagramas, que compõem a UML: o diagrama de classes. Diagrama de Classes O Diagrama de Classes representa graficamente as classes do sistema e o relacionamento estático entre as classes, isto é, o relacionamento que não muda com o tempo. Por exemplo, em um sistema acadêmico, um aluno cursa várias disciplinas. O número de disciplinas e a disciplina que efetivamente está sendo cursada pode alterar, mas o vínculo aluno-cursa-displinas permanece. Para ilustrar o nosso estudo dos diagramas da UML utilizaremos exemplos sobre modelagem de aspectos realidade acadêmica. J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 21 Uma classe é representada no diagrama de classes por meio de um retângulo, que pode ser dividido em até três seções horizontais, como mostrado na figura abaixo: Figura II-3. Forma geral para representação de uma classe. A seção superior é usada para registrar o nome da classe. A seção intermediária é reservada para registro das propriedades da classe, caso existam, e na seção inferior é registrado a assinatura dos métodos que pertencem à classe, caso existam. Por assinatura do método queremos dizer o nome do método, juntamente com seus argumentos e valor de retorno. A figura abaixo mostra uma representação da classe das disciplinas. Figura II-4. Representação da classe das disciplinas De modo geral, por razões de simplicidade, não se representam os métodos que tratam da alteração dos atributos e construtores. Se Assumi que toda classe possui estes métodos. Portanto, podemos simplificar a representação acima, omitindo a seção do métodos. J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 22 Figura II-5. Representação simplificada da classe das disciplinas. Podemos indicar tanto nos atributos quanto nas classes a visibilidade deles em relação a outras classes. As visibilidades possíveis, juntamente com os símbolos adotados estão listados na tabela abaixo: Visibilidade Símbolo Descrição Pública + Sem restrição de acesso. Protegida # Pode ser acessado apenas na própria classe e por subclasses. Privada - Pode ser acessado apenas na própria classe. Tabela II-1. Visibilidades possíveis para atributos e métodos. A visibilidade é atribuída a um atributo ou método precedendo a declaração do método com o símbolo adequado, como na figura abaixo: Figura II-6. Representação com visibilidade. J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 23 As classes podem se relacionar de diversas formas: por associação comum, por agregação e generalização. Abaixo é apresentada cada forma de relacionamento, juntamente com suas as notações. Associação comum A notação utilizada para associar duas classes é simplesmente uma linha unindo as classes. A figura II.7 mostra a associação entre a classe dos alunos e a classe das disciplinas. Figura II-7. Associação entre Aluno e Disciplina. A figura acima expressa que alunos se associam com disciplinas mas não indica se um aluno se relaciona com várias ou apenas uma disciplina. Esta informação é chamada de cardinalidade da relação e é expressa anotando-se o valor da cardinalidade na associação junto à classe que está sendo relacionada. Assim, a figura II.8 expressa que um aluno se relaciona com várias disciplinas. Figura II-8. Associação de um Aluno com várias Disciplinas. Como uma disciplina se relaciona com vários alunos, o diagrama completo é o representado na figura II-9. Figura II-9. Associação de vários Aluno com várias Disciplinas. A tabela II-2 mostra algumas representações de cardinalidade: J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 24 Notação Descrição 1 Exatamente um * ou 0..* Zero ou mais 0..1 Opcional (zero ou um) n..m Máximo e mínimo Tabela II-2. Representações de cardinalidade. Até agora apresentamos apenas associações entre duas classes, mas nada impede que mais de duas classes participem de uma associação. Por exemplo, a figura II-10 ilustra uma associação ternária que representa o fato de um aluno cursar uma disciplina em um período. Figura II-10. Associação entre aluno, disciplina e período. Uma associação pode ter atributos próprios. Ou seja, atributos que não pertençam a nenhuma das classes envolvidas na associação mas sim à própria associação. Na associação entre alunos e disciplina o atributo nota, não pertence a aluno, tampouco à disciplina, uma vez que para saber uma nota é preciso saber quem é o aluno e qual é a disciplina. A representação de atributos da associação é representada por meio de um retângulo ligado à associação por meio de uma linha tracejada. J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 25 Figura II-11. Representação de atributos de associação. Agregação Alguns objetos são compostos por outros objetos. Por exemplo, um carro é composto por chassi, lataria, pneus e motor, que por sua vez é composto pelo carburador, pistões, bloco, etc. Este tipo de associação é representada por uma linha com um losango na ponta. Figura II-12. Agregação entre Curso e Disciplina. Generalização O último tipo de associação entre classes é o que o ocorre entre superclasses e subclasses. Uma superclasse é uma generalização das suas subclasses, que herdam os atributos e métodos da primeira. A notação utilizada para representar a generalização é uma linha com um triângulo na extremidade da associação no lado da classe mais genérica. J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 26 Figura II-13. Representação da generalização. A figura II-14 procura representar todas associações discutidas em um único diagrama. Figura II-14. Associação entre as classes do domínio acadêmico. J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 27 Capítulo III - Introdução à Linguagem Java Existe uma tradição entre os programadores que estabelece que ao se começar a aprender uma nova linguagem de programação, o primeiro programa a ser escrito deve ser um que imprima a frase “Olá mundo” em um dispositivo de saída. Dizem que isto atrai a sorte e espanta os bugs. Independente da crença geral, existem algumas razões bastante justificáveis para se começar o aprendizado de uma linguagem executando logo um programa, mesmo sem ter muita idéia do que se está fazendo. Primeiramente, existe o fator psicológico. Iniciar o aprendizado executando um programa sem erros, aumenta confiança do aluno e elimina temores de se estar aprendendo algo muito complexo. Existe também o fato de que apesar de um programa muito simples dar uma visão um pouco limitada da linguagem, já é possível observar alguns elementos importantes. Afinal trata-se de um programa completo. Portanto, para não fugir a tradição, eis o programa OlaMundo em J ava: public class OlaMundo { public void exibeOla() { System.out.println(“Ola, Mundo!”); } public static void main(String args[]) { OlaMundo obj = new OlaMundo(); Obj.exibeOla(); } } Exemplo III-1. Programa OlaMundo. O programa acima é composto por uma única classe que possui apenas dois métodos. Isto é importante, porque não é possível fazer um programa J ava sem recorrer às classes, uma vez que os procedimentos são definidos como métodos de classes. Isto não é verdade em linguagens como C++, o que diminui o seu “grau” de orientação a objetos. J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 28 Os métodos com o nome main são métodos especiais e servem como ponto inicial para execução do programa. Ou seja, a execução do programa será iniciada a partir da execução de um método main(). A assinatura do método main é sempre a mesma e a sua descrição em detalhes será vista na seção Argumentos da linha de comando. Podemos adiantar apenas que o qualificador public estabelece que este método pode ser chamado por métodos ou procedimentos externos à classe. O qualificador static significa que o método pertence à classe e não às instâncias da classe, e deste modo pode ser invocado mesmo antes de ser criado algum objeto para a classe. void indica que o método não retornará valor algum. J á argumento String args[] é um array de Strings contendo os parâmetros passados na linha de comando. O corpo do método main() possui duas linhas. A primeira instrução cria um objeto da classe OlaMundo e o atribui à variável obj. A segunda linha invoca o método exibeOla() do objeto recém criado. O método exibeOla() invoca o método println() do objeto out da classe System, que faz parte do pacote de classes fornecido com a linguagem. Este método exibe no dispositivo de saída padrão a String que é passada como argumento. Se você olhar os programas “OlaMundo” mostrados em outros livros sobre J ava irá notar que o programa apresentado aqui é um pouco mais complicado que o exibido nesses livros. Geralmente este programa inicial é apresentado apenas com um método: o método main(). A razão de termos usado uma abordagem diferente é que desejamos desenvolver um hábito saudável na programação em J ava: procure usar o método main() apenas para criar os objetos e deixe que os objetos executem a lógica do problema. Este programa deve ser salvo em um arquivo contendo O MESMO NOME DA CLASSE e com as mesmas letras maiúsculas e minúsculas (é importante frisar, uma vez que esquecer este detalhe é um erro muito comum), e com a extensão “.java”. Portanto o arquivo contendo o programa acima deverá se chamar OlaMundo.java. Se você instalou o J ava 2 development kit (SDK), que pode ser obtido gratuitamente no site http://java.sun.com/products/, então para compilar o programa basta digitar o comando: javac OlaMundo.java O código J ava é traduzido (o termo mais comum é compilado) para instruções de uma máquina virtual (bytecodes) para que possa ser executado de forma independente da plataforma (sistema operacional e hardware). O código em bytecodes é armazenado em um arquivo com o mesmo nome do original e com a extensão “.class”. Assim após a execução do comando acima será J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 29 gerado o arquivo OlaMundo.class. A invocação da máquina virtual para executar o código em bytecodes é efetuada por meio do seguinte comando: java OlaMundo Neste ponto nota-se uma desagradável idiossincrasia do SDK. Para compilar o programa para bytecode foi necessário explicitar a extensão do arquivo, porém, para executar o código é preciso omitir a extensão “.class”. Apesar de ser um detalhe que aparentemente não prejudica o uso do ambiente, confunde o usuário, principalmente o iniciante. Quando recebe um arquivo de bytecodes para a executar, a máquina virtual procura o método main() em uma classe com o mesmo nome do arquivo. Uma vez encontrado o método a execução é iniciada com a execução do método. Isto significa que em nosso exemplo será procurado o método main() da classe OlaMundo para servir de ponto de entrada. Podem existir outros métodos main() em outras classes codificadas no mesmo arquivo de bytecodes, mas apenas o método main() da classe com o mesmo nome do arquivo servirá de ponto inicial de execução. Portanto, em nosso caso, o programa será executado produzindo a saída: Ola, Mundo! Agora que já cumprimos o ritual de iniciação na linguagem, podemos passar a descrever os detalhes da linguagem. Grande parte das descrições que seguem abaixo foram baseadas na especificação da linguagem registrada no livro The Java Language Specification, segunda edição, por J ames Gosling e outros. Identificadores Todos os identificadores da linguagem devem iniciar com uma letra, ou o caractere ´_`, ou o caractere ´$`. Deve-se evitar o uso do caractere ´$`, de modo que fique reservado para geração de código automático. Tratamos por letra todo caractere reconhecido pelo método Character.isJavaLetter. Isto inclui uma ampla gama de caracteres do conjunto Unicode 1 , de modo que os programadores podem usar identificadores adaptados a uma ampla gama de idiomas. Após o 1 O conjunto de caracteres Unicode foi criado para substituir o conjunto ASCII. Ele usa 16 bits para representar os caracteres, o que resulta em 65536 caracteres possíveis, em oposição aos 7 bits do código ASCII (8 para o ASCII estendido), o que resulta em 128 caracteres possíveis (256 para 8 bits). J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 30 primeiro caractere, também podem ser usados os caracteres que vão de ´0` até ´9`. A linguagem J ava distingue as letras maiúsculas das minúsculas, portanto o identificador Aluno é distinto do identificador aluno. A tabela III-1 mostra alguns identificadores válidos e inválidos. Válido Inválido Aluno10 Aluno#10 Num_Alunos Num Alunos _disciplina$ !disciplina Professor_10 10Professor αβγ &uuu Não Não? Tabela III-1. Identificadores válidos e inválidos em Java. Palavras Reservadas As seguintes sequências de caracteres são reservadas para o uso como palavras chave e, portanto, não podem ser usadas como identificadores: abstract continue goto package synchronized assert 2 default if private this boolean do implements protected throw break double import public throws byte else instanceof return transient case extends int short try catch final interface static void char finally long strictfp 3 volatile class float native super while const for new switch Tabela III-2. Palavras reservadas da linguagem Java. As palavras chave const e goto, apesar de serem reservadas, não estão sendo usadas correntemente na linguagem. 2 Introduzida a partir da versão 1.4 do SDK. 3 Introduzida a partir da versão 1.2 do SDK J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 31 Literais Literais são elementos do código que representam um valor de tipo primitivo, tipo String ou null. Os literais podem ser numéricos, booleanos, caracteres ou cadeias de caracteres (Strings). Literais numéricos incluem inteiros, ponto flutuante. Literais Inteiros Um literal inteiro é do tipo primitivo long (longo) se possui o sufixo ´L` ou ´l`, caso contrário é do tipo primitivo int (inteiro). Um numeral hexadecimal é prefixado pelos caracteres ´0x` ou ´0X` seguidos de um ou mais dígitos hexadecimais. Os dígitos hexadecimais com valores entre 10 e 15 são representados pela letras ´a` até ´f` ou ´A` até ´F`, nessa ordem. Um numeral octal consiste de um dígito 0 seguido de um ou mais dígitos de 0 até 7. O maior literal decimal do tipo int é 2147483648. Os literais decimais de 0 até 2147483647 podem aparecer em qualquer lugar que um literal inteiro pode aparecer, mas o literal 2147483648 pode aparecer somente como operando de uma negação unária. Os maiores literais hexadecimal e octal positivos inteiros são 0x7fffffff e 017777777777, respectivamente, que correspondem ao valor decimal 2147483647. Os maiores literais hexadecimal e octal negativos inteiros são 0x80000000 e 020000000000, respectivamente, que representam o valor decimal -2147483648 (). Os literais hexadecimal e octal 0xffffffff e 037777777777, representam o valor decimal -1. Abaixo estão listados alguns exemplos de literais inteiros: 0 -12 0372 0xCafe 1999 0x00FF00FF O maior literal decimal do tipo longo é o 9223372036854775808L. Os literais decimais de 0L até 9223372036854775807L podem aparecer em qualquer lugar que um literal inteiro longo pode aparecer, porém o literal 9223372036854775808L pode aparecer somente como operando de uma negação unária. Os maiores literais hexadecimal e octal positivos inteiros longos são 0x7fffffffffffffffL e 0777777777777777777777L, respectivamente, que correspondem ao valor decimal 9223372036854775807L.Os maiores literais J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 32 hexadecimal e octal negativos inteiros longos são 0x8000000000000000L e 01000000000000000000000L, respectivamente, que representam o valor decimal -9223372036854775808L. Os literais hexadecimal e octal 0xffffffffffffffffL e 01777777777777777777777L, representam o valor decimal - 1. Abaixo estão listados alguns exemplos de literais inteiros longos: 0L 0777L 0xC0B0L 0xCafe 1999 0x100000000L Literais de Ponto Flutuante Um literal de ponto flutuante é composto por uma parte inteira seguida de um ponto decimal, uma parte fracionária, um expoente e um sufixo determinando o tipo. O expoente, se presente, é indicado pela letra ‘E’ ou ‘e’, seguido por um inteiro com sinal um opcional. Pelo menos um dígito na parte inteira ou fracionária e o ponto decimal ou o expoente ou o sufixo indicando o tipo são exigidos. Todos os outros componentes são opcionais. Um tipo ponto flutuante é do tipo float se possuir o sufixo ‘F’ ou ‘f’, caso contrário é do tipo double. O tipo double possuir opcionalmente o sufixo ‘D’ ou ‘d’. O tipo float e double de J ava obedecem a especificação IEEE 754 para binários de ponto flutuante de precisão simples (32-bit) e dupla (64-bit). O maior literal positivo do tipo float é 3.40282347e+38f. O menor literal positivo do tipo float diferente de zero é 1.40239846e-45f. O maior literal positivo do tipo double é 1.79769313486231570e+308. O menor literal positivo do tipo double diferente de zero é 4.94065645841246544e-324. Um programa em J ava pode representar quantidades infinitas sem produzir erros de compilação por meio da utilização de expressões constantes tais como 1f/0f e -1d/0d ou pela utilização das constantes predefinidas POSITIVE_INFINITY e NEGATIVE_INFINITY das classes Float e Double. Exemplos de literais do tipo float: 1e1f 2.f .3f 0f 3.14f 6.022137e+23f Exemplos de literais do tipo double: 1e1 2. .3 0.0 3.14 1e-9d 1e137 J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 33 Literais Booleanos O tipo boolean possui dois valores, representados pelos literais true e false. Literais de Caracteres Um literal do tipo char é expresso como um caractere ou uma sequência de escape, envolvida por aspas simples. Os caracteres CR e LF nunca são caracteres de entrada, uma vez que são reconhecidos como terminadores de linha. Exemplos de literais do tipo char: Caractere Descrição 'a' o caractere a. '%' o caractere % '\n' line feed '\t' Tab '\\' o caractere \ '\'' o caractere '. '\u03a9' '\uFFFF' '\177' Tabela III-3. Exemplo de literais do tipo char. Literais de Cadeia de Caracteres (Strings) Um literal do tipo String consiste de zero ou mais caracteres envolvidos por aspas duplas. Cada caractere pode ser representado por uma sequência de escapes. O tipo String não é um tipo primitivo e sim uma classe denominada String. Portanto, um literal é na verdade uma instância da classe String. Uma String longa pode ser particionada em cadeias menores unidas pelo operador de concatenação ´+’. Exemplos de literais do tipo String: Literal Descrição "" Cadeia vazia. J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 34 "\"" Uma cadeia com apenas o caractere ". "esta é uma cadeia" Uma cadeia contendo 17 caracteres. "esta é uma " +"cadeia " Uma cadeia formada por dois literais do tipo String. Tabela III-4. Exemplo de literais do tipo String. Sequências de escape Sequências de escape permitem a representação de alguns caracteres não gráficos, assim como as aspas simples e duplas e a barra invertida. Exemplos de sequências de escape: Sequência Descrição \b \u0008: backspace BS \t \u0009: tab horizontal HT \n \u000a: linefeed LF \f \u000c: form feed FF \r \u000d: carriage return CR \" \u0022: aspas duplas " \' \u0027: aspas simples ' \\ \u005c: barra invertida \ Tabela III-5. Exemplo sequências de escape. O Literal null O tipo null possui apenas um valor, representado pelo literal null. Separadores J ava possui nove separadores (caracteres de pontuação), listados abaixo: ( ) { } [ ] ; , . J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 35 Tipos de dados Os tipos de dados definem como devem ser interpretados os dados armazenados. São essenciais a toda linguagem de programação e devem ser cuidadosamente selecionados para compor a linguagem, de modo a não limitar a sua área de atuação. A maioria da linguagens modernas definem um conjunto de tipos simples, um conjunto de tipos compostos, formados por tipos mais primitivos e alguma forma de definição de novos tipos, de modo que possa modelar mais apropriadamente a realidade. A linguagem J ava obedece esta regra, fornecendo um conjunto de tipos simples e um conjunto de tipos compostos. O mecanismo para definição de novos tipos utilizado por J ava é simplesmente a definição de classes. J ava é uma linguagem fortemente tipada. Isto significa que toda variável e expressão possui um tipo determinado em tempo de compilação. Os tipos primitivos disponíveis em J ava não são classes. Esta é uma das razões porque J ava não é uma linguagem 100% orientada a objetos. No entanto, para cada tipo primitivo existe uma classe correspondente onde são declarados um conjunto de métodos para a manipulação dos valores primitivos, focalizando principalmente na conversão de tipos. Estas classes estão agrupadas no pacote java.lang. Por exemplo, o tipo primitivo int é usado para expressar valores inteiros. A classe correspondente ao tipo int é a Integer. Nela estão declaradas variáveis públicas contendo o valor máximo e mínimo que uma variável do tipo inteiro pode armazenar e métodos para conversão para outros tipos. Tipos de dados simples Tipos de dados simples são aqueles que não podem ser divididos em tipos mais primitivos. Os tipos de dados simples de J ava podem ser divididos em inteiros, ponto flutuante, booleano e caractere. Para se definir uma variável de um determinado tipo basta preceder o nome da variável com o nome do tipo desejado, como na forma abaixo: <nome do tipo> <nome da variável>; Assim, para se declarar uma variável var1 do tipo inteiro basta a linha de código abaixo: int var1; J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 36 Podemos também declarar mais de uma variável em com o mesmo tipo, usando o caractere ‘,’ como separador. Portanto, a declaração int var1, var2; é equivalente às declarações int var1; int var2; J ava também permite que as variáveis sejam inicializadas durante a declaração. Assim, para inicializarmos a variável var1 com o valor 1 na declaração basta a linha de código abaixo: int var1=1; Segue abaixo as categorias de tipos de dados simples. Inteiros Nome Tamanho byte 8 bits short 16 bits int 32 bits long 64 bits Tabela III-6. Tipos inteiros. Ponto Flutuante Nome Tamanho float 32 bits double 64 bits Tabela III-7. Tipos de ponto flutuante. booleanos boolean {true,false} J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 37 Caractere char 16 bits Tipos de dados compostos Os tipos de dados compostos são constituídos por tipos mais simples. Todos os tipos compostos em J ava são Classes. Nesta seção trataremos duas classes de objetos muito usadas em J ava para criação de tipos compostos: os arrays (arranjos) e strings. Posteriormente abordaremos a declaração de objetos no caso geral e no Capítulo VI será apresentado o pacote de classes java.util que contém várias classes para agrupamento de objetos. Arrays Um array é um objeto, e como tal herda todos os métodos da classe Object. Um array contém um certo número de variáveis chamadas de componentes. Em J ava todo objeto é acessado indiretamente, via uma referência. Ou seja, não se cria uma variável do tipo de um objeto e sim uma variável que pode referenciar um objeto. Estamos falando de ponteiros, o que pode parecer contraditório, uma vez que tínhamos mencionado que J ava não possui ponteiros. Na verdade J ava acessa as instâncias de objeto por meio de ponteiros mas eles não estão disponíveis como tipo da linguagem e nem é possível que o programador os manipule diretamente. Pode parecer pouco importante para o programador saber que a variável não armazena o objeto diretamente e sim uma referência a um objeto, uma vez que ele não pode manipular ponteiros. No entanto, acreditamos que esta informação é importante para que o leitor possa entender as etapas para a criação de objetos. A primeira etapa é a declaração da variável para referenciar o objeto e a segunda etapa é a criação do objeto propriamente dito. Para se declarar uma variável para referenciar objetos do tipo array é usada a seguinte sintaxe: tipo identificador[]; tipo[] identificador; Exemplos int numeros[]; char[] letras; J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 38 long grade[][]; Note que não é definido o número de elementos do array. Isto faz parte do objeto. Note também que existem duas formas de declarações de referências a arrays. O número de “[]” indica o número de dimensões do array. A criação do objeto é realizada por meio do operador new, seguido pelo tipo do array e pelo número de componentes de cada dimensão, como nos exemplos abaixo: numeros = new int[10]; char alfabeto[] = new char[26]; grade = new long[10][10]; Alternativamente podemos realizar as duas etapas acima e ainda definir os elementos do array em uma única declaração como no exemplo abaixo, onde é criado um array de três inteiros, referenciados pela variável primos e onde o primeiro elemento é 7, o segundo é 11 e o terceiro é 13. int primos = {7, 11, 13}; De agora em diante não faremos distinção entre referência a objeto do tipo array e o objeto array, a não ser que seja necessário explicitar esta distinção. O acesso aos elementos do array é realizado por meio do nome da variável seguida por um expressão inteira não negativa envolvida pelos caracteres ‘[’ e ‘]’. A expressão inteira é chamada de índice e os valores admissíveis para a expressão vai de 0 a n-1, onde n é número de elementos do array. O índice do primeiro elemento é 0. Abaixo seguem alguns exemplos: alfabeto[0] = ‘a’; grade[0][5] = 10L; for(int i=0; i<10; i++) numeros[i] = i*2; É possível descobrir o tamanho de um array em tempo de execução acessando a variável pública length do objeto, onde está armazenada a capacidade do array. Por exemplo: for(int i=0; i< numeros.length; i++) numeros[i] = i*2; Strings J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 39 O manipulação de cadeias de caracteres (strings) em J ava é realizada por uma classe do pacote java.lang denominada String. Ou seja, não existe um tipo primitivo para tratar cadeias de caracteres. Para se declarar uma variável que faz referencia a um objeto String usa-se a seguinte linha de comando: String nome; Para que a variável faça referência a um objeto basta criar um objeto por meio do operador new ou atribuir a referência de um objeto preexistente. nome = new String(“Pedro”); String x, y; x= nome; y = “Pedro”; No primeiro caso a variável nome faz referencia a um objeto recém criado contendo o valor “Pedro”. J á a variável x faz referencia ao mesmo objeto referenciado pela variável nome. Ou seja, nenhum novo objeto String é criado, ocorrendo um compartilhamento de objetos. J á a variável y faz referência a um objeto String contendo o valor “Pedro”, distinto do objeto referenciado por x e nome. Podemos também inicializar a variável durante a declaração. String nome = “Pedro”; Os objetos do tipo String possuem um conjunto extenso de métodos e construtores para a manipulação e criação de Strings. A tabela abaixo mostra alguns dos mais utilizados. Construtor Descrição String(byte[] bytes, int offset, int num) Constrói uma nova String convertendo o subarray de bytes especificado String(StringBuffer buffer) Constrói uma nova String usando a sequência de caracteres contida no StringBuffer. String(byte[] bytes) Constrói uma nova String convertendo o array de bytes especificado String(String valor) Constrói uma nova String com o mesmo conteúdo da String passada como argumento. String() Constrói uma nova String contendo zero caracteres. String(char[] valor) Constrói uma nova String convertendo o array de caracteres especificado. String(char[] valor, int offset, int num) Constrói uma nova String convertendo o subarray de caracteres especificado. J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 40 Tabela III-8. Principais construtores da classe String. Método Descrição charAt(int indice) Retorna o caractere localizado no índice especificado. compareTo(String outraString) Compara duas Strings lexicograficamente. equals(Object anObject) Verifica se dois objetos são iguais. getChars(int srcBegin, int srcEnd, char[] dst, int dstBegin) Copia os caracteres da String em um array de caracteres. indexOf(String str) Retorna o índice da primeira ocorrência do substring no string. indexOf(String str, int fromIndex) Retorna o índice da primeira ocorrência do substring no string a partir do índice especificado. indexOf(int ch, int fromIndex) Retorna o índice da primeira ocorrência do caractere no string a partir do índice especificado. indexOf(int ch) Retorna o índice da primeira ocorrência do caractere no string . length() Retorna o comprimento do string. replace(char oldChar, char newChar) Retorna uma nova String onde todos os caracteres oldChar foram substituídos pelos caracteres newChar. substring(int beginIndex) Retorna uma nova string que é substring da atual. substring(int beginIndex, int endIndex) Retorna uma nova string que é substring da atual. toLowerCase() Converte para minúsculas. toUpperCase() Converte para Maiúsculas. trim() Remove os espaços em branco do inicio e do fim do String. valueOf(Object obj) Retorna a representação em String do argumento Object. valueOf(char c) Retorna a representação em String do argumento char. valueOf(boolean b) Retorna a representação em String do argumento booleano. valueOf(long l) Retorna a representação em String do argumento long. valueOf(int i) Retorna a representação em String do argumento int.. valueOf(char[] data) Retorna a representação em String do argumento array de caracteres valueOf(float f) Retorna a representação em String do argumento float. valueOf(double d) Retorna a representação em String do argumento double. J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 41 Tabela III-9. Principais métodos da classe String. Os literais de Strings são tratados como instâncias da classe String e como tal possuem os métodos de um objeto da classe String. Por exemplo, após a execução da expressão abaixo a variável x conterá o valor 5. int x = “Pedro”.length(); Conversão de Tipos De forma geral as conversões entre tipos em J ava devem ser especificadas explicitamente. A forma mais comum de se especificar uma conversão é por meio da notação abaixo: (<tipo destino>) <expressão> Por exemplo: int i = 10; char c = (char) i; Este tipo de conversão é chamada de casting. O programador deve ficar bastante atento no que diz respeito a conversões, uma vez que pode acontecer perda de informação quando convertemos um tipo para outro que ocupa um espaço menor na memória. A tabela abaixo mostra as conversões entre tipos primitivos que podem causar perda de informação: do tipo para o tipo byte char short byte, char char byte, short int byte, short, char long byte, short, char, int float byte, short, char, int, long double byte, short, char, int, long, float Tabela III-10. Conversão de tipos que podem causar perda de informação. J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 42 Outra forma de converter tipos é usando os métodos fornecidos pelas classes associadas aos tipos. A tabela abaixo mostra a classe associada a cada tipo primitivo. Tipo Classe int Integer float Float double Double boolean Boolean byte Byte short Short long Long Tabela III-11. Classes associadas a cada tipo primitivo. As classes fornecem métodos para conversão mais sofisticados como do tipo primitivo para String e vice-versa. Por exemplo, a classe Integer fornece um método para converter String para int: int i = Integer.parseInt(“12”); Para se converter um inteiro para String podemos utilizar o método toString(): String s = Integer.toString(12); Existem métodos semelhantes nas outras classes. Existe também um tipo de conversão que somente se aplica aos operandos do operador binário ‘+’ quando um dos operandos é um objeto da classe String. Caso o outro operando não seja um objeto da classe String, então ele será convertido para String e o resultado da operação será a concatenação das duas cadeias. As Conversões entre objetos de classes distintas serão tratadas no próximo Capítulo. Operadores Os operadores atuam sobre valores e variáveis de modo a gerar novos valores ou modificar os valores das variáveis. Os símbolos abaixo representam os 37 operadores da linguagem J ava: J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 43 = > < ! ~ ? : == <= >= != && || ++ -- + - * / & | ^ % << >> >>> += -= *= /= &= |= ^= %= <<= >>= >>>= Os operadores podem ser divididos nas seguintes classes. Unários Descrição Símbolo Incremento ++ Decremento -- Negativo - Complemento de bit ~ Tabela III-12. Operadores unários. Os operadores de incremento e decremento (++ e --) aumentam e decrementam variáveis de tipo inteiro de uma unidade. Estes operadores podem ser usados na forma prefixa ou pósfixa. Na forma prefixa o operador modifica o valor da variável antes que o valor seja usado na expressão onde está a variável. Na forma pósfixa o operador modifica o valor da variável depois que o valor é usado na expressão onde está a variável. Por exemplo, o valor das variáveis x, y e w após a execução do trecho de código abaixo int x = 1, y, w; y = x++; w = --x; é 1. Isto ocorre porque y recebe o valor de x antes de ser incrementado e w recebe o valor de x depois de ser decrementado. O operador de negação unário (-) é usado para mudar o sinal de um valor inteiro. O operador de Complemento de bit (~) inverte cada bit da representação binária de um inteiro. O exemplo abaixo mostra efeito da aplicação deste operador. byte x = 10; // valor em binário 00001010 J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 44 x = ~x; // valor em binário 11110101; valor em decimal 245. O programa abaixo mostra o uso de cada operador unário. public class Unarios { public static void main (String args[]) { int x = 10, y = 0; System.out.println("x = " + x); System.out.println("y = " + y); System.out.println("++x = " + ++x); System.out.println("y++ = " + y++); System.out.println("x = " + x); System.out.println("y = " + y); System.out.println("-x = " + -x); System.out.println("~y = " + ~y); } } Saída: x = 10 y = 0 ++x = 11 y++ = 0 x = 11 y = 1 -x = -11 ~y = -2 Exemplo III-2. Uso dos operadores unários. Binários Descrição Símbolo Adição e concatenação de strings + Subtração - Multiplicação * Divisão / Módulo % E de bit & OU de bit | OU exclusivo de bit ^ deslocamento de bits a esquerda << J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 45 deslocamento de bits a direita >> des. a direita com preenchimento zero >>> Tabela III-13. Operadores binários. Acreditamos que os operadores binários para adição, subtração, multiplicação, divisão e concatenação de Strings não necessitam maiores explicações. O operador de módulo (%) retorna o resto da divisão entre dois operandos inteiros. Por exemplo, o valor da variável x após a execução do trecho de código int x = 7 % 4; é 3. Os operadores &, | e ^implementam operações orientada para bits, ou seja, operações que atuam sobre os bits individuais dos operandos. São úteis quando se usa valores como campos de bits. O operador & realiza a operação lógica E entre cada bit individual dos operandos. Por exemplo, o valor da variável x após a execução do trecho de código byte y = 3; byte x = Y & 5; é 1. O operador | realiza a operação lógica OU entre cada bit individual dos operandos. Por exemplo, o valor da variável x após a execução do trecho de código byte y = 3; byte x = Y | 5; é 7. O operador ^realiza a operação lógica XOU (ou exclusivo) entre cada bit individual dos operandos. Por exemplo, o valor da variável x após a execução do trecho de código byte y = 3; byte x = Y & 5; é 6. y 00000011 4 00000101 & 1 00000001 y 00000011 4 00000101 | 7 00000111 y 00000011 4 00000101 ^ 1 00000110 J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 46 Os operadores <<, >>e >>>implementam operações de deslocamento de bits. Os bits do operando à esquerda serão deslocados o número de posições especificadas pelo operando à direita. Os bits que saem dos limites do campo são perdidos e a variável é preenchida com zeros no lado oposto ao deslocamento. O operador << realiza a operação de deslocamento de bits a esquerda. Por exemplo, o valor da variável x após a execução do trecho de código byte x = 7 << 1; é 14. Note que o deslocamento de uma unidade para a esquerda tem o mesmo efeito da multiplicação por 2, desde que nenhum bit seja. Os operadores >>e >>>realizam a operação de deslocamento de bits a direita. A diferença entre o operador >>e o >>>é que o primeiro não desloca o bit de mais alta ordem usado para indicar números negativos. O programa abaixo ilustra o uso desses operadores: public class Deslocamento { public static void main (String args[]) { int x = 7, y =–7; System.out.println("x = " + x); System.out.println("y = " + y); System.out.println("x << 1= " + (x<<1)); System.out.println("x >> 2= " + (x>>2)); System.out.println("y >> 2= " + (y>>2)); System.out.println("y >>> 31= " + (y>>>31)); } } Saída: x = 7 y = -7 x <<= 14 x >> 2= 1 y >> 2= -2 y >>>30=3 Exemplo III-3. Uso dos operadores de deslocamento de bits. Note que no caso do número positivo o deslocamento à direita teve o efeito de uma divisão inteira por dois. J á quando o número é negativo este efeito J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 47 não ocorre, devido ao fato da representação de números inteiros ser feita em complemento a dois. Relacionais Descrição Símbolo Menor que < Maior que > Menor igual <= Maior igual >= Igual == Diferente != Tabela III-14. Operadores relacionais. Os operadores relacionais são usados para comparar valores. O resultado da aplicação desses operadores é um valore boolano, ou seja :true ou false. O programa abaixo ilustra o uso desses operadores: public class Relacional { public static void main (String args[]) { int x = 7, y = 8; System.out.println("x = " + x); System.out.println("y = " + y); System.out.println("x < y = " + (x < y)); System.out.println("x > 10 = " + (x > 10)); System.out.println("y <= 8 = " + (y <= 8)); System.out.println("y == 5 = " + (y == 5)); System.out.println("x != y = " + (x != y)); } } Saída: x = 7 y = 8 x < y = true x > 10 = false y <= 8 = true y == 5 = false x != y = true Exemplo III-4. Uso dos operadores relacionais. J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 48 Booleanos Descrição Símbolo E & OU | OU Exclusivo ^ E Curto circuito && OU Curto circuito || Negação ! Igualdade == Condicional ?: Tabela III-15. Operadores booleanos. Os operadores booleanos atuam sobre valores booleanos e retornam um valor booleano. A tabela abaixo resume o resultado da aplicação dos operadores booleanos, exceto o operador condicional (?:) e o de negação (!): Operando 1 Operando 2 & | ^ && || == true true true true false true true true true false false true true false true false false true false true true false true false false false false false false false false true Tabela III-16. Resumo dos operadores booleanos. O operador de negação muda o valor booleano como mostrado na tabela abaixo: Operando ! true false false true Tabela III-17. Operador de negação. J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 49 O operador condicional atua sobre três operandos: uma expressão condicional e duas outras expressões quaisquer. A forma geral do operador é <condição> ? <expressão 1> : <expressão 2>. Se <condição> for avaliada como true, então o resultado da aplicação do operador condicional será o retorno da avaliação da <expressão 1>. Caso contrário o resultado da avaliação da <expressão 2> será retornado. O programa abaixo ilustra o uso deste operador: public class Condicional { public static void main (String args[]) { int x = 5; boolean par = (x % 2 == 0) ? true : false; System.out.println("x = " + x); System.out.println("É par = " + par); } } Saída: x = 5 É par = false Exemplo III-5. Uso do operador condicional. Atribuição Descrição Símbolo Simples = Adição += Subtração -= Multiplicação *= Divisão /= Modulo %= AND &= OR |= XOR ^= J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 50 Tabela III-18. Operadores de atribuição. Os operadores de atribuição simplesmente atribuem um valor a uma variável. Exceto o operador de atribuição simples (=), todos os outros operadores na forma <variável> <op>= <expressão> funcionam como uma maneira abreviada de se escrever <variável> = <variável> <op>(<expressão>) Por exemplo, a atribuição x += 2; tem o mesmo significado da atribuição x = x +2; Esta forma de escrever facilita a trabalho de digitação, porém torna o código fonte menos legível. Expressões e Precedência entre Operadores Expressões são trechos de códigos que geram valores. Portanto, o trecho de código 1+2 é uma expressão pois gera o valor 3. Literais também são expressões pois geram o próprio valor que representam. Portanto, o literal 3.14 é também uma expressão. As variáveis que ocorrem em uma expressão são também expressões, pois representam o valor que armazenam. As expressões são combinadas por meio dos operadores para formar expressões maiores. Assim, podemos combinar as expressões 3 e 5*6 por meio de um operador tipo +para formar a expressão 3+5*6 Uma pergunta que pode surgir é: como a expressão é avaliada? Ou seja, que operador é aplicado primeiro: o + ou o *? Dependendo da ordem de aplicação dos operadores o resultado da avaliação da expressão pode ser diferente, como no caso acima. A ordem de aplicação obedece uma prioridade J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 51 definida pela precedência relativa entre os operadores. A tabela abaixo define a precedência entre os operadores. A precedência decresce de cima para baixo. Em se tratando de operadores com a mesma precedência a avaliação em uma expressão é feita da esquerda para a direita. . [] () ++ -- ! ~ * / % + - << >> >>> < > <= >= == != & ^ && || ?: = Tabela III-19. Precedência de operadores. Comentários Comentar o código fonte faz parte das regras que definem um bom estilo de programação. J ava possui três diferentes formas de se comentar o código, ilustradas abaixo: // comentário de linha /* comentário de bloco */ /** comentário de bloco c/ propósito de documentação */ As duas primeiras formas de comentário são familiares para os programadores de C++. Na primeira forma, todos os caracteres posicionados após “//” e antes do final da linha são ignorados pelo compilador. Na segunda forma todos os caracteres que ocorrem após “/*” são ignorados pelo compilador, até que seja encontrado a sequência “*/”. A terceira forma de comentário é semelhante a segunda, com a diferença de que é usada pela ferramenta de documentação javadoc para gerar uma documentação no formato HTML (HyperText Markup Language). O uso dessa ferramenta será tratado no J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 52 final do próximo capítulo. O exemplo III-6 repete o código do exemplo III-5 com a adição de alguns comentários: public class Condicional { // classe para teste do operador ?: public static void main (String args[]) { int x = 5; // variável usada para o teste /* Será impresso true se x for par */ boolean par = (x % 2 == 0) ? true : false; System.out.println("x = " + x); System.out.println("É par = " + par); } } Exemplo III-6. Uso de comentários. Blocos e Escopo Na linguagem J ava, assim como em C/C++um bloco de comandos é delimitado pelos caracteres ‘{’ e ‘}’. Os blocos são utilizados para agrupar comandos que são relacionados. Um bloco também pode conter blocos, chamados de blocos internos. Todas as variáveis declaradas em um bloco podem ser referenciadas apenas dentro do bloco e nos blocos internos ao bloco onde foi definida, desde que não exista nenhuma variável no bloco mais interno com o mesmo nome. As seções onde uma determinada variável pode ser acessada definem a escopo da variável. A figura III-1 ilustra a relação entre blocos e escopo. public class Visivel { public static void main (String args[]){ int x = 5; int y = 2; ... { int x =2; ... } ... } } escopo do x interno escopo do x externo escopo de y { } } } J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 53 Figura III-1. Visibilidade de variáveis. Estruturas de Controle As estruturas de controle definem a sequência de execução das instruções. Não é possível implementar um algoritmo que não seja trivial em uma linguagem de programação que não tenha um conjunto mínimo de estruturas de controle. As estruturas de controle podem ser divididas em seleção, repetição e sequência. A sequência é simplesmente definida pela execução sequencial dos comandos, de cima para baixo. Falta abordarmos as estruturas pertencentes às outras duas classes. Seleção Na seleção o fluxo sequencial de execução é desviado segundo uma condição ou valor. J ava apresenta duas formas seleção: o if e o switch. if O comando de seleção de if possui duas formas básicas: if (condição) comando1 if (condição) comando1 else comando2 (a) (b) Na forma (a) o comando1 é executado se condição for avaliada como true. Na forma (b) o comando1 é executado se condição for avaliada como true senão o comando2 é executado. if (x==0) y = 5; else y+=6; Exemplo III-7. Comando if. J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 54 Se o comando a ser executado for na verdade um conjunto de comandos devemos então usar os delimitadores de bloco ‘{’ e ‘}’ para agrupar os comandos em um único bloco. if (x==0) y = 5; else { y+=6; x++; } Exemplo III-8. Comando if executando um bloco. Os comandos if podem combinar em forma aninhada como mostrado no exemplo III-9. if (x==0) if (y == 1) x= y; else y+=6; Exemplo III-9. ifs aninhados. O aninhamento como no exemplo III-9 deixa uma dúvida com que if o else está relacionado? Com o mais interno ou o mais externo? A regra para esses casos é: o else se relaciona com o if mais interno. Essa regra pode ser alterada, utilizando os delimitadores ‘{’ e ‘}’ para definir os blocos de comandos, como mostrado no exemplo III-10. if (x==0) { if (y == 1) x= y; } else y+=6; J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 55 Exemplo III-10. Uso de blocos para definir o par if-else. switch O comando switch é útil quando existem várias ações distintas que precisam ser realizadas em função do resultado da avaliação de uma expressão. Não é nada que não poderia ser feito com um conjunto de if-else, no entanto o uso do switch facilita codificação e a legibilidade do programa. A forma geral do comando está definida abaixo: switch(Expr) { case const1: com1; ... case constN: comN; default: comDef } O comando switch avalia a expressão Expr e compara o valor resultante com todas as constantes colocadas após a palavra a chave case. A comparação é feita na ordem de cima para baixo e a primeira constante que igualar com o valor resultante da expressão faz com que os comandos após o ‘:’ sejam executados até o fim ou até que seja encontrado o comando break. A palavra chave default é opcional e ela serve como ponto de entrada para os comandos que serão executados caso o valor da expressão não seja igual a nenhuma das constantes. O exemplo III-11 ilustra o uso do comando switch. switch(letra) { case ‘i’: System.out.println(“inserir”); break; case ‘a’: System.out.println(“alterar”); break; case ‘e’: System.out.println(“excluir”); break; default: System.out.println( “Ação ignorada: ”+letra); } J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 56 Exemplo III-11. Uso do comando switch. O comando break é muito importante na composição do switch. Ele faz com que a execução sequencial dos comandos seja interrompida para ser retomada no primeiro comando após o comando switch. O exemplo III-12 mostra as saídas de um programa com um comando switch que não faz uso do comando break. Note que todos os comandos são executados, uma vez que não existe um comando break para interromper a execução. public class Switch { public static void main (String args[]) { char letra = ‘i’; switch(letra) { case ‘i’: System.out.println(“inserir”); case ‘a’: System.out.println(“alterar”); case ‘e’: System.out.println(“excluir”); default: System.out.println( “Ação ignorada: ”+letra); } } } Saída: inserir alterar excluir Ação ignorada:i Exemplo III-12. Uso do comando switch sem break. O leitor pode se estar perguntando porque a sequência de comandos não é interrompida automaticamente em vez de deixar para o programador a tarefa de sinalizar isso por meio do comando break. A verdade é que a omissão do comando break pode ser útil para poupar digitação naqueles casos onde mais de uma opção precisa executar a mesma sequência de comandos. O exemplo III-13 mostra um desses casos. switch (mês) { case 1: J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 57 case 3: case 5: case 7: case 8: case 10: case 12: dias = 31; break; case 4: case 6: case 9: case 11: dias = 30; break; case 2: if (((ano % 4==0) && !(ano % 100 == 0)) || (ano % 400 == 0) ) dias = 29; else dias = 28; break; } Exemplo III-13. Uso útil da omissão do comando break. Repetição Os comandos de repetição, ou de iteração, executam um comando ou um bloco de comandos várias vezes. É uma das formas para executar repetidamente um comando. A outra é a recursão que é a chamada direta ou indireta de um método durante a execução do próprio método. Em J ava existem três formas de comandos de repetição: o while; o do-while; e o for. while A forma geral do comando while é: while(condição) Comando; O comando while executa Comando enquanto condição for avaliada como true. Portanto, para encerrar a iteração é necessário que exista a possibilidade da execução de Comando alterar a avaliação de condição. A condição é testada antes da execução do comando. O exemplo III-14 mostra a soma dos 100 primeiros números inteiros. int i = 1; J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 58 int x = 0; while(i<=100) x +=i++; Exemplo III-14. Soma dos 100 primeiros números inteiros. Se for necessário executar mais de um comando deve-se agrupá-los por meio de delimitadores de bloco, como mostrado no exemplo III-15. while(i<=100) { x *=y; i++; } Exemplo III-15. - Uso de while com bloco. do-while A forma geral do comando do-while é: do Comando; while(condição) O comando do-while executa Comando enquanto condição for avaliada como true. A diferença em relação ao comando while é que a condição é testada após a execução do comando. O exemplo III-16 mostra a soma dos 100 primeiros números inteiros. Os comandos também podem ser agrupados em um bloco. int i = 1; int x = 0; do x +=i++; while(i<100); Exemplo III-16. Soma dos 100 primeiros números inteiros com do-while. J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 59 for A forma geral do comando for é: for(Cod.Inicialização;condição; Cod.passo) Comando; No comando for o Cod.Inicialização (código de inicialização) é executado uma vez, a condição é avaliada antes de cada iteração e o Comando e o Cod.passo é executado a cada iteração. O Cod.passo é executado após o Comando. Todas as seções do comando for podem ser omitidas. Enquanto condição for avaliada como true é executada mais uma iteração. Em geral, o Cod.Inicialização é utilizado para inicializar a variável que será testada na condição e o Cod.passo é utilizado para alterar o valor da mesma. O exemplo III-17 mostra a soma dos 100 primeiros números inteiros. x = 0; for(int i=1; i<=100; i++) x +=i; Exemplo III-17. Soma dos 100 primeiros números inteiros com for. Tanto o Cod.Inicialização como o Cod.passo podem ser compostos de vários comandos. Cada comando é separado por ‘,’. O exemplo III-18 ilustra esta forma de uso do comando for. for(int i=1, x = 0; i<=100; i++) x +=i; Exemplo III-18. Soma dos 100 primeiros números inteiros com for. Como já foi dito, todas as seções do comando for podem ser omitidas. O exemplo III-19 mostra uma iteração infinita, definida pela omissão das seções do comando for. J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 60 for(;;) System.out.println(“ola mundo”); Exemplo III-19. Iteração infinita. break e continue J á vimos o uso do comando break quando discutimos o comando switch. Ele servia para interromper a sequência de execução dos comando e sair do comando switch. Isto não acontece apenas no comando switch. Toda vez que um break é encontrado, o fluxo de execução “salta” para fora do bloco onde está contido o break, sendo retomado no comando após o bloco. A figura III-2 ilustra este comportamento. for (;;) { ... break; ... } fluxo retomado aqui ... Figura III-2. Uso do comando break. O uso mais comum do comando break ocorre em uma condição de teste dentro de uma iteração. O exemplo III-20 é equivalente ao exemplo III-18. for(int i=1, x = 0;; i++) if (i==101) break; else x +=i; Exemplo III-20. Soma dos 100 primeiros números inteiros com for e break. J á o comando continue é usado dentro de uma iteração e faz com que ocorra um desvio para a condição de teste da iteração. Ou seja, o comando continue força um salto para próxima iteração. A figura III-3 ilustra este comportamento. J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 61 while (Condição) { ... fluxo retomado aqui continue; ... } ... Figura III-3. Uso do comando continue. Como no comando break, o uso mais comum do comando continue é em uma condição de teste dentro de uma iteração. Argumentos da linha de comando Os argumentos digitados na linha de comando são passados para o método main() da classe invocada por meio de um vetor de Strings. Por exemplo, se executarmos a linha de comando abaixo java teste um dois três o método main() da classe teste receberá o seguinte vetor de Strings: [0] um [1] dois [2] três Figura III-4. Vetor com os argumentos da linha de comando. Para quem está acostumado a programar na linguagem C note o primeiro elemento do vetor não é o nome do programa e sim o primeiro argumento da linha de comando. O espaço serve como separador de argumentos. Se desejarmos tratar uma cadeia de caracteres com espaço como um único argumento é necessário delimitá-la com aspas duplas. Por exemplo, o comando abaixo J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 62 java teste um “dois três” resulta no seguinte vetor: [0] um [1] dois três Figura III-5. Vetor com os argumentos da linha de comando com aspas. O classe do exemplo III-21 mostra um programa que imprime os argumentos recebidos, um em cada linha do dispositivo de saída. public class ImpLinha { public static void main(String args[]) { int i; for (i=0;i<args.length;i++) System.out.println(args[i]); } } Exemplo III-21. Imprimindo a linha de comando. Todos os argumentos são tratados como Strings. Se houver necessidade de tratar os argumentos como pertencendo a um tipo diferente será preciso realizar as conversões necessárias. No exemplo III-22 os argumentos são convertidos para números inteiros para serem somados. public class Soma { public static void main(String a[]){ int i,soma=0; for (i=0;i<a.length;i++) soma += Integer.parseInt(a[i]); System.out.println(“A soma é:”+soma); } } Exemplo III-22. Imprimindo a soma dos números. J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 63 Assert (Assertivas) Com o surgimento da versão 1.4 do SDK uma nova alteração na linguagem J ava foi introduzida, envolvendo a inclusão de uma nova palavra reservada: a palavra assert. O objetivo desta alteração é incluir na linguagem recursos que permitam que o programador verifique se uma dada condição, chamada de assertiva, é verdadeira em um local específico do código. Isto possibilita o desenvolvimento de códigos mais confiáveis, sendo um recurso utilizado durante a etapa de desenvolvimento e desabilitado em tempo de execução. Devido a inclusão de uma nova palavra chave, códigos fontes preexistentes que incluam a palavra assert como identificador gerarão erro de compilação ao serem compilados com a versão 1.4 do SDK. Este problema de compatibilidade é solucionado por meio de flags de compilação. A linguagem C++possui recurso semelhante, com a diferença de ser implementado por meio de biblioteca e não a nível de linguagem. No caso de J ava, a decisão de não utilizar uma biblioteca para implementação deste recurso deveu-se ao objetivo de se buscar uma implementação mais transparente, apesar do inconveniente da perda de compatibilidade. A inclusão de recursos de para verificação de condições estava presente na especificação original da linguagem, quando ainda era denominada oak, mais foi retirada da primeira versão por se acreditar que não haveria tempo para se desenvolver uma implementação adequada. Sintaxe e semântica A formato geral de uma assertiva é: assert<expressão booleana1>; ou assert<expressão booleana1>:<expressão booleana2>; A avaliação de uma assertiva funciona da seguinte forma: a primeira expressão é avaliada. Caso o resultado seja false e não exista uma segunda expressão então a exceção AssertionError é lançada. Caso o resultado seja false e exista uma segunda expressão então ela é avaliada e o resultado é passado para o construtor da exceção AssertionError antes de ser lançada. Caso o resultado seja da primeira expressão seja true então a segunda expressão, caso exista, não será avaliada. J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 64 O exemplo III-23 mostra um exemplo do uso de assertivas para garantir que uma determinada condição seja verdadeira. ... assert b > 0; int x = a/b; ... Exemplo III-23. Uso de assertivas. Habilitando e Desabilitando Assertivas As assertivas podem ser habilitadas/desabilitadas por flags na linha de comando e chamadas a métodos durante a execução. A habilitação/desabilitação pode ter uma atuação que varia desde o envolvimento de uma única classe até um pacote ou todo o programa. Por default as assertivas estão desabilitadas. Habilitação por flags A forma geral para a habilitação de assertivas é: java [-enableassertions | -ea] [:<nome package>“...” | :<nome classe>] O flag sem argumento habilita as assertivas para todas as classes. Se for seguindo por o nome de um pacote seguido por “...” as assertivas serão habilitadas para o pacote especificado e todos os subpacotes. Se for seguido apenas de “...”as assertivas serão habilitadas para o pacote do diretório corrente e todos os subpacotes. Se o argumento não terminar com “...”, então as assertivas serão habilitadas apenas para a classe especificada. Abaixo segue um exemplo de habilitação de assertivas: java –ea:br.ufv.dpi.apo... Teste A desabilitação de assertivas segue a mesma lógica, mudando apenas o flag que passa ser –disableassertions ou –da. J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 65 Habilitação por métodos Os seguintes métodos da classe ClassLoader podem ser invocados para habilitar/desabilitar as assertivas em tempo de execução: void setDefaultAssertionStatus(boolean status)– habilita/desabilita por default as assertivas. void setPackageAssertionStatus(String nomepack, boolean status)– habilita/desabilita as assertivas no pacote e subpacotes. void setClassAssertionStatus(String nomeclasse, boolean status)– habilita/desabilita as assertivas na classe. void clearAssertionStatus()– retorna ao default. J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 66 Capítulo IV Classes, Pac kages e Interfaces A unidade fundamental de programação em J ava é a Classe. Não é possível fazer um programa em J ava que não tenha pelo menos uma classe. Isso não é verdade para todas as linguagens Orientadas a Objeto. Em C++, por exemplo, é possível fazer um programa que não use classe. Em J ava todas as variáveis e métodos de um programa J ava devem ser definidos dentro de uma classe. Um programa que não contenha classes não é um programa orientado a objetos. No entanto, o fato de um programa conter classes também não o torna merecedor do título de “orientado a objetos”. A programação orientada a objetos é mais um estilo de programação do que um conjunto de palavras-chave colocadas em um programa. Porém, é obviamente melhor implementar este estilo usando uma linguagem que oferece suporte a ele. Neste capítulo estudaremos o suporte oferecido por J ava a este estilo de programação. Não só a implementação de classes será vista, como também conceitos relacionados, como pacotes e interfaces. Classes No Capítulo II foram discutidas Classes e Objetos do ponto de vista de modelagem e de implementação. Nesta seção será detalhada a implementação de classes em J ava. A definição de uma classe em J ava obedece a seguinte forma geral: [public] class <Identificador> { <corpo da classe> } A palavra reservada public é opcional e indica que a classe pode ser referenciada por qualquer outra classe além do próprio pacote (conceito que será visto mais adiante). Apenas uma classe por arquivo pode ser precedida pela palavra reservada public e o nome do arquivo tem que ser idêntico ao nome desta classe. Se a declaração da classe não for precedida de public, então ela só poderá ser referenciada por outras classes do mesmo pacote. Os modificadores J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 67 de acesso private e protected não podem ser aplicados às classes. O <Identificador> é usado para referenciar a classe. No <corpo da classe> são definidos os atributos e métodos da classe. Na comunidade de programadores J ava existe a convenção de associar às classes identificadores que iniciem com letra maiúscula, enquanto que os identificadores de variáveis e métodos devem iniciar com letra minúscula. No exemplo IV-1 é mostrada a definição da classe de uma classe com o identificador Pessoa. Nele foi definido um construtor para atribuir valores aos atributos e definido os métodos para acesso aos atributos. Note que os métodos que atribuem valores às variáveis da classe iniciam com a palavra set e os métodos que retornam os valores armazenados nas variáveis da classe iniciam com a palavra get. Este padrão de nomeação de método foi adotado a partir da versão 1.1 da linguagem e é muito importante seguí-lo, principalmente se pretendemos implementar J avaBeans, como será visto no Capítulo X. public class Pessoa { String nome; String telefone; String endereço; public Pessoa(String n, String t, String e) { nome = n; telefone = t; endereço = e; } public void setNome(String n) { nome=n; } public void setTelefone(String t) { telefone = t; } public void setEndereço(String e) { endereço = e; } public String getNome() { return nome; } public String getTelefone() { return telefone; } public String getEndereço() { return endereço; } } Exemplo IV-1. Definição da classe Pessoa. Unidade de Compilação Cada arquivo fonte J ava é uma unidade de compilação. Ao compilar o arquivo fonte o compilador irá gerar um arquivo .class para cada classe constante no arquivo. O nome de cada arquivo será o nome da cada classe. J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 68 Os atributos ou variáveis declarados na classe são criados somente quando os objetos são criados. Ou seja, cada objeto da classe Pessoa terá a sua variável nome, telefone e endereço. Portanto, a variável pertence à instância da classe e não à classe. Variáveis que pertencem ao objeto são chamadas variáveis de instância. Com os métodos o raciocínio é o mesmo, ou seja, para podermos usar o método getNome() é preciso criar um objeto da classe Pessoa e usar o método getNome() deste objeto. Métodos que pertencem ao objeto são chamados de métodos de instância. Como veremos mais adiante, podemos usar a palavra reservada static para declarar variáveis e métodos que pertencem à classe (métodos e variáveis de classe). Os métodos definem operações sobre os atributos. Eles possuem a forma geral: <modificador> <tipo> <identificador>(<parâmetros>) { <corpo do método> } onde <modificador> é um modificador de acesso, <tipo> é o tipo do valor de retorno do método, <identificador> é o identificador do método e <parâmetros> é uma lista de parâmetros. O corpo do método é composto de comandos, expressões e declarações de variáveis locais ao método. Os métodos do exemplo IV.1 são muito simples. Eles apenas retornam um valor ou atribuem um valor aos atributos do objeto. Construtores Podemos observar no exemplo IV-1 que existe um método público com o mesmo nome da classe e que não define um valor de retorno. Métodos como esse são denominados de construtores e são chamados pelo operador new. Uma classe pode não ter construtor ou ter vários, desde que tenham diferentes tipos de argumentos. O interpretador decidirá qual chamar a partir dos argumentos passados para o construtor. Se o programador não declarar nenhum construtor então o compilador irá criar automaticamente um construtor default para a classe. Valor de Retorno J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 69 Com exceção dos construtores todos os outros métodos precisam retornar algum valor. Se programador não precisa que o método retorne algum valor, então ele deve especificar que o método irá retornar o valor void que é o valor vazio. Se o método não foi definido com valor de retorno void e não é um construtor então é preciso que no corpo do método seja indicado o valor de retorno. Esta indicação deve ser feita em toda ramificação de código que leve ao fim da execução do método e é feita com a palavra chave return seguida de uma expressão: return <expressão> A expressão pode ser uma variável, um literal ou qualquer outra expressão que gere um valor do tipo especificado para o retorno da função. O exemplo IV-2 mostra um método que retorna um inteiro. Note que toda ramificação de código que leve ao fim da execução do método necessita de um comando return. public class ExemploIV2 { ... public int calc(int a) { if (a==0) return 1; else return a*a; } } Exemplo IV-2. Método que retorna um inteiro. Objetos Para se criar um objeto de uma classe (ou uma instância da classe) é preciso primeiro declarar uma variável que irá referenciar o objeto, para depois criar o objeto. A declaração de um referência a um objeto é feita obedecendo a seguinte forma geral: <nome da classe> <identificador>; Por exemplo, para declarar uma referência à um objeto da classe Pessoa devemos adicionar a seguinte linha ao código. J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 70 Pessoa p1; Pode-se declarar mais de uma referência a objetos de uma classe em uma única declaração, separando os identificadores por vírgula. Pessoa p1, p2; Para criar um objeto usa-se o operador new conforme a forma geral abaixo: <identificador> = <nome da classe>(<argumentos>); onde <argumentos> é uma lista de argumentos que serão passados para um método especial da classe, denominado de construtor. Por exemplo, para criar um objeto do tipo Pessoa podemos usar a declaração abaixo: P1 = new Pessoa(“Ana”,”432-6969”,”Rua 17 134”); Podemos também combinar a declaração da referência com a criação do objeto em uma única linha de código. Pessoa P1 = new Pessoa(“Ana”,”432-6969”,”Rua 17 134”); Uma vez criado o objeto podemos acessar os elementos do objeto por meio da referência ao objeto e do operador ‘.’. Por exemplo para acessar o método getNome() do objeto referenciado pela variável P1 devemos usar o seguinte código: P1.getNome(); Para acessar um atributo diretamente a sintaxe é mesma. Por exemplo, para acessar o atributo nome diretamente basta usar a seguinte linha de código. P1.nome; O exemplo IV-3 mostra a criação de um objeto do tipo Pessoa e o acesso a seus membros. Para que a classe seja executável diretamente pela máquina virtual é preciso que ela possua um método main() que servirá como ponto de entrada para a execução. Podemos então, inserir código no método main() para criar uma instância da classe para que, a partir de então, possamos a acessar os J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 71 membros do objeto. O método main() existe antes da criação da instância, e portanto pode ser acessado pela máquina virtual, porque foi declarado com o modificador static. Isto significa que o método pertence à classe (método de classe) e não ao objeto (método de instância). public class Pessoa { ... // igual ao exemplo IV.1 ... public static void main(String args[]) { Pessoa p; p = new Pessoa(“Ana”,”432-6969”,”Rua 17 134”); // Acessa os dados via métodos System.out.println(“Nome:”+getNome()); System.out.println(“Telefone:”+ getTelefone()); System.out.println(“Endereço:”+ getEndereço()); // Altera o endereço setEndereço(”Rua 17 138”) // Acessa os atributos diretamente System.out.println(“Endereço:”+ endereço); } } Exemplo IV-3. Definição da classe Pessoa. As implementações da classe Pessoa mostradas nos exemplos IV-1 e IV-3 possuem alguns problemas que iremos abordar gradativamente. O primeiro problema está relacionado com o acesso direto aos atributos. Em um programa bem projetado o acesso aos atributos da classe a partir de métodos definidos em outras classe é restringido de modo a diminuir a dependência em relação à representação interna de uma classe. Quanto maior for esta independência maior é a facilidade para manutenção do programa. Para se restringir o acesso aos membros da classe deve-se preceder a declaração dos membros com palavras reservadas denominadas de modificadores de acesso que serão descritos a seguir. J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 72 Modificadores de acesso Os modificadores de acesso tem por objetivo definir a visibilidade de um membro de uma classe (atributo ou método) em relação à outras classes. A tabela IV-1 mostra os modificadores de acesso disponíveis em J ava. Modificador Descrição default Somente classes do mesmo package possuem acesso public Todos possuem acesso protected Apenas os membros da classe e subclasse private Apenas os membros da classe Tabela IV-1. Modificadores de acesso. Quando não é especificado nenhum modificador de acesso é assumido o modificador default, também chamado de friend. Ou seja, não existe uma palavra reservada default. O acesso default determina que todas as classes dentro do mesmo package (pacote) podem acessar os membros da classe corrente. O conceito de package será detalhado mais adiante, no entanto podemos adiantar que um package é um agrupamento de classes e interface (também será vista mais adiante). Toda classe e interface pertence a um package, mesmo que o programador não indique o package explicitamente. Se o programador não indicar o package então será assumido que a classe ou interface pertence ao package default. O modificador public determina que todas as classes podem acessar o membro. J á o modificador protected limita o acesso ao membro apenas aos membros da própria classe e aos membros das subclasses da classe. Finalmente, o modificador private limita o acesso ao membro apenas aos membros da própria classe. O exemplo IV-4 é uma redefinição da classe Pessoa, explicitando os modificadores de acesso dos atributos com objetivo de limitar a visibilidade dos mesmos. public class Pessoa { protected String nome; protected String telefone; protected String endereço; ... J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 73 // igual ao exemplo IV.1 ... } Exemplo IV-4. Definição da classe Pessoa com os atributos protegidos. O modificador de acesso usado para os atributos no exemplo IV-4 foi o protected. Portanto, se for criada uma subclasse da classe Pessoa, esta poderá acessar diretamente os atributos da classe Pessoa. O exemplo IV-5 mostra a definição da classe Funcionario como subclasse de Pessoa. Note que o atributo de instância salario da classe Funcionario é definido com o modificador de acesso private, impedindo, dessa forma, acessos diretos ao atributo a não ser por membros da própria classe. Note também que o construtor da classe Funcionario possui uma chamada a um método super. Esta chamada representa, na verdade, uma chamada ao construtor da superclasse. Isto será visto com maiores detalhes mais adiante. public class Funcionario extends Pessoa { private double salario; public Funcionario(String n, String t, String e, double s) { super(n,t,e); salario = s; } public void setSalario(double s) { salario = s; } public double getSalario() { return salario; } } Exemplo IV-5. Definição da classe Funcionario. Para utilizar a classe Funcionario pode-se criar uma terceira classe, como mostra o exemplo IV-6, com o um método main() que serve de ponto de entrada para o início da execução. Foi colocado propositadamente uma tentativa de acesso ao atributo privado por um método externo à classe do atributo, de modo a ilustrar um acesso não permitido. public class TesteFun { public static void main(String a[]) { J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 74 Funcionario f; f = new Funcionario(“Carlos”,”555-7777”, “Av Nova 32”,3000.0); System.out.println(“Nome:”+f.getNome()); // Acesso Ilegal. Use o método. System.out.println(“Salario:”+f.salario); } } Exemplo IV-6. Utilizando a classe Funcionario. Outros Modificadores Além dos modificadores de acesso existem outros modificadores que estabelecem significados distintos da visibilidade. A tabela IV-2 exibe os modificadores adicionais. Modificador Descrição static A variável ou método é comum a todas as instâncias da classe. final O valor da variável não pode ser modificado. synchronized atribui o monitor do objeto ao thread corrente. native indica que se trata de um método nativos. Tabela IV.2– Modificadores de adicionais. O modificador static Um atributo declarado com o modificador static significa que é um atributo da classe e não de instância. Em outras palavras, existirá apenas um atributo, ocupando uma única posição de memória, em vez de um atributo para cada instância, cada um com sua própria posição de memória. Como resultado, o atributo existirá antes mesmo que qualquer objeto seja criado e, se for público, poderá ser acessado prefixando-o com o nome da classe e o operador “.”. O exemplo V-7 mostra uma classe com um atributo static. Note que o não é preciso declarar uma instância da classe X para que o atributo exista e para acessá-lo basta prefixá-lo como nome da classe. class X { J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 75 static public int tot =0; } public class Main { public static void main(String a[]) { X.tot = 2; System.out.println(“Valor de tot:”+X.tot); } } Exemplo IV-7. Classe com atributo static. Outro efeito é que o atributo será compartilhado por todas as instâncias da classe. Desta forma, se um objeto alterar o conteúdo do atributo, a alteração afetará todos os outros objetos da classe. O exemplo V-8 mostra a declaração de dois objetos da classe X. Um dos objetos altera o valor do atributo static e o outro imprime o valor do atributo. Isto mostra que as alterações feitas por um objeto no atributo é percebida por todos os outros objetos da classe. class X { static public int tot =0; public void inc() {tot++;} } public class Main { public static void main(String a[]) { X x1, x2; x1 = new X(); x2 = new X(); // x1 modifica o valor x1.inc(); System.out.println(“Valor de tot:”+x2.tot); } } Saída: J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 76 Valor de tot:1 Exemplo IV-8. Objetos com atributo static. O modificador static também pode ser usado na declaração de métodos. Neste caso o método fica ligado à classe e não à instância, sendo denominado de método de classe. Como consequência o método pode ser acessado antes de existir um objeto da classe. Vários exemplos mostrados neste livro tem pelo menos um método static: o método main(). Ele serve como ponto de entrada para a execução da aplicação J ava, uma vez que inicialmente não existem objetos. Os métodos static não podem acessar atributos e outros métodos não estáticos da classe, a não ser que exista uma declaração de um objeto da classe no corpo do método. Os métodos estáticos são muito usados em classes utilitárias. Classes utilitárias são classes que servem para agrupar métodos que prestam algum tipo de serviço a outras classes. Por exemplo, o método exit() é um método estático da classe System. Ele serve para encerrar a execução da máquina virtual corrente. Sendo um método estático não é preciso criar um objeto da classe System para acessá-lo. A classe System é uma classe utilitária, contendo vários métodos e atributos estáticos úteis para outras classes. O Modificador final O modificador final pode ser usado em atributos, métodos e classes. O modificador final impede se modifique o que está sendo prefixado. Um atributo prefixado com final indica que uma vez definido seu valor ele não será alterado. Ou seja, é uma constante. A definição do valor do atributo pode ser feito tanto na declaração como durante a execução do programa, sendo que esta última possibilidade não era permitida na versão 1.0 da linguagem J ava. O exemplo IV-9 mostra o uso do modificador final em atributos. class X { public int k =0; } public class Main1 { public final int i = 1; public final int j; public final X x = new X(); J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 77 public Main1(){j = 2;} public static void main(String a[]) { Main1 m = new Main1(); m.x.k= 3; // Erro : o atriuto não pode ser modificado // m.j = 4; System.out.println("Valor de k:"+ m.x.k); System.out.println("Valor de j:"+ m.j); System.out.println("Valor de i:"+ m.i); } } Exemplo IV-9. Uso do modificador final em atributos. Note que no exemplo IV-9 o atributo j é inicializado no construtor e não na declaração. No exemplo o atributo k do objeto referenciado pelo atributo x é modificado, mesmo sendo x um atributo final. Isto ocorre porque x é uma referencia a um objeto e não o próprio objeto, portanto, no caso de referencias a objeto, o modificador final implica que o atributo não pode referenciar outro objeto, mas não significa que o objeto referenciado não possa ser modificado. Um método declarado com o modificador com final não pode ser sobrescrito em uma subclasse. Alguns compiladores podem aproveitar isso e verificar se é possível definir as chamadas a esses métodos como inline. Nas chamadas inline o compilador substitui a chamada ao método pelo o código do método, tornando mais eficiente a execução do programa. O exemplo IV-10 mostra o uso do modificador final em um método. class X { public int k =0; public final void mostra() { System.out.println("Valor de k:"+ k); } } class SX extends X { // Erro: Sobrescrita ilegal public void mostra() { J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 78 System.out.println("k:"+ k); } } Exemplo IV-10. Uso do modificador final em métodos. Uma classe declarada com o modificador com final impede que sejam criadas subclasses desta classe. O programador pode decidir fazer isso por razões de projeto ou de segurança, já que neste caso ele terá certeza que a funcionalidade da classe não será alterada. Outra razão pode ser eficiência, uma vez que os métodos de uma classe final também são final, e portanto podem ser otimizados para serem tratados como inline. Os Modificadores synchronized e native O modificador synchronized é para controlar o acesso a áreas críticas no processamento concorrente em J ava. É usado em métodos e blocos de comandos, e seu uso será discutido em detalhes no Capítulo XI. O modificador native é usado em métodos para indicar que o método foi implementado em outra linguagem de programação. O programador deve apenas indicar a assinatura do método, como mostrado abaixo: native int meuMetodo(int a); O uso de chamadas nativas restringe a portabilidade do programa. No momento a linguagem J ava só suporta chamadas nativas implementadas na linguagem C/C++. Referências Compartilhadas Um outro problema das implementações da classe Pessoa mostradas nos exemplos IV-1 e IV-3 está relacionado com o compartilhamento de referências. No construtor, assim como nos métodos setXXX, o argumento é atribuído diretamente às variáveis de instância. Como toda variável de objeto em J ava é na verdade uma referência para o objeto então as variáveis de instância da classe Pessoa e os argumentos do construtor e dos métodos setXXX são referências a objetos do tipo String. Portanto, ao atribuir diretamente o argumento às variáveis de instância geramos um compartilhamento de referências. Essa situação é melhor ilustrada graficamente, como pode ser visto na figura IV-1, J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 79 onde é mostrado o compartilhamento entre o argumento n e a variável de instância nome após a atribuição. O problema do compartilhamento de referências, é que ele permite acesso aos objetos referenciados internamente pelos objetos. Com isso é possível modificar, com métodos externos ao objeto, o objeto referenciado. No entanto, no caso particular dos objetos da classe String, isso não chega a ser um problema. Strings são constantes, ou seja, seus valores não podem ser alterados após a sua criação e, portanto, objetos Strings podem ser compartilhados sem riscos. Toda alteração em um objeto do tipo String retorna um novo objeto String, deixando a original inalterada. Antes da atribuição n nome null Após a atribuição n nome Figura IV-1.Compartilhamento de referencias. Se o programador desejar alterar o valor de uma cadeia de caracteres sem criar um novo objeto, então ele deve optar por utilizar a classe StringBuffer que implementa uma sequência mutável de caracteres. No entanto, é preciso estar alerta para o problema de compartilhamento de referências. O trecho de código do exemplo IV-11 mostra uma ocorrência de compartilhamento de referência. No exemplo a classe Pessoa é definida usando a classe StringBuffer para armazenar as sequências de caracteres. Os objetos da classe X possuem uma referência a um objeto do tipo Pessoa. Na construção do objeto Pessoa são passados como parâmetros as referências dos objetos StringBuffer do objeto da classe X, gerando um compartilhamento de referencias. Quando é invocado o método m1() do objeto da classe X, este concatena a sequência “fim” ao StringBuffer referenciado por n. Como n compartilha o mesmo objeto com a variável de instância nome do objeto Pessoa, então a alteração também afeta o objeto Pessoa, ocasionando um efeito colateral, provavelmente indesejável. Objeto Objeto J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 80 Para se evitar esta situação é necessário que o objeto crie uma cópia do objeto que está recebendo e retorne uma cópia do objeto que referencia. No caso de objetos do tipo StringBuffer basta construir um novo objeto, passando como parâmetro a String correspondente ao StringBuffer anterior, como mostrado abaixo: nome = new StringBuffer(n.toString()); No entanto, no caso de objetos mais complexos é mais complicado criar uma cópia por meio do construtor. Neste caso é necessário criar uma cópia usando o método clone(). Este método será visto na próxima seção. class Pessoa { StringBuffer nome; StringBuffer telefone; StringBuffer endereço; public Pessoa(StringBuffer n, StringBuffer t, StringBuffer e) { nome = n; telefone = t; endereço = e; } // O restante da da classe é semelhante ao exemplo VI.1 // ... } public class X { StringBuffer n = new StringBuffer("Pedro"); StringBuffer t = new StringBuffer("324-6789"); StringBuffer e = new StringBuffer("Rua 17/148 Natal"); Pessoa p; public X() {p = new Pessoa(n,t,e);} public void m1() { n.append("fim");} public static void main(String a[]) { X x = new X(); x.m1(); System.out.println(x.p.nome); } } J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 81 Exemplo IV-11. Exemplo de compartilhamento de referência. Copiando Objetos Como visto na seção anterior, a atribuição envolvendo duas referencias a objetos acarreta um compartilhamento do objeto e não uma cópia do objeto original. No entanto, em algumas situações não queremos compartilhar um objeto e sim criar uma cópia de um objeto que já existe, ou seja um clone. Existem dois tipos de modos de se clonar um objeto: 1- Cópia rasa (shallow copy) - neste caso os atributos do objeto resultante recebem os valores dos atributos correspondentes no objeto original. Os objetos referenciados pelo objeto original serão compartilhados pelo objeto resultante. A figura IV-2 ilustra este tipo de cópia. Note o compartilhamento das referencias. Figura IV-2.Cópia rasa entre os objetos A e D. 2- Cópia profunda (deep copy) - neste caso não só o objeto original é copiado como também todos os objetos por ele referenciados. Este tipo de cópia pode ser bastante ineficiente. A figura IV-3 ilustra este tipo de cópia. J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 82 Figura IV-3.Cópia profunda entre os objetos A e D. J ava oferece suporte para a cópia rasa. Para que a classe esteja apta a ser clonada é preciso que ela implemente a interface Cloneable (interfaces serão discutidas mais adiante). O objeto this Em algumas situações é necessário referenciar o próprio objeto corrente. Por essa razão todo objeto possui uma variável especial identificada por this que contém uma referência para o próprio objeto. O exemplo IV.xx mostra um uso típico do atributo this. Ele mostra um método cujo parâmetro formal possui o mesmo nome de um atributo de instância. Para distinguí-los é necessário qualificar o atributo da instância com o atributo this. public class objetoGeo { protected Color cor; protected int x, y; public objetoGeo(Color cor, int x, int y) { this.cor = cor; this.x=x; this.y = y; } public Color retCor() {return cor}; } Exemplo IV-12. Exemplo de uso do atributo this. Outro uso típico e quando queremos passar a instância corrente como argumento de um método. Como mostra o exemplo IV.xx. class X { ... J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 83 public void mx(Y y) { ...; } } class Y { X x; public void my() { x.mx(this); ... } } Exemplo IV-13. Exemplo de uso do atributo this como argumento. No exemplo IV.xx o objeto da classe Y passa ele próprio como argumento do método mx() do objeto da classe X. Packages Pacotes (Packages) é a solução proposta por J ava para agrupar Classes e Interfaces relacionadas para compor bibliotecas. As Interfaces serão vistas mais adiante. Organizando as classes em pacotes evita a colisão entre os nomes das classes. No caso dos métodos isso não é necessário, pois métodos como o mesmo nome em classes diferentes são facilmente distinguidos uma vez que são qualificados pelos objetos das classes. No entanto como distinguir classes com o mesmo nome? Esse problema é muito comum, principalmente quando se trabalha em equipe. Como impedir que um programador trabalhando no mesmo projeto crie uma classe com o mesmo nome que outra criada por outro programador? Na hora de unir as classes os sistema não irá compilar devido ao problema de redeclaração de classes. Sendo J ava uma linguagem para atuar na Internet o problema é ainda mais grave: como impedir que uma classe que foi baixada de outra máquina não possua o mesmo nome de uma classe. Para contornar todos estes problemas foi criado os Pacotes. Usando Packages J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 84 Toda classe pertence a um pacote. No caso de o programador não indicar a que pacote pertence a classe, o compilador irá assumir que a classe pertence ao pacote default. Para se usar uma classe definida em outro package é preciso usar a palavra chave import seguida do nome da classe qualificada pelo nome do pacote como mostra a forma geral abaixo: import nome_Package.nomeClasse; Por exemplo para importar a classe Color do pacote java.awt o programador deverá usar a diretiva abaixo: import java.awt.Color; Se o programador quiser importar todas as classes do pacote java.awt basta usar o caractere ´*` no lugar do nome da classe: import java.awt.*; É possível usar uma classe declarada em outro pacote sem usar a palavra chave import. Nesse caso é necessário qualificar o nome da classe com o nome do pacote toda vez que a classe for referenciada. Por exemplo, no caso da classe Color seria necessário qualificá-la da seguinte forma: java.awt.Color Existem dois pacotes que não precisam ser importados para que possam ser usadas as suas classes sem a qualificação: o pacote default e o pacote java.lang. O pacote default agrupa todas a classes que estão no diretório corrente e o pacote java.lang agrupa as classes do núcleo básico da linguagem. Criando Packages Para incluir uma unidade compilação em um pacote basta que o programador inclua a seguinte declaração no arquivo. package nomepacote; J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 85 Importante: a declaração acima precisa ser a primeira declaração no arquivo. O nome do pacote pode ser composto por vários nomes separados pelo caractere ‘.’, como no caso do pacote java.awt. As classes de um determinado pacote devem ser colocadas em um diretório obedecendo a mesma estrutura do nome do pacote, a partir de algum diretório constando na variável de ambiente classpath. A variável de ambiente classpath indica os diretórios que servem como pontos de entrada para procura de classes pela máquina virtual. Por exemplo, suponha que eu resolva colocar as classes sobre objetos geométricos em um determinado pacote, digamos br.com.alcione.geo. Para indicar que unidade de compilação pertence a esse pacote devemos colocar a diretiva adequada no início do arquivo fonte, como mostrado no exemplo IV.xx. package br.com.alcione.geo; public class objetoGeo { protected Color cor; protected int x, y; public objetoGeo(Color cor, int x, int y) { this.cor = cor; this.x=x; this.y = y; }; public Color retCor() {return cor}; } Exemplo IV-14. Indicando que uma classe pertence ao pacote br.com.alcione.geo. Para que a máquina virtual consiga achar o pacote devemos colocá-lo em diretório com a mesma estrutura do nome do pacote e que tenha como raiz algum diretório constante na variável de ambiente classpath. Por exemplo, suponha que a variável classpath contenha os seguintes diretórios: CLASSPATH=.;C:\JAVA\LIB;C:\meujavalib então o arquivo objetoGeo.class resultante da compilação do arquivo objetoGeo.java pode ser colocado no diretório: J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 86 C:\meujavalib\br\com\alcione\geo Note que a estrutura do diretório combina com a estrutura do nome do pacote. Para usar a classe em outra unidade de compilação basta incluir a seguinte diretiva no arquivo fonte: import br.com.alcione.geo.*; O formato do nome do pacote não foi escolhido por acaso. Se existe a pretensão de usá-lo na Internet então deve-se adotar um nome que seja único na rede. Para garantir esta unicidade utiliza-se o nome do domínio do criador do pacote com a ordem dos campos invertida, evitando assim a colisão de nome na Internet. Por exemplo, nome do pacote br.com.alcione.geo origina-se da inversão do nome do meu domínio (alcione.dpi.ufv.br) concatenado com o nome geo. Por simplicidade. na maioria dos exemplos adotados neste livro, adotaremos o pacote default. O Mecanismo de Extensão A partir da versão 1.2 a linguagem J ava apresentou um mecanismo para adicionar classes ao núcleo básico da linguagem, dispensando deste modo o uso da variável de ambiente classpath. Este mecanismo é chamado de Mecanismo de Extensão. O mecanismo de extensão também oferece suporte para envio de classes pela rede para uso em Applets. As extensões são agrupadas em arquivos J AR que são abordados no capítulo que trata dos Applets. Uma vez feito isso, pode-se transformar as classes em extensões utilizando uma das duas formas: 1. Extensão Instalada – colocando o arquivo J AR em um local predeterminado na estrutura de diretórios do ambiente de tempo de execução de J ava(Java Runtime Environment ou J RE). 2. Extensão de download –Extensão referenciando o arquivo J AR de uma forma específica, a partir do manifesto de outro arquivo J AR. Extensão Instalada J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 87 Para incluir uma nova classe ou pacote no núcleo básico da linguagem basta colocar o arquivo J AR onde que contém a classe no diretório /lib/ext do ambiente de tempo de execução. O diagrama mostrado na figura IV.xx mostra a localização deste diretório dentro da estrutura de diretórios do ambiente J ava. Figura IV.xx- localização do diretório ext dentro da estrutura de diretórios Java. Por exemplo, suponha que desejamos acrescentar o pacote do exemplo IX. às extensões da linguagem J ava no ambiente local. Para isso devemos criar primeiro o arquivo J AR. No ambiente DOS o comando seria o abaixo, executado no diretório acima do diretório /br: jar cvf geo.jar br\com\alcione\geo\objetoGeo.class Após isso colocaríamos o arquivo geo.jar dentro do diretório /ext. Para usar a classe o procedimento é idêntico ao uso no caso do classpath, ou seja basta colocar a seguinte diretiva no arquivo fonte: import br.com.alcione.geo.*; Extensão de Download As extensões de download ocorrem quando arquivos J AR ou classes são indicados como extensões de outros arquivos J AR. Por exemplo, suponha que J ava 1.3 lib include bin jre bin bin lib audio cmm fonts ext images security J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 88 existem dois arquivos J AR, x.jar e y.jar, localizados no mesmo diretório. Para tornar o arquivo y.jar uma extensão do arquivo y.jar basta colocar a seguinte informação no manifesto do arquivo x.jar: Class-Path: y.jar Deste modo toda classe no arquivo x.jar pode referenciar as classes do pacote y.jar. Se os arquivos não estiverem no mesmo diretório será necessário indicar o caminho relativo até o arquivo da extensão. É possível indicar várias extensões, separando-as por espaço ou usando várias diretivas Class-Path. Para indicar um diretório como extensão basta adicionar o sufixo ‘/’ no ao nome do diretório. Por exemplo: Class-Path: meudir/ Derivando classes O reuso de componentes de programas tem sido uma das principais metas dos projetistas de linguagens de programação. A razão desta busca é que a reutilização de componentes economiza esforços e acelera o processo de desenvolvimento de sistemas de computadores. Essa meta pode ser atingida em parte por meio da criação de classes que são extensões de classes predefinidas. As extensões herdam todos os atributos da classe base ou superclasse. Por exemplo, suponha que alguém tenha definido uma classe para representar os dados de uma conta bancária: Figura IV.2- Classe Conta. Suponha também que além da conta corrente básica mais dois tipos de conta devam ser criadas: poupança e conta especial. A poupança possui todos os Conta -número:num -Saldo: float -CPF:Num J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 89 atributos de uma corrente e mais um atributo contendo a data de abertura da conta que será usada para cálculo do valor dos juros e correção monetária que será acrescentado ao saldo da conta. A conta especial possui todos os atributos de uma corrente e mais um atributo contendo o valor de limite de crédito. No exemplo descrito acima seria um desperdício de tempo e código se precisássemos codificar todos os atributos e métodos relacionados da classe Conta nessas duas novas classes. Para evitar esta duplicação de esforço basta declaramos essas duas novas classes como subclasses da classe Conta. Desta forma apenas os atributos que não estão declarados na superclasse e métodos associados precisam ser codificados, uma vez que os membros da superclasse são herdados pelas subclasses. A figura IV.3 mostra o diagrama de classe contendo o relacionamento entre as classes e o exemplo IV.13 mostra o código J ava correspondente. J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 90 Figura IV.3- Relação entre as classes. public class Conta { long numero; long cpf; double saldo; public Conta(long n, long c, double s) { numero = n; cpf = c; saldo = s; } public long getNumero(){return numero;} public long getCpf(){return cpf;} public double getSaldo(){return saldo;} public void setSaldo(double s){saldo = s;} public void somaSaldo(double v){saldo += v;} } import java.util.*; public class CPoupanca extends Conta { Date dataAber; public CPoupanca(long n, long c, double s, String d) { super(n,c,s); dataAber = new Date(d); Conta -número:num -Saldo: float -CPF:Num CPoupança -DataAber:Data CEspecial -Limite: float J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 91 } public String getData(){return dataAber.toString();} } public class CEspecial extends Conta { double limite; public CEspecial(long n, long c, double s, double l) { super(n,c,s); limite = l; } public double getLimite(){return limite;} public double getSaldo(){return saldo+limite;} public void setLimite(double l){ limite = l;} } Exemplo IV-15. Implementação das Classes. Podemos notar que para declarar uma classe como subclasse de outra basta colocarmos a palavra-chave extends após o nome da classe e o nome da superclasse após a palavra-chave. Apenas o nome de uma superclasse é permitido, portanto J ava não permite que uma classe seja subclasse de mais de uma classe (herança múltipla). Herança Múltipla A capacidade de possuir mais de uma superclasse é chamada de herança múltipla. A linguagem C++suporta a herança múltipla. No entanto, a implementação desta facilidade é complexa e sua utilização tende a gerar erro. Além disso poucos são os casos que demandam o uso desta solução, e mesmo nestes casos é possível utilizar outras soluções. Por essas razões a linguagem J ava não implementou a herança múltipla. Nos casos onde é necessário que um objeto adote o comportamento de mais de uma classe devemos utilizar as interfaces, como será visto mais adiante. Todos os membros declarados com os modificadores public e protected podem ser acessados diretamente na subclasse. O membros declarados com o modificador private não são visíveis dentro da subclasse. super J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 92 Podemos notar também que no construtor das subclasses do exemplo IV.12 existe uma chamada a um método super(). Este método representa a chamada ao construtor da superclasse. Se não for chamado explicitamente o compilador irá gerar código para chamar o construtor default da superclasse. No entanto, se desejamos chamar explicitamente um construtor com argumentos devemos usar o método super(). A classe Object É importante salientar que toda classe em J ava, com exceção da classe Object é subclasse de alguma classe. Se o programador não declarar a classe base então a classe base será a classe Object. Desta forma a classe Object é a raiz da hierarquia de classes da linguagem J ava. Sendo assim, todo objeto na linguagem J ava herda os seguintes métodos públicos e protegidos da classe Object. Métodos Públicos Descrição public boolean equals(Object obj) Verifica se dois objetos são iguais. A verificação é feita sobre o endereço do objeto . public final native Class getClass() Retorna a classe do objeto em tempo de execução. public native int hashCode() Retorna o valor do objeto. public final native void notify() Notifica um thread que está esperando sobre um objeto. public final void notifyAll() Notifica todos os threads que está esperando sobre um objeto. public String toString() Retorna uma representação em String do objeto. public final void wait() Espera para ser notificado por outro thread. public final void wait(long timeout) Espera para ser notificado por outro thread. public final void wait(long timeout, int nanos) Espera para ser notificado por outro thread. Métodos Protegidos Descrição protected native Object clone() Cria uma cópia do objeto corrente. protected void finalize() Chamado pelo coletor de lixo antes de liberar a área de memória. Tabela IV.xx- Métodos público e protegidos da classe Object. J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 93 Sobrescrita e Polimorfismo Outro ponto interessante que podemos observar no exemplo IV.12 é que a classe CEspecial possui um método com a mesma assinatura que um método da superclasse. É o método getSaldo() que no caso da classe CEspecial deve incluir o limite de crédito como parte do saldo. Neste caso o método da subclasse está sobrescrevendo ou ocultando o método da superclasse. Toda vez que o método getSaldo() for invocado dentro de algum método da classe CEspecial ou for chamado por meio de um objeto da classe CEspecial o método chamado será uma instância do declarado na classe e não do declarado na superclasse. No entanto, é possível invocar uma instância do método declarado na superclasse de dentro de algum método da subclasse, bastando qualificar o método com a palavra chave super como mostrado abaixo: super.getSaldo(); Por exemplo, o método getSaldo() da classe CEspecial poderia ser rescrito da seguinte forma: public double getSaldo() { return super.getSaldo()+limite; } Suponha agora uma classe como mostrado no exemplo IV.13. O método imprimeSaldo() espera um objeto da classe Conta mas recebe um objeto da classe CEspecial. Isto não causa nenhum problema porque todo objeto da classe CEspecial é também um objeto da classe Conta. No entanto fica a pergunta: que método getSaldo() é invocado dentro o método imprimeSaldo()? O da classe Conta ou o da classe CEspecial? public class TestaConta { public void imprimeSaldo(Conta c) { System.out.println(“O Saldo e’:”+c.getSaldo()); } public static void main(String a[]) J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 94 { TestaConta testa = new TestaConta(); CEspecial c= new CEspecial(1, 123456, 500.0, 100.0); testa.imprimeSaldo(c); } } Exemplo IV-16. Teste de polimorfismo. A resposta é: o método chamado pertence a instância da classe Cespecial. Portanto a regra é a seguinte: sempre é chamado o método da declarado na classe a que o objeto pertence e não da classe superclasse. Para que essa regra seja implementada é necessário que a associação da chamada do método com o código do método em casos como no exemplo IV.13 seja feito em tempo de execução. Isso ocorre porque somente em tempo de execução será possível saber a classe do objeto que está sendo recebido como parâmetro. O momento em que ocorre a associação de um identificador com a objeto que identifica é chamado tempo de amarração. Se este momento ocorre em tempo de compilação a amarração é dita estática (static binding). Se associação ocorre em tempo de execução a amarração é dita dinâmica (dynamic binding). Assim, na linguagem J ava, em se tratando da associação da chamada do método ao código do método a amarração é dinâmica. A única exceção é se o método for declarado como final. Neste caso a amarração é estática porque não é possível sobrescrever o método. A implementação da amarração dinâmica é claramente mais ineficiente que a estática, uma vez que o compilador precisa gerar código para determinar a classe do objeto em tempo de execução. No entanto, existem vantagens consideráveis na adoção da amarração dinâmica. Ela facilita o reuso e a extensão de programas. Por exemplo, se criarmos uma nova subclasse da classe Conta que também sobrescrevesse o método imprimeSaldo(), nenhuma modificação precisaria ser feita em métodos como o imprimeSaldo() da classe TestaConta, uma vez que o método correto sempre é chamado. Portanto, podemos ampliar a hierarquia de um conjunto de classes sem precisar alterar, na maioria das vezes, os métodos que fazem uso dessa hierarquia. A amarração dinâmica de métodos também acarreta um comportamento polimórfico. Por exemplo, o objeto que é passado para o método imprimeSaldo() pode exibir comportamento diversos, dependendo da classe a qual realmente o objeto pertence. Amarração Dinâmica em C++ J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 95 Apesar da amarração dinâmica entre os identificadores dos métodos e o corpo dos métodos ser considerada uma característica importante para linguagens orientadas a objetos, nem todas a linguagens deste estilo de programação adotam este comportamento como padrão. Por exemplo, a linguagem C++adota a amarração estática como padrão. Se o programador desejar que os métodos possuam amarração dinâmica então o deve instruir o compilador explicitamente prefixando os métodos com a palavra-chave virtual. A razão para esta decisão dos projetistas da linguagem está na eficiência. A amarração dinâmica é bem mais ineficiente que a amarração estática e a linguagem C++se propõe a ser uma linguagem que é capaz de gerar código que executa eficientemente. O exemplo IV.14 procura esclarecer melhor a amarração dinâmica dos métodos na linguagem J ava. public class Animal { public void fala(){} } public class Cachorro extends Animal { public void fala(){System.out.println(“Au au!”);} } public class Gato extends Animal { public void fala(){System.out.println(“Miau!”);} } public class UsaAnimal { public void falaAnimal(Animal a){a.fala();} } Exemplo IV-17. Classes de vozes dos animais. No exemplo IV.14 o método falaAnimal() da classe UsaAnimal recebe qualquer subclasse da classe Animal é invoca o método fala() da subclasse. Para incluir mais um animal na hierarquia basta declarar mais uma subclasse: public class Pato extends Animal { public void fala(){System.out.println(“Quá Quá!”);} } J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 96 Exemplo IV-18. Classe com a voz do pato. Nada precisa ser alterado nas outras classes. A nova classe pode ser inclusive subclasse de outra subclasse que não haverá problema: public class GatoRonronando extends Gato { public void fala(){System.out.println(“Purr!”);} } Exemplo IV-19. Classe com a voz do pato. Podemos observar que a classe Animal propriamente dita não faz nada a não ser servir de “cabide” para as outras classes. É pouco provável que ela seja instanciada, já que o seu único método não faz nada. Classes como essa funcionam como um esquema para as subclasses e deveriam ser tratadas adequadamente pelo compilador. Este tópico é tratado adequadamente na próxima seção. Classes e Métodos Abstratos Classes abstratas são esquemas ou esqueletos de classes cujo único propósito e servir de “cabides” para “pendurar” classes. Uma classe abstrata não pode ser instanciada e possui um ou mais métodos abstratos. Um método abstrato é um método sem corpo cuja a assinatura é precedida da palavra-chave abstract. Na verdade o programador não é obrigado a declarar métodos abstratos em classes abstratos, porém raramente surgira uma situação em que não isso não faça sentido. As subclasses precisam sobrescrever os métodos abstratos, caso contrário também não poderão ser instanciadas. A classe Animal do exemplo IV.14 é uma ótima candidata à classe abstrata. Para torná-la uma classe abstrata basta preceder a declaração da classe com a palavra chave abstract e declarar um ou mais de seus métodos como abstratos, como mostra o exemplo IV.17. abstract public class Animal { abstract public void fala(); } J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 97 Exemplo IV-20. Classe abstrata Animal. Além de não poder ser instanciada a classe abstrata possui outras limitações. Uma classe abstrata não pode possuir métodos estáticos e construtores. Outra limitação é que os métodos abstratos não podem ser privados, uma vez que as subclasses precisam sobrescrever os métodos abstratos. Interfaces As Interfaces podem ser encaradas como classes abstratas completamente não implementadas. Ou seja, todos os métodos são abstratos e os atributos só podem ser do tipo static final (constantes). A forma geral de uma Interface obedece o seguinte esquema: interface identificador { corpo da interface } A classe que implementa uma interface precisa fornecer uma implementação para todas os métodos definidos na Interface. Para que uma determinada classe seja vista como uma implementação de uma interface é preciso indicar isto colocando a palavra chave implements na declaração da classe e após a palavra chave a lista com o nome de todas as interfaces que se deseja implementar, separados por vírgula. O esquema abaixo mostra a forma geral do uso de Interfaces. class Identificador implements Interface1, Interface2,... { corpo da classe } O leitor deve estar se perguntando: se a interface é apenas uma classe abstrata completamente não implementada então porque não criar simplesmente uma classe abstrata? A resposta está relacionada com o tipo de herança que ocorre entre as classes de J ava. Como já mencionamos a linguagem J ava não permite a herança múltipla, ou seja, uma classe só pode ser subclasse de apenas uma classe. No entanto, existem situações em que é necessário definir uma classe que possa ser vista de modos distintos. É ai que entram as Interfaces. A interface define uma forma de ver uma classe, restando para a classe a implementação desta visão e a linguagem J ava permite que uma classe J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 98 implemente tantas Interfaces quantas se desejar. Desta forma J ava evita os principais problemas da herança múltipla (herdando múltiplas definições e não múltiplas implementações), sem abrir mão de criar classes que podem ser vistas de formas diferentes. Por exemplo, suponha que um método receba um objeto que representa a interface de saída de um determinado programa. O objetivo deste método é usar um método do objeto de interface para escrever uma mensagem na tela. O exemplo IV.xx mostra o código da classe que contém o método. public class EscreveMem { public static void escreve(InterUsuario inter) { inter.exibeMensagem(“ola mundo!”); } } Exemplo IV-21. Classe que recebe um objeto de interface. Suponha também que desejamos que o programa tenha tanto uma interface com o usuário em modo gráfico e em modo texto. Usaremos de agora em diante as iniciais IU para significar interface com o usuário para evitar a confusão com o conceito de Interfaces que estamos introduzindo. Se desejamos que o método escreve() possa receber instâncias dos dois tipos de IU é necessário que ambas sejam do tipo InterUsuario. No entanto, como veremos mais adiante, as classes que implementam IU gráficas devem ser subclasses de classes predefinidas como por exemplo da classe java.awt.Frame. Portanto, temos agora um problema de herança múltipla. A classe que implementa a IU gráfica de herdar de uma classe como a Frame e da classe InterUsuario. A solução é simples. Basta definir InterUsuario como uma interface como mostra o exemplo IV.xx. interface InterUsuario { abstract public void exibeMensagem(String men); } Exemplo IV-22. Definição de como InterUsuario interface. Agora basta que cada classe que representa um tipo de IU implemente a interface InterUsuario. O exemplo IV.xx mostra a implementação de uma J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 99 IU gráfica simples e o exemplo IV.xx mostra a implementação de uma IU em modo texto simples. import java.awt.*; import java.awt.event.*; public class Janela extends Frame implements InterUsuario { private Label label1; public Janela() { addWindowListener (new WindowAdapter() { public void windowClosing(WindowEvent evt) { System.exit(0); } } ); setLayout (new BorderLayout()); label1 = new Label(); label1.setText ("Mensagem"); add (label1, "South"); setSize(200,100); } public void exibeMensagem(String men) { label1.setText(men); } } Exemplo IV-23. IU gráfica. public class Console implements InterUsuario { public void exibeMensagem(String men) { System.out.println(men); } } Exemplo IV-24. IU em modo texto. O exemplo IV.xx mostra uma forma de uso das classes definidas nos exemplos de IV.xx a IV.xx. J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 100 public class UsaIUs { public static void main(String args[]) { Console c = new Console(); Janela j = new Janela(); j.setVisible(true); EscreveMem. escreve(c); EscreveMem. escreve(j); } } Exemplo IV-25. Uso das IUs. As Interfaces também são úteis sob o aspecto de projeto. Em C++os programadores estão acostumados separar a interface de uma classe de sua implementação. Geralmente dentro das classes são declaradas apenas as interfaces dos métodos sendo que a implementação dos mesmos pode ser feita posteriormente. Isto permite que se possa dividir a etapa da especificação do uso de uma classe (o que ela faz) de sua implementação (como ela faz). Isto reduz a complexidade da tarefa de implementação e permite melhor divisão de tarefas. No entanto, na linguagem J ava os métodos de uma classe são geralmente implementados dentro da classe. As Interfaces e Classes podem ser utilizadas para permitir a separação entre a interface e a implementação a exemplo de como e feito em C++. Classes Internas A partir da versão 1.1 da linguagem J ava foi incluída a permissão para declarar uma classe dentro da declaração de outra classe. As classes declaradas dentro de outra classe são denominadas de Classes Internas. A figura IV.xx mostra um esquema contendo o posicionamento relativo de uma classe interna. public class Externa { ... class Interna { ... } J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 101 } Figura IV.xx- Posicionamento relativo das classes Interna e Externa. Os atributos e métodos declarados na classe externa são visíveis pela classe interna, mesmo os declarados como protected ou private. No entanto, o contrário não é verdadeiro, ou seja, os atributos e métodos da instância da classe interna só são visíveis pela classe interna se forem declarados como públicos. Como já foi dito as classes mais externas (nível topo) só podem ser declaradas como públicas ou friendly (default). No entanto, as classes internas podem também receber os qualificadores private e protected. O efeito destes qualificadores sobre a visibilidade da instância da classe é o mesmo obtido sobre um atributo qualquer. O exemplo IV.xx mostra o caso de uma classe contendo uma classe interna privada. O exemplo tem por objetivo apenas ilustrar o uso de classes internas. public class Calc { Incrementa inc1, inc5; public Calc () { inc1=new Incrementa(1); inc5=new Incrementa(5); } private class Incrementa { int i; public Incrementa(int ai){i=ai;} public int inc(int num){return i+num;} } public int calcula(int x){return inc1(x)+inc5(x);} } Exemplo IV-26. Uso de classes internas. A inclusão de classes internas foi uma modificação a nível de linguagem em J ava e o leitor deve estar se perguntando o que motivou a uma modificação tão profunda em J ava. A reposta está no novo modelo de eventos proposto pela versão 1.1. No novo modelo, como poderá ser constatado no capítulo que trata sobre a AWT, as classes internas se adequam perfeitamente. Aliado a esta J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 102 motivação existe o fato de que esta alteração na linguagem não acarretou nenhuma modificação na máquina virtual, tratada pelo compilador. Desta modo esta facilidade não gerou qualquer impacto em termos desempenho dos programas. O exemplo IV.xx mostra o exemplo de uso de uma classe interna para capturar eventos. Este tipo de uso será tratada com maiores detalhes no capítulo que trata sobre AWT. public class Janela extends Frame { public Janela (String Titulo){ super(Titulo); addWindowListener(new JanelaListener()); setSize(100,50); } private class JanelaListener implements WindowListener { public void windowOpened(WindowEvent we){} public void windowClosed(WindowEvent we){} public void windowIconified(WindowEvent we){} public void windowDeiconified (WindowEvent we){} public void windowActivated(WindowEvent we){} public void windowDeactivated(WindowEvent we){} public void windowClosing(WindowEvent we){ setVisible(false); dispose(); System.exit(0); } }; }; Exemplo IV-27. Uso de classes internas para receber eventos. Classes Internas Anônimas J ava permite que se crie um objeto de uma classe sem nome. As classes são declaradas no momento do retorno de um método ou durante a passagem de parâmetros e o objeto é criado quando o argumento ou a expressão de retorno é avaliada. O exemplo IV.xx mostra o retorno de um objeto de uma classe anônima. public class TesteAnonima { public Object retAnon() J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 103 { return new Object() { // Definição da classe anônima private int i=10; public int value(){return i;} }; } }; Exemplo IV-28. Retorno de um objeto de uma classe anônima. Pode parecer pelo exemplo IV.xx que o objeto retornado é uma instância da classe Object, no entanto é na verdade uma instância de uma classe anônima que é subclasse da classe Object. A classe anônima possui atributos e métodos próprios que a diferenciam da classe Object e estão definidos na declaração da classe. Usamos a classe Object apenas para exemplificar com uma classe conhecida. Qualquer classe pode fazer o papel de superclasse. O exemplo IV.xx mostra o uso de uma classe anônima na criação de um objeto para lidar com eventos sobre um botão. class BT { Button bt; public BT() { bt = new Button(“OK); bt.addActionListener( new ActionListener() { public void actionPerformed(ActionEvent e) { dispose(); } }); } } Exemplo IV-29. Uso de uma classe anônima para tratamento de eventos. As classes anônimas são adequadas para situações onde necessitamos em um local específico de um objeto de uma classe ligeiramente diferente de outra classe já declarada. Com a classe anônima evitamos ter que declarar uma nova classe para atender apenas uma necessidade local. J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 104 Conversão Uma variável declarada como referencia para objetos de uma determinada classe pode receber referencias de objetos de subclasses sem a necessidade de se usar qualquer operador de conversão. A conversão é feita automaticamente, uma vez que toda instância de uma subclasse é também uma instância da superclasse. Por exemplo: Object o; String s = “teste”; o = s; A atribuição acima é possível porque a classe Object é uma superclasse de String (Na verdade é superclasse de todas as classes). J á a conversão inversa só é possível se o objeto for realmente do tipo da subclasse, e mesmo assim é preciso utilizar um operador de conversão forçada (casting): Object o; String s = “teste”; String t = “teste”; o = s; t = (String) o; Exceções Quanto maior o número de situações de exceção que um determinado programa consegue lidar, mais robusto é este programa. Para construir um programa robusto o programador deve prever, além das situações onde os dados de entrada e os recursos se apresentam de forma adequada, as situações onde algo pode impedir a computação normal. Existem três abordagens típicas para esses casos: 1) Ignorar – neste caso a ação será a padrão do sistema, provavelmente a interrupção do programa, ou no mínimo do comando onde ocorreu o erro, e a exibição de uma mensagem ininteligível na saída padrão, gerada pelo sistema. Esta é abordagem mais simples, mas certamente não é a mais interessante, principalmente do ponto de vista do usuário final. J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 105 2) Retornar o código de erro. No caso da linguagem J ava este retorno só pode ser feito via valor de retorno da função. No entanto algumas vezes não existem valores disponíveis para indicar o erro. Por exemplo, quando um método retorna um valor inteiro costuma-se usar os valores 0 ou –1 para sinalizar uma condição de erro, mas como agir no caso desses e todos os outros valores de inteiros serem valores válidos em uma execução normal? Existe ainda o problema de que esta abordagem não resolve o problema de erros que ocorrem nos construtores. Como os construtores não retornam valores não é possível sinalizar erros dessa forma. Esta é uma abordagem muito comum entre os programadores de C e Pascal, e é ainda usada pelos programadores de C++. No entanto, as linguagens mais modernas como C++e J ava disponibilizam recursos que permitem um melhor tratamento de exceções, como veremos na terceira abordagem. 3) Declarar um ou mais tratadores de exceções. Um tratador de exceção é uma parte do código que tem por objetivo recuperar a execução do programa após a ocorrência da exceção, permitindo que o sistema se comporte “suavemente”, mesmo sob condições adversas. A sintaxe e semântica dos tratadores de exceção variam muito de linguagem para linguagem, mas tem tendido a uma certa uniformização nas linguagens mais modernas. A linguagem J ava, por ter como objetivo ser uma linguagem segura, obriga que os programador capture todas as exceções, menos as derivadas da classe RuntimeException. Na linguagem J ava a amarração do tratador à exceção é definida em tempo de compilação. Nela, o programador envolve os comandos passíveis de gerar exceções em blocos try/catch com o formato geral mostrado abaixo: try { código que pode gerar exceções } catch(classe de exceção1 objeto) { tratamento da exceção1 } catch(classe de exceção2 objeto) { ... } catch(classe de exceçãoN objeto) { tratamento da exceçãoN } J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 106 As exceções são representadas como objetos A porção catch do bloco possui um bloco com comandos para tratamento da exceção. O bloco que trata das exceções não tem acesso às variáveis declaradas dentro do bloco try{}, uma vez que o escopo destas variáveis está delimitado pelo bloco. Por exemplo, o método estático parseInt() da classe Integer lança como exceção um objeto da classe NumberFormatException se receber como argumento um String que não pode traduzido para uma representação inteira. O código para capturar este tipo de exceção poderia ser semelhante ao apresentado no exemplo XX.XX. public class Excecao1 { public static void main(String a[]) { try { int i = Integer.parseInt(a[0]); System.out.println("A cadeia passada pode ser ”+ ”tranformada para número :“+i); } catch(NumberFormatException e) { System.out.println("A cadeia passada não pode ser ”+ ”tranformada para número :“+a[0]); } } } Exemplo IV.XX- Capturando a exceção NumberFormatException. Se o bloco catch não possuir nenhum comando de return ou alguma chamada à função exit(), então o programa é reassumido após o último bloco catch. A hierarquia de Exceções As exceções são objetos que pertencem a classes que estão organizadas em uma hierarquia e cuja classe que se encontra no topo da hierarquia é a classe Throwable. A classe Throwable possui duas subclasses: Exception e Error. Exception e suas subclasses são usadas para indicar condições que podem ser recuperadas. Error e suas subclasses indicam condições que em geral não podem ser recuperadas, causando a terminação do programa. J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 107 Todos os objetos da classes Exception devem ser capturados ou explicitamente relançados para um nível acima na pilha de chamadas. Isto só não é válido para os objetos da classes RuntimeException. A RuntimeException é suas subclasses fazem definem as exceções que podem ser lançadas durante a execução normal da máquina virtual, como divisão por zero ou indexação fora dos limites do array. A exceção NumberFormatException mostrada no exemplo é uma subclasse da RuntimeException. Como essas exceções podem ser lançadas por um número muito grande de métodos, seria trabalhoso para o programador se ele fosse obrigado a incluir código para capturar todas essas exceções. Portanto, instâncias da classe RuntimeException e suas subclasses não precisam ser capturadas. A figura IV.XX mostra uma parte da hierarquia Throwable até o nível 3. O número de classes nesta hierarquia é muito grande como o leitor pode verificar no apêndice XX. O programador pode também adicionar classes a esta hierarquia como veremos mais adiante. Figura IV.XX- Hierarquia de exceções. Capturando mais de uma exceção Um determinado método pode lançar mais de uma exceção, assim como pode existir mais de um método dentro de um bloco try/catch com potencial de lançar exceções. Portanto, muitas vezes é preciso definir um bloco try/catch com capacidade de tratar mais de uma exceção. Isto é feito por meio da definição de mais de uma cláusula catch. Cada cláusula catch trata uma exceção. Como a busca pela cláusula catch que deve tratar uma determinada exceção é feita de cima para baixo é preciso tomar o cuidado de posicionar as o tratamento das exceções mais genéricas mais abaixo do que o tratamento das exceções mais específicas. Caso contrário o tratamento das exceções mais Throwable Exception RuntimeException Error ThreadDeathError VirtualMachineError LinkageError . . . . . . J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 108 especificas nunca será executado. O exemplo IV.xx mostra um trecho de código cujo bloco try/catch é capaz de capturar as exceções IOException, NumberFormatException e Exception. Note que por ser uma exceção mais genérica que as outras duas o tratamento da exceção Exception é posicionado após os outros. Como as exceções NumberFormatException e IOException não se encontram na mesma linha de herança, a posição de uma relativa a outra não tem significado. import java.io.*; class Excecao2 { public static void main(String a[]) { try { BufferedReader in = new BufferedReader( new InputStreamReader(System.in)); System.out.print("Entre um número:"); int i = Integer.parseInt(in.readLine()); }catch(IOException e) {System.out.println(“Erro de leitura!”);} catch(NumberFormatException e) {System.out.println(“Não é um número!”);} catch(Exception e) {System.out.println(“ocorreu algum erro”);} } } Exemplo IV.XX- Capturando a várias exceções. Lançando exceções Até agora temos visto apenas como capturar as exceções lançadas. Está na hora de olharmos o outro lado da moeda, ou seja, como lançar as exceções. As exceções são lançadas utilizando-se o operador throw. A forma geral do comando é a seguinte: throw <objeto> Pode-se também criar o objeto no instante do lançamento, invocando o construtor. Neste caso a forma geral do comando seria a seguinte: J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 109 throw new <classe>(<parâmetros>) Note que pode-se passar parâmetros para o construtor que podem ajudar na recuperação do problema pelo tratador de exceções. O método onde a exceção é lançada deve tratar a exceção ou passá-la para método que o invocou. Para passar a exceção para o método que o invocou a assinatura do método corrente deve conter uma cláusula throws após a lista de argumentos, como mostrado abaixo: <modificador><tipo retorno> <identificador>(<argumentos>) throws <exceção 1 >,<exceção 2 >,...,<exceção n > { } Note que é possível indicar na assinatura do método o repasse de várias exceções. O exemplo IV.xx mostra tanto o lançamento de um objeto da classe Exception caso os parâmetros para o construtor da classe não sejam corretos. import java.io.*; class NumPos{ private int num; public NumPos(int aNum) throws Exception { if (aNum < 1) throw new Exception(“Número não positivo”); num = aNum; } } public class Excecao3 { public static void main(String a[]) { try { NumPos np = new NumPos(Integer.parseInt(a[0])); }catch(Exception e) {System.out.println(e.getMessage());} } } Exemplo IV.XX- Lançando exceções. Na verdade o pode-se usar a cláusula throws na assinatura do método para repassar qualquer exceção que se desejar. O exemplo IV.xx , mostra o J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 110 método m1 que repassa qualquer objeto da classe Exception que for lançado em seu corpo, mesmo os que não forem explicitamente lançado por meio do comando throw: public class Excecao4 { public void m1(int i) throws Exception { if (i == 0 ) throw new NumberFormatException(); System.out.println(2/i); } } Exemplo IV.XX- Repassando todas as exceções. Comportamento do Sistema diante das Exceções Agora que já vimos como tratar e como lançar exceções podemos discutir como é o comportamento global do sistema diante das exceções. Ou seja, como é a sequência de busca por um tratador após a ocorrência de uma exceção? Quando ocorre uma exceção em um método o sistema verifica se o comando onde ocorreu a exceção está incluso em bloco try/catch com uma cláusula catch associada à exceção. Neste caso o controle é transferido para o bloco de tratamento da cláusula catch. Caso contrário, o sistema desempilha um nível na pilha de chamada de métodos e verifica se a chamada ao método anterior está inclusa em bloco try/catch com uma cláusula catch associada à exceção. O sistema prossegue desta forma até que um bloco tratador seja encontrado. Caso encontre o bloco tratador e este não possua nenhum comando de saída (return ou System.exit()), então o controle será retomado no nível de chamada onde foi tratado após o último bloco catch. e não onde ocorreu a exceção. Caso não encontre nenhum bloco tratador, ao atingir o nível mais alto da pilha de chamadas o programa é abortado. A figura IV.xx procura ilustrar o comportamento do sistema diante de uma exceção, tratada em um nível da pilha de chamada diferente de onde ocorreu. Pilha de chamadas Código fonte m1 m2 m3 public class Excecao5 { public void m1(int i) { try { m2(i) }catch (Exception e){...}; ... } public void m2(int i) throws Exception { m3(i); } J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 111 Figura IV.XX- Propagação das exceções. Criando suas próprias exceções Se o programador concluir que nenhuma exceção pré-existente se encaixa no tipo de erro que pretende sinalizar, então criar uma classe nova para representar essa exceção. A única condição é que a nova classe seja uma extensão da subclasse Throwable ou de uma de suas subclasses. Procure estender a classe Exception ou uma de suas subclasses, exceto o ramo da RuntimeException, uma vez que, como já foi dito, esta ramificação da hierarquia de exceções é utilizada para exceções do sistema. Por exemplo, podemos rescrever o exemplo IV.xx de modo que o método lance uma exceção definida pelo programador. import java.io.*; class NumPosException extends Exception { public NumPosException(String m){super(m);} } class NumPos { private int num; public NumPos(int aNum) throws NumPosException { if (aNum < 1) ocorrência da exceção tratamento da exceção J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 112 throw new NumPosException (“Número não positivo”); num = aNum; } } public class Excecao6 { public static void main(String a[]) { try { NumPos np = new NumPos(Integer.parseInt(a[0])); }catch(NumPosException e) {System.out.println(e.getMessage());} } } Exemplo IV.XX- Lançando exceções. A cláusula finally Existe mais uma cláusula opcional do bloco try/catch. Esta é a cláusula finally. A cláusula finally abriga uma trecho de código que deve ser executado independente se ocorreu ou não uma exceção no bloco try/catch. Pode ser usada para realizar operações de finalizações, como por exemplo fechamento de arquivos e canais de comunicação. O exemplo IV.xx ilustra o uso da cláusula finally. import java.io.*; class Excecao7 public static void main(String a[]) { try { BufferedReader in = new BufferedReader( new InputStreamReader(System.in)); System.out.print("Entre um número:"); int i = Integer.parseInt(in.readLine()); }catch(IOException e) {System.out.println(“Erro de leitura!”);} catch(NumberFormatException e) {System.out.println(“Não é um número!”);} finally() {System.out.println(“Terminou.”);} J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 113 } } Exemplo IV.XX- Usando a cláusula finally. No exemplo IV.xx se não ocorrer nenhuma exceção a cláusula finally é executada e a execução é reassumida após a cláusula finally. Caso ocorra alguma exceção no bloco try/catch e exista alguma cláusula catch associada à exceção então a cláusula finally é executada após o tratamento da exceção e a execução é reassumida após a cláusula finally. Caso ocorra alguma exceção no bloco try/catch e não exista alguma cláusula catch associada à exceção então a cláusula finally é executa antes da transferência do controle para o método chamador. Documentando o código Uma das maiores dificuldades no desenvolvimento de software é conseguir que os programadores produzam uma documentação relacionada com os programas que estão desenvolvendo. Eles não gostam de interromper o desenvolvimento para escrever paralelamente a documentação e após o término da implementação não se animam a passar um grande período de tempo documentando todo o código escrito. Pensando nisso os projetistas de J ava desenvolveram um meio do programador embutir a documentação no próprio código, de modo que, não precisasse interromper a implementação. A documentação é inserida como um comentário e usa uma sintaxe especial que pode ser interpretada pelo programa javadoc para gerar um documento HTML. Todo comentário que deve ser interpretado pelo javadoc deve ser iniciado por “/**” e terminado por “*/”. O programador pode inserir a documentação de duas formas: 1. rótulos (tags) iniciados pelo caractere ‘@’ e que denotam comandos de documentação; ou 2. por meio de código HTML embutido. J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 114 Rótulos O javadoc relaciona os comentários com a estrutura codificada após o comentário. As estruturas relacionadas com os comentários são as classes, variáveis e métodos. A exceção do rótulo @see todos os rótulos estão relacionados com a documentação de classes ou métodos. Rótulo @see O rótulo @see é usado para gerar links para documentação de outras classes. Os links não serão verificados pelo javadoc. Os formatos possíveis são os seguintes: @see <nome da classe> @see <nome da classe>#<nome do método> Rótulos para documentação de classes Os seguintes rótulos são usados na documentação de classes: Rótulo Descrição @version <versão> Usado para incluir informação sobre a versão. @author <autor> Usado para incluir informação sobre o autor. Tabela IV.xx- Rótulos para documentação de classes. Rótulos para documentação de métodos Os seguintes rótulos são usados na documentação de métodos: Rótulo Descrição @param <nome> <descrição> Usado para incluir informação sobre o parâmetro do método. @return <descrição> Usado para incluir informação sobre o valor de retorno do método. @exception <nome da classe> <descrição> Usado para incluir informação sobre a exceção que pode ser lançada pelo método. J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 115 @deprecated Incluído a partir da versão 1.1. Indica que o método pode não ser mantido nas próximas versões da linguagem. Tabela IV.xx- Rótulos para documentação de métodos. Exemplo de Documentação O exemplo IV.xx ilustra o uso dos rótulos para documentação do código. /** Pessoa Mantem os dados pessoais de uma pessoa. @author Alcione de Paiva Oliveira @author [email protected] @version 1.0 */ public class Pessoa { private String Nome; private String Tel; private String End; // Construtor /** @param n String contendo o nome @param t String contendo o telefone @param e String contendo o endereço @return não retorna valor */ public Pessoa(String n, String t, String e) { Nome = n; Tel = t; End = e; } /** Retorna o nome da pessoa @return uma String contendo o nome */ public String getNome(){return Nome;} /** Retorna o telefone da pessoa @return uma String contendo o telefone */ public String getTel(){return Tel;} /** Retorna o endereço da pessoa J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 116 @return uma String contendo o endereço */ public String getEnd(){return End;} } Exemplo IV.XX- Exemplo de documentação. HTML embutida O programador pode incluir código HTML dentro dos comentários para documentação da mesma forma que faria em um documento da Web, como mostra o exemplo IV.xx. /** * <b>Pessoa<\b> * Possui os seguintes objetivos * <ol> * <li> Objetivo1 * <li> Objetivo2 * </ol> */ public class Pessoa { ... } Exemplo IV.XX- Uso de HTML embutida para documentar código. O javadoc irá ignorar os caracteres ‘*’ no início de cada linha ao gerar a documentação. Não use rótulos de títulos como <h1> ou <hr>, uma vez que o javadoc irá inserir rótulos de títulos automaticamente. Agenda Eletrônica versão Console 1.0 Iniciaremos agora a primeira versão de um programa que implementa uma agenda eletrônica de endereços e telefones. Este programa evoluirá ao longo do livro, de acordo com os conhecimentos que serão apresentados. Em sua primeira versão a agenda eletrônica terá uma interface baseada em linha de comando e não armazenará o seu conteúdo em algum dispositivo de memória J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 117 permanente, o que não a torna muito útil em termos práticos. Nas versões posteriores a agenda eletrônica armazenará o seu conteúdo em arquivos e Banco de Dados, ganhará interface gráfica, poderá ser utilizada em navegadores de Web, terá uma arquitetura Cliente/Servidor, e outras facilidades. Esperamos com isso mostrar os vários aspectos da linguagem durante a evolução do programa. Outros exemplos serão apresentados de acordo com a necessidade, porém a agenda eletrônica permanecerá como exemplo principal. O diagrama de classes da figura III.xx mostra os objetos que compõe a versão console da agenda. Figura III. – Diagrama de classes da versão console da agenda eletrônica. Os objetos da classe Pessoa armazenam os dados de uma pessoa. Ela fornece os métodos para o acesso a esses dados. O objeto da classe Agenda é composto por vários dados de pessoa. Este fato está representado no diagrama pela associação de agregação. A classe Agenda possui os métodos necessários para inserção e recuperação dos dados. O método inserir(), trata de inserir uma pessoa na lista de pessoas, o método getPessoas(), retorna uma lista contendo todos os objetos da classe Pessoa e o método getPessoa() retorna um objeto da classe Pessoa com o nome especificado. AgendaInt +obterPessoa() +exibirLista() +exibirPessoa() Agenda +inserir(pessoa p) +pessoas getPessoas() +Pessoa getPessoa(String Nome) Pessoa -String Nome -String Tel -String End +getNome() +getTel() +getEnd() 0..* J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 118 A Classe AgendaInt é responsável por fornecer os métodos que implementam a interface com o usuário. Ela foi criada por questões de projeto, uma vez que é importante manter separado o código das interfaces do sistema. Primeiramente apresentaremos o código da classe Pessoa. /** Pessoa */ public class Pessoa { private String Nome; private String Tel; private String End; // Construtor public Pessoa(String n, String t, String e) { Nome = n; Tel = t; End = e; } // Metodos public String getNome(){return Nome;} public String getTel(){return Tel;} public String getEnd(){return End;} } Por questões de simplicidade a classe pessoa possui apenas um construtor. As variáveis de instância são declaradas private para prevenir acessos que não sejam por meio dos métodos da classe. Segue abaixo o código da classe Agenda: /** AGENDA Versão Console 1.0. */ public class Agenda { Pessoa pessoas[]; // Construtor public Agenda() {pessoas = null;} J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 119 // Metodos /** inserir */ public void inserir(Pessoa p) { if (pessoas == null) pessoas = new Pessoa[1]; else AumentaCapacidade(); pessoas[pessoas.length-1] = p; } /** Consultar */ public Pessoa getPessoa(String nome) { Pessoa aux = null; for (int i=0; i< pessoas.length; i++) if (pessoas[i].getNome().equals(nome)) aux = pessoas[i]; return aux; } /** listar */ public Pessoa[] getPessoas(){return pessoas;} /** AumentaCapacidade */ private void AumentaCapacidade () { Pessoa aux[] = new Pessoa[pessoas.length+1]; for (int i=0;i< pessoas.length;i++) aux[i] = pessoas[i]; pessoas = aux; } } De modo a manter o programa com poucas linhas não inserimos código para tratar erros para testar os valores que são passados para os métodos ou retornados por eles. O leitor deve ter isto em mente se por acaso pretende utilizar trechos deste programa para aplicações profissionais. J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 120 Existem várias formas de representar a associação entre duas classes. Escolhemos representar a agregação por meio de um array de objetos da classe Pessoa. Um outra forma, mais fácil de se trabalhar, será apresentada na seção xx. A variável que referencia o array é inicializada com null. Toda vez que é inserido um novo objeto o array precisa ser redimensionado. Isto é feito pelo método privado AumentaCapacidade(). Na verdade o método cria um novo array maior em uma unidade que o anterior. Esta não é uma solução muito eficiente. O melhor seria incrementar o array em várias unidades, de modo que o redimensionamento seria necessário em intervalos menores. Contudo, o código necessário para gerenciar a inserção seria bem mais complexo. O método getPessoa() retorna um objeto com o dado nome igual ao passado ao método. Se não existir tal objeto é retornado null. J á o método getPessoas() retorna uma referencia para array de objetos do tipo Pessoa. /** AgendaInt Interface console da agenda. */ import java.io.*; public class AgendaInt { Agenda ag; BufferedReader in; // Construtor public AgendaInt() { ag = new Agenda(); in = new BufferedReader(new InputStreamReader(System.in)); } // Metodos /** Exibirlista */ public void Exibirlista() { Pessoa p[]=ag.getPessoas(); for (int i= 0; i<p.length; i++) System.out.println("\nNome:"+p[i].getNome()+"\nTelef one:" +p[i].getTel()+"\nEndereço:"+p[i].getEnd()+"\n"); J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 121 } /** exibirPessoa */ public void exibirPessoa() { String nome=null; try { System.out.println("Entre com o nome:"); nome = in.readLine(); if (nome.length()<1) System.exit(-1); } catch(IOException e) {System.out.println(e.getMessage());System.exit(-1);} Pessoa p = ag.getPessoa(nome); if (p!=null) { System.out.println("\nNome:"+p.getNome()+"\nTelefone:" +p.getTel()+"\nEndereço:"+p.getEnd()); } } /** obterPessoa */ public void obterPessoa() { String nome; String tel; String end; try { System.out.println("Entre com o nome:"); nome = in.readLine(); if (nome.length()<1) System.exit(-1); System.out.println("\nEntre com o Telefone:"); tel = in.readLine(); System.out.println("\nEntre com o Endereço:"); end = in.readLine(); ag.inserir(new Pessoa(nome ,tel,end)); } catch(IOException e) {System.out.println(e.getMessage());System.exit(-1);} } // main public static void main(String args[]) { J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 122 AgendaInt agInt = new AgendaInt(); String opcao=""; for(;;) { System.out.println( "\nAgenda Tabajara\n***********************\n"); System.out.print( "Opcoes:\n(i)nserir\n(c)onsultar\n(l)istar\n(f)im=>" ); try { opcao = agInt.in.readLine(); if (opcao.length()==0) continue; } catch(IOException e) {System.out.println(e.getMessage());System.exit(- 1);} switch(opcao.charAt(0)) { case 'f': System.exit(0); break; case 'i': agInt.obterPessoa(); break; case 'c': agInt.exibirPessoa(); break; case 'l': agInt.Exibirlista(); break; } } } } O método main da classe AgendaInt cria um objeto da própria classe, que por sua vez possui um objeto da classe Agenda. A partir de então o programa entra em um laço, que aguarda e atende às solicitações do usuário. Todos os métodos da classe recebem e exibem dados por meio dos dispositivos de E/S padrão. Para ler os dados do dispositivo de entrada padrão, uma linha por vez, foi necessário encapsular o objeto System.in em objetos das classes BufferedReader e InputStreamReader . Não detalharemos aqui o uso destas classes, que será abordado no próximo capítulo. No momento basta observarmos que a entrada de dados será realizada por meio do método readLine(), que retorna a linha digitada pelo usuário. J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 123 Capítulo V – Entrada e Saída (java.io) Acesso Sequencial As funções de entrada e saída são suportadas por classes agrupadas no pacote java.io. Todas se baseiam no conceito de stream de bytes (corrente ou sequência), onde a entrada/saída é um duto onde se retira/coloca cada byte como em uma fila do tipo First in-first out. A figura V.1 ilustra este tipo de enfoque. Figura V.1 Stream de bytes. O produtor e o consumidor podem se selecionados de uma ampla variedade que vai desde arquivos e portas de protocolos TCP/IP até arranjos (arrays) de bytes e Strings. De modo a pode lidar com essa ampla variedade de fontes/consumidores de dados e prover todo o tipo de facilidade de leitura o pacote java.io possui um conjunto razoavelmente grande classes. Essas classes são combinadas, formando camadas, onde as classes das camadas inferiores fornecem serviços básicos como leitura/escrita de um byte, enquanto que as classes superiores fornecem serviços de leitura/escrita mais sofisticados, como leitura de linha, leitura de um tipo float, etc. A figura V.2 mostra este tipo de combinação. C Ca am ma ad da a n n C Ca am ma ad da a 1 C Cl l a as ss se e I I/ /O O B Bá ás si i c ca a N Ní ív ve el l d do os s s se er rv vi i ç ço os s J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 124 Figura V.2 Organização das classes de I/O. Para complicar um pouco mais a situação, a linguagem J ava Possui dois conjuntos distintos de classes para I/O. Um originado da versão 1.0 e outro introduzido na versão 1.1. A razão para isso é que as classes projetadas na versão 1.0 são orientadas para bytes e portanto não são adequadas para lidar códigos Unicodes que utilizam dois bytes. A versão 1.2 não introduziu grandes modificações, realizando apenas um aperfeiçoamento na java.io.File e algumas mudanças na assinatura de alguns métodos . O leitor deve então estar pensando que basta então utilizar o novo conjunto de classes e esquecer do antigo, porém a coisa não é tão simples assim. Primeiramente, é importante conhecer o conjunto antigo de classes uma vez que existe um grande número de programas escrito na versão 1.0 e o leitor pode se ver obrigado a ler ou dar manutenção no código destes programas. Em segundo lugar, algumas vezes será preciso combinar as funções antigas com as novas para obter algum tipo de funcionalidade. E em terceiro lugar a Sun adicionou novas facilidades ao conjunto antigo na versão 1.1, dando sinais que não pretende simplesmente abandoná-lo. Tudo isto faz com que o tratamento de entrada e saída em J ava não seja algo muito simples de se dominar. As figuras V.3 e V.4 apresentam diagramas que mostram algumas das camadas dos dois conjuntos de classes, de modo que o leitor possa ter uma idéia da sua equivalência, embora nem sempre uma classe de um conjunto possua uma classe correspondente em outro conjunto. Figura V.3 Comparação entre as classes de entrada. S St tr ri in ng gB Bu uf ff fe er rI In np pu ut tS St tr re ea am m B By yt te eA Ar rr ra ay yI In np pu ut tS St tr re ea am m P Pi ip pe ed dI In np pu ut tS St tr re ea am m F Fi il le eI In np pu ut tS St tr re ea am m I In np pu ut tS St tr re ea am m i in nt ts s f fl lo oa at ts s S St tr ri in ng g e et tc c. . b by yt te es s F Fi il lt te er rI In np pu ut tS St tr re ea am m D Da at ta aI In np pu ut tS St tr re ea am m B Bu uf ff fe er re ed dI In np pu ut tS St tr re ea am m L Li in ne eN Nu um mb be er rI In np pu ut tS St tr re ea am m i in nt ts s f fl lo oa at ts s S St tr ri in ng g e et tc c. . c ca ar ra ac ct te er re e P Pi ip pe ed dR Re ea ad de er r C Ch ha ar rA Ar rr ra ay yR Re ea ad de er r S St tr ri in ng gR Re ea ad de er r F Fi il le eR Re ea ad de er r R Re ea ad de er r F Fi il lt te er rR Re ea ad de er r B Bu uf ff fe er re ed dR Re ea ad de er r L Li in ne eN Nu um mb be er rR Re ea ad de er r J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 125 Figura V.4 Comparação entre as classes de saída. Note que não existe uma equivalência perfeita entre os dois conjuntos, nem entre o par entrada/saída em um mesmo conjunto. Por exemplo, a classe StringBufferedInputStream não possui uma classe equivalente StringBufferedOutputStream nem a DataInputStream possui uma equivalente DataWriter. Esta é uma das razões para o uso combinado dos dois conjuntos. Para realizar a conversão entre os dois conjuntos são fornecidas as classes InputStreamReader e OutputStreamWriter. As funções básicas são tratadas pelas classes abstratas básicas InputStream e OutputStream. Para as funções de entrada e saída com Buffers são utilizadas as classes BufferedInputStream, BufferedOutputStream, BufferedReader e BufferedWriter. O tamanho padrão do buffer é de 2048 bytes. A tradução entre InputStream, OutputStream e BufferedReader, BufferedWriter é feita por InputStreamReader e OutputStreamWriter. Para IO em arquivos podem ser utilizadas classes de baixo nível: FileInputStream, FileOutputStream, FileReader e FileWriter. De modo a possibilitar a comparação e o entendimento do uso dos dois conjuntos os exemplos abaixo mostram a mesma função implementada com cada grupo de classes. a) Streams b) I/O 1.1 import java.io.*; class TesteIO101 import java.io.*; class TesteIO111 B By yt te eA Ar rr ra ay yO Ou ut tp pu ut tS St tr re ea am m P Pi ip pe ed dO Ou ut tp pu ut tS St tr re ea am m F Fi il le eO Ou ut tp pu ut tS St tr re ea am m O Ou ut tp pu ut tS St tr re ea am m i in nt ts s f fl lo oa at ts s S St tr ri in ng gs s e et tc c. . b by yt te es s F Fi il lt te er rO Ou ut tp pu ut tS St tr re ea am m P Pr ri in nt tS St tr re ea am m D Da at ta aI In np pu ut tS St tr re ea am m B Bu uf ff fe er re ed dO Ou ut tp pu ut tS St tr re ea am m i in nt ts s f fl lo oa at ts s S St tr ri in ng gs s e et tc c. . c ca ar ra ac ct te er re e P Pi ip pe ed dW Wr ri it te er r C Ch ha ar rA Ar rr ra ay yW Wr ri it te er r S St tr ri in ng gW Wr ri it te er r F Fi il le eW Wr ri it te er r W Wr ri it te er r F Fi il lt te er rW Wr ri it te er r B Bu uf ff fe er re ed dW Wr ri it te er r P Pr ri in nt tW Wr ri it te er r J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 126 { public static void main(String a[]) { try { DataInputStream in = new DataInputStream( new BufferedInputStream( new FileInputStream(a[0]))); String str; while((str = in.readLine())!= null) System.out.println(str); } catch(IOException e) { System.out.println(e.getMessage());} } } { public static void main(String a[]) { try { BufferedReader in = new BufferedReader( new FileReader(a[0])); String str; while((str = in.readLine())!= null) System.out.println(str); } catch(IOException e) {System.out.println(e.getMessage());} } } Exemplo V.1 – Leitura não formatada de arquivos. O exemplo V.1 mostra a leitura de um arquivo passado pela linha de comando feita (a) por meio de stream e (b) por meio de reader. Em ambos os casos a leitura é utiliza buffers, de modo a otimizar os acessos a disco. No caso (b) basta utilizar um objeto da classe BufferedReader, uma vez que a leitura é não formatada, com o uso do método readLine(). J á no caso (a), é preciso “envolver” o objeto da classe BufferedInputStream em um objeto da classe DataInputStream, apesar de não ser uma leitura formatada, uma vez que o método readLine() pertence à classe DataInputStream. Mesmo assim o programa receberá a mensagem The method java.lang.String readLine() in class java.io.DataInputStream has been deprecated. significando que o método readLine() está sendo descontinuado para a classe DataInputStream, tendendo a desaparecer nas próximas versões. a) Streams b) I/O 1.1 import java.io.*; class TesteIO102 { public static void main(String a[]) { try { PrintStream out = new PrintStream( new BufferedOutputStream( new FileOutputStream("saida.out"))); out.println("Linha de teste 1"); import java.io.*; class TesteIO112 { public static void main(String a[]) { try { PrintWriter out = new PrintWriter( new BufferedWriter( new FileWriter("saida.out"))); out.println("Linha de teste 1"); J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 127 out.println("Linha de teste 2"); out.close(); }catch(IOException e) { System.out.println(e.getMessage());} } } out.println("Linha de teste 2"); out.close(); }catch(IOException e) { System.out.println(e.getMessage());} } } Exemplo V.2 – Escrita não formatada em arquivos. a) Streams import java.io.*; class TesteIO103 { public static void main(String a[]) { try { DataOutputStream out = new DataOutputStream( new BufferedOutputStream( new FileOutputStream("a.out"))); out.writeBytes("O valor de pi: \n"); out.writeDouble(3.14159); out.close(); DataInputStream in = new DataInputStream( new FileInputStream("a.out")); System.out.println(in.readLine()); System.out.println(in.readDouble()); }catch(IOException e) {System.out.println(e.getMessage());} } } Exemplo V.3 – Escrita e leitura formatada em arquivos.. A leitura/escrita formatada utiliza as classes DataInputStream e DataOutputStream. Como não existem as correspondentes DataReader e DataWriter, somos obrigados a usar o conjunto antigo de classes para realizar a leitura e escrita formatada. Os dispositivos de entrada e saída padrões (equivalentes na linguagem C++a cin, cout e cerr), são mantidos pela por variáveis estáticas da classe System, referenciadas por System.in, System.out e System.error. Como essas variáveis referenciam a objetos das classes InputStream e OutputStream, se desejarmos manipular os dispositivos de E/S padrões com o conjunto de classes novos devemos utilizar as classes tradutoras J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 128 InputStreamReader e OutputStreamWriter, como mostrado no exemplo V.4. import java.io.*; class TesteIO114 { public static void main(String a[]) { try { BufferedReader in = new BufferedReader( new InputStreamReader(System.in)); System.out.print("Entre uma linha:"); System.out.println(in.readLine()); }catch(IOException e) {System.out.println(e.getMessage());} } } Exemplo V.4 – Leitura e Escrita nos dispositivos de saída e entrada padrões. Acesso Direto Até agora lidamos com dispositivos de entrada e saída que tratam sequência de bytes ou caracteres (streams). No entanto, alguns dispositivos como disco rígido e CDs permitem o acesso direto à um byte sem a necessidade de ler todos os outros bytes posicionados antes. Este tipo de acesso é chamado de acesso direto e J ava permite este tipo acesso a arquivos armazenados em dispositivos que suportam o acesso direto por meio da classe RandomAccessFile. Esta classes não pertence a nenhuma das hierarquias apresentadas anteriormente uma vez que não acessa os dados na forma de sequência. A única semelhança com as outras hierarquias é o fato de implementar as interfaces InputStream e OutputStream. De fato, a classe RandomAccessFile torna muito conveniente o acesso a arquivos por implementar simultaneamente as duas interfaces, permitindo desta forma tanto a leitura quanto a escrita no arquivo. Os construtores da classe são os seguintes: public RandomAccessFile(File file, String mode) throws IOException public RandomAccessFile(String name, String mode) throws IOException J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 129 O primeiro construtor recebe um objeto da classe File e um objeto da classe String especificando o modo de acesso do arquivo. O acesso direto será feito sobre o objeto da classe Files. Os modos acesso possíveis são: modo de leitura, especificado pela String “r” e modo de leitura e escrita, especificado pela String “rw”. O segundo construtor recebe um objeto da classe String contendo o nome do arquivo e outro objeto da classe String especificando o modo de acesso do arquivo. Ambos os construtores lançam a exceção IOException. A maioria dos métodos implementados por RandomAccessFile são implementados pela classe FileInputStream ou pela classe FileOutputStream, uma vez que ela lida tanto com leitura como saída, porém, a classe RandomAccessFile possui alguns métodos adicionais que permitem definir a posição corrente no arquivo e estabelecer onde será lido/escrito o próximo dado. A tabela V.1 mostra os métodos da classe RandomAccessFile. Método Descrição close() Fecha o arquivo. getFD() Retorna o descritor arquivo objeto associado com este stream. getFilePointer() Retorna a posição corrente neste arquivo. length() Retorna o tamanho deste arquivo. read(byte[] b, int off, int len) Lê até len bytes em um array de bytes. read() Lê um byte. read(byte[] b) Lê até to b.length bytes em um array de bytes. readBoolean() Lê um boolean. readByte() Lê um um byte. readChar() Lê um caracter Unicode. readDouble() Lê um double. readFloat() Lê um float. readFully(byte[] b, int off, int len) Lê exatamente len bytes no array byte. readFully(byte[] b) Lê b.length bytes no array byte. readInt() Lê um inteiro de 32-bit com sinal. readLine() Lê a próxima linha de texto. readLong() Lê um inteiro com sinal de 64-bits. J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 130 readShort() Lê um inteiro com sinal de 16-bits. readUnsignedByte() Lê um inteiro sem sinal de 8-bits. readUnsignedShort() Lê um inteiro sem sinal de 16-bits. readUTF() Lê um String usando a codificação UTF-8. seek(long pos) Define pos como a posição em bytes no arquivo onde será feita a próxima leitura/escrita. skipBytes(int n) Salta exatamente n bytes. write(int b) Escreve um byte. write(byte[] b, int off, int len) Escreve len bytes do array iniciando em off. write(byte[] b) Escreve b.length bytes do array. writeBoolean(boolean v) Escreve um boolean como um valor 1-byte. writeByte(int v) Escreve um byte como um valor 1-byte. writeBytes(String s) Escreve uma String como uma sequência de bytes. writeChar(int v) Escreve um caractere como um valor 2-byte, byte alto primeiro. writeChars(String s) Escreve uma String como uma sequência de caracteres. writeDouble(double v) Escreve um double como um valor de 8-bytes. writeFloat(float v) Escreve um float como um valor de 4-bytes. writeInt(int v) Escreve um inteiro como quatro bytes. writeLong(long v) Escreve um long como oito bytes. writeShort(int v) Escreve um short como dois bytes. writeUTF(String str) Escreve uma String usando a codificação UTF-8. Tabela V.1 – Métodos públicos da classe RandomAccessFile. O Exemplo V.5 mostra o uso da classe RandomAccessFile para acessar o byte no meio do arquivo. import java.io.*; class RandomTeste { public static void main (String args[]) { if (args.length ==0) { System.err.println("Forneça o nome do arquivo!"); System.exit(0); J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 131 } try { RandomAccessFile f = new RandomAccessFile(args[0], "r"); long tam = f.length(); if (tam==0) { System.out.println("Arquivo vazio!"); } else { f.seek(tam>>1); System.out.println("O byte no meio é: "+f.read()); } } catch (Exception e) { System.out.println("Erro: " + e.toString()); } } Exemplo V.1 – Uso da classe RandomAccessFile. J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 132 Capítulo VI – java.util O pacote java.util reúne um conjunto interessante de classes e interfaces úteis ao desenvolvimento de aplicações. Contém recursos para lidar com coleções, data e hora, internacionalização, arrays de bits, tokenização de cadeias de caracteres e etc. Nesta seção abordaremos as classes e interfaces mais utilizadas. Lidando com Coleções Antigamente, era muito comum que, durante o desenvolvimento de um sistema, os programadores tivessem a necessidade de implementar uma estrutura de dados como uma lista, pilha ou tabela hash para agrupar e gerenciar um conjunto de elementos. Como as primeiras linguagens de programação não possuíam recursos para que se implementasse estas estruturas de forma genérica, de modo que pudessem ser usadas independentemente do tipo dos elementos, o programador era obrigado a implementar deste o início toda a estrutura para cada novo tipo de elemento. Com as linguagens de programação mais recentes este quadro mudou. As novas linguagens permitem que o programador implemente estruturas de dados genéricas, independentes de tipos. Além disso, a maioria delas já implementam, embutidas na linguagem ou na forma de bibliotecas, as estruturas de dados mais comuns. Este é o caso da linguagem J ava que possui, dentro do pacote java.util classes e interfaces que implementam estruturas de dados como listas (List), arrays crescentes (Vector), pilhas (Stack), tabela hash (Hashtable) e etc. O pacote possui também interfaces para percorrer os elementos das estruturas (Iterator e Enumeration). Apresentaremos aqui apenas as classes e interfaces mais usadas. As Interfaces Iterator e Enumeration As interfaces Iterator e Enumeration são usadas na criação de objetos que tem por objetivo percorrer sequencialmente os elementos de uma coleção. Como ambas as interfaces possuem o mesmo objetivo o leitor deve J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 133 estar se perguntando sobre necessidade de existirem duas interfaces. O fato é que até a versão 1.1 da linguagem J ava existia apenas a interface Enumeration. No entanto, como o nome deste tipo de objeto na comunidade de Engenharia de Software é iterator a Sun aproveitou para incluir uma interface com este nome na versão 1.2 e fazer algumas pequenas modificações. O métodos que devem ser implementados em cada interface são semelhantes e estão descritos nas tabelas VI.1 e VI.2. Método Descrição boolean hasMoreElements() Testa se Enumeration possui mais elementos. Object nextElement() Retorna o próximo elemento da Enumeration. Tabela VI.1 – Métodos da interface Enumeration. Método Descrição boolean hasNext() Testa se Iterator possui um próximo elemento. Object next() Retorna o próximo elemento do Iterator. void remove() Remove da coleção associada o último elemento retornado pelo Iterator. Tabela VI.2 – Métodos da interface Iterator. Um objeto que implementa a interface Enumeration é retornado por alguma classe que implementa uma estrutura de dados sempre que invocamos o método elements() da classe. Por exemplo, suponha que desejamos imprimir os elementos reunidos por um objeto v da classe Vector. O trecho de código para cumprir essa tarefa poderia ter a seguinte forma: for (Enumeration e = v.elements() ; e.hasMoreElements() ;) { System.out.println(e.nextElement()); } J á no caso da interface é preciso usar o método iterator() herdado por toda subclasse da interface Collection. Neste caso o trecho de código para cumprir essa tarefa poderia ter a seguinte forma: for (Iterator it = v.iterator() ; it.hasNext() ;) { System.out.println(it.next()); J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 134 } Vector public class Vector extends AbstractList implements List, Cloneable, Serializable A classe Vector implementa um array dinâmico. Ou seja, um array que cresce ou diminui a medida da necessidade. O uso de objetos classe Vector é indicado para situações onde o programador precisa manter uma lista de elementos mas não sabe, a priori, o número de elementos que pertencerão à lista ou o número de elementos da lista irá variar durante o processamento. Hierarquia java.lang.Object | +--java.util.AbstractCollection | +--java.util.AbstractList | +--java.util.Vector Construtores Construtor Descrição public Vector() Constrói um Vector vazio com espaço inicial para 10 elementos e incremento 0. public Vector(Collection c) Constrói um Vector contendo os elementos do objeto do tipo Collection. public Vector(int iniCapac) Constrói um Vector vazio com espaço inicial para iniCapac elementos e incremento 0. public Vector(int iniCapac, int inc) Constrói um Vector vazio com espaço inicial para iniCapac elementos e incremento inc. Tabela VI.3 – Construtores da classe Vector. J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 135 Atributos Atributo Descrição protected int capacityIncrement Quantidade que é incrementada à capacidade do Vector toda vez que o tamanho se torna maior que a capacidade. protected int elementCount Número de elementos referenciados protected Object[] elementData Array onde são armazenadas as referencias aos elementos. Tabela VI.4 – Atributos públicos da classe Vector. Métodos Devido ao grande número de métodos da classe Vector mostraremos apenas os mais usados. Método Descrição void add(int i, Object o) Adiciona um elemento na posição indicada por i. public boolean add(Object o) Adiciona um elemento no final do Vector. public boolean addAll (Collection c) Adiciona todos os elementos da coleção especificada no final do Vector. public void addElement(Object o) Adiciona um elemento no final do Vector. public int capacity() Retorna a capacidade atual. public void clear() Remove todos os elementos do Vector. public Object clone() Retorna um clone do Vector. public boolean contains(Object o) Testa se o objeto é um elemento do Vector. public boolean containsAll(Collection c) Retorna true se o Vector contém todos os elementos referenciados por c. public void copyInto (Object[] a) Copia os elementos do Vector no array. public Object elementAt(int i) Retorna o elemento na posição i. public Enumeration elements() Retorna um objeto do tipo Enumeration que permite percorrer os elementos do Vector. public void ensureCapacity(int min) Aumenta a capacidade do Vector para o valor min. public boolean equals(Object o) Compara se o é igual ao Vector. Retorna true somente se o objeto é também do tipo List, possui o mesmo tamanho e referencia os mesmos elementos na mesma ordem. J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 136 public Object firstElement() Retorna o primeiro elemento. public Object get(int i) Retorna o elemento da posição i. public int indexOf(Object o) Retorna a posição da primeira ocorrência de o. O método equals() do objeto é usado para o teste de igualdade. public int indexOf(Object o,int i) Retorna a posição da primeira ocorrência de o a partir de i. O método equals() do objeto é usado para o teste de igualdade. public void insertElementAt(Object o, int i) Insere o objeto o na posição i. public boolean isEmpty() Testa se o Vector está vazio. public Object lastElement() Retorna o último elemento. public int lastIndexOf(Object o) Retorna a posição da última ocorrência de o. O método equals() do objeto é usado para o teste de igualdade. public Object remove(int i) Remove o elemento da posição i. public boolean removeAll(Collection c) Remove todos os elementos que também pertencem à Collection. public void removeAllElements() Remove todos os elementos. public void removeElementAt(int i) Remove o elemento da posição i. public boolean retainAll(Collection c) Mantém apenas os elementos que também pertencem à Collection. public Object set(int i, Object o) Substitui o objeto na posição i pelo objeto o. public void setSize(int s) Define o tamanho do Vector. public int size() Retorna o tamanho do Vector. public List subList(int i, int f) Retorna uma parte dos elementos do Vector entre fromIndex, inclusive, e toIndex, exclusive. public Object[] toArray() Retorna um array contendo todos os elementos no Vector na ordem correta. public String toString() Retorna uma representação na forma de String do Vector. Tabela VI.5 – Métodos da classe Vector. Exemplo J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 137 O exemplo a seguir mostra a inserção das strings passadas pela linha de comando em um objeto da classe Vector. Os elementos do objeto Vector podem ser exibidos na tela invocando o método print(). import java.util.*; public class TesteVector { Vector v; public TesteVector(String a[]) { v = new Vector(a.length); v.copyInto(a); } public void print() { for (Iterator it = v.iterator(); it.hasNext() ;) { System.out.println((String)it.next()); } } public static void main(String args[]) { TesteVector teste = new TesteVector(args); teste.print(); } } Exemplo VI.xx – Uso do Vector. Stack public class Stack extends Vector A classe Stack implementa a estrutura de dados pilha. Ou seja, uma estrutura de dados que segue a regra LIFO (last-in-first-out – último a entrar, primeiro a sair). Esta regra impõe define uma lista que é acessada apenas por uma extremidade, de modo que o último elemento a ser inserido na pilha tenha que ser o primeiro elemento a sair. J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 138 Hierarquia java.lang.Object | +--java.util.AbstractCollection | +--java.util.AbstractList | +--java.util.Vector | +--java.util.Stack Construtor Construtor Descrição public Stack() Constrói um Stack vazio. Tabela VI.6 – Construtor da classe Stack. Métodos Método Descrição public boolean empty() Testa se a Stack está vazia. public Object peek() Retorna uma referência para o elemento no topo da Stack sem retirá-lo. public Object pop() Retorna uma referência para o elemento no topo da Stack e o retira. public Object push(Object o) Insere o objeto o no topo da Stack. public int search(Object o) Retorna a distância do topo da pilha da primeira ocorrência de o. O elemento no topo da pilha possui a distância 1. O método equals() do objeto é usado para o teste de igualdade. Tabela VI.5 – Métodos da classe Stack. Exemplo J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 139 O exemplo a seguir mostra a inserção das strings passadas pela linha de comando em um objeto da classe Stack. Os elementos do objeto Stack podem ser exibidos na tela invocando o método print(). import java.util.*; public class TesteStack { Stack s; public TesteStack(String a[]) { s = new Stack(); for (int i=0; i<a.length; i++) s.push(a); } public void desempilha () { String aux = (String)s.pop(); while (aux !=null) { System.out.println(aux); } } public static void main(String args[]) { TesteStack teste = new TesteStack(args); teste.desempilha(); } } Exemplo VI.xx – Uso do Stack. Hashtable public class Hashtable extends Dictionary implements Map, Cloneable, Serializable A classe Hashtable implementa uma estrutura de dados conhecida como tabela hash. Neste tipo de estrutura todo elemento armazenado possui uma J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 140 chave que permite recuperar diretamente o elemento, sem a necessidade de ir examinando cada elemento armazenado até encontrar o elemento desejado, como seria o caso em uma lista. Cada chave identifica apenas um elemento. Existem vários bons livros sobre estruturas de dados nas livrarias que o leitor pode consultar para se aprofundar no tema. Hierarquia java.lang.Object | +--java.util.Dictionary | +--java.util.Hashtable Construtores Construtor Descrição public Hashtable() Constrói um Hashtable vazio com fator de carga 0,75. public Hashtable(int capac) Constrói um Hashtable com capacidade inicial capac e com fator de carga 0,75. public Hashtable(int capac, float fatCarga) Constrói um Hashtable com capacidade inicial capac e com fator de carga fatCarga. public Hashtable(Map m) Constrói um Hashtable com os elementos de m. Tabela VI.xx – Construtores da classe Hashtable. Métodos Devido ao grande número de métodos da classe Hashtable mostraremos apenas os mais usados. Método Descrição public void clear() Remove todos os elementos do Hashtable. public Object clone() Retorna um clone raso do Hashtable. public boolean contains(Object o) Testa se o objeto é acessado por alguma chave do Hashtable. public boolean containsKey(Object o) Testa se o objeto é chave no Hashtable. J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 141 public boolean containsValue(Object o) Testa se o objeto é acessado por alguma chave do Hashtable. public Enumeration elements() Retorna um objeto do tipo Enumeration que permite percorrer os elementos do Hashtable. public Set entrySet() Retorna um Set contendo os elementos do Hashtable. public boolean equals(Object o) Compara se o é igual ao Hashtable. Retorna true somente se o objeto é também do tipo Map e faz o mesmo mapeamento. public Object get(Object key) Retorna o elemento da posição mapeado pela chave key. public boolean isEmpty() Testa se o Hashtable está vazio. public Enumeration keys() Retorna um objeto do tipo Enumeration que permite percorrer as chaves do Hashtable. public Set keySet() Retorna um Set contendo as chaves do Hashtable. public Object put(Object key,Object o) Insere o objeto o no Hashtable tendo como chave key. protected void rehash() Reorganiza internamente o Hashtable para acessar e acomodar os elementos mais eficientemente. public Object remove(Object key) Remove o elemento que tem como chave key. public int size() Retorna o número de chaves do Hashtable. public String toString() Retorna uma representação na forma de String do Hashtable. public Collection values() Retorna um Collection contendo os elementos do Hashtable. Tabela VI.xx – Métodos da classe Hashtable. Exemplos O exemplo a seguir mostra a inserção das objetos da classe Integer em um objeto Hashtable. A chave usada para cada objeto é o número por extenso. Hashtable numbers = new Hashtable(); numbers.put("one", new Integer(1)); numbers.put("two", new Integer(2)); numbers.put("three", new Integer(3)); ... Integer n = (Integer)numbers.get("two"); if (n != null) { System.out.println("two = " + n); J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 142 } ... for (Enumeration e = numbers.elements();e.hasMoreElements();){ Integer i = (Integer)e.nextElement() System.out.println( i.toString()); } Exemplo VI.xx – Uso do Hashtable. O exemplo a seguir mostra a inserção das objetos da classe Pessoa em um objeto Hashtable. A chave usada para cada objeto é o nome da pessoa. class pessoa { String nome; String tel; public pessoa(String nome, String tel) {this.nome = nome; this.tel = tel;} public String getNome(){return nome;} public String getTel(){return tel;} } ... Hashtable pessoas = new Hashtable(); ... pessoa P1 = new pessoa("Pedro", "899-1313"); pessoas.put(P1.getNome(), P1); ... pessoa P2 =(pessoa) pessoas.get("Pedro"); if (P2!=null) System.out.println ("Nome:"+P2.getNome(), "\nTel:"+P2.getTel()); Exemplo VI.xx – Uso do Hashtable. Miscelânea de Classes do pacote java.util Arrays public class Arrays extends Object J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 143 Esta classe foi inserida no pacote java.util a partir da versão 1.2 do SDK. É uma classe utilitária, fornecendo métodos de ordenação e busca em arrays. Hierarquia java.lang.Object | +--java.util.Arrays Métodos Método Descrição public static List asList(Object[] a) Retorna uma List criada a partir do array. public static int binarySearch(byte[] a, byte key) public static int binarySearch(char[] a, char key) public static int binarySearch(double[] a, double key) public static int binarySearch(float[] a, float key) public static int binarySearch(int[] a, int key) public static int binarySearch(long[] a, long key) public static int binarySearch(Object[] a, Object key) public static int binarySearch(short[] a, short key) Realiza uma pesquisa binária retornando a posição de key no array. public static int binarySearch(Object[] a, Object key, Comparator c) Realiza uma pesquisa binária retornando a posição de key no array. O array tem que estar ordenado ascendentemente de acordo com o Comparator. public static boolean equals(boolean[] a, boolean[] a2) public static boolean equals(byte[] a, byte[] a2) public static boolean equals(char[] a, char[] a2) public static boolean equals(double[] a, double[] a2) public static boolean equals(float[] a, float[] a2) public static boolean equals(int[] a, int[] a2) public static boolean equals(long[] a, long[] a2) public static boolean equals(Object[] a, Object[] a2) Retorna true se os dois arrays são iguais. J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 144 public static boolean equals(short[] a, short[] a2) public static void fill(boolean[] a, boolean val) public static void fill(byte[] a, byte val) public static void fill(double[] a, double val) public static void fill(float[] a, float val) public static void fill(int[] a, int val) public static void fill(long[] a, long val) public static void fill(Object[] a, Object val) public static void fill(short[] a, short val) Preenche o array com o valor especificado. public static void fill(boolean[] a, int ini, int fim, boolean val) public static void fill(byte[] a, int ini, int fim, byte val) public static void fill(double[] a, int ini, int fim, double val) public static void fill(float[] a, int ini, int fim, float val) public static void fill(int[] a, int ini, int fim, int val) public static void fill(long[] a, int ini, int fim, long val) public static void fill(Object[] a, int ini, int fim, Object val) public static void fill(short[] a, int ini, int fim, short val) Preenche o array com o valor especificado a partir do elemento de índice ini inclusive até o elemento de índice fim exclusive. public static void sort(byte[] a) public static void sort(double[] a) public static void sort(float[] a) public static void sort(int[] a) public static void sort(long[] a) public static void sort(Object[] a) public static void sort(short[] a) Ordena o array em ordem ascendente. public static void sort(byte[] a, int ini, int fim) public static void sort(double[] a, int ini, int fim) public static void sort(float[] a, int ini, int fim) public static void sort(int[] a, int ini, int fim) public static void sort(long[] a, int ini, int fim) public static void sort(Object[] a, int ini, int fim) public static void sort(short[] a, int ini, int fim) Ordena o array em ordem ascendente a partir do elemento de índice ini inclusive até o elemento de índice fim exclusive. public static void sort(Object[] a, Comparator c) Ordena o array em ordem ascendente definida pelo objeto Comparator. public static void sort(Object[] a, int ini, int fim, Comparator c) Ordena o array em ordem ascendente definida pelo objeto Comparator a partir do elemento de índice ini inclusive J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 145 até o elemento de índice fim exclusive. Tabela VI.5 – Métodos da classe Arrays. O algoritmo de ordenação O algoritmo de ordenação é uma variação quicksort apresentado por J on L. Bentley e M. Douglas McIlroy's "Engineering a Sort Function", Software-Practice and Experience, Vol. 23(11) P. 1249-1265 (November 1993). Este algoritmo possui complexidade n*log(n) em conjunto de dados que causariam a degradação de outros algoritmos de quicksort para um desempenho de ordem quadrática. Exemplo O exemplo a seguir mostra a impressão ordenada das strings passadas pela linha de comando. import java.util.arrays; class TestArrays { public static void main(String a[]) { if(a.length>0) { Arrays.sort(a); for(int i=0;i<a.length; i++) System.out.println(a[i]); } } } Exemplo VI.xx – Uso do Arrays. Date public class Date extends Object implements Serializable, Cloneable, Comparable J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 146 Esta classe é responsável pela representação da data com precisão de milisegundos. Antes da versão 1.1 do SDK a classe Date também tinha as funções de formatação e análise de datas no tipo String assim como de interpretar as datas em anos, meses, dias, horas, minutos e segundos. Devido à complexidade da internacionalização estas funções foram transferidas para as classes DateFormat e Calendar. O métodos da classe Date que realizavam essas funções tornaram-se deprecated. A data base padrão, a partir da qual são contados os milisegundos é 01/01/1970, 00:00:00 GMT. Hierarquia java.lang.Object | +--java.util.Date Construtores Os construtores deprecated não estão listados. Construtor Descrição public Date() Constrói um Date representando a data do momento de sua alocação. public Date(long data) Constrói um Date contendo a data calculada a partir número de millisegundos representados por data tendo como base a data 01/01/1970, 00:00:00 GMT. Tabela VI.xx – Construtores da classe Date. Métodos Os métodos deprecated não estão listados. Método Descrição public boolean after(Date d) Testa se a data é anterior à data passada como argumento. public boolean before(Date d) Testa se a data é posterior à data passada como argumento. public Object clone() Cria um clone do objeto. public int compareTo(Date d) Compara duas datas. Retorna 0 se as duas J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 147 datas são iguais. Retorna um valor maior que 0 se a data passada como argumento é menor e um valor menor que 0 se data passada como argumento e maior. public boolean equals(Object o) Retorna true se duas datas são iguais. public long getTime() Retorna o número de milisegundos desde 01/01/1970, 00:00:00 GMT até a data representada pelo objeto corrente. public void setTime(long time) Define a data desde objeto usando o número de milisegundos desde 01/01/1970, 00:00:00 GMT passado como argumento. Tabela VI.5 – Métodos da classe Date. Exemplo O exemplo a seguir exibe a representação da data corrente em milisegundos. import java.util.Date; class TestDate { public static void main(String a[]) { System.out.println(new Date().getTime()); } } Exemplo VI.xx – Uso da classe Date. Observable public class Observable extends Object A classe Observable permite criar objetos “observáveis”. Objeto observável é um objeto que, quando sofre alguma alteração, notifica outros objetos, chamados de “observadores”. Os objetos observáveis e observadores fazem parte do padrão de projeto conhecido como MVC (Modelo–Visão– Controle) introduzido na linguagem Smalltalk. A utilização deste padrão de J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 148 projeto facilita a implementação programas onde um modelo possui várias visões que devem ser atualizadas toda vez que o modelo muda. O controle atua sobre o modelo, ocasionando alterações. Por exemplo, suponha que modelo que armazene os valores de ações da bolsa. Podemos exibir várias visões desses dados, como gráficos de barras e em forma de pizza. Os controladores modificariam o modelo que em seguida notificaria as visões para refletirem o estado atual do modelo. As figuras VI.XX e VI.XX ilustram esquematicamente o padrão MVC. Na linguagem J ava, para um objeto ser um observador é preciso implementar a interface Observer. Figura VI.XX – Esquema do padrão MVC. Observável Figura VI.XX – Duas formas de visualização de um modelo. Visão Visão Modelo Controle Controle a b x 30 45 window X window X a b a=30; b=45; J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 149 Hierarquia java.lang.Object | +--java.util.Observable Construtor Construtor Descrição public Observable() Constrói um Observable possuindo zero observadores. Tabela VI.XX – Construtor da classe Observable. Métodos Método Descrição public void addObserver(Observer o) Adiciona um observador ao conjunto de observadores do objeto. protected void clearChanged() Limpa a indicação de que o objeto mudou. public int countObservers() Retorna o número de objetos observadores deste objeto. public void deleteObserver(Observer o) Remove o objeto passado como argumento do conjunto de objetos observadores. public void deleteObservers() Remove todos os objetos observadores do conjunto de objetos observadores. public boolean hasChanged() Retorna true se o objeto sofreu uma mudança. public void notifyObservers() Se o método hasChanged() retornar true notifica todos os objetos observadores e chama o método clearChanged(). public void notifyObservers(Object arg) Se o método hasChanged() retornar true notifica todos os objetos observadores e chama o método clearChanged(). O objeto arg é passado como argumento para o método update() do observador. protected void setChanged() Marca este objeto como tendo sofrido uma mudança. Tabela VI.XX – Métodos da classe Observable. J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 150 Exemplo O exemplo VI.xx mostra uma classe Observavel que muda de valor aleatoriamente. A classe implementa a interface Runnable, uma vez que será executada concorrentemente. A concorrência será abordada detalhadamente no Capítulo XI. import java.util.*; public class Observavel extends Observable implements Runnable { int valor =0; public void notifyObservers() { setChanged(); super.notifyObservers(); } public int getValue(){return valor;} public void run() { for(;;) { if (Math.random()>0.5) { valor =(new Double(Math.random()*200)).intValue(); this.notifyObservers(); } try {Thread.sleep(300);} catch(Exception e){}; } } } Exemplo VI.xx – Uma classe Observable. O exemplo a seguir mostra uma classe que define um observador. Para isso é necessário implementar a interface Observer, cujo único método a ser codificado é o método update(). Este método é chamado quando o objeto observado notifica alguma alteração. No exemplo o objeto observador simula uma barra de progresso que movimenta de acordo com a mudança do valor do objeto observado. J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 151 import java.awt.*; import java.util.*; public class FrameOb extends Frame implements Observer { Observavel ob = new Observavel(); Thread t = new Thread(ob); Canvas progress = new Canvas(); public FrameOb() { setLayout(null); setSize(300,60); add(progress); progress.setBackground(Color.blue); progress.setBounds(0,0,0,30); setTitle("Exemplo de Observável"); SymWindow aSymWindow = new SymWindow(); this.addWindowListener(aSymWindow); ob.addObserver(this); t.start(); } static public void main(String args[]) { (new FrameOb()).setVisible(true); } class SymWindow extends java.awt.event.WindowAdapter { public void windowClosing(java.awt.event.WindowEvent event) { System.exit(0); } } public void update(Observable o, Object arg) { progress.setSize(ob.getValue(), 100); } } Exemplo VI.xx – Uso da classe Observable. StringTokenizer J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 152 public class StringTokenizer extends Object implements Enumeration A StringTokenizer é uma classe que permite quebrar uma cadeia de caracteres em subcadeias denominadas de tokens. um token é uma subcadeia cercada por caracteres denominados delimitadores. Portanto, o que é um token dependerá da especificação dos delimitadores. Por exemplo, dada cadeia de caracteres “ola mundo,ola vida” teremos os tokens “ola mundo” e “ola vida” caso o delimitador seja o caractere ‘,’ e os tokens “ola”, “mundo,ola” e “vida” caso o delimitador seja o caractere ‘ ’ Hierarquia java.lang.Object | +--java.util.StringTokenizer Construtores Construtor Descrição public StringTokenizer(String s) Constrói um StringTokenizer para s. public StringTokenizer(String s, String del) Constrói um StringTokenizer para s, usando como delimitadores os caracteres contidos em del. public StringTokenizer (String s, String del, boolean voltaTokens) Constrói um StringTokenizer para s, usando como delimitadores os caracteres contidos em del. Se voltaTokens possui o valor true, então os caracteres contidos em del também são retornados. Tabela VI.XX – Construtores da classe StringTokenizer. Métodos Método Descrição J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 153 public int countTokens() Retorna o número de tokens. public boolean hasMoreElements() Mesmo efeito que hasMoreTokens(). public boolean hasMoreTokens() Retorna true se existir mais tokens. public Object nextElement() Retorna o próximo token como uma instância de Object. public String nextToken() Retorna o próximo token como uma instância de String. public String nextToken(String del) Retorna o próximo token como uma instância de String usando como delimitadores os caracteres contidos em del. Tabela VI.XX – Métodos da classe StringTokenizer. Exemplo import java.util.*; public class TestToken { static public void main(String args[]) { StringTokenizer st = new StringTokenizer("ola mundo louco"); while (st.hasMoreTokens()) println(st.nextToken()); } } Saída: ola mundo louco Exemplo VI.XX – Uso da classe StringTokenizer. Agenda Eletrônica versão Console 2.0 Neste ponto já estamos preparados para fazer alguns aperfeiçoamentos na nossa agenda eletrônica de endereços. As alterações serão de dois tipos: passaremos a utilizar um objeto da classe Hashtable para agrupar as pessoas da agenda, e acrescentaremos métodos para gravar e recuperar os dados da J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 154 agenda em um arquivo. Estas modificações acarretarão mudanças apenas nos códigos das classes Agenda e AgendaInt. O diagrama de classes da figura IV.XX mostra as adições dos métodos gravarAgenda() e recuperarAgenda(), responsáveis pela gravação e recuperação dos dados em arquivo. Figura VI.xx – Diagrama de classes da versão console 2.0 da agenda eletrônica. Como a classe Pessoa não sofrerá nenhuma modificação. Ela é apresentada abaixo apenas por questões de comodidade. /** Pessoa */ public class Pessoa { private String Nome; private String Tel; private String End; // Construtor public Pessoa(String n, String t, String e) { Nome = n; Tel = t; End = e; } /** getNome */ public String getNome(){return Nome;} /** getNome AgendaInt +obterPessoa() +exibirLista() +exibirPessoa() +gravarAgenda() +recuperarAgenda() Agenda +inserir(pessoa p) +getPessoas() +getPessoa(String Nome) Pessoa -String Nome -String Tel -String End +getNome() +getTel() +getEnd() 0..* J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 155 */ public String getTel(){return Tel;} /** getEnd */ public String getEnd(){return End;} } A classe Agenda será alterada de modo que o a associação de agrupamento com a classe Pessoa seja implementada por meio de um objeto da classe Hashtable e não por um array. /** AGENDA Versão Console 2.0. */ import java.util.*; public class Agenda { Hashtable pessoas; // Construtor public Agenda() {pessoas = new Hashtable();} /** inserir */ public void inserir(Pessoa p) {pessoas.put(p.getNome(),p);} /** Consultar */ public Pessoa getPessoa(String nome) {return (Pessoa) pessoas.get(nome);} /** listar */ public Enumeration getPessoas(){return pessoas.elements();} } Podemos notar que o código fica bem mais simples com a adoção da instância da classe Hashtable. Foi alterada a assinatura do método getPessoas(), de modo que não mais retorna um array e sim um objeto da J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 156 classe Enumeration. A classe AgendaInt será alterada para tratar o retorno do método getPessoas() e receberá mais dois novos métodos. O método main() também será alterado para receber solicitações para a execução das novas funções. /** AgendaInt Interface console da agenda. */ import java.io.*; import java.util.*; public class AgendaInt { Agenda ag; BufferedReader in; // Construtor public AgendaInt() { ag = new Agenda(); in = new BufferedReader(new InputStreamReader(System.in)); } /** Exibirlista */ public void Exibirlista() { Pessoa p; for(Enumeration e = ag.getPessoas(); e.hasMoreElements();) { p = (Pessoa) e.nextElement(); System.out.println("\nNome:"+p.getNome()+"\nTelefone:" +p.getTel()+"\nEndereço:"+p.getEnd()+"\n"); } } /** exibirPessoa */ public void exibirPessoa() { String nome=null; try { System.out.println("Entre com o nome:"); J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 157 nome = in.readLine(); if (nome.length()<1) System.exit(-1); } catch(IOException e) {System.out.println(e.getMessage());System.exit(-1);} Pessoa p = ag.getPessoa(nome); if (p!=null) { System.out.println("\nNome:"+p.getNome()+"\nTelefone:" +p.getTel()+"\nEndereço:"+p.getEnd()); } } /** obterPessoa */ public void obterPessoa() { String nome; String tel; String end; try { System.out.println("Entre com o nome:"); System.out.flush(); nome = in.readLine(); if (nome.length()<1) System.exit(-1); System.out.println("\nEntre com o Telefone:"); System.out.flush(); tel = in.readLine(); System.out.println("\nEntre com o Endereço:"); System.out.flush(); end = in.readLine(); ag.inserir(new Pessoa(nome ,tel,end)); } catch(IOException e) {System.out.println(e.getMessage());System.exit(-1);} } /** gravar */ public void gravar() { try { Pessoa p; BufferedWriter fout = new BufferedWriter( new FileWriter("agenda.dat")); for (Enumeration e = ag.getPessoas(); e.hasMoreElements();) J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 158 { p = (Pessoa) e.nextElement(); fout.write(p.getNome()+"\n"+p.getTel()+"\n" +p.getEnd()+"\n"); } fout.flush(); fout.close(); } catch(FileNotFoundException e) { System.out.println("Arq. Não encontrado");} catch(IOException e) {System.out.println("Erro na gravação!");} } /** carregar */ public void carregar() { try { String nome; String tel; String end; BufferedReader fin = new BufferedReader( new FileReader("agenda.dat")); while ((nome = fin.readLine()) != null) { tel = fin.readLine(); end = fin.readLine(); ag.inserir(new Pessoa(nome,tel,end)); } fin.close(); } catch(FileNotFoundException e) {System.out.println("Arq. Não encontrado");} catch(IOException e) {System.out.println("Erro na leitura!"); } } // main public static void main(String args[]) { AgendaInt agInt = new AgendaInt(); String opcao=""; for(;;) { System.out.println( J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 159 "\nAgenda Tabajara\n***********************\n"); System.out.print("Opcoes:\n(i)nserir\n(c)onsultar"+ "\n(l)istar\n(g)ravar\n(r)ecuperar\n(f)im=>"); System.out.flush(); try { opcao = agInt.in.readLine(); if (opcao.length()==0) continue; } catch(IOException e) {System.out.println(e.getMessage());System.exit(-1);} switch(opcao.charAt(0)) { case 'f': System.exit(0); break; case 'i': agInt.obterPessoa(); break; case 'c': agInt.exibirPessoa(); break; case 'l': agInt.Exibirlista(); break; case 'g': agInt.gravar(); break; case 'r': agInt.carregar(); break; } } } } J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 160 Capítulo VII - Serialização e Persistência Um objeto é serializável se podemos transformá-lo em uma sequência de bytes de modo que seja possível o seu armazenamento em arquivo ou envio através de um stream. Desta forma o estado do objeto pode ser preservado em memória não volátil. Neste caso dizemos que o objeto possui persistência, ou seja o estado do objeto persiste enter as execuções do programa. A persistência implementada pela serialização é chamada de persistência leve, uma vez que o programador deve providenciar o armazenamento. Em uma persistência completa o programador apenas informa ao sistema que o objeto é persistente, ficando a cargo do ambiente o armazenamento e recuperação do objeto de forma transparente. A serialização de objetos foi adicionada à linguagem J ava a partir da versão 1.1 com o intuito de possibilitar a transmissão de objetos entre máquinas executando diferentes plataformas operacionais, viabilizando dessa forma o RMI (Remote Method Invocation) e permitir a utilização JavaBeans, que são componentes configuráveis em tempo de execução, e que mantém a configuração entre ativações. Para que os objetos de uma classe sejam serializáveis basta que a classe implemente a interface Serializable. A partir de então é possível armazenar e recuperar os objetos da classe por meio de instâncias das classes ObjectOutputStream e ObjectInputStream, respectivamente. O exemplo VII.1 mostra como armazenar e recuperar um objeto da classe Pessoa em um arquivo p.dat. class pessoa implements Serializable { String Nome; String Tel; public pessoa(String Nome, String Tel) {this.Nome = Nome; this.Tel = Tel;} } ... Pessoa p = new pessoa(“Ana”,”234-6757”); FileOutputStream fout = null; ObjectOutputStream out; J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 161 try { fout = new FileOutputStream(”p.dat"); out = new ObjectOutputStream(fout); out.writeObject(p); out.close(); } catch(Exception e) {System.out.println(”Erro:");return;} FileInputStream fin = null; ObjectInputStream in; try { fin = new FileInputStream(”p.dat"); in = new ObjectInputStream(fin); p = (pessoa) in.readObject(); in.close(); } catch(Exception e) { System.out.println(”Erro:”+e); return;} Exemplo VII.1 – Armazenamento e recuperação de um objeto da classe Pessoa. Uma questão interessante é: o que acontece com os objetos que são referenciados pelo objeto que está sendo armazenado? Devem ser armazenados também ou não? Se os objetos referenciados não forem armazenados juntos com o objeto corrente, a recuperação do mesmo posteriormente poderá resultar em uma instância incompleta. Por exemplo, um objeto do tipo Pessoa como mostrado no exemplo VII.1, referencia dois objetos do tipo String que armazenam o nome e o telefone da pessoa representada pelo objeto. Se estes objetos não forem armazenados junto com a instância de Pessoa, qual o sentido da posterior recuperação do objeto? Portanto, quando um objeto é armazenado todos os objetos referenciados por ele também são armazenados, desde que os objetos implementem a interface Serializable. Caso o objeto referenciado também referencie outro objeto, este último também será armazenado, e assim por diante, até que toda os objetos da cadeia de referencias sejam armazenados. Isto pode fazer com que o objeto armazenado ocupe muito mais espaço do que o imaginado inicialmente pelo programador. Outra pergunta seria: o que acontece então quando um objeto é referenciado mais de uma vez ou quando acontece um ciclo no grafo de referências? Neste caso, o objeto é armazenado apenas uma vez. J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 162 Agenda Eletrônica versão Console 2.1 Com o objetivo de exemplificar o que foi discutido neste capítulo, modificamos a agenda eletrônica para que a gravação e a carga do arquivo seja feita através da serialização do objeto Agenda. Primeiramente, foi modificada as classes Pessoa e Agenda de modo que implementassem a interface Serializable. Para isto basta importar a interface do pacote java.io e modificar a declaração das classes, como mostrado abaixo. Como o restante do código dessas duas classes permanece inalterado, não o repetimos aqui. O leitor deve se reportar às versões anteriores caso deseje recordar sobre o código omitido. /** Pessoa */ import java.io.*; public class Pessoa implements Serializable { ... Sem alteração ... } /** AGENDA Versão Console 2.1. */ import java.util.*; import java.io.*; public class Agenda implements Serializable { ... Sem alteração ... } A classe AgendaInt sofre modificações nos métodos gravar() e carregar(), que os tornam bem mais simples, uma vez que o trabalho de seguir as referências do objeto da classe Agenda para serializá-los e armazená- los é feito implicitamente. Segue abaixo apenas o código dos métodos modificados classe AgendaInt. Note que foi preciso usar a hierarquia stream J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 163 para gravar e recuperar os objetos, uma vez que não existem classes do tipo ObjectReader e ObjectWriter no novo conjunto de classes de E/S. /** AgendaInt Interface console da agenda. */ import java.io.*; import java.util.*; public class AgendaInt { ... Sem alteração ... /** gravar */ public void gravar() { try { ObjectOutputStream fout = new ObjectOutputStream( new FileOutputStream("agenda.dat")); fout.writeObject(ag); fout.close(); } catch(FileNotFoundException e) { System.out.println("Arq. Não encontrado");} catch(IOException e) {System.out.println("Erro na gravação!");} } /** carregar */ public void carregar() { try { ObjectInputStream fin = new ObjectInputStream( new FileInputStream("agenda.dat")); ag = (Agenda) fin.readObject(); fin.close(); } catch(FileNotFoundException e) {System.out.println("Arq. Não encontrado");} catch(Exception e) {System.out.println("Erro na leitura!"); } } J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 164 ... Sem alteração ... } J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 165 Capítulo VIII – AWT (Abstract Window Toolkit) Até este ponto, todos o exemplos apresentados neste livro foram de aplicações em modo texto, também chamadas de aplicações não gráficas ou de console. Hoje em dia, não é possível lançar no mercado uma linguagem de ampla aplicação que não possua ferramentas para construção de interfaces gráficas sofisticadas. A linguagem J ava além de ter a necessidade de atender este requisito possui a condição adicional de prover recursos gráficos que permitam o desenvolvimento de interfaces independentes de plataforma. Talvez, devido a esta forte exigência, a primeira versão do pacote que implementa o suporte gráfico acabou sendo conhecido como um excelente exemplo de como não se deve realizar um projeto. O pacote conhecido como AWT 1.0 (Abstract Window Toolkit), não era muito harmônico, tinha um conjunto muito pobre de componentes gráficos e possuía um modelo para tratamento de eventos muito ineficiente. A versão AWT 1.1 solucionou vários deste problemas, com o oferecimento de um novo modelo para tratamento dos eventos e com a introdução de um modelo para programação de componentes, denominados de JavaBeans. Na versão 1.2 da linguagem novas facilidades foram incorporadas com a adição de um conjuntos de componentes leves, denominados de Swing, ou J FC (J ava Foundation Classes). A denominação de componentes leves para a J FC se deve ao fato dos componentes não utilizarem os componentes nativos da GUI (Graphical User Interface) do ambiente. Eles utilizam primitivas de baixo nível para sua construção na tela, obtendo assim um alto nível de portabilidade. J á a AWT utiliza os componentes da GUI (widgets), para sua exibição e por isso são chamados de componentes pesados. Por isso, para atingir uma portabilidade razoável, apenas componentes básicos estão presentes na AWT. Mesmo assim a portabilidade da AWT não é tão grande quanto a J FC, apresentando, em alguns casos, grandes diferenças de visualização entre ambientes operacionais. Neste capítulo, abordaremos a versão 1.1 do AWT. Os componentes da J FC/Swing serão tratados isoladamente no capítulo XX. A Hierarquia de componentes J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 166 O AWT é um pacote contendo classes e interfaces para a criação de objetos para interação em janelas gráficas. Com essas classes é possível criar e manter objetos tais como: ♦ Botões (Classe Button) ♦ Caixa de Escolha (Classe Choice) ♦ Diálogos (Classe Dialog) ♦ J anelas (Classe Window) ♦ Menus (Classe Menu) ♦ Barra de Rolamento (Classe Scrollbar) ♦ Etc. As classes se organizam em uma hierarquia. A figura VI.XX mostra a organização hierárquica das classes. ! Color ! Component " Button " Canvas " Checkbox " Choice " Container # Panel # ScrollPane # Window • Dialog ♦ FileDialog • Frame " Label " List " Scrollbar " TextComponent # TextArea # TextField ! MenuComponent " MenuBar " MenuItem # Menu J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 167 Figura VI.1 – Hierarquia das classes no pacote AWT. Olá Mundo AWT Para usar classes definidas na AWT é preciso importá-las: import java.awt.*; Algumas classes são usadas para criar objetos que servirão para conter outros objetos. Estas classes são descendentes da classe Container. Cada classe possui um conjunto de atributos que podem ser acessados por meio de métodos. Como de praxe, apresentaremos primeiramente a versão AWT do “olá mundo” para ilustrar os principais componentes de uma aplicação import java.awt.*; class OlaFrame extends Frame { public OlaFrame() { super(“Ola”); setSize(100,50); add(new Label("ola mundo")); } public stati c void main(String args[]){ new OlaFrame().setVisible(true); } } Exemplo XX.XX – Versão AWT do “Olá mundo”. Para criarmos uma aplicação gráfica precisaremos de algum tipo de janela para colocarmos os componentes. Por isso a classe OlaFrame do exemplo XX.XX é uma subclasse da classe Frame que, por sua vez, e subclasse da classe Window. A classe Frame implementa uma janela gráfica com título e margem, sendo que a área do Frame inclui a margem. No exemplo construtor da classe invoca o construtor da superclasse, passando como argumento o título da janela. J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 168 A classe Frame cria uma janela com 0 x 0 pixels e invisível. Para dimensioná-la e torná-la visível é necessário usar os métodos setSize() e setVisible(). A classe Label é usada para criar um objeto de texto que é adicionado ao Frame. O objeto é posicionado dentro do Frame de acordo com um esquema de posicionamento, denominado de layout. Se você executou este programa deve ter notado que não foi possível finalizar o programa a não ser por meio de um Ctrl-C na console ou algum outro método indireto como o kill da shell Unix. Isto acontece porque os objetos gráficos na linguagem J ava não possuem tratadores implícitos de eventos. Ou seja, se você quer que algum evento seja tratado, então forneça o código para isto. Existem dois modelos para tratamento dos eventos que incidem sobre a interface. O primeiro foi introduzido na versão 1.0 do AWT e o segundo foi introduzido na versão 1.1 do AWT com o objetivo de substituir o primeiro, uma vez que ele sofre de deficiências de projeto e desempenho. Neste livro abordaremos apenas o modelo de eventos 1.1. Tratamento de Eventos O estilo de programação relacionado com interfaces gráficas é a programação voltada para eventos. Ou seja, o usuário define a interface e um conjunto de procedimentos para lidar com os eventos que incidirão sobre a interface. A construção de uma aplicação gráfica exige a criação de pelo menos um objeto que defina uma área no vídeo para exibir os componentes da aplicação. Para isso pode-se utilizar um objeto da classe Window ou Frame. O exemplo XX.XX contém o código de um programa que exibe uma mensagem na tela. Posteriormente é necessário definir quem deve receber os eventos gerados pela interação com a interface. Cada linguagem apresenta uma solução distinta para o tratamento de eventos. A solução J ava implementada no modelo de eventos 1.1 é explicada a seguir. Modelo de Eventos 1.1 O Modelo de eventos do AWT1.1 é um grande avanço sobre o modelo 1.0. Ele não só é mais flexível e orientado a objetos, como permitiu o desenvolvimento da interface de aplicação JavaBeans, que viabiliza a descrição de componentes visuais. J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 169 A idéia por trás do novo modelo é que uma fonte de eventos possa enviar os eventos para um ou mais objetos cadastrados para o recebimento do evento, denominados de event listeners. Geralmente, a fonte de evento é um componente, ou seja, um objeto da classe Component. Um listener é um objeto que implementa a interface EventListener ou alguma de suas sub- interfaces. A figura IV.XX ilustra a estratégia adotada. Figura VI.XX – Esquema de delegação de eventos AWT1.1. Note que um Listener pode estar registrado em mais de um componente. Um componente pode ter mais de uma lista de Listener, dependendo dos tipos de eventos que ele pode gerar. Uma ação sobre um componente pode gerar mais de um evento. Para ilustrar o tratamento de eventos, o exemplo XX.XX mostra como criar uma janela da classe Frame que trata o evento para fechar a janela, gerado quando o usuário clica o ícone × ×× ×, situado no canto superior direito do Frame. class FrameX extends Frame implements WindowListener { public FrameX() {this("FrameX");} public FrameX(String Titulo){ super(Titulo); addWindowListener(this); setSize(100,50); } Componente 1 A Componente 2 B Listener Listener Listener Listener Listener evento evento J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 170 public void windowOpened(WindowEvent we){} public void windowClosed(WindowEvent we){} public void windowIconified(WindowEvent we){} public void windowDeiconified (WindowEvent we){} public void windowActivated(WindowEvent we){} public void windowDeactivated(WindowEvent we){} public void windowClosing(WindowEvent we){ setVisible(false); dispose(); System.exit(0); } }; Exemplo XX.XX – Código para tratar o evento para fechar a janela. Para simplificar o exemplo o próprio componente (do tipo Frame) se registrou como um Listener para os eventos gerados por ele. De modo a se habilitar a receber os eventos gerados por um componente do tipo Window a classe deve implementar a interface WindowListener que é uma sub- interface da EventListener. class FrameX extends Frame implements WindowListener É preciso também se registrar junto ao componente para receber os eventos. Como no exemplo VI.XX o componente é uma instância da própria classe que implementa a interface, basta então adicionar a linha addWindowListener(this) no construtor da classe. Como a classe implementa a interface é necessário incluir código para implementar todos os métodos da interface. Cada método da interface trata um tipo de evento. No caso do exemplo VI.XX, estamos interessados em tratar eventos que solicitam o fechamento da janela. Portanto, é necessário incluir o código para tratamento do evento neste método. Os outros métodos são implementados sem linhas de código, para cumprir a exigência do compilador. Note que todos os métodos da classe FrameX recebem um tipo de evento: WindowEvent. No modelo de eventos 1.1, na versão SDK1.3, existem 14 classes para os eventos, definidas no pacote java.awt.event, todas subclasses da classe java.awt.event.AwtEvent e cada uma representando um conjunto de eventos relacionados, como mostrado na tabela VI.XX. Evento Descrição ComponentEvent Indica que um componente moveu, foi escondido, exibido ou mudou de tamanho. J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 171 FocusEvent Indica que um componente recebeu ou perdeu o foco. KeyEvent Indica que um componente recebeu uma entrada pelo teclado. MouseEvent Indica que um componente recebeu uma entrada pelo mouse. ActionEvent Indica que um componente foi ativado. AdjustmentEvent Indica que uma barra de rolamento ou componente similar foi movido. HierarchyEvent indica uma mudança na hierarquia de componentes a qual o componente pertence. InputEvent É o evento raiz de todos os eventos de entrada de componentes. InputMethodEvent Contém informação sobre o texto que está sendo editado. Sempre que o texto for alterado este evento será enviado. InvocationEvent O evento que executa o método run() em um tipo Runnable quando disparado pelo thread despachador de eventos da AWT. ItemEvent Indica que um item de um componente como lista, checkbox, ou similar foi selecionado. ContainerEvent Indica que um componente foi adicionado ou removido de um container. WindowEvent Indica que a janela mudou de estado. TextEvent Indica que uma mudança ocorreu em um campo de texto. Tabela VI.XX – Classes de Eventos. O exemplo XX.XX mostra o código de uma janela contendo um botão. A classe que implementa a janela se cadastra junto ao componente Button para receber o eventos sobre os componente. Para diminuir a complexidade do código a classe FrameBotao é subclasse da FrameX definida anteriormente. Toda vez que o componente Button é acionado, gerando um evento ActionEvent, o método actionPerformed() da instância FrameBotao é invocado. O método alterna o texto exibido pelo botão a cada chamada entre os textos “Aperte aqui” e “Valeu”. import java.awt.*; import java.awt.event.*; public class FrameBotao extends FrameX implements ActionListener { Button b; public FrameBotao() { setLayout(null); setSize(100,80); b = new Button("Aperte aqui"); J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 172 add(b); b.setBounds(10,40,80,30); b.addActionListener(this); } public void actionPerformed(ActionEvent e) { if (b.getLabel().equals("Aperte Aqui")) b.setLabel(“Valeu”); else b.setLabel("Aperte Aqui"); } public static void main(String args[]){ (new FrameBotao()).setVisible(true); } } Exemplo XX.XX – Código para tratar um evento gerado em botão. Tratamento de Eventos com classes Internas Agora vamos supor que uma determinada classe necessitasse receber vários eventos de vários componentes. O código da classe poderia ficar um pouco confuso, além da lista enorme de interfaces que necessitariam de ser declarada após a palavra implements. Uma forma mais elegante de se lidar com estes casos é por meio de classes internas, ou inner classes. Desta forma, o código para tratar cada grupo de eventos fica localizado em uma única classe, e como as classes internas possuem acesso à classe externa, os atributos e métodos da classe externa podem ser diretamente referenciados. O exemplo XX.XX mostra o exemplo XX.XX alterado de forma que o evento ActionEvent gerado acionamento do componente Button seja tratado por uma classe interna. import java.awt.*; import java.awt.event.*; public class FrameBotao extends FrameX { Button b; Public FrameBotao() J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 173 { setLayout(null); setSize(100,80); b = new Button("Aperte Aqui"); add(b); b.setBounds(10,40,80,30); b.addActionListener(new Bl()); } class Bl implements ActionListener { public void actionPerformed(ActionEvent e) { if (b.getLabel().equals("Aperte Aqui")) b.setLabel(“Valeu”); else b.setLabel("Aperte Aqui"); } } public static void main(String args[]){ (new FrameBotao()).setVisible(true); } } Exemplo XX.XX – Código para tratar um evento gerado em botão. Cada componente pode receber um determinado conjunto de listener para receber eventos. A Figura VI.XX mostra a relação entre alguns componentes e os eventos que podem ser gerados por eles. J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 174 Figura XX.XX – Relação entre os componentes e os eventos. FocusEvent ActionEvent AdjustmentEvent ItemEvent ContainerEvent TextEvent WindowEvent TextField MouseEvent Scrollbar Component Label Dialog Panel MenuItem Button Frame Choice Menu ComponentEvent KeyEvent J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 175 As instâncias das classes de eventos mantém as informações relativas ao evento. Por exemplo, a classe MouseEvent possui os seguintes métodos para recuperação de informação sobre o evento: Método Descrição int getId() tipo do evento. Por exemplo: MouseEvent.MOUSE_CLICKED, MouseEvent.MOUSE_PRESSED int getX() coordenada x do evento. int getY() coordenada y do evento. Point getPoint() coordenada x e y do evento. int getClickCount() retorna o número de mouse clicks. boolean isPopupTrigger() indica se o evento é um disparo para menu pop-up. void translatePoint(int x, int y) move a posição do evento por x e y. Tabela VI.XX – Métodos da classe MouseEvent. Basicamente, para se codificar um tratador para um determinado evento basta seguir os seguintes passos: 1. Determine o grupo de eventos deseja tratar. 2. No nome do grupo de eventos substitua o termo trecho Event por Listener. Este é o nome da interface que você deve implementar. Crie uma classe interna que implementa a interface. 3. Codifique os métodos que tratam os eventos que você deseja tratar. Todos os métodos da interface tem que ser implementados, mas o corpo dos métodos que não interessam podem ser deixados vazios. 4. Registre um objeto da classe interna junto ao componente, por meio do método addXXX, onde XXX é o nome da interface implementada. Por exemplo, no exemplo VI.XX, para tratar o grupo de eventos ActionEvent foi criada uma classe interna que implementa a interface ActionListener, e um objeto da classe interna é registrado no componente por meio do método addActionListener. Portanto, tendo o nome do grupo de eventos é fácil obter o nome da interface e do método de registro no componente. Segue abaixo uma lista com alguns métodos para registro de listeners. ♦ Button.addActionListener(ActionListener l) J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 176 ♦ Frame.addWindowListener(WindowListener l) ♦ Choice.addItemListener(ItemListener l) ♦ TextField.addTextListener(TextListener l) ♦ Scrollbar.addAdjustamentListener(AdjustamentListene r l) ♦ Component.addComponentListener(ComponentListener l) ♦ Component.addFocusListener(FocusListener l) ♦ Component.addKeyListener(KeyListener l) Algumas interfaces exigem a implementação de um grande número de métodos, como por exemplo a interface WindowListener, como foi visto no exemplo VI.XX. Isto pode tornar a implementação das interfaces uma tarefa um tanto tediosa. Com o objetivo de auxiliar esta tarefa, foi incluído no pacote java.awt.event seis classes abstratas, denominadas adapters que facilitam a implementação de Listener. O programador só precisa criar uma subclasse da classe adapter escolhida e sobrescrever o método desejado. Para determinar o nome da classe adapter basta substituir no nome da interface a subcadeia Listener por adapter. A tabela VI.XX apresenta os nomes das classes adapters. ComponentAdapter ContainerAdapter FocusAdapter KeyAdapter MouseAdapter MouseMotionAdapter WindowAdapter Tabela VI.XX – Classes adapters. O exemplo XX.XX mostra o exemplo XX.XX alterado de forma que o evento ActionEvent gerado acionamento do componente Button seja tratado por uma classe interna que é subclasse da classe MouseAdapter. import java.awt.*; import java.awt.event.*; public class Botao extends FrameX { Button b; public Botao() { J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 177 setLayout(null); setSize(100,80); b = new Button("Aperte Aqui"); add(b); b.setBounds(10,40,80,30); b.addActionListener(new Bl()); } class Bl extends MouseAdapter { public void actionPerformed(ActionEvent e) { if (b.getLabel().equals("Aperte Aqui")) b.setLabel("Valeu"); else b.setLabel("Aperte Aqui"); } } public static void main(String args[]){ (new Botao()).setVisible(true); } } Exemplo XX.XX – Código para tratar um evento gerado em botão. No entanto, existem algumas desvantagens em se usar classes abstratas no lugar de interfaces. A primeira delas é que elimina a possibilidade da classe ser subclasse de outra classe e a segunda é que se o programador cometer algum erro na digitação do nome de algum método, ele será encarado como um novo método e o original não será sobrescrito. Exemplo Básico Apresentaremos agora um exemplo um pouco mais complexo, porém simples o suficiente para que seja facilmente entendido. O objetivo deste exemplo e servir de base para apresentarmos os principais componentes da biblioteca AWT. Portanto, a medida que novos componentes forem apresentados, iremos exemplificar o uso deste componentes acrescentando-os em nosso exemplo básico. A função de nosso exemplo é servir como ambiente para execução de comandos MS-DOS, tais como dir e type. O exemplo foi projetado para funcionar nos ambientes MS-WINDOWS 95 e MS-WINDOWS 98 mas pode ser facilmente adaptado para funcionar em outros ambientes operacionais. A figura XX.XX mostra a interface da primeira versão de nosso exemplo. Note que, apesar de simples, o nosso exemplo apresenta três componentes em sua J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 178 interface: um botão e duas caixas de texto, sendo uma com barras de rolamento e capacidade para múltiplas linhas. Figura XX.XX – Aparência da aplicação para execução de comandos MS- DOS. A caixa de texto com capacidade para uma linha de texto é um componente TextField e sua função é permitir a entrada de comandos dos para a execução. A caixa de texto com capacidade para múltiplas linhas de texto é um componente TextArea e sua função é exibir o resultado da execução do comando. Finalmente, o botão é implementado por um componente Button e sua função e gerar eventos para execução do comando digitado. O exemplo XX.XX mostra o código da aplicação, implementado pela classe Comando. 1 2 3 4 5 6 import java.io.*; import java.awt.*; import java.awt.event.*; public class Comando extends Frame { private TextField tfComando; J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 179 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 private TextArea taSaida; private Button btExec; private String com; public Comando() { com ="command.com /c "; // Altere essa linha para outros ambientes operacionais tfComando =new TextField(50); taSaida =new TextArea(20,60); btExec =new Button(“Executa”); taSaida.setEditable(false); add(tfComando,BorderLayout.NORTH); add(btExec,BorderLayout.SOUTH); add(taSaida,BorderLayout.CENTER); addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent evt) { System.exit(0); } }); btExec.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent evt) { btExecActionPerformed(evt); } }); pack(); } private void btExecActionPerformed(ActionEvent evt) { String s =tfComando.getText(); if (s==null) return; try { Process p =Runtime.getRuntime().exec(com+s); InputStream in =p.getInputStream(); BufferedReader br =new BufferedReader(new InputStreamReader(in)); String line; taSaida.setText(""); while ((line =br.readLine()) !=null) { taSaida.append(line+'\n'); } taSaida.append("Fim!"); }catch(Exception e) {taSaida.setText(e.getMessage()); return;} } public stati c void main(String args[]) { new Comando().show(); J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 180 56 57 } } Exemplo XX.XX – Código que executa um comando DOS. Na linha 12 é criado um objeto String contendo o parte inicial do comando necessário para invocar a shell do MS-DOS. Essa parte inicial deve ser alterada para adaptar o exemplo para outros sistemas operacionais. Nas linhas 14 a 16 são criados os componentes que farão parte da interface. Na linha 14 o componente TextField é criado com espaço para exibir 50 caracteres. Na linha 15 o componente TextArea é criado com espaço para exibir 20 linhas e 50 caracteres por linha. Na linha 16 o componente Button é criado com um o texto “Executa”. Na linha 18 é chamado o método setEditable() do componente TextArea com o argumento false para impedir que o seu conteúdo seja editado. Isso ocorre porque a função do componente é apenas exibir o resultado da execução dos comandos. O leitor deve ter notado que a criação das classes que irão tratar os eventos é um pouco diferente do que mostramos até agora. Nas linhas 24 a 28 e 30 a 34 as declarações das classes são feitas no mesmo local onde os objetos são instanciados. Este tipo de classe interna é denominado de classe interna anônima. A classe é denominada de anônima pois nenhum nome é associado a ela. A classe anônima se torna automaticamente subclasse da classe mencionada após o operador new. É vantajoso usar este tipo de construção quando apenas uma instância da classe será gerada, uma vez que amarra a declaração da classe com seu uso. No entanto, em alguns casos esta técnica pode diminuir a legibilidade do código. Outro fato que pode intrigar o leitor é a distribuição dos componentes na janela. Aparentemente, pouco código foi dedicado ao posicionamento dos elementos na interface. Mesmo assim os componentes foram arranjados de forma razoavelmente ordenada na interface. Isto se deve ao fato de todos os componentes do tipo Container seguirem um arranjo predefinido (layout). Este arranjo é implementado pelas classes BorderLayout, CardLayout, FlowLayout, GridBagLayout e GridLayout, sendo o BorderLayout o arranjo default. Existe também a possibilidade de não se adotar nenhum layout. Neste caso é preciso posicionar os componentes em termos de coordenadas dentro do objeto do tipo Container. Os objetos de layout serão tratados mais adiante. No momento basta saber que o layout adotado é o default e que este tipo de layout arranja os componentes de acordo com os pontos cardeais (NORTH, SOUTH, EAST, WEST, CENTER). As linhas 20 a 22 J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 181 mostram as adições de componentes dentro do Frame, com a indicação do seus posicionamentos relativos segundo o layout adotado. A adição de componentes é feita por meio do método add(). As linhas 38 a 52 mostram o código do método que executa o comando MS- DOS e exibe o resultado no componente TextArea. Na linha 42 o comando MS-DOS é executado por meio do método exec() do objeto Runtime. O objeto Runtime é obtido pelo método estático getRuntime() da classe Runtime, uma vez que não pode ser instanciado diretamente. O processo filho, uma vez iniciado, é executado de forma concorrente com o processo pai. O método exec() retorna um objeto do tipo Process, tornando possível controlar o processo e obter informações sobre ele. No nosso caso estamos interessados em capturar a saída padrão do processo filho para exibi-la na interface. Isso é feito na linha 43 por meio do método getInputStream() do objeto Process. O objeto InputStream é canalizado para um objeto BufferedReader com o objetivo de facilitar sua manipulação. As linhas restantes do método tratam da leitura da saída padrão do processo filho e de sua exibição na TextArea. Na linha 46 é usado o método setText() para colocar um texto vazio no componente e, dessa forma, limpar a TextArea. Na linha 48 a String lida da saída padrão do processo filho é anexada na TextArea. Acrescentando Cores Podemos alterar as cores de alguns componentes do exemplo XX.XX para torná-lo visualmente mais interessante por meio de objetos da classe Color. Esta classe encapsula a criação de cores por meio dos níveis RGB. Ela mantém também um conjunto de atributos constantes com valores de cores predefinidos. Podemos, por exemplo, alterar a cor de fundo do componente TextArea por meio do método setBackground(). taSaida.setBackground(Color.lightGray); Podemos alterar também a cor de fundo do componente Button. Neste caso vamos obter para definir a cor por meio de um número que representa seus componentes RGB. btExec.setBackground(new Color(0x08FDDF10)); J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 182 Para modificar a cor frontal é preciso utilizar o método setForeground(). Todos os componentes possuem métodos equivalentes a estes para alteração das cores. Gerenciando o Layout O posicionamento de componentes dentro de objetos do tipo Container é determinado por uma instância de objeto denominado de gerenciador de layout (layout managers). A AWT fornece 5 classes para a implementação de layouts, descritas na tabela XX.XX. O layout default é o implementado pela classe BorderLayout. O pacote Swing acrescenta a classe BoxLayout ao conjunto de gerenciadores de layout. Gerenciador de Layout Descrição BorderLayout Posiciona e redimensiona os componentes de um Container em cinco regiões: norte, sul, leste e oeste. Cada região é identificada respectivamente pelas constantes: NORTH, SOUTH, EAST, WEST, e CENTER. FlowLayout Posiciona os componentes de um Container em um fluxo da esquerda para a direita, como linhas em um parágrafo. Cada linha é centralizada. CardLayout Trata cada componente do Container como uma a carta. Somente uma carta é visível em um determinado instante e o Container funciona como uma pilha de cartas. O primeiro componente adicionado é o primeiro visível quando o Container é exibido. GridBagLayout É o gerenciador de layout mais flexível e o mais complexo. Ele arranja os componentes verticalmente e horizontalmente sem exigir que os componentes tenham o mesmo tamanho. Mantém um grade retangular de células, sendo que cada componente pode ocupar mais de uma célula. GridLayout Posiciona e redimensiona os componentes de um Container em uma grade retangular. O Container é dividido em retângulos de tamanhos iguais e um componente é colocado em cada retângulo. Tabela XX.XX –Gerenciadores de Layout. J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 183 Para definir o layout de um objeto do tipo Container basta utilizar o método setLayout() do objeto, passando como argumento um objeto gerenciador de layout, como mostrado abaixo: Frame f = new Frame(); f.setLayout(FlowLayout); É possível também não adotar layout algum, optando por posicionar os componentes em coordenadas absolutas dentro do Container. Para isso basta passar null como argumento para o método setLayout(). O posicionamento absoluto de componentes pode ser feito pelos métodos setLocation() ou setBounds() do componente, como mostrado abaixo. Frame f = new Frame(); f.setLayout(null); Button b = new Button(“Teste”); f.add(b); b. setLocation(10, 10); Porém, a adoção de gerenciadores de layout permite o desenvolvimento que se adaptam mais facilmente à redimensionamentos e à mudanças de linguagens. Isto é o posicionamento e dimensionamento absoluto pode fazer com que componentes tornem-se parcialmente ou totalmente ocultos com o redimensionamento da janela. O mesmo pode ocorrer com os “labels” dos componentes quando forem traduzidos para outra língua. J á estes problemas não ocorrem com o posicionamento e dimensionamento relativo. Exemplo com BorderLayout O exemplo XX.XX utiliza este gerenciador de layout para distribuir os componentes na interface. O exemplo abaixo mostra distribuição de cinco botões em um Frame por meio deste tipo de gerenciador de layout. import java.awt.*; public class BorderTeste extends FrameX { public BorderTeste() { setLayout(new BorderLayout()); add(new Button("Norte"), BorderLayout.NORTH); J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 184 add(new Button("Sul"), BorderLayout.SOUTH); add(new Button("Leste"), BorderLayout.EAST); add(new Button("Oeste"), BorderLayout.WEST); add(new Button("Centro"), BorderLayout.CENTER); } public stati c void main(String a[]){new BorderTeste().show();} } Exemplo XX.XX – Frame com BorderLayout. A figura XX.XX mostra o resultado da execução do código do exemplo XX.XX. Figura XX.XX – Aparência do Frame com BorderLayout. Exemplo com FlowLayout O exemplo abaixo mostra distribuição de cinco botões em um Frame por meio deste tipo de gerenciador de layout. import java.awt.*; public class FlowTeste extends FrameX { public FlowTeste() { setLayout(new FlowLayout()); J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 185 add(new Button("UM")); add(new Button("DOIS")); add(new Button("TRÊS")); add(new Button("QUATRO")); add(new Button("CINCO")); setSize(200,100); } public static void main(String a[]){new FlowTeste().show();} } Exemplo XX.XX – Frame com FlowLayout. A figura XX.XX mostra o resultado da execução do código do exemplo XX.XX. Note que quando acaba o espaço em uma linha os componentes são posicionados na linha de baixo seguindo da esquerda para a direita. Note também que cada linha é centralizada no Container. Figura XX.XX – Aparência do Frame com FlowLayout. Exemplo com CardLayout O exemplo abaixo mostra usar o CardLayout para alternar a exibição de componentes. Um botão é adicionado à interface para receber eventos. Quando o botão é pressionado o tratador de eventos alterna a exibição dos componentes do CardLayout por meio de chamadas ao método next() do gerenciador de layout. A J FC fornece o componente JtabbedPane que apresenta maiores facilidades. import java.awt.*; import java.awt.event.*; public class CardTeste extends FrameX { J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 186 Panel p; CardLayout cl; Button b; public CardTeste() { setLayout(new BorderLayout()); p =new Panel(); b =new Button("Muda carta"); cl =new CardLayout(); p.setLayout(cl); p.add(new Label("Este Label está na primeira carta"),"um"); p.add(new Label("Este Label está na segunda carta"),"dois"); add(b, BorderLayout.NORTH); add(p, BorderLayout.SOUTH); b.addActionListener( new ActionListener() { public void actionPerformed(ActionEvent evt) { cl.next(p); } }); pack(); } public stati c void main(String a[]){new CardTeste().show();} } Exemplo XX.XX – Frame com CardLayout. Note que ao adicionar um componente ao Container é preciso passar um identificador. A figura XX.XX mostra o resultado da execução do código do exemplo XX.XX. Figura XX.XX – Aparência do Frame com CardLayout. J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 187 Exemplo com GridLayout O exemplo abaixo mostra distribuição de cinco botões em um Frame por meio deste tipo de gerenciador de layout. import java.awt.*; public class GridTeste extends FrameX { public GridTeste() { setLayout(new GridLayout(3,2)); add(new Button("UM")); add(new Button("DOIS")); add(new Button("TRÊS")); add(new Button("QUATRO")); add(new Button("CINCO")); } public stati c void main(String a[]){new GridTeste().show();} } Exemplo XX.XX – Frame com GridLayout. A figura XX.XX mostra o resultado da execução do código do exemplo XX.XX. Figura XX.XX – Aparência do Frame com GridLayout. J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 188 Exemplo com GridBagLayout Este gerenciador é o mais complexo, mas é também o que permite um maior controle sobre o posicionamento dos elementos, sem perder as vantagens de um posicionamento relativo. O exemplo XX.XX mostra distribuição de nove botões em um Frame por meio deste tipo de gerenciador de layout. import java.awt.*; public class GridBagTeste extends FrameX { public GridBagTeste() { Button b; GridBagLayout gridbag =new GridBagLayout(); GridBagConstraints c =new GridBagConstraints(); setLayout(gridbag); for (int i=1;i<10;i++) { switch(i) { case 1: c.fill =GridBagConstraints.BOTH; c.weightx =1.0; break; case 6: case 3: c.gridwidth =GridBagConstraints.REMAINDER; c.weightx =1.0; break; case 4: c.weightx =0.0; break; case 5: c.gridwidth =GridBagConstraints.RELATIVE; c.weightx =1.0; break; case 7: c.gridwidth =1; c.gridheight =2; c.weighty =1.0; break; case 8: c.gridwidth =GridBagConstraints.REMAINDER; c.weighty =0.0; c.gridheight =1; break; } b =new Button("Botão "+i); gridbag.setConstraints(b, c); add(b); J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 189 } pack(); } public static void main(String args[]) { new GridBagTeste().show(); } } Exemplo XX.XX – Frame com GridBagLayout. O posicionamento relativo dos componentes é controlado por meio de um objeto da classe GridBagConstraints. Ele possui vários atributos públicos que orientam o gerenciador de layout no momento de posiciona e reorganizar os componentes. O objeto é associado a cada componente por meio do método setConstraints() da instância GridBagLayout. Os atributos utilizados no exemplo foram: fill – usado quando a área para a exibição do componente é maior que a área é requisitada pelo componente. Os valores possíveis (definidos na classe GridBagConstraints) são NONE (default), HORIZONTAL (torna o componente largo o suficiente para preencher a área horizontalmente), VERTICAL (torna o componente alto o suficiente para preencher a área na vertical), e BOTH (preenche nos dois sentidos). weightx, weighty – determina como o espaço excedente é distribuído, definindo o comportamento para o redimensionamento. Ou seja define a proporção do espaço que excedente que será distribuído entre os componentes. Se não for especificado para pelo menos uma linha (weightx) e coluna (weighty), todos os componentes serão agrupados no centro do Container, uma vez que o peso de cada um é zero (default). gridwidth, gridheight – Especifica o numero de células em um linha (gridwidth) ou coluna (gridheight) ocupadas pela área de exibição de um componente. O valor default é 1. Os valores possíveis (definidos na classe GridBagConstraints) são REMAINDER para especificar que o componente deve ser o último da linha (para gridwidth) ou coluna (para gridheight). RELATIVE para especificar que o componente deve ser o vizinho do último na linha (para gridwidth) ou coluna (para gridheight). Ou valores inteiros especificando o número de linhas e colunas ocupadas. J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 190 A figura XX.XX mostra o resultado da execução do código do exemplo XX.XX. Figura XX.XX – Aparência do Frame com GridBagLayout. Utilizando Listas Muitas vezes é necessário que o usuário selecione um item a partir de uma lista de itens disponíveis. A AWT torna possível a seleção de um item em uma lista de itens por meio do componente Choice. Este componente possui métodos para a adição, remoção e acesso a itens. O exemplo abaixo mostra a alteração do exemplo XX.XX que usa uma lista de escolha para armazenar os comandos anteriores. Desta forma o usuário economiza na digitação de comandos repetidos. 1 2 3 4 5 6 7 8 9 10 11 12 13 import java.io.*; import java.awt.*; import java.awt.event.*; public class Comando2 extends java.awt.Frame { private TextField tfComando; private TextArea taSaida; private Button btExec; private Panel panel; private Choice ch; private String com; public Comando2() { J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 191 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 com ="command.com /c "; tfComando =new TextField(50); taSaida =new TextArea(20,60); panel =new Panel(); btExec =new Button("Executa"); ch =new Choice(); ch.setVisible(false); btExec.setBackground(new Color(0x08FDDF10)); taSaida.setBackground(Color.lightGray); taSaida.setEditable(false); add(panel,BorderLayout.NORTH); panel.add(tfComando,BorderLayout.NORTH); panel.add(ch,BorderLayout.CENTER); add(btExec,BorderLayout.SOUTH); add(taSaida,BorderLayout.CENTER); addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent evt) { System.exit(0); } }); btExec.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent evt) { btExecActionPerformed(evt); } }); ch.addItemListener( new ItemListener() { public void itemStateChanged(ItemEvent evt) { if (evt.getStateChange() ==ItemEvent.SELECTED) tfComando.setText(ch.getSelectedItem()); } }); pack(); } private void btExecActionPerformed(ActionEvent evt) { String s =tfComando.getText(); if (s==null) return; boolean inclui =true; for (int i=ch.getItemCount()-1;i>=0;i--) if (s.equals(ch.getItem(i))) inclui=false; if (inclui) { ch.add(s); ch.setVisible(true); J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 192 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 pack(); } try { Process p =Runtime.getRuntime().exec(com+s); InputStream in =p.getInputStream(); BufferedReader br =new BufferedReader(new InputStreamReader(in)); String line; taSaida.setText(""); while ((line =br.readLine()) !=null) taSaida.append(line+'\n'); taSaida.append("Fim!"); }catch(Exception e) {taSaida.setText(e.getMessage()); return;} } public static void main(String args[]) {new Comando2().show(); } } Exemplo XX.XX – Aplicação para execução de comandos MS-DOS com lista de escolha. Na linha 21 é usado o método setVisible() para que o componente Choice fique invisível inicialmente. Isto é feito porque no início a lista de itens está vazia e, portanto, não é preciso exibi-la. Nas linhas 45 a 50 é adicionado um objeto ItemListener à lista de listener do objeto Choice. Este objeto receberá os eventos de seleção de itens na lista de escolha. Nas linhas 58 a 64 é verificado se o comando digitado já está incluído na lista de itens. Caso não esteja o item é incluído e o método pack() do Frame é chamado, para que a janela se ajuste ao tamanho dos componentes. A figura XX.XX mostra o resultado da execução do código do exemplo XX.XX. J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 193 Figura XX.XX – Aparência da aplicação para execução de comandos MS-DOS com lista de escolha. Trabalhando com Menus e Diálogos Outros componentes importantes para criação de interfaces com o usuário são os menus e as caixas de diálogos. De forma a ilustrar o uso desses recursos modificaremos o exemplo XX.XX para conter uma barra de menus com apenas um menu. O menu conterá dois itens de menu. O acionamento do primeiro item de menu ocasionará a exibição de um pequeno texto de ajuda no componente TextArea. O acionamento do segundo item de menu ocasionará a exibição de uma caixa de diálogo contendo informações sobre a aplicação. 1 2 3 4 5 6 import java.io.*; import java.awt.*; import java.awt.event.*; public class Comando3 extends java.awt.Frame { private TextField tfComando; J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 194 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 private TextArea taSaida; private Button btExec; private Panel panel; private Choice ch; private String com; public Comando3() { com ="command.com /c "; tfComando =new TextField(50); taSaida =new TextArea(20,60); panel =new Panel(); btExec =new Button("Executa"); ch =new Choice(); ch.setVisible(fal se); btExec.setBackground(new Color(0x08FDDF10)); taSaida.setBackground(Color.lightGray); taSaida.setEditable(false); Menu menu =new Menu("Ajuda"); menu.add("Resumo"); menu.addSeparator(); menu.add("Sobre"); MenuBar mb =new MenuBar(); mb.add(menu); setMenuBar(mb); add(panel,BorderLayout.NORTH); panel.add(tfComando,BorderLayout.NORTH); panel.add(ch,BorderLayout.CENTER); add(btExec,BorderLayout.SOUTH); add(taSaida,BorderLayout.CENTER); addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent evt) { System.exit(0); } }); btExec.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent evt) { btExecActionPerformed(evt); } }); ch.addItemListener( new ItemListener() { public void itemStateChanged(ItemEvent evt) { if (evt.getStateChange() ==ItemEvent.SELECTED) J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 195 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 tfComando.setText(ch.getSelectedItem()); } }); menu.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent evt) { if (evt.getActionCommand().equals("Sobre")) mostraSobre(); else if (evt.getActionCommand().equals("Resumo")) taSaida.setText("Digite o Comando e pressione “+ ”o botão <executa>para executar."); } }); pack(); } private void mostraSobre() { Dialog d =new Dialog(this,"Sobre Comando"); d.setModal(true); Label lb1 =new Label("Comando Versão 1.2", Label.CENTER); Label lb2 =new Label("Alcione de Paiva Oliveira", Label.CENTER); lb1.setFont(new Font ("Dialog", Font.BOLD|Font.ITALIC, 18)); lb2.setFont(new Font ("Courier New", 0, 16)); d.add(lb1,BorderLayout.NORTH); d.add(lb2,BorderLayout.SOUTH); d.setBackground(Color.cyan); d.addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent evt) { evt.getWindow().dispose(); } }); d.pack(); d.show(); } private void btExecActionPerformed(ActionEvent evt) { String s =tfComando.getText(); if (s==null) return; boolean inclui =true; for (int i=ch.getItemCount()-1;i>=0;i--) if (s.equals(ch.getItem(i))) inclui=false; if (inclui) { ch.add(s); ch.setVisible(true); pack(); } try { Process p =Runtime.getRuntime().exec(com+s); InputStream in =p.getInputStream(); BufferedReader br =new BufferedReader(new InputStreamReader(in)); J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 196 105 106 107 108 109 110 111 112 113 114 String line; taSaida.setText(""); while ((line =br.readLine()) !=null) taSaida.append(line+'\n'); taSaida.append("Fim!"); }catch(Exception e) {taSaida.setText(e.getMessage()); return;} } public static void main(String args[]) {new Comando3().show(); } } Exemplo XX.XX – Aplicação para execução de comandos MS-DOS com barra de menus e uma caixa de diálogo. As linhas 27 a 33 contém o código para a inserção da barra de menus e do menu. Na linha 27 é criado um componente Menu com o label “Ajuda”. Nas linhas 28 a 30 são adicionados dois itens de menu e um separador ao componente Menu. Nas linha 31 e 32 é criado um componente MenuBar e o componente Menu é adicionado ao MenuBar. Na linha 33 o componente MenuBar é definida como a barra de menu da janela corrente. Nas linhas 60 a 67 é adicionado um objeto ActionListener à lista de Listener do objeto Menu. Este objeto receberá os eventos de seleção de itens do menu. Caso o item de menu selecionado seja o “Sobre” então o método mostraSobre() é invocado. Caso o item de menu selecionado seja o “Resumo” então é exibido um texto de ajuda no componente TextArea. As linhas 71 a 88 contém o código do método mostraSobre(). Este método é responsável pela montagem e exibição da caixa de diálogo com informações sobre a aplicação. Na linha 72 é criada uma instância da classe Dialog. O construtor desta classe exige que seja passado como parâmetro um objeto da classe Frame ou Dialog para deter a posse do objeto a ser criado. Quando o objeto possuidor é tornado invisível ou minimizado a janela Dialog é automaticamente tornada invisível ou minimizada. No caso do exemplo o Frame corrente é passado como parâmetro. Na linha 73 é invocado o método setModal() para torna a o diálogo modal. O diálogo modal bloqueia toda entrada em todas as janelas no contexto da aplicação, exceto pelas janelas criadas tendo o diálogo como possuidor. A figura XX.XX mostra o resultado da execução do código do exemplo XX.XX. J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 197 Figura XX.XX – Aparência da aplicação para execução de comandos MS-DOS com barra de menus. Capturando Eventos do Teclado Principais Classes Color Esta classe encapsula a criação de cores por meio dos níveis RGB. Ela mantém também um conjunto de atributos constantes com valores de cores predefinidos. J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 198 Hierarquia java.lang.Object | +--java.awt.Color Atributos públicos Atributo Descrição Color black A cor preta. Color blue A cor azul. Color cyan A cor azul claro. Color darkGray A cor cinza escuro. Color gray A cor cinza. Color green A cor verde. Color lightGray A cor cinza claro. Color magenta A cor violeta. Color orange A cor laranja. Color pink A cor rosa. Color red A cor vermelha. Color white A cor branca. Color yellow A cor amarela. Construtores Construtor Descrição Color(float r, float g, float b) Cria uma cor opaca RGB com os valores red, green, e blue especificados dentro da faixa (0.0 - 1.0). Color(int rgb) Cria uma cor opaca com os valores RGB combinados no parâmetro rgb. O componente red está definido nos bits 16-23, o componente green está definido nos bits 8- 15, e o componente blue está definido nos bits 0-7. Color(int r, int g, int b) Cria uma cor opaca RGB com os valores red, green, e blue especificados dentro da faixa (0 - 255). Métodos mais usados Método Descrição Color brighter() Cria uma versão mais clara da cor corrente. Color darker() Cria uma versão mais escura da cor corrente. Color decode(String nm) Converte a String em um inteiro e retorna a cor J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 199 opaca relacionada. equals(Object obj) Verifica se o objeto é igual a essa cor. int getBlue() Retorna o componente azul da cor. int getGreen() Retorna o componente verde da cor. int getRed() Retorna o componente vermelho da cor. int getTransparency() Retorna o modo de transparência desta cor. String toString() Retorna a representação em String desta cor. Exemplo Color c = new Color(0xFFC0C0C0); Component Um componente é um objeto que possui uma representação gráfica que pode ser exibida em uma tela e pode interagir com o usuário. Hierarquia java.lang.Object | +--java.awt.Component Atributos públicos Atributo Descrição float BOTTOM_ALIGNMENT Alinhamento na parte inferior. float CENTER_ALIGNMENT Alinhamento ao centro. loat LEFT_ALIGNMENT Alinhamento à esquerda. float RIGHT_ALIGNMENT Alinhamento à esquerda. float TOP_ALIGNMENT Alinhamento na parte superior. Construtor Construtor Descrição Component() Constrói um novo Component. J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 200 Métodos mais usados Método Descrição void add(PopupMenu popup) Adiciona um menu do tipo PopupMenu ao componente. void addComponentListener( ComponentListener l) Adiciona o ComponentListener para receber eventos sobre o componente. boolean contains(int x, int y) Verifica se o componente possui a coordenada. float getAlignmentX() Retorna o alinhamento no eixo x. float getAlignmentY() Retorna o alinhamento no eixo y. Color getBackground() Retorna a cor de fundo do componente. Rectangle getBounds() Retorna as dimensões do componente. Component getComponentAt( int x, int y) Retorna o componente do Component que contém a coordenada especificada. Apenas o primeiro nível de descendência é examinado. Cursor getCursor() Retorna o cursor deste componente. Font getFont() Retorna o fonte deste componente. Color getForeground() Retorna a cor de frente deste componente. Graphics getGraphics() Cria um contexto gráfico para este componente. int getHeight() Retorna a altura deste componente. Point getLocation() Retorna a localização deste componente. String getName() Retorna o nome deste componente. Container getParent() Retorna o Container onde está inserido este componente. Dimension getSize() Retorna as dimensões deste componente. Toolkit getToolkit() Retorna o Toolkit deste componente. int getWidth() Retorna a largura deste componente. int getX() Retorna a coordenada X deste componente. int getY() Retorna a coordenada Y deste componente. boolean isDisplayable() Verifica se este componente pode se exibido. boolean isEnabled() Verifica se este componente está habilitado. boolean isFocusOwner() Verifica se este componente possui o foco. boolean isVisible() Verifica se este componente esta visível. void list(PrintStream out) Lista este componente no PrintStream. void paint(Graphics g) Pinta o este Container. Este método deve ser sobrescrito se o programador deseja desenhar no Container. Neste caso super.paint(g) deve ser invocado. void processEvent(AWTEvent e) Processa os eventos ocorrendo neste componente. void remove(MenuComponent popup) Remove o menu popup menu deste componente. void setBounds(int x, int y, int width, Define os limites deste componente. J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 201 int height) void setEnabled(boolean b) Define este componente como habilitado. void setFont(Font f) Define o fonte deste Component. void void setName(String name) Define o nome deste componente. void setSize(int width, int height) Define o tamanho deste componente. void setVisible(boolean b) Define este componente como visível. String toString() Retorna a representação em String deste componente. Exemplo Component c= new Component(); Button Os objetos da classe Button são objetos gráficos que simulam botões e geram eventos quando são pressionados. Hierarquia java.lang.Object | +--java.awt.Component | +--java.awt.Button Construtores Construtor Descrição Button() Constrói um botão sem texto. Button(String lbl) Constrói um botão com um texto. Métodos mais usados Método Descrição String getLabel() Retorna o texto apresentado no botão. void setLabel(String lbl) Define o texto apresentado no botão como o referenciado por lbl. J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 202 Exemplo Button b= new Button(“ok”); Label Os objetos da classe Label servem para exibir textos que não serão editados pelo usuário. Hierarquia java.lang.Object | +--java.awt.Component | +--java.awt.Label Construtores Construtor Descrição Label() Constrói um Label sem texto. Label(String lbl) Constrói um Label com um texto. Label(String lbl, int alin) Constrói um Label com um texto e com o alinhamento especificado por alin. Os valores possíveis para o alinhamento são Label.LEFT, Label.RIGHT, e Label.CENTER. Métodos mais usados Método Descrição String getText() Retorna o texto apresentado no Label. void setText(String lbl) Define o texto apresentado no Label como o referenciado por lbl. int getAlignment() Retorna o alinhamento. void setAlignment(int alin) Estabelece o novo alinhamento, especificado por alin. Os valores possíveis para o J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 203 alinhamento são Label.LEFT, Label.RIGHT, e Label.CENTER. List O componente List permite que o programador crie uma lista de elementos passíveis de rolamento. Dependendo de como a lista foi definida o usuário pode selecionar um item ou múltiplo itens. Hierarquia java.lang.Object | +--java.awt.Component | +--java.awt.List Construtores Construtor Descrição List() Constrói uma nova lista rolável. List(int linhas) Constrói uma nova lista rolável com o número de linhas visíveis igual a linhas. List(int linhas, boolean sm) Constrói uma nova lista rolável com o número de linhas visíveis igual a linhas e o parâmetro sm indicando se é para permitir seleções múltiplas. Métodos mais usados Método Descrição void add(String item) Adiciona o item referenciado por item no final da lista. void add(String item, int ind) Adiciona o item referenciado por item na posição indicada por ind. A base é 0. Se o valor de ind for menor que 0 ou maior que o número de elementos, o item será inserido no final da lista. J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 204 void deselect(int ind) remove a seleção do item no índice especificado. String getItem(int ind) Obtém o item no índice especificado. int getItemCount() Obtém o número de itens na lista. String[] getItems() Obtém todos os itens da lista. int getRows() Obtém o número de linhas visíveis da lista. int getSelectedIndex() Obtém o índice do elemento selecionado. Retorna -1 se nenhum elemento ou mais de um elemento estiver selecionado. int[] getSelectedIndexes() Retorna todos os itens selecionados. String getSelectedItem() Obtém o elemento selecionado. String[] getSelectedItems() Obtém todos os elementos selecionados. boolean isMultipleMode() Indica se o modo de seleção é múltiplo. void makeVisible(int ind) Torna visível o elemento no índice especificado. void remove(int ind) Remove o elemento no índice especificado. void removeAll() Remove todos os elementos na lista. void select(int ind) Seleciona o elemento no índice. Exemplo List lista = new List(3); lista.add("Laranja"); lista.add("Abacate"); lista.add("Pera"); lista.add("Uva"); TextField O componente TextField é usado para implementar entrada e edição de uma linha de texto. Hierarquia java.lang.Object | +--java.awt.Component | +--java.awt.TextComponent | +--java.awt.TextField J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 205 Construtores Construtor Descrição TextField() Constrói um novo TextField. TextField(int cols) Constrói um novo TextField com o número de colunas especificado. TextField(String txt) Constrói um novo TextField exibindo o texto especificado. TextField(String txt,int cols) Constrói um novo TextField com o número de colunas e o texto especificados. Métodos mais usados Método Descrição int getColumns() Retorna o número de colunas. String getText() Retorna o texto exibido no componente. void setColumns(int cols) Define o número de colunas. void setText(String txt) Define o número o texto a ser editado. Exemplo TextField tf; tf = new TextField("Ola mundo!"; TextArea O componente TextArea é usado para implementar entrada e edição de texto com múltiplas linhas. Hierarquia java.lang.Object | +--java.awt.Component | J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 206 +--java.awt.TextComponent | +--java.awt.TextArea Construtores Construtor Descrição TextArea() Constrói um novo TextArea. TextArea(int lin, int cols) Constrói um novo TextArea com o número de linhas e colunas especificados. TextArea(String txt) Constrói um novo TextArea exibindo o texto especificado. TextArea(String txt,int cols) Constrói um novo TextArea com o número de linhas, colunas e o texto especificados. Métodos mais usados Método Descrição int getColumns() Retorna o número de colunas. int getRows() Retorna o número de linhas. String getText() Retorna o texto exibido no componente. void setColumns(int cols) Define o número de colunas. void setRows(int lin) Define o número de linhas. void setText(String txt) Define o número o texto a ser editado. Exemplo new TextArea("Ola mundo!", 3, 20); Containers Containers são componentes usados para conter outros componentes. Um objeto da classe Container pode conter inclusive outros containers, permitindo assim a construção de interfaces complexas. Hierarquia J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 207 java.lang.Object | +--java.awt.Component | +--java.awt.Container Construtor Construtor Descrição Container() Constrói um novo Container. Métodos mais usados Método Descrição Component add( Component comp) Adiciona o componente no final da lista de componentes deste Container. Component add( Component comp, int ind) Adiciona o componente na posição ind da lista de componentes deste Container. Component findComponentAt( int x, int y) Retorna o componente visível do Container que contém a coordenada especificada. Todos os descendentes são examinados (SDK 1.2). float getAlignmentX() Retorna o alinhamento no eixo x. float getAlignmentY() Retorna o alinhamento no eixo y. Component getComponent( int n) Retorna o componente na posição n na lista de componentes deste Container. Component getComponentAt( int x, int y) Retorna o componente do Container que contém a coordenada especificada. Apenas o primeiro nível de descendência é examinado. int getComponentCount() Retorna o número de componentes deste Container. Component[] getComponents() Retorna todos os componentes deste Container. Insets getInsets() Retorna o Insets deste Container. Insets indicam o tamanho da borda do Container. LayoutManager getLayout() Retorna o Layout deste Container. boolean isAncestorOf(Component c) Indica se o componente está contido neste Container. void list(PrintStream out, int indent) Imprime uma lista com o conteúdo deste Container. void paint(Graphics g) Pinta o este Container. Este método deve ser sobrescrito se o programador deseja J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 208 desenhar no Container. Neste caso super.paint(g) deve ser invocado. void remove(Component comp) Remove o componente deste Container. void remove(int index) Remove o componente com o índice especificado deste Container. void removeAll() Remove todos os componentes deste Container. void setFont(Font f) Define o fonte deste Container. void setLayout(LayoutManager mgr) Define o layout deste Container. Panel Esta classe é o Container mais simples. O Panel fornece um espaço onde podem ser colocados outros componentes, inclusive outros objetos do tipo Panel. Hierarquia java.lang.Object | +--java.awt.Component | +--java.awt.Container | +--java.awt.Panel Construtor Construtor Descrição Panel() Constrói um novo Panel. Panel(LayoutManager layout) Constrói um novo Panel usando o LayoutManager especificado. Método Método Descrição void addNotify() Cria um par (peer) para o Panel. O par permite modificar o J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 209 Panel sem alterar a sua aparência. Exemplo Ver exemplo VI.XX. Frame Um Frame é uma janela com título e borda. A área do Frame inclui a área destinada para a borda. As dimensões da borda pode ser obtida por meio do método getInsets(). Hierarquia java.lang.Object | +--java.awt.Component | +--java.awt.Container | +--java.awt.Window | +--java.awt.Frame Construtores Construtor Descrição Frame() Constrói um novo Frame. Frame(String title) Constrói um novo Frame com o título especificado. Métodos mais usados Método Descrição static Frame[] getFrames() Retorna todos os frames criados pela aplicação. Image getIconImage() Retorna a imagem usada pelo Frame quando J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 210 minimizado. MenuBar getMenuBar() Retorna a barra de menu deste Frame. int getState() Retorna o estado deste Frame. String getTitle() Retorna o título deste Frame. boolean isResizable() Indica se o tamanho deste Frame pode ser mudado. void setIconImage(Image im) Define a imagem a ser usada como ícone para este Frame. void setMenuBar(MenuBar mb) Define o menu a ser usada como ícone para este Frame. void setResizable(boolean r) Define se o tamanho deste Frame pode ser mudado. void setState(int state) Define o estado deste Frame. void setTitle(String title) Define o título deste Frame. Exemplo Ver exemplo VI.XX. ### Exemplo import java.awt.*; import java.awt.event.*; class testeFrame3 extends FrameX { Button b; class SymMouse extends MouseAdapter { public void mouseClicked(MouseEvent event) { Object object = event.getSource(); if (object == b) b.setLabel("Clik"); } } public testeFrame3 (String Titulo) { super(Titulo); b = new Button("ola"); b.addMouseListener(new SymMouse()); this.add(b); setSize(100,80); } public static void main(String args[]) J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 211 { testeFrame3 t = new testeFrame3("Teste 3"); t.show(); } } public class testeFrame4 extends FrameX { public testeFrame4(String titulo) { super(titulo); setLayout(new GridLayout(1,2)); textField1 = new TextField(20); add(textField1); button1 = new Button("Duplica"); add(button1); button1.addMouseListener(new SymMouse()); } public synchronized void show() { move(50, 50); setSize(200,50); super.show(); } TextField textField1; Button button1; class SymMouse extends java.awt.event.MouseAdapter { public void mouseClicked(java.awt.event.MouseEvent event){ Object object = event.getSource(); if (object == button1) { int i = Integer.parseInt(textField1.getText()); textField1.setText(""+i*2); } } } public static void main(String args[]){ new testeFrame4("Teste 4").show(); } } public class testeFrame5 extends FrameX { public testeFrame5(String titulo) { super(titulo); setSize(220,70); setLayout(null); textField1 = new TextField(); J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 212 textField1.setBounds(10,30,100,25); add(textField1); button1 = new Button("Duplica"); button1.setBounds(110,30,100,25); button1.addMouseListener(new SymMouse()); add(button1); } public class testeFrame6 extends FrameX { public testeFrame6(String titulo) { super(titulo); setSize(200,250); move(50, 50); theImage = tk.getImage("skisor.gif"); } private static Toolkit tk = Toolkit.getDefaultToolkit(); private Image theImage; public void paint(Graphics g) { g.drawImage(theImage, 20, 20, getSize().width-20, getSize().height-20, this); } public static void main(String args[]){ new testeFrame6("Teste 6").show(); } } O objeto Toolkit oferece recursos para carregar imagens da forma correta para cada plataforma. O método paint() é responsável por desenhar a figura. Ele é chamado toda vez que ocorre um evento sobre o componente, de modo que a figura será sempre redesenhada. Agenda Eletrônica versão Gráfica 1.0 /** AgendaInt Interface Gráfica da agenda. */ import java.awt.*; import java.awt.event.*; import java.io.*; import java.util.*; J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 213 public class AgendaInt extends FrameX { Agenda ag; TextArea textArea1 = new TextArea(); TextField txtNome = new TextField(); TextField txtEnd = new TextField(); TextField txtTel = new TextField(); Button btCarregar = new Button(); Button btGravar = new Button(); Button brInserir = new Button(); Button btListar = new Button(); Button btConsultar = new Button(); Label label1 = new Label(); Label lbStatus = new Label(); Label label2 = new Label(); Label label3 = new Label(); public AgendaInt() { ag = new Agenda(); setLayout(null); setBackground(new Color(112,170,192)); setSize(400,320); setVisible(false); add(textArea1); textArea1.setBackground(Color.white); textArea1.setBounds(12,120,372,168); btCarregar.setLabel("Carregar"); add(btCarregar); btCarregar.setBackground(Color.lightGray); btCarregar.setBounds(12,12,72,24); btGravar.setLabel("Gravar"); add(btGravar); btGravar.setBackground(Color.lightGray); btGravar.setBounds(84,12,72,24); brInserir.setLabel("Inserir"); add(brInserir); brInserir.setBackground(Color.lightGray); brInserir.setBounds(156,12,72,24); btListar.setLabel("Listar"); add(btListar); btListar.setBackground(Color.lightGray); btListar.setBounds(228,12,84,24); btConsultar.setLabel("Consultar"); add(btConsultar); btConsultar.setBackground(Color.lightGray); btConsultar.setBounds(312,12,72,24); J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 214 add(txtNome); txtNome.setBackground(Color.white); txtNome.setBounds(60,48,144,24); label1.setText("Nome:"); add(label1); label1.setFont(new Font("Serif", Font.BOLD, 12)); label1.setBounds(12,48,48,24); add(lbStatus); lbStatus.setBackground(Color.lightGray); lbStatus.setForeground(Color.red); lbStatus.setBounds(0,300,396,16); label2.setText("Telefone:"); add(label2); label2.setFont(new Font("Serif", Font.BOLD, 12)); label2.setBounds(216,48,60,24); add(txtTel); txtTel.setBackground(Color.white); txtTel.setBounds(276,48,108,24); label3.setText("Endereço:"); add(label3); label3.setFont(new Font("Serif", Font.BOLD, 12)); label3.setBounds(12,84,48,24); add(txtEnd); txtEnd.setBackground(Color.white); txtEnd.setBounds(72,84,312,24); setTitle("Agenda Eletônica"); // REGISTRA OS LISTENERS SymWindow aSymWindow = new SymWindow(); this.addWindowListener(aSymWindow); SymMouse aSymMouse = new SymMouse(); btGravar.addMouseListener(aSymMouse); btCarregar.addMouseListener(aSymMouse); btListar.addMouseListener(aSymMouse); brInserir.addMouseListener(aSymMouse); btConsultar.addMouseListener(aSymMouse); } public AgendaInt(String title) { this(); setTitle(title); } class SymWindow extends WindowAdapter { public void windowClosing(WindowEvent event) { Object object = event.getSource(); J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 215 if (object == AgendaInt.this) AgendaIntAwt_WindowClosing(event); } } void AgendaIntAwt_WindowClosing(WindowEvent event) { setVisible(false); } class SymMouse extends MouseAdapter { public void mouseClicked(MouseEvent event) { Object object = event.getSource(); if (object == btGravar) btGravar_MouseClicked(event); else if (object == btCarregar) btCarregar_MouseClicked(event); else if (object == btListar) btListar_MouseClicked(event); else if (object == btConsultar) btConsultar_MouseClicked(event); else if (object == brInserir) brInserir_MouseClicked(event); } } void btGravar_MouseClicked(MouseEvent event) { gravar(); } void btCarregar_MouseClicked(MouseEvent event) { carregar(); } void btListar_MouseClicked(MouseEvent event) { Exibirlista(); } void btConsultar_MouseClicked(MouseEvent event) { String nome = txtNome.getText(); if (nome.length()>0) exibirPessoa(nome); J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 216 else lbStatus.setText("É necessário preencher o campo Nome!"); } void brInserir_MouseClicked(MouseEvent event) { String nome = txtNome.getText(); String tel = txtTel.getText(); String end = txtEnd.getText(); if (nome.length()>0) ag.inserir(new Pessoa(nome ,tel,end)); else lbStatus.setText("É necessário preencher o campo Nome!"); } /** gravar */ public void gravar() { ObjectOutputStream fout; try { fout = new ObjectOutputStream( new FileOutputStream("agenda.dat")); fout.writeObject(ag); fout.close(); } catch(FileNotFoundException e) { System.out.println("Arq. Não encontrado");} catch(IOException e){System.out.println("Erro na gravação!");} } /** carregar */ public void carregar() { ObjectInputStream fin; try { fin = new ObjectInputStream( new FileInputStream("agenda.dat")); ag = (Agenda) fin.readObject(); fin.close(); } catch(FileNotFoundException e) { System.out.println("Arq. Não encontrado");} catch(Exception e) {System.out.println("Erro na leitura!");} } J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 217 /** Exibirlista */ public void Exibirlista() { Pessoa p; textArea1.setText(""); for (Enumeration e = ag.getPessoas(); e.hasMoreElements();) { p = (Pessoa) e.nextElement(); textArea1.append("\nNome:"+p.getNome()+"\nTelefone:" +p.getTel()+"\nEndereço:"+p.getEnd()+"\n"); } } // main public static void main(String args[]) { (new AgendaIntAwt("Agenda")).setVisible(true); } /** exibirPessoa */ public void exibirPessoa(String nome) { Pessoa p = ag.getPessoa(nome); if (p!=null) { txtTel.setText(p.getTel()); txtEnd.setText(p.getEnd()); } else textArea1.setText("Pessoa não cadastrada!"); } } Exercícios Faça um programa para converter Graus Celcius em Farenheit J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 218 Capítulo IX - Applets Applets são aplicações J ava projetadas para serem executadas por navegadores da World Wide Web (WWW). Isto é feito através de chamadas especiais dentro do código HTML (Hyper Text Markup Language) das páginas. A possibilidade de desenvolver páginas HTML com a capacidade de invocar programas em J ava foi uma das razões para o sucesso inicial, inédito em termos de linguagem de programação, de J ava. Até então, as páginas HTML eram basicamente objetos passivos, com capacidade limitada de interação, a não ser por meio de CGI (Common Gateway Interface), que implementa a interatividade com o uso de códigos executáveis (geralmente escritos em Perl) rodados no servidor. Esse tipo de arquitetura onera muito o servidor uma vez qualquer processamento tem que ser efetuado nele. Figura VII.1 – Interação via CGI. A idéia do uso de J ava é enviar código para o lado cliente através da rede, para que parte do processamento seja realizado no próprio cliente, liberando o servidor para tarefas mais importantes. Claro que isto será razoável se o código puder trafegar pela rede rapidamente. Os programas em Bytecodes ocupam pouco espaço e podem ser compactados antes de serem enviados pela rede, o que reduz bastante o tempo de transmissão. Cliente com browser Servidor Requisição de página Web Página Web construída pelo programa CGI O servidor Web executa o programa CGI que constrói a página Web. 800 APO J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 219 Figura VII.2 – Interação via Java. No entanto, é preciso ter cuidado quando se trata de enviar código pela rede para se executado em uma máquina cliente. Existe um grande potencial para o desenvolvimento acidental ou proposital de programas que podem atuar nocivamente nas máquinas clientes, seja pela leitura de dados privados, seja por destruição não autorizada de informações e programas. Preocupados com o isso, os projetistas da linguagem J ava determinaram que, no caso de Applets, a máquina virtual impede que o programa tenha acesso limitado aos recursos da máquina. Nessa forma de execução o programa é dito estar sendo executado dentro de uma “caixa de areia” (Sand Box). Esta solução é em alguns casos por demais restritiva e tem sido relaxada nas últimas versões de J ava para permitir o acesso aos recursos das máquinas clientes a Applets digitalmente assinados, denominados de Trusted (confiáveis). Outro benefício do uso de J ava é possibilidade da produção de páginas para a WWW com animação. Inicialmente, as páginas em HTML não possuíam recurso para animação, a não ser por meio de GIFs animadas. Com J ava é possível desenvolver animações bem mais sofisticadas. Para desenvolver um Applet é preciso criar uma subclasse da classe classe java.applet.Applet. Abaixo é mostrada forma geral de um código que implementa um Applet: import java.awt.*; import java.applet.Applet; Cliente com browser Servidor Requisição de página Web Página Web e bytecodes O servidor Web envia página Web e código embytecodes Executa o código em bytecodes 800 APO J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 220 public class indentificador extends Applet { corpo do applet } A figura VII.3 mostra a hierarquia de classes para a classe Applet. Figura VII.3 – Hierarquia de classes. O exemplo VII.1 mostra o código de um Applet que exibe o texto “ola mundo!” quando executado. import java.awt.*; import java.applet.*; public class Applet1 extends Applet { Label label1 = new Label(); public void init() { setLayout(null); setSize(150,87); label1.setText("Ola Mundo!"); add(label1); label1.setBounds(24,12,84,48); java.awt.Component java.lang.Object java.awt.Container java.awt.Panel java.applet.Applet J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 221 } } Exemplo VII.1 – Applet simples. O leitor pode observar algumas diferenças do código do exemplo VII.1 para o código do exemplo III.1 que implementava um programa que exibia “ola Mundo” na console. A primeira diferença é que não existe um método main(). Isto ocorre porque os Applets foram projetados para serem executados por outra aplicação: um navegador Web. A aplicação é responsável por criar uma instância da subclasse da classe Applet e chamar os métodos de acordo com a necessidade. No exemplo acima será executado o método init(), que é o primeiro método executado em um Applet e é executado apenas uma vez. Para executarmos o Applet, além de ser necessário compilá-lo, é preciso criar um arquivo com código HTML com um link para o código em bytecode. Isto é necessário já que o Applet vai ser executado por um navegador. O exemplo VII.2 mostra o código HTML que possui um link para a classe Applet1. O link para o código em bytecodes pode possuir vários parâmetros. No exemplo VII.2 são mostrados apenas os parâmetros que indicam a largura e altura que o Applet ocupará na página. Os outros parâmetros serão abordados mais adiante. <html> <applet code=Applet1.class width=400 height=300> </applet> </html> Exemplo VII.2 – HTML simples. Para executar o código basta direcionar o navegador para o HTML do exemplo VII.2. O SDK possui também uma aplicação capaz de executar Applets. É o appletviewer. A linha de comando abaixo mostra como executar o Applet por meio do appletviewer. Note que é preciso passar o nome do arquivo HTML como parâmetro: appletviewer applet1.html J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 222 A figura VII.4 mostra o resultado da execução do Applet1 por meio do appletviewer. Figura VII.4 – Execução do Applet1. Descrição do código HTML Não entraremos em detalhes da codificação em HTML. Abordaremos aqui apenas os parâmetros relacionados com o link para o código em bytecode. A forma geral de um link deste tipo é mostrada abaixo. <html> <applet..code= ... width=... height=... archive=... codebase=... align=... alt=... name=... hspace=... vspace=... mayscript=... > <param name=“...” value=“...”> ... <param name=“...” value=“...”> </applet> </html> O link HTML possui vários parâmetros, sendo a maioria deles opcionais. A tabela abaixo descreve cada um dos parâmetros. Parâmetro Descrição J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 223 code Nome da classe em bytecode. Por exemplo: Applet1.class. width Largura que o Applet vai ocupar na página HTML. height Altura que o Applet vai ocupar na página HTML. archive Opcional. Se a classe estiver armazenada em um arquivo J AR (J ava Archive), então este parâmetro indica o nome do arquivo. Por exemplo: minhasclasses.jar. codebase Opcional. URL onde está localizada o Applet. align Alinhamento do Applet na página. (left, right, top, texttop, middle, absmiddle, baseline, bottom e absbottom). alt Mensagem alternativa caso o navegador não seja capaz de executar bytecodes. name Nome do Applet. Pode ser usado por outros Applets na mesma página para comunicação. hspace Define o número de pixels livres nos lados direito e esquerdo do Applet. vspace Define o número de pixels livres acima e abaixo do Applet. mayscript Valor booleano que indica se um Applet pode interagir com código J avascript. param Opcional. É usado para passar parâmetros para o Applet. Cada entrada param possui dois subparâmetros, name e value, indicando o nome do parâmetro e o valor respectivamente. Mais adiante será mostrado como o código J ava recebe esses parâmetros. Tabela VII.1 – Parâmetros do link para o código em bytecodes. Métodos da Classe Applet A classe Applet define um número razoável de métodos. No entanto, na maioria das aplicações, o programador sobrescreverá um pequeno subconjunto deste métodos. A tabela VII.2 descreve os métodos mais utilizados na implementação de Applets. Método Descrição init É o primeiro método executado e é executado apenas uma vez. É utilizado para as inicializações do Applet. start É executado toda vez que o Applet aparece no navegador. É utilizado para iniciar a operação normal do Applet. stop É executado toda vez que o Applet passa a não ser exibido pelo navegador. É usado para terminar operações caras em termos computacionais. destroy É executado quando o navegador não precisa mais do Applet. É usado para liberar recursos. paint É executado toda vez que o Applet aparece no navegador. Recebe uma J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 224 instância da classe Graphics. O usuário pode solicitar a sua invocação por meio de uma chamada ao método repaint(). Neste caso o método paint() não será chamado imediatamente, e sim escalado para ser executado em um tempo menor do que 100msec. Tabela VII.2 –Principais métodos da classe Applet. Esses métodos são invocados pelo navegador conforme as situações descritas acima. A motivação por trás dos métodos start() e stop() é que não faz sentido desperdiçar ciclos de processador com um Applet que não está sendo visto. O método que talvez possa causar mais dúvidas quanto a sua utilidade é o método paint(). O método paint() é na verdade um método herdado da classe container e é utilizado para desenhar em um objeto. O objeto da classe Graphics, recebido pelo método, representa a superfície onde podem ser feitos os desenhos e possui um conjunto de métodos para desenhar. Para ilustrar o uso do paint() o exemplo VII.3 mostra o código de um Applet que desenha um quadrado na tela que segue o mouse. import java.awt.*; import java.applet.*; import java.awt.event.*; public class Applet2 extends Applet { int x=0, y=0; public void init(){ setLayout(null); setSize(426,266); this.addMouseMotionListener(new SymMouseMotion()); } public void paint(Graphics g) { if (x>0)g.drawRect(x, y, 30, 30); } class SymMouseMotion extends MouseMotionAdapter{ public void mouseMoved(MouseEvent event){ if (event.getSource()== Applet2.this) { x = event.getX(); y = event.getY(); repaint(); } } J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 225 } } Exemplo VII.3 – Applet que desenha um quadrado que acompanha o mouse. Note que o método paint() não é chamado diretamente pelo método mouseMoved(). O que ocorre é que o método repaint() solicita a máquina virtual para chamar o método update() que por sua vez chama o método paint(). A máquina virtual não executa imediatamente o método update(). Ela escala o método update() para ser executado em pelo menos 100 milisegundos. Se antes deste período ocorrer outra solicitação ela será ignorada. O diagrama da figura VII.5 ilustra o ciclo repaint-update-paint. Figura VII.5 – Ciclo repaint-update-paint. Ao executar o Applet do exemplo VII.3 o leitor notará que apenas um quadrado permanece desenhado, apesar de existir código apenas para desenhar e nenhum para apagar. A pergunta é: o que ocorre com os quadrados desenhados anteriormente? Acontece que o método update() sempre apaga os desenhos que feitos anteriormente desenhando um retângulo com o tamanho da área de desenho preenchido com a cor de fundo. É possível sobrescrever o método update() para alterar esse comportamento, como veremos no capítulo sobre animação, no entanto é necessário sempre repintar todos os desenhos que precisam permanecer a cada ciclo, até por que eles podem ser afetados pela movimentação ou encobrimento da área de desenho. No exemplo VII.3 utilizamos um método do objeto da classe Graphics para desenhar um quadrado. Essa classe possui vários métodos semelhantes para desenho de figuras e texto. A tabela VII.3 descreve alguns métodos desta classe. Método Descrição drawArc desenha um arco elíptico. drawChars desenha o texto especificado por um array de bytes drawline desenha uma linha entre dois pontos. drawRect desenha um retângulo. drawRoundRect desenha um retângulo com bordas arredondadas. drawImage desenha um imagem repaint() update(Graphics g) escala (dentro de 100 ms) paint(Graphics g) Chama J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 226 draw3Drect desenha um retângulo 3D. drawOval desenha um oval. drawPolygon desenha um polígono usando um vetor de pontos. drawString desenha um string. fillRect desenha um retângulo preenchido. fillRoundRect desenha um retângulo com bordas arredondadas preenchido. fill3Drect desenha um retângulo 3D preenchido. fillOval desenha um oval preenchido. fillPolygon desenha um polígono usando um vetor de pontos preenchido. getColor obtém a cor corrente. setColor define a cor corrente. getFont obtém a fonte corrente. setFont define a fonte corrente. Tabela VII.3 –Principais métodos da classe Graphics. O exemplo VII.4 mostra os uso de alguns métodos da classe Graphics. O programa, ao ser executado, mostra uma figura com um texto ao usuário e cada vez que o usuário pressiona o mouse a figura muda. import java.awt.*; import java.awt.event.*; import java.applet.*; public class Applet3 extends Applet { private int i =0; private static final int x[] = {10,100,105,80,20}; private static final int y[] = {10,15,70,40,65}; public void init() { setLayout(null); setSize(200,150); this.addMouseListener(new SymMouse()); } public void paint(Graphics g) { switch(i) { case 0: g.setColor(Color.green); g.fillRect(10,10,100,100); g.setColor(Color.black); g.drawString("Quadrado",30,50); break; case 1: g.setColor(Color.red); J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 227 g.fillOval(10,10,100,70); g.setColor(Color.black); g.drawString("Oval",50,50); break; case 2: g.setColor(Color.blue); g.fillPolygon(x,y,5); g.setColor(Color.black); g.drawString("Poligono",40,30); break; } i = (i+1)%3; } class SymMouse extends MouseAdapter { public void mouseClicked(MouseEvent event) { if (event.getSource()== Applet3.this) repaint(); } } } Exemplo VII.4 – Applet que desenha várias figuras na tela. Exibindo uma imagem Um dos métodos mais interessantes desta classe Graphics é o método drawImage(), que permite exibir uma imagem. O exemplo VII.5 contém o código de um applet que mostra uma imagem que muda quando o mouse é pressionado. A imagem é carregada no exemplo está codificada no formato jpeg. Os tipos de imagens suportadas dependem do navegador sendo utilizado. A figura VII.6 mostra o resultado da execução do Applet. import java.awt.*; import java.awt.event.*; import java.applet.*; public class Applet4 extends Applet { Image img[]; private static int i=0; public void init() { img = new Image[2]; J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 228 img[0] = getImage(getDocumentBase(),"baleia1.jpg"); img[1] = getImage(getDocumentBase(),"baleia2.jpg"); this.addMouseListener(new SymMouse()); } public void paint(Graphics g) { g.drawImage(img[i],0,0,this); i=i==0?1:0; } class SymMouse extends MouseAdapter { public void mouseClicked(MouseEvent event) { if (event.getSource() == Applet4.this) repaint(); } } } Exemplo VII.5 – Applet que exibe imagens. Primeiramente foi necessário obter objetos to tipo Image. Para isso utilizamos o método getImage() que possui dois parâmetros. O primeiro é uma URL que indica a localização do arquivo contendo a figura e o segundo é o nome do arquivo da figura, que pode conter informação de diretório relativo à URL. Se o arquivo estiver no mesmo diretório de onde foi carregado o arquivo HTML então basta usar a método getDocumentBase(), como no exemplo VII.5. Se o Se o arquivo estiver no mesmo diretório de onde foi carregado o Applet então basta usar o método getCodeBase(). Figura VII.6 – Saídas do Applet4. J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 229 O método getImage() retorna null se o arquivo não foi encontrado, caso contrário um objeto do tipo Image é retornado. A carga de uma imagem pode ser muito demorada, dependendo do tamanho é formato de armazenamento, principalmente em se tratando de imagens transmitidas através de uma rede com alto trafego de dados. Além disso, no caso de Applets, o usuário pode mudar de página antes mesmo de uma imagem ser completamente exibida. Por essas razões, o método getImage() foi projetado para retornar imediatamente, antes mesmo da carga da imagem, de modo que a tarefa de carregar a imagem é iniciada no momento em que a imagem é usada e é feita em paralelo com a execução do programa por outra linha (Thread) de execução. Threads são descritos em detalhes no capítulo VIII. Por ora basta visualizá-los como um tipo de processo leve que compartilha o mesmo espaço de endereçamento com outros Threads. Uma vez de posse de um objeto tipo Image podemos exibi-lo por meio do método: Graphics.drawImage(Image img,int x,int y, ImageObserver O) O primeiro parâmetro é o objeto tipo Image. Os segundo e terceiro definem a posição dentro Applet. O quarto parâmetro do tipo ImageObserver é o que necessita ser melhor descrito. O objeto passado como quarto parâmetro deve implementar a interface ImageObserver, o que o habilita a receber notificações sobre a carga da imagem. Como a classe Component implementa esta interface, então qualquer subclasse da classe Component também a implementa, portanto, podemos passar o próprio Applet como parâmetro, usando a palavra chave this. O método drawImage() desenha a imagem no estágio em que ela estiver. Deste modo, se apenas metade da imagem tiver sido carregada então apenas metade será mostrada. Em alguns casos o programador pode querer exibir uma imagem apenas quando ela for completamente carregada. Isto pode ser feito por meio de um objeto da classe MediaTracker. A classe MediaTracker é usada para acompanhar o status de objetos de mídia. Um objeto de mídia pode ser imagens ou sons, no entanto, no momento apenas imagens são suportadas. O exemplo XX.XX mostra o uso de um objeto desta classe para monitorar o status de uma imagem. public void init() { J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 230 MediaTracker tr = new MediaTracker(this); im = getImage(getDocumentBase(),”img.gif”); tr.addImage(im,0); try { tr.waitForID(0); } catch (InterruptedException excep) {} } Exemplo XX.XX – Uso do MediaTracker. No exemplo XX.XX é criado uma instância da classe MediaTracker e os objetos que devem ser monitorados são adicionados à instância por meio do método addImage(). Além da imagem também é atribuído um identificador único que será usado para controlar a ordem de prioridade de carga do objeto. Também pode ser usado para identificar subconjuntos imagens. As imagens com número menor possuem maior prioridade no processo de carga em relação as imagens com número maior. O início da carga da imagem é feita por meio do método: public void waitForID(int id) throws InterruptedException O identificador da imagem (ou do subconjunto de imagens) é passado por parâmetro para o método. Este método espera até que todas as imagens com o identificador sejam carregadas. Se ocorrer algum erro na carga a imagem é considerada carregada. O programador deve usar os métodos isErrorAny() e isErrorID() para detectar a ocorrência de erros. Áudio O versão J DK1.0 oferece suporte para execução de arquivos de áudio apenas no formato AU. No entanto, o SDK1.2 estendeu este suporte para arquivos de áudio no formato AIFF, WAV, MIDI (tipo 0 e 1) e RMF. J ava pode manipular dados de áudio em 8 e 16bits. A classe usada tratamento de áudio é a AudioClip, que contém os seguintes métodos: • play() – Inicia a execução do audio clip. • loop() – Executa o audio clip em um loop. J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 231 • stop() – Para a execução do audio clip. Para se criar uma instância da classe AudioClip pode-se usar o método getAudioClip() da classe Applet: getAudioClip(URL url, String nome) O método retorna imediatamente mesmo que o arquivo de áudio não exista. O arquivo será carregado quando o programa tentar usá-lo. O Exemplo VII.xx mostra um Applet que faz uso de um objeto AudioClip. import java.lang.*; import java.net.URL; public class ExSom extends java.applet.Applet { AudioClip som; public void init() { som = getAudioClip(getDocument(),”ola.au”); } public void start() {som.play();} } Exemplo VII.XX – Applet que executa um arquivo de audio. Obtendo parâmetros Na seção XX comentamos o formato do link HTML que faz referência a um arquivo em bytecodes. Comentamos também a existência de marcadores no formato <param name=“...” value=“...”> que são usados para passagem de parâmetros do HTML para o programa em bytecodes. Nesta seção é mostrado o código J ava necessário para capturar esses parâmetros. De modo a facilitar a explicação faremos uso de um exemplo. O exemplo VII.7 mostra o código HTML que passa dois valores e o exemplo VII.8 mostra um código em J ava que recebe os parâmetros. <HTML> J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 232 <applet code=“Applet5.class” width=400 height=250> <param name=“numero” value=“5”> <param name=“nome” value=“Ana”> </APPLET> </HTML> Exemplo VII.7 – Código HTML passando valores. import java.awt.*; public class Applet5 extends java.applet.Applet { Label label1 = new Label(); public void init() { setLayout(null); int i = Integer.parseInt(getParameter(“numero”)); setSize(150,87); label1.setText(getParameter(“nome”)+ “:”+i); add(label1); label1.setBounds(24,12,84,48); } } Exemplo VII.8 – Applet que recebe parâmetros. Os valores são recebidos por meio do método getParameter() que recebe como argumento o nome do parâmetro definido no código HTML. Os valores são retornados como objetos do tipo String. Se não existir um parâmetro com o nome passado para o método getParameter() o retorno será null. O programador também pode definir o método getParameterInfo(), cuja única ação é retornar um array de duas dimensões, contendo as informações sobre os parâmetros. As informações armazenadas são o nome, tipo e descrição de cada parâmetro. Este método pode ser usado pelos navegadores Web para ajudar ao usuário a definir os valores a serem passados para o Applet. public String[][] getParameterInfo() { String[][] info = {{“numero”,”int”,”number qualquer”}, {“nome”,”string”,”nome de alguem”}}; J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 233 return info; } Exemplo VII.9 – Definindo informações sobre os parâmetros . Executando um Applet como aplicação Algumas vezes desejamos desenvolver um programa que possa funcionar como aplicação e como Applet. Podemos atingir este objetivo criando um método main() dentro da subclasse Applet que está sendo implementada. O método main() deve criar uma instância da classe Frame e adicionar a ele um objeto da subclasse do Applet. O exemplo VII.10 mostra como criar um método main() para o Applet do exemplo VII.1. import java.awt.*; import java.applet.*; public class Applet6 extends Applet { Label label1 = new Label(); public void init() { setLayout(null); setSize(150,87); label1.setText("Ola Mundo!"); add(label1); label1.setBounds(24,12,84,48); } static public void main(String[] args) { FrameX frame = new FrameX("OLA"); Applet6 applet = new Applet6(); frame.add("Center", applet); frame.setSize(200,100); frame.setVisible(true); applet.init(); applet.start(); } } Exemplo VII.10 – Applet com método main. Preparando Applets para produção e arquivos JARs J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 234 Após o desenvolvimento e teste do Applet chega o momento de colocá-lo em produção. Isto é feito colocando a página HTML que faz referência ao arquivo em bytecodes, resultante da compilação do Applet, em um diretório acessado por um servidor Web. Você já deve ter notado que cada classe J ava gera um arquivo em bytecodes. Deste modo, um arquivo contendo várias classes em J ava gerará, após a compilação vários arquivos de bytecodes, um para cada classe. No caso de Applets com um tamanho razoável pode ser gerado um grande número de arquivos. Isto pode ser um problema em se tratando de uma aplicação que é projetada para ser transmitida em uma rede de longa distância por meio do protocolo HTTP, já que cada classe irá gerar uma transação HTTP quando for referenciada pela primeira vez. Além disso, apesar do código em bytecodes ser menor do que o código gerado pela compilação da maioria dos programas escritos em outras linguagens de programação seria importante diminuir o máximo possível o tamanho dos arquivos, de modo a minimizar o tráfego na rede. Para solucionar esses e outros problemas foi introduzido a partir da versão 1.1 da linguagem J ava o formato de arquivo J AR (Java Archive). O formato de arquivo J AR permite o agrupamento de vários arquivos em um único arquivo. Além disso, o formato J AR permite a compressão de dados de modo otimizar o armazenamento e a transmissão de dados. O principais benefícios do uso do formato J AR são os seguintes: • Menor tempo de carga de Applets: Se todos os arquivos relacionados com um Applet estão agrupados em um único arquivo J AR, apenas uma transação HTTP será necessária para carregar o Applet. Este tempo será ainda menor se os arquivos estiverem compactados. • Segurança: o conteúdo do arquivos J AR podem ser assinados digitalmente. Deste modo, os usuários que reconhecerem a sua assinatura podem conceder privilégios para acessar recursos que não estariam disponíveis, caso contrário. • Portabilidade: a API para manipulação dos arquivos J AR é parte integrante do núcleo da biblioteca de classes de J ava, o que a torna independente de plataforma. Outros benefícios foram adicionados com o lançamento da versão 1.2 do SDK, como por exemplo, informações sobre versão. Os arquivos contidos em um arquivo J AR podem ser de tipos variados como, como bytecodes, imagens e sons, podendo pertencer a um Applet, aplicação ou simplesmente a uma biblioteca de classes. Além disso, um arquivo J AR pode conter uma descrição dos arquivos armazenados, chamada de manifest. O SDK J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 235 vem com uma ferramenta para criação e manutenção de arquivos J AR. O formato mais comum de invocação do programa é: jar [opções] destino arquivo(s)-de-entrada onde destino é o nome do arquivo, arquivo(s)-de-entrada representa o nome dos arquivos que serão incluídos no arquivo J AR e opções é uma coleção de letras com o seguinte significado: Opções Descrição c Cria um arquivo novo. t Lista o conteúdo. x Extrai todos os arquivos. x file Extrai o arquivo especificado. f Indica que o nome do arquivo de saída, caso seja a criação de um arquivo, ou de entrada caso contrário, será especificado. Sem essa opção o programa jar assume que saída ou a entrada será a padrão. m Indica que o primeiro argumento será o nome de um arquivo manifest criado pelo usuário. v Gera saída com os detalhes da execução. O Não comprime os arquivos. Usada para criar arquivos J AR que podem ser colocados no classpath. M Não cria o arquivo manifest. Se um subdiretório for incluído nos arquivos de entrada então o subdiretório será automaticamente inserido no arquivo J AR, incluindo os seus subdiretórios. As informações sobre o caminho até o arquivo é também armazenada. Para invocar um Applet inserido em um arquivo J AR utilize a palavra chave archive no arquivo HTML. O exemplo VII.11 mostra como invocar um Applet desta forma. <applet code=meuApplet.class archive=meuarquivo.jar width=width height=height> </applet> Exemplo VII.10 – Applet com método main. No caso de uma aplicação é necessário passar o arquivo J AR como parâmetro. Na versão J DK1.1 é preciso usar o comando jre no lugar do J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 236 comando java. Por exemplo, suponha que armazenamos um aplicação, cuja classe principal é Mainclass, em um arquivo arq.jar. Para executá-la basta digitar o comando: jre -cp arq.jar Mainclass onde a opção cp indica que o arquivo arq.jar deve ser anexado ao classpath. J á na versão SDK1.2 usa-se o comando java, sendo que o arquivo J AR deve conter um manifest indicando a classe principal: java -jar arq.jar A tabela VII.4 mostra alguns exemplos do uso do utilitário que gera arquivos J AR. jar cf meuarq.jar *.class Cria um arquivo J AR com o nome meuarq.jar contendo todos os arquivos de classes do diretório corrente. Um arquivo manifest é gerado automaticamente. jar cmf meuarq.jar meuManifest.mf *.class Como o exemplo anterior, porém o arquivo manifest meuManifest.mf é adicionado. jar tf meuarq.jar Lista o conteúdo do arquivo meuarq.jar. jar xf meuarq.jar Extrai todos os arquivos contidos em meuarq.jar. Tabela VII.4 –Exemplos do uso do comando jar. Criando os próprios arquivos manifest A opção m da ferramenta para criação de arquivos J AR permite adicionar informações ao arquivo manifest default. É necessário que o usuário crie um arquivo contendo as adições que devem ser feitas. O formato básico do comando é: jar cmf arq_adições arq_jar arquivo(s) O arquivo de adições é simplesmente um arquivo texto é composto por um conjunto de declarações. Por exemplo se o usuário quiser especificar que a classe que deve ser usada como ponto de entrada é a classe Mainclass, então basta inserir a seguinte linha no arquivo de adições: Main-Class: Mainclass J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 237 Exercícios Faça uma calculadora com o seguinte layout: Agenda Eletrônica versão Applet 1.0 0 . = 3 2 1 6 5 4 9 8 7 / * - + J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 238 Capítulo X JavaBean O que é um JavaBean? A API J avaBeans é a especificação que torna possível escrever componentes de software na linguagem J ava. Componentes são peças de software, análogas a bloquinhos de montar, reutilizáveis, auto-contidas e que podem ser usadas para comporem visualmente outros componentes, Applets, aplicações e Servlets usando um ambiente de desenvolvimento que suporte a especificação de J avaBeans. Componentes J avaBean são conhecidos como Beans. Diferente de outras ferramentas de programação, como Delphi ou VB, o suporte ao desenvolvimento de componentes foi acrescentado, de forma elegante, na linguagem J ava sem alterá-la. JavaBeans são classes Java comuns que seguem na sua construção uma especificação que permite o seu uso visual em ambientes RAD. J avaBeans foram projetados para trabalhar em conjunto com ferramentas de desenvolvimento "J avaBeans-enabled". JavaBeans e ferramentas RAD As ferramentas de desenvolvimento expõem as características públicas do Bean visualmente. Eles obtêm as características do Bean (propriedades, métodos e eventos) em um processo conhecido como introspection. A introspection pode ser feita de duas maneiras: • Através da utilização de Reflection para o obtenção dos métodos, e do uso das convenções para determinação dos eventos e propriedades. • Por uso de uma class de informações denominada BeanInfo. Uma classe BeanInfo implementa a interface BeanInfo. Uma classe BeanInfo explicitamente lista todas as características do Bean que devem ser expostas para o ambiente de desenvolvimento. As tão faladas propriedades são características do Bean que podem ser mudadas durante a programação. O ambiente de desenvolvimento faz a J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 239 introspeção em um Bean para descobrir as propriedade e expo-las para manipulação. Os Beans utilizam eventos para se comunicarem com outros Beans. Os ambientes de desenvolvimento pode examinar os Beans e determinarem que eventos um determinado Bean pode disparar (fire) ou que eventos ele pode manipular (receive). Os métodos das classes que compõem um Bean são métodos J ava simples, que podem ser chamados por qualquer outras classes. J avaBean é um padrão desenhado para ser utilizado por um ambientes de desenvolvimento RAD. E todas as suas características foram projetas para permitir o uso fácil por programadores sem modificar a linguagem J ava. Nos próximos tópicos veremos as convenções de nomes e o funcionamento e utilização dessa poderosa especificação. Propriedades Em J avaBeans propriedade é um atributo público simples que pode ser de leitura/escrita, apenas leitura ou apenas escrita. Existem quatro tipos de propriedades: simples, indexada, ligada (bound), e restrita (constrained). Simples Representa um valor simples (Ex.: int, float, Object, ...). Pode ser definida com um par de métodos getXxx/setXxx, onde xxx é o nome da propriedade. Se for definido os dois métodos para a propriedade, esta será de leitura/escrita, caso seja definido apenas o método getXxx a propriedade será apenas leitura, e finalmente se for definida apenas o método setXxx será apenas escrita. Quando uma propriedade for booleana, a convenção e modificada para isXxx ou invés de getXxx (no desenho dessa característica resolveram aderir a máxima “toda regra possui uma exceção”). Abaixo temos o exemplo de uma classe que define uma propriedade: J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 240 Class Pessoa { private String nome; private String telefone; private String endereço; public Pessoa(String n, String t, String e) { nome = n; telefone = t; endereço = e; } public void setNome(String n) { nome=n; } public void setTelefone(String t) telefone = t; } public void setEndereço(String e) { endereço = e; } public String getNome() { return nome; } public String getTelefone() { return telefone; } public String getEndereço() { return endereço; } } A classe acima define as propriedades simples: telefone, nome e endereço, sendo todas as três propriedades de leitura/escrita. Indexada Representa um array de valores (ex.: int[], float[], Object[] , ...). Os métodos getXxx/setXxx que definem a propriedade devem ter como parâmetro um inteiro que será o índice de acesso ao array. A propriedade pode também suportar getXxx/setXxx para todo o array. O exemplo abaixo trás a definição parcial de uma classe agenda: Public class Agenda { private Pessoa pessoas[]; . . . J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 241 public Pessoa[] getPessoas(){return pessoas;} public void setPessoas(Pessoa[] i){ this.pessoas = i; } public Pessoa getPessoas(int indice){ return pessoas[indice]; } public void setPessoas(int indice, Pessoa p){ pessoas[indice] = p; } . . . } Neste exemplo, definimos uma propriedade indexada pessoas, que pode ser acessada tanto na forma de uma array completo, quanto nos seus elementos, através dos seus índice. Ligada (Bound) Uma propriedade bound notifica outros objetos quando o seu valor e alterado. Toda vez que o valor de uma propriedade bound é modificado, a propriedade dispara um evento PropertyChange que contém o nome, o novo e o velho valores da propriedade. Class Pessoa { private String nome; private String telefone; private String endereço; Private PropertyChangeSupport changes = New PropertyChangeSupport(this); public Pessoa(String n, String t, String e) {nome = n; telefone = t; endereço = e;} public void addPropertyChangeListener(PropertyChangeListener l){ changes.addPropertyChangeListener(l); } public J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 242 void removePropertyChangeListener(PropertyChangeListener l) { changes.removePropertyChangeListener(l); } public void setNome(String n) { String nomevelho = nome; Nome = n; Changes.firePropertyChange(“nome”, nomevelho, nome); } public void setTelefone(String t) { String telefonevelho = telefone; telefone = t; Changes.firePropertyChange(“telefone”, telefonevelho, telefone); } public void setEndereço(String e) { String endereçovelho = endereço; endereço = e; Changes.firePropertyChange(“endereço”, nomeendereço, endereço); } public String getNome() { return nome; } public String getTelefone() { return telefone; } public String getEndereço() { return endereço; } } A classe Pessoa já foi implementada anteriormente neste capítulo, as modificações introduzidas tornaram as propriedades nome, telefone e endereço ligadas, i. e., sempre que elas sofrerem alterações, os listener que implementam PropertyChangeListener e estão registrados como ouvintes da nossa instância da classe, serão notificados. Além das alterações no corpo dos métodos setXxx, que adicionou o código que dispara o evento PropertyChange, nosso Bean também ganhou um novo objeto changes e dois métodos que cuidam do registro e exclusão de listener. Restringidas(Constrained) Um objeto com propriedade restrita permite que outros objetos vetem a alteração do valor dessa propriedade. listener de propriedades restritas podem J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 243 vetar uma mudança de valor da propriedade através de uma exceção PropertyVetoException que é levantada. Geralmente, apesar de não ser obrigatório, as propriedades restringidas também são ligadas. class CartãoCrédito { private String número; private PropertyChangeSupport changes = new PropertyChangeSupport(this); private VetoableChangeSupport vetos = new VetoableChangesSupport(this); public addVetoableChangeListener(VetoableChangeListener l){ vetos.addVetoableChangeListener(l); } public removeVetoableChangeListener(VetoableChangeListener l){ vetos.removeVetoableChangeListener(l); } . . . public void setNúmero(String n) { String numeroVelho = numero; Vetos.fireVetoableChange(número,numeroVelho,n); número = n; changes.firePropertyChange(número,numeroVelho,numero); } . . . } No exemplo acima definimos parcialmente uma classe com o nome CartãoCrédito, esta classe possui uma propriedade de nome número que é uma propriedade restringida. Para implementamos esta característica, primeiro introduzimos uma novo objeto, veto, que nos dá o suporte ao registro dos listener que terão poder de veto sobre a propriedade. Em seguida aparecem dois métodos que cuidam apropriadamente, repassando para VetoableChangeSupport, do registro dos listener que podem vetar alterações. J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 244 A parte mais interessante fica por conta do método setNúmero, ele é muito parecido com um método de uma propriedade ligada, mas acrescenta uma linha antes de alterar o valor da variável número. Sendo assim, a alteração só será realizada, se o evento disparado com fireVetoableChange não levantar nenhuma exceção. Depois disso tudo, as coisa voltam a funcionar como em uma propriedade ligada convencional. Eventos Sempre que estudamos algo, inicialmente obtemos uma grande quantidade de informações e depois começamos a fazer generalizações transformando o estudado em conhecimento. Bem, até agora vimos que J avaBeans é um padrão que nos permite utilizar um ambiente de programação RAD, e vimos algumas características de classes que seguem este padrão. Mas além desse padrão definir como as classes interage com o ambiente de programação, ele também define como objetos destas classes interagem entre si. Aparece agora o conceito de eventos, que já vimos em capítulo anterior e agora veremos com as convenções de nossa API de componentes. Um bom lugar para começarmos é: o que é um evento? Para nossos propósitos um evento é um objeto que encapsula dados a respeito de alguma coisa que ocorreu. Por exemplo, o mouse foi movido, uma tecla foi digitada, chegou um pacote UDP pela rede. Todas estas ocorrências podem ser modeladas como eventos e informações sobre o que aconteceu podem ser inclusas no objeto evento. Só para relembrar, no novo modelo de eventos da biblioteca de classes J ava, um evento e “escutado” por classes que estão “capacitadas” para tratá-los, estas classes são chamadas de EventListener, e são um mecanismo geral de comunicação entre objetos sem a utilização de herança. Nesta seção, devemos nos preocupar apenas com as convenções de nomes de eventos e classes relacionadas para utilização na construção de Beans. Suponhamos que nos desejamos construir um Bean de nome Timer. Nosso evento teria o nome TimerEvent, e nosso Listener para este evento TimerEventListener. J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 245 Na próxima seção veremos como juntar todos estes conceitos e o que aprendemos ate agora em um exemplos de Bean completo um não-visual, um Bean visual será mostrado no capítulo sobre MVC. Um Bean para ser visual deve ser subclasse de java.awt.Component ou de classes que herdem java.awt.Component. Desenvolvimento do exemplo O Bean de exemplo que estudaremos agora é um componente que dispara um evento periodicamente em um intervalo de tempo definido pelo seu utilizador, este componente e semelhante ao componente Timer do Delphi e ao ActiveX Timer do VB. Suas aplicações incluem atualizar o display de um relógio ou acionar o método que mostra o próximo frame de uma animação. Nosso primeiro Bean será composto das seguintes classes: TimerBean - A classe principal do Bean; TimerEvent - A classe do evento disparado pelo Bean; TimerEventListener - A interface “ouvinte” para o evento do Bean; TimerEventListener A classe TimerEventListener implementa a interface que será utilizada para que o nosso Bean notifique seus listeners sobre cada evento. Package timer; public interface TimerEventListener extends java.util.EventListener { void timerEventDisparado(TimerEvent te); } Todos os listeners para J avaBeans devem herdar java.util.EventListener ou de alguma subclasse dela. O nome da interface deve ser XxxxListener, onde Xxxx corresponde ao nome do evento que esta interface habilita a tratar. No nosso exemplo temos o evento TimerEvent e consequentemente a interface se chama TimerEventListener. J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 246 Uma interface listener pode declarar quantos métodos seu desenvolvedor desejar. Normalmente estes métodos têm como parâmetro um objeto event. No nosso caso declaramos somente um método. TimerEvent A classe TimerEvent implementa o evento que será passado para cada TimerEventListener. package timer; public class TimerEvent extends java.util.EventObject { public TimerEvent(Object source) { super(source); } } Todos eventos J avaBeans devem herdar java.util.EventObject e seu nome deve obedecer ao padrão XxxxEvent, onde Xxxx é arbitrário. No nosso caso ficamos com o nome TimerEvent. Nosso construtor para a classe TimerListener tem como parâmetro um objeto do tipo Object, e quando este construtor for chamado passaremos a referência para o objeto que o esta chamando. Eventos, por serem classes comuns, também podem carregar informações adicionais, o que não é o nosso caso. TimerBean Esta é a nossa classe principal, e é onde realmente implementamos nosso Bean. package timer; import java.util.*; J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 247 import java.beans.*; public class TimerBean implements java.io.Serializable, Runnable { protected long intervalo = 1000; transient protected Thread th; protected java.util.Vector listeners = new java.util.Vector(); private boolean ativo = false; private java.beans.PropertyChangeSupport mudancas = new java.beans.PropertyChangeSupport(this); public TimerBean() { super(); th = new Thread(this); th.start(); } public void addPropertyChangeListener( PropertyChangeListener l) { mudancas.addPropertyChangeListener(l); } public void addTimerEventListener(TimerEventListener tl) { listeners.addElement(tl); } public void disparaEvento(TimerEvent e) { Vector v; synchronized (this) { v = (Vector) listeners.clone(); } for (int elem = 0; elem < v.size(); elem++) { ((TimerEventListener) v.elementAt(elem)).timerEventDisparado(e); } } public long getIntervalo() { return intervalo; J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 248 } public synchronized void inicia() { ativo = true; } public synchronized void para() { ativo = false; } public void removePropertyChangeListener(PropertyChangeListener l) { mudancas.removePropertyChangeListener(l); } public void removeTimerEventListener(TimerEventListener tl) { listeners.removeElement(tl); } public void run() { try { while (true) { th.sleep(intervalo); if (ativo) { TimerEvent evento = new TimerEvent(this); disparaEvento(evento); } } }catch (Exception e) {e.printStackTrace(); } } public void setIntervalo(long interv) { long valorVelho = intervalo; intervalo = interv; mudancas.firePropertyChange("intervalo", new Long(valorVelho), new Long(intervalo)); } } J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 249 Comecemos pelo princípio. Todos os Beans devem ser serializáveis, este é o mecanismo que é normalmente utilizado para armazenar aplicações J avaBeans. Para tornarmos nossa classe serializável declaramos que esta implementa a interface java.io.Serializable e marcamos nossos campos que não são serializáveis como transient ou providenciarmos métodos de serialização customizados. Nossa classe também implementa Runnable para podermos utilizá-la em um thread. Em nossa classe possuímos os campos: 1. intervalo: que armazena de quanto em quanto tempo desejamos que um evento seja disparado. Através dos métodos getIntervalo() e setIntervalo(long) implementamos uma propriedade bound de nome “intervalo”. 2. th: Este campo é transient, isto é, quando esta classes for serializada esta informação não será gravada. Para nosso Bean th é o thread que periodicamente dispara eventos para seus listeners. 3. listeners: Um Vector armazena a lista de listeners que estão registrados neste Bean para receber os eventos. Novos métodos são colocados em nossa classes para registrar ou remover listeners, este métodos tem nomes que seguem o padrão J avaBeans e têm as formas addXxxx e removeXxxx, onde Xxxx é o nome do tipo de listener que deve ser registrado ou removido. No nosso exemplo, temos addTimerEventListener e removeTimerEventListener. 4. ativo: este campo é utilizado para indicar para o Bean se ele deve ou não disparar eventos para os seus listeners. Ativo não é acessado diretamente, e sim através dos métodos sincronizado para() e inicia(). Os métodos que merecem destaque são: 1. run(): método que implementa o thread, em nosso caso ele contém um laço infinito e dentro do laço, depois de fazer nosso thread “dormir”, com sleep, ele verifica se deve disparar um evento. Se deve dispará-lo, um evento e criado e o método disparaEvento e chamado. 2. disparaEvento(TimerEvent e): inicialmente este método cria dentro de uma seção sincronizado uma cópia do Vector listener. Em seguida através de um laço for, percorre este Vector chamando o método timerEventDisparado para cada listener registrado. J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 250 Temos agora um exemplo completo de um J avaBeans. Mas esta tecnologia tem pouca ou nenhuma utilidade se não a utilizarmos com ferramentas de programação visual como Visual Age, Visual Café, BDK e tantos outros. Como o BDK é de graça, nós o utilizaremos para mostrar nosso Bean em ação. Instalando o Beans Development Kit (BDK) O Beans Development Kit (BDK) é uma aplicação J ava pura, portanto só depende do SDK para rodar. O BDK possui suporte para a API J avaBeans e um container de teste (BeanBox), que manipula Beans visualmente. Para instalar o BDK basta ter na maquina o SDK instalado (acima do 1.1.4), entrar na pagina http://www.javasoft.com e fazer download e descompactá-lo. Pronto, e só rodar. Acompanhando o BDK temos também documentação e J avaBeans com código fonte para serem explorados. Testando exemplo no BDK Para rodar o BDK utilizamos o arquivo run.bat ou run.sh, dependendo do seu sistema operacional. Quando o aplicativo e inicializa sua área de trabalha fica parecido com a figura abaixo. O BDK é composto de 3 janelas: • BeanBox: que é a janela onde os Beans são manipulados visualmente; • ToolBox: uma lista de Beans que podem ser usados; • Properties: um janela que se altera dependendo do Bean que esta selecionado no BeanBox. Ela mostra as propriedades do Bean selecionado e através dela o desenvolvedor pode alterar estas propriedade no Componente; J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 251 Com o BDK rodando nosso próximo passo e criar um arquivo jar contendo o nosso Bean compilado. Para criarmos um jar contendo Beans, devemos informar para a ferramenta na qual este Bean será utilizado, quais classes são Beans. Para isso utilizamos o arquivo de manifesto do jar. Que ficaria assim para o nosso caso: Nome do arquivo: manifest.mf Manifest-Version: 1.0 Name: timer/TimerBean.class Java-Bean: True Name: timer/TimerEventListener.class Name: timer/TimerEvent.class Para criar o arquivo jar, dentro do diretório que contenha o diretório timer, que é onde estão nossas classes compiladas, digite: jar cvfm timer.jar manifest.mf . J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 252 Com nosso jar criado temos agora que apresenta-lo ao BDK. Para isso devemos utilizar a opção File->LoadJ ar do menu da J anela BeanBox. Escolhemos então o arquivo jar que geramos, e o BDK irá carrega-lo. Após carregar nosso Bean a lista de Beans do ToolBean será adicionada de um entrada chamada TimerBean, como mostrado na figura abaixo: Agora é tudo mais divertido, acabou a parte chata!!! Vamos começar a colocar os Beans na janela BeanBox para criarmos uma aplicação. Devemos colocar os seguintes Beans: J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 253 Bean Propriedade(mudada) Novo valor Ourbutton label Iniciar Ourbutton label Parar TimerBean intervalo 2000 EventMonitor A posição dos Beans deve ser como na figura abaixo: Depois de colocar todos os Beans no lugar e mudarmos suas propriedades, devemos conectar os eventos nos métodos corretos de cada Bean. Para ligar um evento disparado por um Bean a um método de outro, devemos selecionar o primeiro e clicarmos em Edit->Events, escolhermos o evento e clicarmos no Bean alvo. Um dialogo aparecerá com os métodos que podem ser escolhidos para serem chamados. J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 254 Bean fonte Evento Bean alvo Método TimerBean TimerEvent- timerEventDisparado EventMonitor InitiateEventSourceMonito ring() OurButton (iniciar) action-actionPerformed TimerBean inicia() OurButton (parar) action-actionPerformed TimerBean para() A figura abaixo mostra parte da operação. Depois de tudo ligado, para testar basta clicar no botão de iniciar, então eventos serão disparados e aparecerão no Monitor de eventos. Depois clique no botão parar parar para que os eventos deixem de ser disparados pela instância de TimerBean . Uma opção interessante para teste no BDK e clicarmos na opção de menu View->Disable Design Mode. A figura abaixo mostra o teste do Bean. J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 255 J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 256 Capítulo XI - Concorrência Um sistema operacional é dito concorrente se permite que mais que uma tarefa seja executada ao mesmo tempo. Na prática a concorrência real ou paralelismo só é possível se o hardware subjacente possui mais de um processador. No entanto, mesmo em computadores com apenas um processador é possível obter um certo tipo de concorrência fazendo com o processador central execute um pouco de cada tarefa por vez, dando a impressão de que as tarefas estão sendo executadas simultaneamente. Dentro da nomenclatura empregada, uma instância de um programa em execução é chamada de processo. Um processo ocupa um espaço em memória principal para o código e para as variáveis transientes (variáveis que são eliminadas ao término do processo). Cada processo possui pelo menos uma linha de execução (Thread). Para ilustrarmos o que é uma linha de execução suponha um determinado programa prog1. Ao ser posto em execução é criado um processo, digamos A, com uma área de código e uma área de dados e é iniciada a execução do processo a partir do ponto de entrada. A instrução inicial assim como as instruções subsequentes formam uma linha de execução do processo A. Portanto, um thread nada mais é que uma sequência de instruções que está em execução de acordo com que foi determinado pelo programa. O estado corrente da linha de execução é representada pela instrução que está sendo executada. A figura IX.1 mostra a relação entre estes elementos. arquivo prog1 Memória Principal Figura IX.1 – Relação entre Programa, Processo e Thread. É possível existir mais de uma linha de execução em um único processo. Cada linha de execução pode também ser vista como um processo, com a 101001101 010110010 010101100 100011101 100101010 101001010 Área de dados Área de código Linha de execução (thread) Processo 101001101 010110010 010101100 100011101 J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 257 diferença que enquanto cada processo possui sua área de código e dados separada de outros processos, os threads em um mesmo processo compartilham o código e a área de dados. O que distingue um thread de outro em um mesmo processo é a instrução corrente e uma área de pilha usada para armazenar o contexto da sequência de chamadas de cada thread. Por isso os threads também são chamados de processos leves (light process). A figura IX.2 mostra esquematicamente a diferença entre processos e threads. Memória Processo Figura IX.2 – (a) Processos; (b) Threads. Sistemas monotarefas e monothreads como o DOS possuem apenas um processo em execução em um determinado instante e apenas um thread no processo. Sistemas multitarefas e monothreads como o Windows 3.1 permitem vários processos em execução e apenas um thread por processo. Sistemas multitarefas e multithread como o Solaris, OS/2, Linux e Windows 95/98/NT permitem vários processos em execução e vários threads por processo. Como os threads em um mesmo processo possuem uma área de dados em comum, surge a necessidade de controlar o acesso a essa área de dados, de modo que thread não leia ou altere dados no momento que estão sendo alterados por outro thread. A inclusão de instruções para controlar o acesso a áreas compartilhadas torna o código mais complexo do que o código de processos monothreads. Uma pergunta pode surgir na mente do leitor: se a inclusão de mais de um thread torna o código mais complexo porque razão eu deveria projetar código multithread. Processos com vários threads podem realizar mais de uma 10100 01001 11001 01010 1010 0111 10100 01001 11001 01010 1010 0111 10100 01001 11001 01010 1010 0111 A B C 1010001001110 0101010101010 1010000101101 1010100010101 0101011011001 0101010100101 0101010010101 0101001000000 1010101010101 10100111001 01010101010 10101010101 01010101000 11110101010 Thread 1 Thread 2 Código Dados Área de pilha do thread1 Área de pilha do thread2 J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 258 tarefa simultaneamente. São úteis na criação de processos servidores, criação de animações e no projeto de interfaces com o usuário que não ficam travadas durante a execução de alguma função. Por exemplo, imagine um processo servidor a espera de requisições de serviços, podemos projetá-lo de modo que ao surgir uma solicitação de um serviço por um processo cliente ele crie um thread para atender a solicitação enquanto volta a esperar a requisição de novos serviços. Com isto os processos clientes não precisam esperar o término do atendimento de alguma solicitação para ter sua requisição atendida. O mesmo pode ser dito em relação ao projeto de interfaces com o usuário. O processo pode criar threads para executar as funções solicitadas pelo usuário, enquanto aguarda novas interações. Caso contrário, a interface ficaria impedida de receber novas solicitações enquanto processa a solicitação corrente, o que poderia causar uma sensação de travamento ao usuário. Outra aplicação para processos multithread é a animação de interfaces. Nesse caso cria-se um ou mais threads para gerenciar as animações enquanto outros threads cuidam das outras tarefas como por exemplo entrada de dados. A rigor todas as aplicações acima como outras aplicações de processos multithread podem ser executados por meio de processos monothreads. No entanto, o tempo gasto na mudança de contexto entre processos na maioria dos sistemas operacionais é muito mais lenta que a simples alternância entre threads, uma vez que a maior parte das informações contextuais são compartilhadas pelos threads de um mesmo processo. Mudança de Contexto (task switch) É o conjunto de operações necessárias para gravar o estado atual do processo corrente e recuperar o estado de outro processo de modo a torná-lo o processo corrente. Mesmo que você não crie mais de um thread todo processo J ava possui vários threads: thread para garbage collection, thread para monitoramento de eventos, thread para carga de imagens, etc. Criando threads em Java Processos Multithread não é uma invenção da linguagem J ava. É possível criar processos multithread com quase todas as linguagens do mercado, como C++, e Object Pascal. No entanto J ava incorporou threads ao núcleo básico da linguagem tornado desta forma mais natural o seu uso. Na verdade o uso de J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 259 threads está tão intimamente ligado a J ava que é quase impossível escrever um programa útil que não seja multithread. A classe Thread agrupa os recursos necessários para a criação de um thread. A forma mais simples de se criar um thread é criar uma classe derivada da classe Thread. Por exemplo: class MeuThread extends Thread { ... } É preciso também sobrescrever o método run() da classe Thread. O método run() é o ponto de entrada do thread, da mesma forma que o método main() é ponto de entrada de uma aplicação. O exemplo IX.1 mostra uma classe completa. public class MeuThread extends Thread { String s; public MeuThread (String as) { super(); s = new String(as); } public void run() { for (int i = 0; i < 5; i++) System.out.println(i+” “+s); System.out.println("FIM! "+s); } } Exemplo IX.1 – Subclasse da classe Thread. No exemplo IX.1foi inserido um atributo para identificar o thread, apesar de existir formas melhores de se nomear um thread como veremos mais adiante. O método run() contém o código que será executado pelo thread. No exemplo IX.1 o thread imprime cinco vezes o atributo String. Para iniciar a execução de um thread cria-se um objeto da classe e invoca-se o método start() do objeto. O método start() cria o thread e inicia sua execução pelo método run(). Se o método run() for chamado diretamente nenhum thread novo será criado e o método run() será executado no thread corrente. O exemplo IX.2 mostra uma forma de se criar um thread usando a classe definida no exemplo IX.1. J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 260 public class TesteThread1 { public static void main (String[] args) { new MeuThread("Linha1").start(); } } Exemplo IX.2 – Criação de um Thread. No exemplo acima apenas um thread, além do principal é criado. Nada impede que sejam criados mais objetos da mesma classe para disparar um número maior de threads. O exemplo IX.3 mostra a execução de dois threads sobre dois objetos de uma mesma classe. public class TesteThread2 { public static void main (String[] args) { new MeuThread("Linha1").start(); new MeuThread("Linha2").start(); } } Exemplo IX.3 – Criação de dois Threads. Cada thread é executado sobre uma instância da classe e, por consequência, sobre uma instância do método run(). A saída gerada pela execução do exemplo IX.3 depende do sistema operacional subjacente. Uma saída possível é a seguinte: 0 Linha2 0 Linha1 1 Linha2 1 Linha1 2 Linha2 2 Linha1 3 Linha2 3 Linha1 4 Linha2 4 Linha1 FIM! Linha2 FIM! Linha1 A saída acima mostra que os threads executam intercaladamente. No entanto, em alguns sistemas operacionais os threads do exemplo IX.3 executariam um após o outro. A relação entre a sequência de execução e o sistema operacional e dicas de como escrever programas multithread com J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 261 sequência de execução independente de plataforma operacional serão em uma seção mais adiante neste mesmo capítulo. Criando threads por meio da interface Runnable Algumas vezes não é possível criar uma subclasse da classe Thread porque a classe já deriva outra classe, por exemplo a classe Applet. Outras vezes, por questões de pureza de projeto o projetista não deseja derivar a classe Thread simplesmente para poder criar um thread uma vez que isto viola o significado da relação de classe-subclasse. Para esses casos existe a interface Runnable. A interface Runnable possui apenas um método para ser implementado: o método run(). Para criar um thread usando a interface Runnable é preciso criar um objeto da classe Thread, passando para o construtor uma instância da classe que implementa a interface. Ao invocar o método start() do objeto da classe Thread, o thread criado, inicia sua execução no método run() da instância da classe que implementou a interface. O exemplo IX.4 mostra a criação de um thread usando a interface Runnable. public class TesteThread2 implements Runnable { private String men; public static void main(String args[]) { TesteThread2 ob1 = new TesteThread2 (“ola”); Thread t1 = new Thread(ob1); t1.start(); } public TesteThread2 (String men) {this.men=men;} public void run() { for(;;) System.out.println(men); } } Exemplo IX.4 – Criação de um thread por meio da interface Runnable. Note que agora ao invocarmos o método start() o thread criado iniciará a execução sobre o método run() do objeto passado como parâmetro, e não sobre o método run() do objeto Thread. J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 262 Nada impede que seja criado mais de um thread executando sobre o mesmo objeto: Thread t1 = new Thread(ob1); Thread t2 = new Thread(ob1); Neste caso alguns cuidados devem ser tomados, uma vez que existe o compartilhamento das variáveis do objeto por dois threads. Os problemas que podem advir de uma situação como esta serão tratados mais adiante. A classe Thread A classe Thread é extensa, possuindo vários construtores, métodos e variáveis públicas. Aqui mostraremos apenas os mais usados. Hierarquia A classe Thread deriva diretamente da classe Object. java.lang.Object java.lang.Thread Construtores Construtor Descrição Thread(ThreadGroup g, String nome) Cria um novo thread com o nome especificado dentro do grupo g. Thread(Runnable ob, String nome) Cria um novo thread para executar sobre o objeto ob, com o nome especificado. Thread(ThreadGroup g, Runnable ob, String nome) Cria um novo thread para executar sobre o objeto ob, dentro do grupo g, com o nome especificado. Thread(String nome) Cria um novo thread com o nome especificado. Thread() Cria um novo thread com o nome default. Thread(Runnable ob) Cria um novo thread para executar J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 263 sobre o objeto ob. Thread(ThreadGroup g, Runnable ob) Cria um novo thread para executar sobre o objeto ob, dentro do grupo g. Tabela IX.1 – Construtores da classe Thread. A tabela X.1 mostra os principais construtores da classe Thread. Podemos notar que é possível nomear os threads e agrupá-los. Isto é útil para obter a referência de threads por meio do seu nome. Métodos Método Descrição currentThread() Retorna uma referência para o thread corrente em execução. destroy() Destroi o thread sem liberar os recursos. dumpStack() Imprime a pilha de chamadas do thread corrente. enumerate(Thread[] v) Copia para o array todos os thread ativos no grupo do thread. getName() Obtém o nome do thread. getPriority() Obtém a prioridade do thread. getThreadGroup() Retorna o grupo do thread. resume() Reassume a execução de um thread previamente suspenso. run() Se o thread foi construído usando um objeto Runnable separado então o método do objeto Runnable é chamado. Caso contrário nada ocorre. setName(String name) Muda o nome do thread. setPriority(int newPriority) Muda a prioridade do thread. sleep(long millis) Suspende o thread em execução o número de milisegundos especificados. sleep(long millis, int nanos) Suspende o thread em execução o número de milisegundos mais o número de nanosegundos especificados. start() Inicia a execução do thread. A máquina virtual chama o método run() do thread. stop() Força o encerramento do thread. suspend() Suspende a execução de um thread. yield() Faz com que o thread corrente interrompa permitindo que outro thread seja executado. Tabela IX.2 – Métodos da classe Thread. J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 264 A tabela X.2 apresenta os principais métodos do classe Thread. Alguns métodos muito usados nas versões anteriores do SDK1.2 foram depreciados na versão atual por serem considerados inseguros ou com tendência a causarem deadlock . Os métodos depreciados são: stop(), suspend(), resume() e destroy(). Deadlock Travamento causado pela espera circular de recursos em um conjunto de threads. O travamento por deadlock mais simples é o abraço mortal onde um thread A espera que um thread B libere um recurso, enquanto que o thread B só libera o recurso esperado por A se obter um recurso mantido por A. Desta forma os dois threads são impedidos indefinidamente de prosseguir. Existem alguns métodos da classe Object que são importantes para o controle dos threads. O leitor pode estar se perguntando porque métodos relacionados threads estão na superclasse Object que é “mãe” de todas as classe em J ava. A razão disso é que esses métodos lidam com um elemento associado a todo objeto e que é usado para promover o acesso exclusivo aos objetos. Esse elemento é chamado de monitor. Na seção que aborda a sincronização os monitores serão discutidos mais detalhadamente. Os métodos herdados relacionados com controle dos threads estão descritos na tabela IX.3. Método Descrição notify() Notifica um thread que está esperando sobre um objeto. notifyAll() Notifica todos os threads que está esperando sobre um objeto. wait() Espera para ser notificado por outro thread. wait(long timeout, int nanos) Espera para ser notificado por outro thread. wait(long timeout) Espera para ser notificado por outro thread. Tabela IX.3 – Métodos da classe Object relacionados com threads. Variáveis públicas As variáveis públicas da classe Thread definem valores máximo, mínimo e default para a prioridade de execução dos threads. J ava estabelece dez valores de prioridade. Como essas prioridades são relacionadas com as prioridades do J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 265 ambiente operacional depende da implementação máquina virtual e pode influenciar no resultado final da execução do programa. Mais adiante abordaremos a influência do ambiente operacional na execução de programas multithread. Variável Descrição static final int MAX_PRIORITY A prioridade máxima que um thread pode ter. static final int MIN_PRIORITY A prioridade mínima que um thread pode ter. static final int NORM_PRIORITY A prioridade default associado a um thread. Tabela IX.4 – Variáveis públicas. Ciclo de Vida dos Threads Um thread pode possuir quatro estados conforme mostra a figura IX.3. Podemos observar que uma vez ativo o thread alterna os estados em execução e suspenso até que passe para o estado morto. A transição de um estado para outro pode ser determinada por uma chamada explícita a um método ou devida a ocorrência de algum evento a nível de ambiente operacional ou de programa. Estados Ativos Figura IX.3 – Estados de um thread. A transição de um thread do estado novo para algum estado ativo é sempre realizada pela invocação do método start() do objeto Thread. J á as transições do estado em execução para o estado suspenso e vice-versa e desses para o estado morto podem ser disparadas tanto pela invocação de variados métodos como pela ocorrência de eventos. O exemplo IX.5 mostra as ocorrência de transição em um código. thread novo thread em Execução thread morto thread suspenso J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 266 public class TesteThread3 extends Thread { public TesteThread3 (String str) {super(str);} public void run() { for (int i = 0; i < 10; i++) { System.out.println(i + " " + getName()); try { // Comando para suspender o thread por // 1000 milisegundos (1 segundo) // Transição do estado em execução para o // estado suspenso sleep(1000); } catch (InterruptedException e) {} // Evento: fim do tempo de suspensão // Transição do estado em suspenso para o // estado em execução } System.out.println("FIM! " + getName()); // Evento: fim da execução do thread // Transição do estado ativo suspenso para o // estado morto } public static void main(String args[]) { TesteThread3 t1 = new TesteThread3(args[0]); t1.start(); // Transição para um estado ativo } } Exemplo IX.5 – Alguns comandos e eventos que acarretam transição de estados. sleep(), yield(), join(), destroy(), stop(), suspend() e resume(). Agora que vimos os estados que podem ser assumidos por um thread em seu ciclo de vida vamos examinar mais detalhadamente alguns dos métodos responsáveis pela mudança de estado de um thread. J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 267 sleep() O método sleep()é um método estático e possui as seguintes interfaces: static void sleep(long ms) throws InterruptedException ou static void sleep(long ms, int ns) throws InterruptedException Onde ms é um valor em milisegundos e ns é um valor em nanosegundos. O método sleep() faz com que o thread seja suspenso por um determinado tempo, permitindo que outros threads sejam executados. Como o método pode lançar a exceção InterruptedException, é preciso envolver a chamada em um bloco try/catch ou propagar a exceção. O exemplo IX.6 define uma espera mínima de 100 milisegundos entre cada volta do loop. Note que o tempo de suspensão do thread pode ser maior que o especificado, uma vez que outros threads de maior ou mesmo de igual prioridade podem estar sendo executados no momento em que expira o tempo de suspensão solicitado. public class ThreadComYield extends Thread { String s; public ThreadComYield(String as) { super(); s = new String(as); } public void run() { for (int i = 0; i < 5; i++) { System.out.println(i+” “+s); try{ Thread.sleep(100); catch(InterruptedException e){} } System.out.println("FIM! "+s); } } Exemplo IX.6 – Uso do método sleep(). Outro problema com o sleep() é que a maioria dos Sistemas Operacionais não suportam resolução de nanosegundos. Mesmo a resolução a nível de unidade de milisegundo não é suportada pela maioria dos SOs. No caso do SO não suportar a resolução de tempo solicitada, o tempo será arredondado para a nível de resolução suportado pela plataforma operacional. J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 268 yield() O método yield() é um método estático com a seguinte interface: static void yield() Uma chamada ao método yield() faz com que o thread corrente libere automaticamente a CPU para outro thread de mesma prioridade. Se não houver nenhum outro thread de mesma prioridade aguardando, então o thread corrente mantém a posse da CPU. O exemplo IX.7 altera o exemplo IX.1 de modo a permitir que outros threads de mesma prioridade sejam executados a cada volta do loop. Public class ThreadComYield extends Thread { String s; public ThreadComYield(String as) { super(); s = new String(as); } public void run() { for (int i = 0; i < 5; i++) { System.out.println(i+” “+s); Thread.yield(); } System.out.println("FIM! "+s); } } Exemplo IX.7 – Uso do método yield(). join() O método join() é um método de instância da classe Thread e é utilizado quando existe a necessidade do thread corrente esperar pela término da execução de outro thread. As versões do método join() são as seguintes: public final void join(); public final void join(long millisecond); public final void join(long millisecond, int nanosecond); J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 269 Na primeira versão o thread corrente espera indefinidamente pelo encerramento da execução do segundo thread. Na segunda e terceira versão o thread corrente espera pelo término da execução do segundo thread até no máximo um período de tempo prefixado. O exemplo IX.8 mostra o como usar o método join(). class ThreadComJoin extends Thread { String s; public ThreadComJoin(String as) { super(); s = new String(as); } public void run() { for (int i = 0; i < 10; i++) System.out.println(i+” “+s); System.out.println("Fim do thread!"); } } public class TestaJoin { public static void main(String args[]) { ThreadComJoin t1 = new ThreadComJoin(args[0]); t1.start(); // Transição para um estado ativo t1.join(); // Espera pelo término do thread System.out.println("Fim do programa!"); } } Exemplo IX.8 – Uso do método join(). stop(), suspend(), resume() e destroy() A partir da versão 1.2 do SDK os métodos stop(), suspend(), and resume() tornaram-se deprecated uma vez que a utilização desses métodos tendia a gerar erros. No entanto, devido a grande quantidade de código que ainda utiliza estes método, acreditamos que seja importante mencioná-los. O método stop() é um método de instância que encerra a execução do thread ao qual pertence. Os recursos alocados ao thread são liberados. É recomendável substituir o método stop() pelo simples retorno do método run(). J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 270 O método suspend() é um método de instância que suspende a execução do thread ao qual pertence. Nenhum recurso é liberado, inclusive os monitores que possuir no momento da suspensão (os monitores serão vistos mais adiante e servem para controlar o acesso à variáveis compartilhadas). Isto faz com que o método suspend() tenda a ocasionar deadlocks. O método resume() é um método de instância que reassume a execução do thread ao qual pertence. Os métodos suspend() e resume() devem ser substituídos respectivamente pelos métodos wait() e notify(), como veremos mais adiante. O método destroy() é um método de instância que encerra a execução do thread ao qual pertence. Os recursos alocados ao thread não são liberados. Não é um método deprecated mas é recomendável substituí-lo pelo simples retorno do método run(). Daemon Threads Daemon threads são threads que rodam em background com a função de prover algum serviço mas não fazem parte do propósito principal do programa. Quando só existem threads do tipo daemon o programa é encerrado. Um exemplo de daemon é o thread para coleta de lixo. Um thread é definido como daemon por meio do método de instância setDaemon(). Para verificar se um thread é um daemon é usado o método de instância isDaemon(). O exemplo IX.9 mostra o como usar esses métodos. import java.io.*; class ThreadDaemon extends Thread { public ThreadDaemon() { setDaemon(true); start(); } public void run() { for(;;) yield(); } } public class TestaDaemon { J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 271 public static void main(String[] args) { Thread d = new ThreadDaemon(); System.out.println("d.isDaemon() = " + d.isDaemon()); BufferedReader stdin = new BufferedReader( new InputStreamReader(System.in)); System.out.println("Digite qualquer coisa"); try { stdin.readLine(); } catch(IOException e) {} } } Exemplo IX.9 – Uso dos métodos relacionados com daemons. No exemplo IX.9 o método main() da classe TestaDaemon cria um objeto da classe ThreadDaemon. O construtor da classe ThreadDaemon define o thread como daemon por meio do método setDaemon() e inicia a execução do thread. Como é apenas um thread de demonstração o método run() da classe ThreadDaemon não faz nada, apenas liberando a posse da CPU toda vez que a adquire. Após a criação da instância da classe ThreadDaemon no método main() é testado se o thread criado é um daemon, utilizando para esse fim o método isDaemon(). Depois disso o programa simplesmente espera o usuário pressionar a tecla <enter>. O programa termina logo em seguida ao acionamento da tecla, mostrando dessa forma que o programa permanece ativo apenas enquanto existem threads não daemons ativos. Influência do Sistema Operacional no Comportamento dos Threads Apesar da linguagem J ava prometer a construção de programas independentes de plataforma operacional, o comportamento dos threads pode ser fortemente influenciado pelo sistema operacional subjacente. Portanto, o programador deve tomar alguns cuidados se deseja construir programas que funcionem da mesma forma, independente do ambiente onde está sendo executado. J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 272 Alguns sistemas operacionais não oferecem suporte a execução de threads. Neste caso, cada processo possui apenas um thread. Mesmo em sistemas operacionais que oferecem suporte a execução de múltiplos threads por processo o projetista da máquina virtual pode optar por não usar o suporte nativo a threads. Deste modo, é responsabilidade da máquina virtual criar um ambiente multithread. Threads implementados desta forma, a nível de usuário, são chamados de green-threads. As influências da plataforma operacional podem agrupadas em dois tipos: 1) Forma de escalonamento de threads. O ambiente pode adotar um escalonamento não preemptivo ou preemptivo. No escalonamento não preemptivo (também chamado de cooperativo) um thread em execução só perde o controle da CPU (Central Processing Unit) se a liberar voluntariamente ou se necessitar de algum recurso que ainda não está disponível. J á no escalonamento preemptivo, além das formas acima um thread pode perde o controle da CPU por eventos externos, como o fim do tempo máximo definido pelo ambiente para a execução contínua de um thread (fatia de tempo) ou porque um thread de mais alta prioridade está pronto para ser executado. Exemplos de sistemas operacionais não preemptivos são Windows 3.1 e IBM OS/2. Exemplos de sistemas operacionais preemptivos são Windows 95/98/NT e Linux, QNX, e muitos outros. Alguns sistemas operacionais adotam uma abordagem híbrida, suportando tanto o modelo cooperativo como o preemptivo, como o Solaris da Sun. 2) Relacionamento entre os níveis de prioridades definidas na linguagem Java e os níveis de prioridades definidas nos Sistemas Operacionais. Em um SO preemptivo um thread de uma determinada prioridade perde a posse da CPU para um thread de prioridade mais alta que esteja pronto para ser executado. A linguagem J ava prevê dez níveis de prioridades que podem ser atribuídas aos threads. No entanto, cada SO possui um número de prioridades diferente e o mapeamento das prioridades da linguagem J ava para as prioridades do SO subjacente pode influenciar o comportamento do programa. Forma de escalonamento de threads J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 273 A especificação da máquina virtual J ava determina que a forma de escalonamento de threads seja preemptiva. Portanto, mesmo em ambiente operacionais cooperativos a máquina virtual deve garantir um escalonamento preemptivo. No entanto, um escalonamento preemptivo não obriga a preempção por fim de fatia de tempo. Podemos ter um escalonamento preemptivo onde um thread de mais alta prioridade interrompe o thread que tem a posse da CPU mas não existe preempção por fim de fatia de tempo. Um escalonamento onde threads de mesma prioridade intercalam a posse da CPU por força do fim da fatia de tempo é chamado de escalonamento Round-Robin. A especificação da máquina virtual J ava não prevê o escalonamento Round-Robin, mas também não o descarta, abrindo uma possibilidade de implementações distintas de máquina virtual e introduzindo o não determinismo na execução de programas multithread. Por exemplo, o exemplo IX.3 poderia ter uma saída distinta da apresentada anteriormente caso seja executado por uma máquina virtual que não implementa o escalonamento Round-Robin. Nesse caso a saída seria a seguinte: 0 Linha2 1 Linha2 2 Linha2 3 Linha2 4 Linha2 FIM! Linha2 0 Linha1 1 Linha1 2 Linha1 3 Linha1 4 Linha1 FIM! Linha1 Neste caso, se o programador deseja que a execução de threads se processe de forma alternada, independentemente da implementação da máquina virtual, então é necessário que ele insira código para a liberação voluntária da CPU. Isso pode ser feito com o método yield() ou com o método sleep(). Relacionamento entre os níveis de prioridades definidas na linguagem Java e os níveis de prioridades definidas nos Sistemas Operacionais. Como já dissemos a linguagem J ava prevê dez níveis de prioridades que podem ser atribuídas aos threads. Na verdade são onze prioridades, mas a prioridade nível 0 é reservada para threads internos. As prioridades atribuídas J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 274 aos threads são estáticas, ou seja não se alteram ao longo da vida do thread, a não ser que por meio de chamadas a métodos definidos para esse propósito. A classe thread possui variáveis públicas finais com valores de prioridade predefinidos, como mostrado na tabela IX.4. No entanto, os sistemas operacionais podem possuir um número maior ou menor de níveis de prioridades. Vamos citar um exemplo: o MSWindows 95/98/NT. Este sistema possui apenas sete níveis de prioridades e estes sete níveis devem ser mapeados para os onze níveis de prioridades especificados em J ava. Cada máquina virtual fará este mapeamento de modo diferente, porém a implementação comum é mostrada na tabela IX.5. Prioridades Java Prioridades MSWindows 0 THREAD_PRIORITY_IDLE 1(Thread.MIN_PRIORITY) THREAD_PRIORITY_LOWEST 2 THREAD_PRIORITY_LOWEST 3 THREAD_PRIORITY_BELOW_NORMAL 4 THREAD_PRIORITY_BELOW_NORMAL 5(Thread.NORM_PRIORITY) THREAD_PRIORITY_NORMAL 6 THREAD_PRIORITY_ABOVE_NORMAL 7 THREAD_PRIORITY_ABOVE_NORMAL 8 THREAD_PRIORITY_HIGHEST 9 THREAD_PRIORITY_HIGHEST 10(Thread.MAX_PRIORITY) THREAD_PRIORITY_TIME_CRITICAL Tabela IX.5 –Mapeamento das prioridades de Java para MSWindows. Note que nesta implementação níveis de prioridades diferentes em J ava serão mapeados para um mesmo nível de prioridade em MSWindows. Isto pode levar a resultados inesperados caso o programador projete uma aplicação esperando, por exemplo, que um thread de prioridade 4 irá interromper um thread de prioridade 3. Para evitar este tipo de problema o programador pode adotar dois tipos de abordagem: 1) utilizar, se for possível, apenas as prioridades Thread.MIN_PRIORITY, Thread.NORM_PRIORITY e Thread.MAX_PRIORITY para atribuir prioridades aos threads; ou 2) não se basear em níveis de prioridades para definir o escalonamento de threads, utilizando, alternativamente, primitivas de sincronização que serão abordadas na próxima seção. J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 275 Compartilhamento de Memória e Sincronização Como já foi dito, mais de um thread pode ser criado sobre um mesmo objeto. Neste caso cuidado especiais devem ser tomados, uma vez que os threads compartilham as mesmas variáveis e problemas podem surgir se um thread está atualizando uma variável enquanto outro thread está lendo ou atualizando a mesma variável. Este problema pode ocorrer mesmo em threads que executam sobre objetos distintos, já que os objetos podem possuir referências para um mesmo objeto. O exemplo IX.8 mostra a execução de dois threads sobre um mesmo objeto. O nome do thread é usado para que o thread decida que ação tomar. O thread de nome “um” ontem um número de 0 a 1000 gerado aleatoriamente e o coloca na posição inicial de um array de dez posições. As outras posições do array são preenchidas com os nove números inteiros seguintes ao número inicial. O thread de nome “dois” imprime o conteúdo do vetor. Obviamente o programa é apenas ilustrativo, não possuindo aplicação prática. A intenção inicial do projetista é obter na tela sequências de dez números inteiros consecutivos iniciados aleatoriamente. No entanto, como os dois threads compartilham o mesmo objeto e não existe qualquer sincronismo entre sí, é pouco provável que o projetista obtenha o resultado esperado. public class CalcDez implements Runnable { private int vetInt[]; public CalcDez () {vetInt=new int[10]; } public void run() { if (Thread.currentThread().getName().equals(”um”)) for (;;) { vetInt[0] = (int)(Math.random() * 1000); for (int i=1;i<10;i++) vetInt[i]= vetInt[0]+i; } else for (;;) { System.out.println(“Serie iniciada por”+ vetInt[0]); for (int i=1;i<10;i++) System.out.println(vetInt[i]+ “ “); } } public static void main(String args[]) { J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 276 CalcDez ob = new CalcDez(); Thread t1 = new Thread(ob,”um”); Thread t2 = new Thread(ob,”dois”); t1.start(); t2.start(); } } Exemplo IX.8 – Dois threads executando sobre o mesmo objeto. Se a máquina virtual não implementar um escalonamento Round-Robin apenas um thread será executado, visto que os dois threads possuem a mesma prioridade. J á no caso da máquina virtual implementar um escalonamento Round- Robin a alternância da execução dos threads produzirá resultados imprevisíveis. Um trecho de uma das saídas possíveis pode ser visto na figura IX.4. Ele foi obtido em Pentium 100MHz executando a máquina virtual da Sun, versão 1.2, sob o sistema operacional MSWindows 95. 258 259 Serie iniciada por573 574 575 576 577 578 579 580 581 582 Serie iniciada por80 81 82 Figura IX.4 – Saída do exemplo IX.8. Podemos notar as sequências estão misturadas, mostrando que cada thread interrompe o outro no meio da execução da tarefa especificada. O mesmo problema pode mesmo em threads que executam sobre objetos diferentes, bastando que cada thread possua referência para um mesmo objeto. O exemplo IX.9 mostra a execução de dois threads sobre objetos distintos. J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 277 class Compartilhada { private int vetInt[]; public Compartilhada() {vetInt=new int[10];} public void setVal() { for (;;) { vetInt[0] = (int)(Math.random() * 1000); for (int i=1;i<10;i++) vetInt[i]= vetInt[0]+i; } } public int getVal(int i) {return vetInt [i];} } public class CalcDez2 extends Thread { private Compartilhada obj; private int tipo; public CalcDez2 (Compartilhada aObj, int aTipo) { obj = aObj; tipo = aTipo;} public void run() { for (;;) if (tipo==1) obj.setVal(); else { System.out.println(“Serie iniciada por”+ obj.getVal(0)); for (int i=1;i<10;i++) System.out.println(obj.getVal(i)+ “ “); } } public static void main(String args[]) { Compartilhada obj = new Compartilhada(); CalcDez2 t1 = new CalcDez2(obj,1); CalcDez2 t2 = new CalcDez2(obj,2); t1.start(); t2.start(); } } Exemplo IX.9 – Dois threads executando sobre objetos distintos. J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 278 É importante que o leitor não confunda o exemplo IX.9 com o exemplo IX.8 achando que nos dois exemplos os dois threads executam sobre o mesmo objeto, uma vez que a etapa da criação dos threads é bem parecida. No entanto, no exemplo IX.9 foi declarada uma subclasse da classe Thread e não uma classe que implementa a interface Runnable. Apesar de parecer que no exemplo IX.9 ambos os threads executaram sobre um mesmo objeto da classe Compartilhada que é passado como argumento, na verdade cada thread executará sobre sua própria instância da classe CalcDez2, sendo que o objeto da classe Compartilhada é referenciado pelos dois threads. O comportamento do código do exemplo IX.9 é semelhante ao do exemplo IX.8, com a diferença que no primeiro a sequência de inteiros é encapsulado pelo objeto da classe Compartilhada. Este tipo de situação, onde o resultado de uma computação depende da forma como os threads são escalonados, é chamada de condições de corrida (Race Conditions). É um problema a ser evitado uma vez que o programa passa a ter um comportamento não determinístico. Atomicidade de Instruções e Sincronização do Acesso à Sessões Críticas A condição de corrida ocorre porque os acesso à áreas de memória compartilhada não é feita de forma atômica, e nem de forma exclusiva. Por forma atômica queremos dizer que o acesso é feito por meio de várias instruções e pode ser interrompido por outro thread antes que toda as instruções que compõem o acesso sejam executadas. Por forma exclusiva queremos dizer que um thread podem consultar/atualizar um objeto durante a consulta/atualização do mesmo objeto por outros threads. Poucas operações são atômicas em J ava. Em geral, as atribuições simples, com exceção dos tipos long e double, são atômicas, de forma que o programador não precisa se preocupar em ser interrompido no meio de uma operação de atribuição. No entanto, no caso de operações mais complexas sobre variáveis compartilhadas é preciso que o programador garanta o acesso exclusivo a essas variáveis. Os trechos de código onde é feito o acesso às variáveis compartilhadas são chamados de Seções Críticas ou Regiões Críticas. Uma vez determinada uma região crítica como garantir o acesso exclusivo? A linguagem J ava permite que o programador garanta o acesso exclusivo por meio utilizando o conceito de monitor. O conceito de monitor foi proposto por C. A. R. Hoare em 1974 e pode ser encarado como um objeto que J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 279 garante a exclusão mútua na execução dos procedimentos a ele associados. Ou seja, apenas um procedimento associado ao monitor pode ser executado em um determinado momento. Por exemplo, suponha que dois procedimentos A e B estão associados a um monitor. Se no momento da invocação do procedimento A algum o procedimento B estiver sendo executando o processo ou thread que invocou o procedimento A fica suspenso até o término da execução do procedimento B. Ao término do procedimento B o processo que invocou o procedimento A é “acordado” e sua execução retomada. O uso de monitores em J ava é uma variação do proposto por Hoare. na linguagem J ava todo objeto possui um monitor associado. Para facilitar o entendimento podemos encarar o monitor como um detentor de um “passe”. Todo thread pode pedir “emprestado” o passe ao monitor de um objeto antes de realizar alguma computação. Como o monitor possui apenas um passe, apenas um thread pode adquirir o passe em um determinado instante. O passe tem que ser devolvido para o monitor para possibilitar o empréstimo do passe a outro thread. A figura IX.5 ilustra essa analogia. J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 280 Instante 1: o thread t1 solicita Instante 2: o thread t2 solicita o passe ao monitor o passe ao monitor do objeto x. do objeto x e é bloqueado. Instante 3: o thread t1 libera Instante 4: o thread t2 recebe o passe. o passe do monitor do objeto x. Figura IX.5 – Uma possível sequência na disputa de dois threads pela autorização de um monitor. Nos resta saber como solicitar o passe ao monitor. Isto é feito por meio da palavra chave synchronized. Existem duas formas de se usar a palavra chave synchronized: na declaração de métodos e no início de blocos. O exemplo IX.10 mostra duas versões da classe FilaCirc que implementa uma fila circular de valores inteiros: uma com métodos synchronized e outra com blocos synchronized. Um objeto desta classe pode ser compartilhado por dois ou mais threads para implementar o exemplo clássico de concorrência do tipo produtor/consumidor. Objeto x Monitor de x $ passe thread t1 $ $ thread t2 Monitor de x $ Monitor de x Monitor de x Objeto x Objeto x Objeto x J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 281 a) Versão com métodos synchronized b) Versão com blocos synchronized class FilaCirc { private final int TAM = 10; private int vetInt[]; private int inicio, total; public FilaCirc() { vetInt=new int[TAM]; inicio=0; total =0; } public synchronized void addElement(int v) throws Exception { if (total == TAM) throw new Exception("Fila cheia!"); vetInt[(inicio+total)%TAM] = v; total++; } public synchronized int getElement() throws Exception { if (total == 0 ) throw new Exception("Fila vazia!"); int temp = vetInt[inicio]; inicio = (++inicio)%TAM; total--; return temp; } } class FilaCirc { private final int TAM = 10; private int vetInt[]; private int inicio, total; public FilaCirc() { vetInt=new int[TAM]; inicio=0; total =0; } public void addElement(int v) throws Exception { synchronized(this) { if (total == TAM) throw new Exception("Fila cheia!"); vetInt[(inicio+total)%TAM] = v; total++; } } public int getElement() throws Exception { synchronized(this) { if (total == 0 ) throw new Exception("Fila vazia!"); int temp = vetInt[inicio]; inicio = (++inicio)%TAM; total--; } return temp; } } Exemplo IX.10 – Duas versões de uma classe que implementa uma fila circular de inteiros. A palavra chave synchronized na frente dos métodos de instância significa que o método será executado se puder adquirir o monitor do objeto a quem pertence o método 4 . Caso contrário o thread que invocou o método será suspenso até que possa adquirir o monitor. Este forma de sincronização é abordada no exemplo IX.10.a. Portanto, se algum thread chamar algum método de um objeto da classe FilaCirc nenhum outro thread que compartilha o mesmo objeto poderá executar um método do objeto até que o método chamado 4 Não usaremos mais a analogia com a aquisição do passe do monitor. Ela foi usada apenas para facilitar o entendimento do leitor. Quando se trata de monitores os termos mais usados são: “adquirir o monitor” e “liberar o monitor”. J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 282 pelo primeiro thread termine. Caso outro thread invoque um método do mesmo objeto ficará bloqueado até que possa adquirir o monitor. O leitor pode estar se perguntando sobre a necessidade de sincronizar os métodos da classe FilaCirc uma vez que ocorrem apenas atribuições simples a elementos individuais de um vetor e as atribuições de inteiros são atômicas. De fato o problema ocorre não na atribuição dos elementos e sim na indexação do array. Por exemplo, a instrução inicio = (++inicio)%TAM; do método getElement() não é atômica. Suponha que a os métodos da classe FilaCirc não são sincronizados e que as variáveis inicio e total possuem os valores 9 e 1 respectivamente. Suponha também que thread invocou o método getElement() e foi interrompido na linha de código mostrada acima após o incremento da variável inicio mas antes da conclusão da linha de código. Nesse caso o valor de inicio é 10. Se neste instante outro thread executar o método getElement() do mesmo objeto ocorrerá uma exceção IndexOutOfBoundsException ao atingir a linha de código int temp = vetInt[inicio]; Se alterarmos a linha de código para inicio = (inicio+1)%TAM; evitaremos a exceção, mas não evitaremos o problema de retornar mais de uma vez o mesmo elemento. Por exemplo, se um thread for interrompido no mesmo local do caso anterior, outro thread pode obter o mesmo elemento, uma vez que os valores de inicio e total não foram alterados. Na verdade o número de situações problemáticas, mesmo para esse exemplo pequeno, é enorme e perderíamos muito tempo se tentássemos descreve-las em sua totalidade. Em alguns casos pode ser indesejável sincronizar todo um método, ou pode-se desejar adquirir o monitor de outro objeto, diferente daquele a quem pertence o método. Isto pode ser feito usando a palavra chave synchronized na frente de blocos. Este forma de sincronização é mostrada no exemplo IX.10.b. Neste modo de usar a palavra-chave synchronized é necessário indicar o objeto do qual tentara-se adquirir o monitor. Caso o monitor seja adquirido o bloco é executado, caso contrário o thread é suspenso até que possa adquirir o monitor. O monitor é liberado no final do bloco. J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 283 No exemplo IX.10.b o monitor usado na sincronização é o do próprio objeto do método, indicado pela palavra chave this. Qualquer outro objeto referenciável no contexto poderia ser usado. O que importa que os grupos de threads que possuem áreas de código que necessitam de exclusão mútua usem o mesmo objeto. No exemplo IX.10 não existe vantagem da forma de implementação a) sobre a forma de implementação b) ou vice-versa. Isso ocorre principalmente quando os métodos são muito pequenos ou não realizam computações muito complexas. No entanto, se o método for muito longo ou levar muito tempo para ser executado, sincronizar todo o método pode “travar” em demasia a execução da aplicação. Nesses casos, a sincronização somente das seções críticas é mais indicada. Outra vantagem da segunda forma de sincronização é a liberdade no uso de monitores qualquer objeto referenciável. Isto permite a implementação sincronizações mais complexas como veremos mais adiante. O exemplo IX.11 mostra como pode ser usado um objeto da classe FilaCirc. public class TestaFilaCirc extends Thread { private FilaCirc obj; private int tipo; public TestaFilaCirc (FilaCirc aObj, int aTipo) { obj = aObj; tipo = aTipo;} public void run() { for (;;) try { if (tipo==1){ int i = (int)(Math.random() * 1000); System.out.println("Elemento gerado:"+i); obj.addElement(i); } else System.out.println("Elemento obtido:"+obj.getElement()); } catch(Exception e) {System.out.println(e.getMessage());} } public static void main(String args[]) { FilaCirc obj = new FilaCirc(); TestaFilaCirc t1 = new TestaFilaCirc(obj,1); TestaFilaCirc t2 = new TestaFilaCirc(obj,2); t1.start(); t2.start(); } } Exemplo IX.11 – Uso da fila circular de inteiros. Um trecho possível da saída obtida na execução do programa do exemplo IX.11 seria o seguinte: ... Elemento obtido:154 J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 284 Elemento gerado:725 Fila vazia! Elemento gerado:801 Elemento obtido:725 Elemento gerado:204 Elemento obtido:801 ... É importante observar que o monitor em J ava por si só não implementa a exclusão mútua. Ele é apenas um recurso que pode ser usado pelo programador para implementar o acesso exclusivo à variáveis compartilhadas. Cabe ao programador a responsabilidade pela uso adequado deste recurso. Por exemplo se o programador esquecer de sincronizar um bloco ou método que necessita de exclusão mútua, de nada adianta ter sincronizado os outros métodos ou blocos. O thread que executar o trecho não sincronizado não tentará adquirir o monitor, e portanto de nada adianta os outros threads terem o adquirido. Outro ponto que é importante chamar a atenção é ter o cuidado de usar a palavra chave synchronized com muito cuidado. A sincronização custa muito caro em se tratando de ciclos de CPU. A chamada de um método sincronizado é por volta de 10 vezes mais lenta do que a chamada de um método não sincronizado. Por essa razão use sempre a seguinte regra: não sincronize o que não for preciso. Comunicação entre Threads: wait() e notify() O exemplo IX.10 não é um modelo de uma boa implementação de programa. O thread que adiciona elementos à fila tenta adicionar um elemento à cada volta do laço de iteração mesmo que a fila esteja cheia. Por outro lado, o thread que retira os elementos da fila tenta obter um elemento a cada volta do laço de iteração mesmo que a fila esteja vazia. Isto é um desperdício de tempo de processador e pode tornar o programa bastante ineficiente. Alguém poderia pensar em uma solução onde o thread testaria se a condição desejada para o processamento ocorre. Caso a condição não ocorra o thread poderia executar o método sleep() para ficar suspenso por algum tempo para depois testar novamente a condição. O thread procederia desta forma até que a condição fosse satisfeita. Este tipo de procedimento economizaria alguns ciclos de CPU, evitando que a tentativa incessante de executar o procedimento mesmo quando não há condições. O nome desta forma de ação, J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 285 onde o procedimento a cada intervalo de tempo pré-determinado testa se uma condição é satisfeita é chamado de espera ocupada (pooling ou busy wait). No entanto, existem alguns problemas com este tipo de abordagem. Primeiramente, apesar da economia de ciclos de CPU ainda existe a possibilidade de ineficiência, principalmente se o tempo não for bem ajustado. Se o tempo for muito curto ocorrerá vários testes inúteis. Se for muito longo, o thread ficará suspenso além do tempo necessário. Porém, mais grave que isto é que o método sleep() faz com que o thread libere o monitor. Portanto, se o trecho de código for uma região sincronizada, como é o caso do exemplo IX.10, de nada adiantará o thread ser suspenso. O thread que é capaz de realizar a computação que satisfaz a condição esperada pelo primeiro thread ficará impedido de entrar na região crítica, ocorrendo assim um deadlock: o thread que detém o monitor espera que a condição seja satisfeita e o thread que pode satisfazer a condição não pode prossegui porque não pode adquirir o monitor. O que precisamos é um tipo de comunicação entre threads que comunique que certas condições foram satisfeitas. Além disso, é preciso que, ao esperar por determinada condição, o thread libere o monitor. Esta forma de interação entre threads é obtido em J ava com o uso dos métodos de instância wait(), notify() e notifyAll(). Como vimos anteriormente, esses métodos pertencem à classe Object e não à classe Thread. Isto ocorre porque esses métodos atuam sobre os monitores, que são objetos relacionados a cada instância de uma classe J ava e não sobre os threads. Ao invocar o método wait() de um objeto o thread é suspenso e inserido em uma fila do monitor do objeto, permanecendo na fila até receber uma notificação. Cada monitor possui sua própria fila. Ao invocar o método notify() de um objeto, um thread que está na fila do monitor do objeto é notificado. Ao invocar o método notifyAll() de um objeto, todos os threads que estão na fila do monitor do objeto são notificados. A única exigência é que esses métodos sejam invocados em um thread que detenham a posse do monitor do objeto a que pertencem. Essa exigência faz sentido uma vez que eles sinalizam a threads que esperam na fila desses monitores. Devido a essa exigência a invocação desses métodos ocorre em métodos ou blocos sincronizados. O exemplo IX.12 mostra as formas mais comuns de chamadas desses métodos. Note que o thread deve possuir o monitor do objeto ao qual pertence o método. Por isso, nos exemplo IX.12 b e c, o objeto sincronizado no bloco é o mesmo que invoca os métodos notify() e notifyAll(). a) b) class X class Y J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 286 { ... public synchronized int ma() { ... // Espera uma condição while(!cond) wait(); // Prossegue com a // condição satisfeita ... } ... } { X ob; ... public int mb() { ... synchronized (ob) { // Notifica algum thread ob.notify(); ... } ... } c) class Z { X ob; ... public int mc() { ... synchronized (ob) { // Notifica todos os threads que esperam na fila // do monitor de ob ob.notifyAll(); ... } ... } Exemplo IX.12 – Exemplos de chamadas dos métodos wait(), notify() e notifyAll(). Outra observação importante é que o thread que invoca o método wait() o faz dentro de um laço sobre a condição de espera. Isto ocorre porque apesar de ter sido notificado isto não assegura que a condição está satisfeita. O thread pode ter sido notificado por outra razão ou entre a notificação e a retomada da execução do thread a condição pode ter sido novamente alterada. Uma vez notificado o thread não retoma imediatamente a execução. É preciso primeiro retomar a posse do monitor que no momento da notificação pertence ao thread que notificou. Mesmo após a liberação do monitor nada J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 287 garante que o thread notificado ganhe a posse do monitor. Outros threads podem ter solicitado a posse do monitor e terem preferência na sua obtenção. O exemplo IX.12 mostra apenas um esquema para uso dos métodos para notificação. O exemplo IX.13 é uma versão do exemplo IX.10a que usa os métodos de notificação para evitar problemas como a espera ocupada. O exemplo IX.11 pode ser usado sem modificações para testar essa versão. class FilaCirc { private final int TAM = 10; private int vetInt[]; private int inicio, total; public FilaCirc() { vetInt=new int[TAM]; inicio=0; total =0; } public synchronized void addElement(int v) throws Exception { while (total == TAM) wait(); vetInt[(inicio+total)%TAM] = v; total++; notify(); } public synchronized int getElement() throws Exception { while (total == 0 ) wait(); int temp = vetInt[inicio]; inicio = (++inicio)%TAM; total--; notify(); return temp; } } Exemplo IX.13 – Classe que implementa uma fila circular de inteiros com notificação. A necessidade de se testar a condição em loop pode ser observada na figura IX.6 que mostra a evolução da execução de três threads sobre objetos que compartilham uma instância da classe FilaCirc. O thread 3 executa o método addElement(), no entanto, em virtude da condição total==TAM é obrigado a invocar o método wait() e esperar uma notificação. O próximo J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 288 thread a assumir a CPU é o thread 1 que executa o método getElement(), que estabelece a condição total<TAM e executa um notify(). No entanto, o próximo thread a assumir a CPU é o thread 2 e não o thread 3. O thread 2 executa o método addElement(), o qual estabelece novamente a condição total==TAM. Quando o thread 3 assumi novamente a CPU, uma vez que foi notificado, testa a condição e invoca novamente o método wait() para esperar a condição favorável à execução. Caso não testasse a condição em um loop o thread 3 tentaria inserir um elemento em uma fila cheia. O método notify() não indica que evento ocorreu. No caso do exemplo IX.13 existem dois tipos de eventos (a fila não está cheia e a fila não está vazia), no entanto, podemos observar que não existe a possibilidade de um thread ser notificado em decorrência de um evento diferente do que está aguardando. Tempo Figura IX.6 – Uma possível sequência na execução de três threads. Porém, existem alguns casos mais complexos onde podem existir vários threads aguardando em um mesmo monitor mas esperando por evento diferentes. Neste caso podemos usar o notifyAll() para notificar todos os threads que esperam em um único monitor que um evento ocorreu. Cada thread, a medida que fosse escalado, testaria se ocorreu condição para a execução e em caso positivo prosseguiria na execução e em caso contrário voltaria a aguardar no monitor. O exemplo IX.14 mostra o código de um gerenciador de mensagens. Ele é responsável por receber mensagens destinadas à vários threads. As mensagens thread 3 thread 2 thread 1 Método: addElement() condição: total == TAM Método: getElement() Método: addElement() condição: total < TAM Executando Esperando CPU Esperando notificação J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 289 de cada thread são colocadas em uma fila implementada por um objeto da classe Vector. Cada fila é por sua vez colocada em uma tabela hash onde a chave é um nome associado ao thread a que as mensagens se destinam. As filas são criadas na primeira tentativa de acesso, tanto na leitura quanto no armazenamento. Não existe bloqueio devido à fila cheia, uma vez que as filas são implementadas por objetos da classe Vector que crescem conforme a necessidade. Portanto, o único evento que necessita ser notificado é a chegada de alguma mensagem. Como todos os threads aguardam sobre o mesmo monitor é usado o método notifyAll() para notificar todos os threads. import java.util.*; class GerenteMen { private Hashtable tamMen; public GerenteMen() {tamMen=new Hashtable(); } // Método para adicionar uma mensagem à fila de // um destinatário public synchronized void addMen(String dest, String men){ if (dest==null || men==null) return; Vector listaMen = (Vector) tamMen.get(dest); if (listaMen==null) listaMen = new Vector(); listaMen.addElement(men); tamMen.put(dest, listaMen); notifyAll(); } // Método para obtenção da mensagem public synchronized String getMen(String dest) throws Exception { if (dest==null) return null; Vector listaMen = (Vector) tamMen.get(dest); // Se não existe a fila para esse thread cria uma vazia if (listaMen==null) { listaMen = new Vector(); tamMen.put(dest, listaMen); } // A fila está vazia, portanto thread deve esperar // a chegada de mensagens while(listaMen.size()==0) wait(); String temp = (String) listaMen.firstElement(); J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 290 // A mensagem é removida da fila listaMen.removeElementAt(0); return temp; } } Exemplo IX.14 – Gerenciador de mensagens. O exemplo IX.15 mostra como pode ser usado o gerente de filas do exemplo IX.14. Devido o uso da classe ThreadGroup assim como vários de seus métodos, resolvemos numerar as linhas de código do exemplo IX.15 para melhor podermos explicar o seu funcionamento. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 class Receptor extends Thread { private GerenteMen ger; public Receptor(ThreadGroup tg, String nome, GerenteMen aGer) { super(tg,nome); ger = aGer; } public void run() { String nome = Thread.currentThread().getName(); for (;;) try { String men = ger.getMen(nome); if (men.equals("fim")) return; System.out.println(nome+">Mensagem recebida:"+men); } catch(Exception e) {System.out.println(e.getMessage());} } } class Gerador extends Thread { private GerenteMen ger; public Gerador(ThreadGroup tg, String nome, GerenteMen aGer) { super(tg,nome); ger = aGer; } public void run() { String nome = Thread.currentThread().getName(); ThreadGroup tg = Thread.currentThread().getThreadGroup(); Thread[] tl=null; for (int i=0;i<100;i++) { if (tl==null || tl.length!=tg.activeCount()) tl= new Thread[tg.activeCount()]; tg.enumerate(tl); int n = (int)(Math.random() * 1000)%tl.length; if (tl[n]!= Thread.currentThread()) { System.out.println(nome+">Mensagem enviada para "+ tl[n].getName()+":mensagem "+i); ger.addMen(tl[n].getName(),"mensagem "+i); } J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 291 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 } tl= new Thread[tg.activeCount()]; tg.enumerate(tl); for (int i=0;i<tl.length;i++) if (tl[i]!= Thread.currentThread()) ger.addMen(tl[i].getName(),"fim"); } } public class TestaGerenteMen { public static void main(String args[])throws Exception { GerenteMen ger = new GerenteMen(); ThreadGroup tg = new ThreadGroup("tg"); Receptor r1 = new Receptor(tg,"r_um",ger); Receptor r2 = new Receptor(tg,"r_dois",ger); Gerador g = new Gerador(tg,"g",ger); r1.start(); r2.start(); g.start(); } } Exemplo IX.15 – Uso do gerenciador de filas. Um objeto da classe ThreadGroup agrupa um conjunto de threads. Um ThreadGroup pode possuir como membros outros objetos da ThreadGroup formando assim uma árvore onde todos os grupos, exceto o primeiro possui um grupo pai. O objetivo de se agrupar os threads em conjuntos é facilitar a sua manipulação. No caso do exemplo IX.15 usaremos esse agrupamento para poder acessar cada thread. As linhas 1 a 18 definem a classe que será usada para criação de objetos receptores de mensagens. Na linha 3 é declarada a variável que irá referenciar um objeto do tipo GerenteMen. As linhas 4 a 8 contém o código do único construtor da classe. Ele recebe uma referência para o grupo de threads ao qual deve se associar, o nome que deve ser atribuído ao thread e a referência ao gerente de filas. Na linha 6 os primeiros dois parâmetros são passados ao construtor da superclasse. Na linha 7 a referência ao gerente de filas é atribuída à variável da instância. As linhas 9 a 17 contém o código do método run() que é o método de entrada do thread. Na linha 10 é invocado o método Thread.currentThread().getName(); para se obter o nome do thread corrente. O nome do thread é usado para referenciar a fila de mensagens do thread. Entre as linhas 11 e 16 é executado um laço infinito onde o thread recebe e imprime as mensagens recebidas. Na linha 14 o thread testa se a mensagem recebida é igual a “fim”. Neste caso o thread encerra sua execução. J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 292 As linhas 20 a 51 definem a classe que será usada para criação do objeto gerador de mensagens. Este exemplo foi projetado para lidar com apenas um thread gerador de mensagem. Modificações devem ser realizadas para tratar de aplicações com mais de um thread gerador de mensagens. Na linha 22 é declarada a variável que irá referenciar um objeto do tipo GerenteMen. As linhas 23 a 27 contém o código do único construtor da classe. Ele recebe uma referência para o grupo de threads ao qual deve se associar, o nome que deve ser atribuído ao thread e a referência ao gerente de filas. Na linha 25 os primeiros dois parâmetros são passados ao construtor da superclasse. Na linha 26 a referência ao gerente de filas é atribuída à variável da instância. As linhas 28 a 50 contém o código do método run() que é o método de entrada do thread. Na linha 29 é obtido o nome do thread corrente que será usado na impressão de mensagens. Na linha 30 o método Thread.currentThread().getThreadGroup(); obtém uma referência para o grupo de threads ao qual pertence o thread corrente. Na linha 31 é declarada uma variável que irá referenciar um vetor contendo referências a todos os threads ativos do grupo. Entre as linhas 32 e 44 é executado um laço com 100 iterações que produz e armazena as mensagens. Na linha 34 é realizado um teste para a verificação da necessidade de criar o vetor que irá conter as referências para os threads ativos. Ele deve ser criado a primeira vez e toda vez que a capacidade do vetor for diferente do número de threads ativos do grupo. O tamanho do vetor é determinado pelo método de instância activeCount() da classe ThreadGroup. A linha 36 contém o código tg.enumerate(tl); que atribui as referencias aos threads no vetor. O comando int n = (int)(Math.random()*1000)%tl.length; da linha 37 calcula um número que será usado para acessar o thread dentro do vetor de referências. O teste da linha 38 impede que seja enviada uma mensagem para o próprio gerador. Essas mensagens são descartadas. As linhas 40 a 42 tratam da impressão e envio da mensagem construída. J á fora da iteração, as linhas 45 a 49 tratam da do envio da mensagem “fim” para todos os threads receptores, o que fará com que encerrem sua execução. J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 293 As linhas 53 a 64 definem a classe que será usada como ponto de entrada da aplicação. Ela é responsável pela criação dos objetos e disparos dos threads. No exemplo, além do thread gerador apenas dois threads receptores são criados. É interessante notar que não é preciso indicara para o thread gerador as referências para os threads receptores. Elas são obtidas dinamicamente por meio do grupo de threads. Um trecho possível da saída obtida na execução do programa do exemplo IX.15 seria o seguinte: ... g>Mensagem enviada para r_dois:mensagem 88 r_um>Mensagem recebida:mensagem 87 g>Mensagem enviada para r_dois:mensagem 90 r_dois>Mensagem recebida:mensagem 88 g>Mensagem enviada para r_um:mensagem 91 r_dois>Mensagem recebida:mensagem 90 g>Mensagem enviada para r_um:mensagem 93 r_um>Mensagem recebida:mensagem 91 g>Mensagem enviada para r_um:mensagem 95 r_um>Mensagem recebida:mensagem 93 g>Mensagem enviada para r_um:mensagem 96 r_um>Mensagem recebida:mensagem 95 g>Mensagem enviada para r_um:mensagem 97 r_um>Mensagem recebida:mensagem 96 g>Mensagem enviada para r_dois:mensagem 99 r_um>Mensagem recebida:mensagem 97 r_dois>Mensagem recebida:mensagem 99 Pressione qualquer tecla para continuar . . . Otimizando a Programação Multithread Existe um problema óbvio com a abordagem do exemplo IX.14: a mensagem é dirigida a apenas um thread mas todos serão notificados, sobrecarregando o sistema, uma vez que todos os threads precisaram testar se a mensagem é destinada a eles. Para contornar esses problema é necessário vislumbrar uma forma de notificar apenas o thread destinatário. Essa solução pode ser obtida se cada thread esperar em um monitor de um objeto diferente. Não importa o tipo do objeto desde que seja referenciável pelo thread receptor e pelo thread que irá armazenar a mensagem. Um candidato natural é a fila de mensagem de cada thread. Existe uma fila para cada thread e o J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 294 thread que armazena a mensagem tem acesso a todas a filas por meio da tabela hash. O exemplo IX.16 mostra uma versão do exemplo IX.14 que utiliza esta técnica para criar uma aplicação multi-thread mais otimizada. import java.util.*; class GerenteMen { private Hashtable tamMen; public GerenteMen() {tamMen=new Hashtable(); } // Método para adicionar uma mensagem à fila de // um destinatário public void addMen(String dest, String men){ if (dest==null || men==null) return; Vector listaMen = (Vector) tamMen.get(dest); if (listaMen==null) listaMen = new Vector(); synchronized (listaMen) { listaMen.addElement(men); tamMen.put(dest, listaMen); listaMen.notify(); }; } // Método para obtenção da mensagem public String getMen(String dest) throws Exception { if (dest==null) return null; Vector listaMen = (Vector) tamMen.get(dest); // Se não existe a fila para esse thread cria uma vazia if (listaMen==null) { listaMen = new Vector(); tamMen.put(dest, listaMen); } // A fila está vazia, portanto thread deve esperar while(listaMen.size()==0) synchronized (listaMen) {listaMen.wait();} String temp = (String) listaMen.firstElement(); // A mensagem é removida da fila listaMen.removeElementAt(0); return temp; } } J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 295 Exemplo IX.16 – Gerenciador de mensagens otimizado. Note que os métodos wait() e notify() invocados pertencem à fila relacionada com cada thread. O exemplo IX.15 pode ser usado sem modificações para testar essa versão. Criando outros mecanismos de sincronização Existem várias propostas de primitivas de sincronização. Dentre as mais comuns podemos citar os semáforos, mutex, variáveis condicionais, monitores e encontros (rendevouz). Cada uma dessas primitivas é mais adequada a um determinado propósito. A implementação de monitores na linguagem J ava, juntamente com os métodos wait() e notify() que formam um tipo de variáveis condicionais podem ser combinadas para implementar muitas dessas outras primitivas, de modo a atender objetivos específicos. Para exemplificar essa possibilidade mostraremos como implementar um semáforo usando as primitivas de sincronização da linguagem J ava. Um semáforo é uma variável inteira sobre a qual pode-se realizar as seguintes operações: Operação Descrição inicializar Um valor inteiro maior ou igual a zero é atribuído ao semáforo. P Se o semáforo é maior que zero, o semáforo é decrementado. Caso contrário, o thread é suspenso até que o semáforo contenha um valor maior que zero. V Incrementa o semáforo e acorda os threads que estiverem bloqueados na fila de espera do semáforo. Tabela IX.6 –Operações sobre um semáforo. Semáforo é um mecanismo de sincronização muito utilizado quando existe a necessidade de comunicação entre dois ou mais processos, como no caso de sistemas do tipo produtor/consumidor. Por exemplo, suponha dois processos onde um coloca mensagens em buffer e outro retira as mensagens. Os processos podem usar dois semáforos para sincronizar o acesso ao buffer de mensagens: um para controlar a entrada na região crítica e outro para contar o número de mensagens. A figura IX.xx mostra os esquemas dos processos. A implementação dos semáforos em J ava pode ser visto no exemplo IX.xx e o uso dos semáforos J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 296 em uma situação como a ilustrada pela figura IX.xx pode ser visto no exemplo IX.xx. Produtor Consumidor s=1; n=0; início loop início loop Produz mensagem P(n) // Verifica se existe mensagens P(s) // Verifica se pode entrar na região // crítica P(s) // Verifica se pode entrar na região //crítica Coloca mensagem no buffer Retira mensagem V(n) // incrementa no, de mensagens V(s) // sai da região crítica V(s) // sai da região crítica Consome Mensagem fim loop fim loop Figura IX.xx – Comunicação entre processos usando semáforos. public class Semaforo { private int cont; public Semaforo(){cont =0;} public Semaforo(int i){cont =i;} public synchronized void P() throws InterruptedException { while(cont <=0) this.wait(); cont--; } public synchronized void V() { cont++; notifyAll(); } } Exemplo IX.XX – Implementação de um Semáforo. J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 297 import java.util.Vector; class Consumidor extends Thread { private Vector buff; private Semaforo s,n; public Consumidor(Vector aBuff, Semaforo as, Semaforo an) { super(); buff = aBuff; s = as; n = an; } public void run() { for (;;) try { n.p(); // Verifica se existe mensagens s.p(); // Verifica se pode entrar na região crítica String men = (String)buff.firstElement(); buff.removeElementAt(0); s.v(); if (men.equals("fim")) return; System.out.println("Mensagem recebida:"+men); } catch(Exception e) {System.out.println(e.getMessage());} } } class Produtor extends Thread { private Vector buff; private Semaforo s,n; public Produtor(Vector aBuff, Semaforo as, Semaforo an) { super(); buff = aBuff; s = as; n = an; } public void run() { for (int i=0;i<11;i++) { try { s.p();// Verifica se pode entrar na região crítica if (i<10) { buff.addElement(""+i); J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 298 System.out.println("Mensagem enviada: "+ i); } else buff.addElement("fim"); n.v(); // incrementa o número de mensagens s.v(); // abandona a região crítica Thread.yield(); } catch(Exception e) {System.out.println(e.getMessage());} } } } public class TestaSemaforo { public static void main(String args[])throws Exception { Vector buff = new Vector(); Semaforo s = new Semaforo(1); Semaforo n = new Semaforo(0); Produtor t1 = new Produtor(buff,s,n); Consumidor t2 = new Consumidor (buff,s,n); t1.start(); t2.start(); } } Exemplo IX.XX – Uso de semáforos por dois threads. J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 299 Capítulo XII - Animação Animação é exibir uma figura que muda com o tempo. No momento o suporte a animação da API central do J ava é limitado. Espera-se para o final de 1997 uma API que dê suporte avançado para animação. A animação pode ser controlado por um thread que é executado em um certo intervalo pré-definido. Exemplo básico de animação “in-place”. import java.awt.*; import java.applet.Applet; public class exemplo10 extends Applet implements Runnable { Image imgs[]; int ind=0; Thread t1; public void init() {imgs = initImgs(); t1=new Thread(this); t1.start();} public void paint(Graphics g) {g.draw.Image(imgs[ind],0,0,this);} public void start() { if (t1 == null) { t1 = new Thread(this); t1.start();} } public void stop() { if (t1 != null) {t1.stop();t1 = null;} } public void run() { while (true){ try {Thread.sleep(100};} catch(InterruptedException ex){} repaint(); ind=++ind % imgs.length; } } } Problemas com o exemplo J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 300 Ele não permite interromper a animação. O repaint() chama o update() default que repinta todo o fundo, o que causa “flicker” na animação. Existe um problema relacionado com a integridade da variável ind. A variável é atualizada pelo Thread t1 (run) e lida pelo Thread update (paint). permitir interromper a animação. boolean pause = false; public boolean mouseDown(Event e, int x, int y) { if (pause) {t1.resume();} else {t1.suspend();} pause = !pause; return true; } Eliminar o “flicker” Default public void update(Graphics g) { g.setColor(getBackground()); g.fillRect(0,0,width, height); g.setColor(getForeground()); paint(g); } Mudança public void update(Graphics g) {paint(g);} Eliminando conflitos public synchronized void paint(Graphics g) { g.draw.Image(imgs[ind],0,0,this); } public synchronized void mudaInd() J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 301 { ind = ++ind % imgs.length; } Copiando a figura public void drawStickFigure (Graphics g, int nX, int nY) { g.drawOval (nX + 10, nY + 20, 20, 40); g.drawLine (nX + 20, nY + 60, nX + 20, nY + 100); g.drawLine (nX + 10, nY + 70, nX + 30, nY + 70); g.drawLine (nX + 10, nY + 150, nX + 20, nY + 100); g.drawLine (nX + 20, nY + 100, nX + 30, nY + 150); } public void paint (Graphics g, Applet Parent) { if (bFirstTime) { bFirstTime = false; drawStickFigure (g, nX, nY); } else { g.copyArea (nX, nY, 35, 155, 5, 0);} } Double-buffer offScreenImage = createImage (nWidth, nHeight); offScreenGraphic = offScreenImage.getGraphics(); ... offScreenGraphic.setColor (Color.lightGray); offScreenGraphic.fillRect (0, 0,nWidth, nHeight); offScreenGraphic.setColor (Color.black); ... offScreenGraphic. drawOval(10,10,20,20); ... g.drawImage (offScreenImage, 0, 0, this); Ticker-Tape class TextScrolling extends AnimationObject { String pcMessage; // The message int nXPos; // The location of the message int nYPos; // The location of the message int nAppletWidth; // The width of the applet int nMessageWidth; // The width of the message public TextScrolling (String pcMsg, int nWide) J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 302 { pcMessage = pcMsg; nAppletWidth = nWide; nMessageWidth = -1; nYPos = -1; nXPos = 0; } public void paint (Graphics g, Applet parent) { if (nYPos < 0) { nYPos = (g.getFontMetrics ()).getHeight (); char pcChars []; pcChars = new char [pcMessage.length() + 2]; pcMessage.getChars(0, pcMessage.length()- 1, pcChars, 0); nMessageWidth = (g.getFontMetrics ()).charsWidth (pcChars, 0, pcMessage.length()); } g.drawString (pcMessage, nXPos, nYPos); } public void clockTick () { if (nMessageWidth < 0) return; // Move Right nXPos -= 10; if (nXPos < -nMessageWidth) nXPos = nAppletWidth - 10; } public void run() { int ndx = 0; Thread.currentThread().setPriority (Thread.MIN_PRIORITY); while (size().width > 0 && size().height > 0 && kicker != null) { AnimatedObjects[0].clockTick (); repaint(); try {Thread.sleep(nSpeed);} catch (InterruptedException e){} } } J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 303 Capítulo XIII - Programação em rede Diferentemente das linguagens mais populares atualmente, J ava foi projetada na era da Internet, e por isso mesmo ferramentas para comunicação dentro da Grande Rede foram incorporadas à linguagem desde a sua concepção. Classes para manipulação de URLs e dos protocolos que constituem a Internet fazem parte do núcleo básico da linguagem. Isto facilita muito a tarefa de desenvolver aplicações que para a Internet ou outras redes que fazem uso do mesmo conjunto de protocolos. Esta é uma das principais forças da linguagem J ava. De modo a entendermos como desenvolver aplicações em rede com J ava é importante o compreensão de alguns conceitos básicos sobre protocolos de comunicação. Conceitos Sobre Protocolos Usados na Internet Um protocolo de comunicação é um conjunto de formatos e regras usadas para transmitir informação. Computadores distintos devem obedecer estas regras e formatos de modo que se possam comunicar. Podemos encarar o protocolo como a definição de uma linguagem comum de modo a possibilitar a comunicação entre diferentes entidades. Visando diminuir a complexidade de implementação e uso do protocolo, ele é divido e organizado em forma de camadas de protocolos, onde a camada relativamente inferior na pilha a outra estabelece as regras para a camada superior sobre a utilização de seus serviços. As camadas inferiores fornecem serviços mais básicos de transmissão de dados, enquanto que as camadas superiores oferecem serviços de mais alto nível. Esta forma de organização hierárquica de protocolos é também chamada de pilha de protocolos. A principal pilha de protocolo sobre o qual a Internet se organiza é o TCP/IP. Por simplicidade chamaremos a pilha de protocolos TCP/IP apenas como protocolo TCP/IP ou TCP/IP. A figura XI.1 mostra como se organizam alguns dos protocolos que fazem parte do TCP/IP. J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 304 A camada física é a responsável pela transporte efetivo dos dados sobre o meio físico. A camada de rede é responsável pela interface lógica entre os computadores. A camada de transporte provê transferência de dados a nível de serviço, e a camada de aplicação provê comunicação a nível de processos ou aplicações. Exemplos de protocolos a nível de aplicação são: FTP, usado para transferência de arquivos; HTTP, usado para transmissão de páginas Web; TELNET provê capacidade de log-on remoto; SMTP provê serviços básicos de correio eletrônico; SNMP usado para gerência da rede; e MIME que é uma extensão do SMTP para lidar com mensagens com conteúdos diversos. Figura XI.1 –Alguns protocolos da pilha TCP/IP. No processo de transmissão de dados sobre uma rede TCP/IP os dados são divididos em grupos chamados de pacotes. Cada camada adiciona um alguns dados a mais no início de cada pacote para permitir que o pacote chegue ao destino. Os dados adicionados são chamados de headers. Ethernet, X.25, Token Ring Camada física IP Camada de rede UDP TCP Camada de Transporte SNMP FTP HTTP SMTP TELNET MIME Camada de Aplicação FTP - File Transfer Protocol SMTP - Simple Mail Transfer Protocol HTTP - Hypertext Transfer Protocol SNMP - Simple Network Management Protocol IP - Internet Protocol TCP - Transmission Control Protocol MIME - Multi-purpose Internet Mail Extensions UDP - User DatagramProtocol J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 305 Figura XI.2 –Headers adicionados a cada camada de protocolo. Na camada de transporte existem dois protocolos que fazem uso do protocolo IP: o protocolo TCP/IP e o UDP. TCP O protocolo TCP é um protocolo orientado a conexão que provê um fluxo confiável de dados entre dois computadores. Por protocolo orientado a conexão queremos dizer que é estabelecido um canal de comunicação ponto-a- ponto onde os dados podem trafegar em ambas as direções. O TCP garante que os dados enviados em uma ponta cheguem ao destino, na mesma ordem que foram enviados. Caso contrário, um erro é reportado. Protocolos como HTTP, FTP e TELNET exigem um canal de comunicação confiável e a ordem de recebimento dos dados é fundamental para o sucesso dessas aplicações. UDP No entanto, nem todas as aplicações necessitam destas características do protocolo TCP e o processamento adicional exigido para garantir a confiabilidade e a ordenação dos dados podem inviabilizá-las. Para esses casos existe o protocolo de transporte UDP. UDP é um protocolo para envio de Dados TCP Header Dados Dados Aplicação Dados TCP Header Transporte TCP Header IP Header Rede IP Header Ethernet Header Física J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 306 pacotes independentes de dados, chamados de datagramas, de um computador a outro, sem garantias sobre a chegada dos pacotes. O protocolo UDP não é orientado a conexão. IDENTIFICAÇÃO DE HOSTS (Número IP) Cada computador conectado a uma rede TCP/IP é chamado de Host e é identificado por um único número de 32 bits, denominado de número IP. O número IP é representado por quatro grupos de 8 bits, limitando desta forma o valor numérico de cada grupo ao valor máximo de 255. Um exemplo de número IP é 200.65.18.70. Uma vez que é muito difícil lembrar e atribuir significado a números existe uma forma alternativa de identificar os computadores da rede por meio de nomes. Um ou mais computadores da rede fazem o papel de resolvedores de nomes, mantendo bases de dados que associam o nomes do Hosts à seus números IP. Desta forma é possível um computador comunicar com outro computador por meio do nome lógico e não por meio do número IP. A figura XI.3 ilustra a comunicação entre dois computadores. A Internet é representada como uma nuvem devido a complexidade da rede. Figura XI.3 –Representação da comunicação entre dois computadores. O representação dos nomes dos computadores na Internet é feita por substrings separadas por ‘.’ e obedecem uma regra de nomeação que define que o primeiro substring representa o nome da máquina, e os restantes representa o domínio onde está inserida a máquina. Um domínio é um agrupamento de computadores que pertencem a uma instituição, órgão, empresa, ou uma organização qualquer. Assim, no exemplo da figura XI.3 o computador meucomp pertence ao domínio com.br. No Brasil a FAPESP (Fundação de Host meucomp.com.br IP: 200.18.46.12 Host outrocomp.edu IP: 205.50.30.75 J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 307 Amparo à Pesquisa do Estado de São Paulo) responsável pela gerência dos nomes dos domínios na Internet. Identificação de Processos (Portas) A comunicação entre dois processos em uma rede TCP/IP é assimétrica, no sentido um processo faz o papel de servidor, oferecendo um serviço e outro faz o papel de cliente do serviço. Em um único Host vários processos podem estar fazendo o papel de servidor, oferecendo serviços através de um único meio físico. Portanto, é preciso uma forma de identificar as servidores em um mesmo Host. Isto é feito por meio da associação de um número inteiro, chamado de porta, ao processo servidor. Essa associação é feita pelo processo assim que é carregado, por meio de uma chamada ao sistema. O número da porta pode variar de 1 a 65535, no entanto os números de 1 a 1023 são reservados para serviços conhecidos como FTP e HTTP. O programador não deve usar estas portas a não ser que esteja implementando algum desses serviços. Nos ambientes Unix as portas que vão de 6000 a 6999 são usadas pelo gerenciador de Interfaces X Windows e 2000 a 2999 por outros serviços, como o NFS. Nestes ambientes, estas faixas de números de portas também devem ser evitadas. A tabela XI.1 mostra o número da porta de alguns dos serviços mais conhecidos. Protocolo Porta HTTP 80 echo 7 FTP 20,21 SMTP 25 Finger 79 Daytime 13 pop3 110 Tabela XI.1 – Número das portas dos principais serviços. Uma vez associado a uma porta o serviço pode ser acessado por uma aplicação cliente, bastando para isso que ela indique o nome do Host e o número da porta ao se comunicar. J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 308 Programação em Rede com Java O pacote java.net contém as classes e interfaces usadas para programação de sistemas em rede com J ava. As classes podem ser enquadradas em três categorias: 1. Classes para comunicação básica em rede. Tratam da comunicação em baixo nível entre aplicações. Outros protocolos podem ser implementados usando como base esta comunicação básica. 2. Classes para comunicação dentro da Web. Estas classes provêem facilidades para acessar conteúdos por meio de URLs. 3. Classes para tratamento dos formatos estendidos da Web. Utilizadas para tratar novos protocolos e tipos MIME. Comunicação Básica Entre Aplicações As classes Socket, ServerSocket, DatagramSocket, DatagramPacket e InetAddress, fornecem os métodos necessários para a comunicação básica entre dois processos. A tabela XI.2 descreve sucintamente cada uma das classes. Classe Descrição Socket Provê um socket cliente para comunicação orientada à conexão via protocolo TCP. ServerSocket Provê um socket servidor para comunicação orientada à conexão via protocolo TCP. DatagramSocket Provê um socket UDP para comunicação não orientada à conexão. DatagramPacket Representa um datagrama que pode ser enviado usando DatagramSocket. InetAddress Representa os dados de um Host (Nome e endereço IP) Tabela XI.2 – Classes para comunicação básica. As classes Socket e ServerSocket são utilizadas para comunicação orientada à conexão, enquanto que as classes DatagramSocket e DatagramPacket são utilizadas para comunicação não orientada à conexão. J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 309 Comunicação orientada à conexão (cliente) Para comunicar via protocolo TCP é preciso que a aplicação cliente crie um objeto Socket. É preciso passar o nome ou número IP do Host e o número da porta onde o servidor está esperando as solicitações de serviço. A Classe Socket possui os métodos getInputStream() e getOutputStream(), que são usados para obter Streams associados ao Socket. Deste modo, a transmissão de dados via Socket é idêntica à leitura e escrita em arquivos via Streams. O exemplo XI.1 mostra o código de um cliente que acessa um servidor de Daytime. O serviço de Daytime é disponibilizado nas plataformas UNIX e é acessado via porta 13. Sua função enviar, aos processos clientes, uma linha de texto contendo a data e a hora corrente . import java.io.*; import java.net.*; public class ClienteData { public static void main(String[] args) throws IOException { Socket socket = null; BufferedReader in = null; try { socket = new Socket(args[0], 13); in = new BufferedReader(new InputStreamReader( socket.getInputStream())); }catch (UnknownHostException e) {System.err.println("Não achou o host:"+args[0]); System.exit(1);} catch (IOException e) {System.err.println("Erro de I/O."+e.getMessage()); System.exit(1);} System.out.println("Data: " + in.readLine()); } in.close(); socket.close(); } } Exemplo XI.1 – Cliente para o serviço de Daytime. No programa do exemplo XI.1 o usuário precisa passar o nome do Host servidor pela linha de comando. Você pode testar este programa mesmo que seu J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 310 computador não esteja conectado em uma rede, desde que o protocolo TCP/IP esteja instalado. É que você pode passar como parâmetro o nome do seu computador ou, alternativamente, o nome localhost, ainda o número IP 127.0.0.0. O nome localhost e o número IP127.0.0.0 sempre identificam o computador local. Note que após a criação do objeto Socket, o método getInputStream() é chamado e o objeto retornado é envolvido por um objeto BufferedReader de modo a comunicar dados via rede da mesma forma que é realizada uma operação de E/S. Ao se terminar a operação é preciso fechar tanto a instância BufferedReader quanto o objeto Socket. A instância da Classe de E/S sempre deve ser fechada primeiro. Os Hosts que utilizam as variações do sistema operacional UNIX possuem o serviço de Daytime, no entanto, outros sistemas operacionais podem não implementar este serviço. O programador pode resolver este problema implentando ele mesmo um servidor de Daytime. A próxima seção mostrará como isto pode ser feito. Comunicação orientada à conexão (servidor) Para criar um processo servidor é preciso associá-lo à uma porta. Isto é feito ao se criar uma instância da classe ServerSocket. Se estamos criando um novo serviço é preciso associar a uma porta com valor maior que 1023. Se estamos implementando um serviço já estabelecido é preciso obedecer as especificações definidas para o serviço. O exemplo XI.2 mostra o código de um servidor do serviço Daytime. Como não queremos substituir o serviço padrão de Daytime utilizaremos o número de porta 5013 no lugar do número 13. Para se testar este servidor com o programa cliente do exemplo X.1 é preciso alterar o número da porta no código do cliente. J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 311 import java.util.*; import java.io.*; import java.net.*; public class ServerData { public static void main(String[] args) throws IOException { SeverSocket ssocket = null; Socket socket = null; BufferedWriter out = null; ssocket = new SeverSocket (5013,5); for(;;) { socket = ssocket.accept(); out = new BufferedWriter (new OuputStreamWriter ( socket.getOuputStream())); out.write((new Date()).toString()+”\n”); } out.close(); socket.close(); } } Exemplo XI.2 – Servidor de Daytime. Ao criar uma instância da classe ServerSocket o programador pode indicar o tamanho da fila de solicitações de conexão. As conexões são colocadas na fila até que o servidor possa atende-las. Se chegar alguma conexão e não houver espaço na fila a conexão será recusada. No nosso exemplo passamos como parâmetro o valor 5. Após criar o objeto ServerSocket o servidor deve indicar que está disposto a receber conexões. Isto é feito por meio da execução do método accept() do objeto ServerSocket. Ao executar este método o processo passa para o estado bloqueado até que alguma conexão seja solicitada. Quando a conexão é solicitada o método accept() retorna um objeto Socket igual ao do processo cliente, que será usado para obter os Streams onde será efetuada a comunicação. O Stream obtido do objeto Socket é encapsulado em objeto BufferedWriter que será encarregado de enviar a cadeia de caracteres contendo a data e a hora para o cliente. O Servidor em implementa um laço infinito, recebendo e tratando solicitações de serviços. J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 312 O servidor implementado acima trata uma solicitação de serviço por vez. Isto pode ser problemático quando existem vários clientes solicitando serviços ao mesmo tempo e o servidor leva um longo tempo tratando cada solicitação. Nesta situação o cliente pode ficar um longo tempo a espera de atendimento. Isto pode ser remediado por meio da implementação de servidores Multithreaded. Apesar do serviço implementado pelo exemplo XI.2 não exigir um servidor Multithreaded, uma vez que o cliente é atendido rapidamente, o servidor Daytime foi alterado para exemplificar a implementação de um servidor Multithreaded. O exemplo XI.3 mostra o código da versão Multithreaded do servidor. import java.util.*; import java.net.*; import java.io.*; public class ServerData { public static void main(String args[]) { ServerSocket ssocket=null; try { ssocket = new ServerSocket(pt); } catch(Exception e) {System.err.println(e); System.exit(1);} while(true) { try { Socket socket = ssocket.accept(); (new serversec(socket)).start(); } catch(Exception e) {System.err.println(e);} } } } class serversec extends Thread { Socket socket; public serversec(Socket aSocket) {socket = aSocket;} public void run() { J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 313 try { BufferedWriter out = new BufferedWriter (new OuputStreamWriter ( socket.getOuputStream())); out.write((new Date()).toString()+”\n”); out.flush(); out.close(); socket.close(); } catch(Exception e) {System.err.println(e);} } } Exemplo XI.3 – Servidor de Daytime Multithreaded. No exemplo XI.3, ao receber uma conexão o servidor cria um Thread para atender o cliente e fica disponível para receber novas solicitações. Esse exemplo pode ser usado como esqueleto para desenvolvimento de servidores mais complexos, porém, neste caso é necessário limitar o número de Threads que podem ser criados. Comunicação Sem Conexão (UDP) Como já dissemos na seção anterior, nem sempre é necessário um canal de comunicação confiável entre duas aplicações. Para estes casos existe o protocolo UDP, que provê uma forma de comunicação onde a aplicação envia pacotes de dados, chamados de datagramas, para outra aplicação, sem garantias se e quando a mensagem vai chegar, nem se o conteúdo está preservado. As portas do protocolo UDP obedecem a mesma distribuição das TCP porém são distintas uma da outra, de modo que o programador pode associar uma porta TCP de um determinado número à uma aplicação em um host e o mesmo número de porta UDP a outra aplicação no mesmo host. As classes DatagramPacket e DatagramSocket contém os métodos necessários para realizar este tipo de comunicação. Para ilustrar o uso destas classes modificaremos os exemplos XI.1 e XI.2 para implementarmos uma aplicação Cliente/Servidor Daytime que usa o protocolo UDP. O Exemplo XI.4 mostra o código fonte do cliente e o Exemplo XI.5 mostra o código do servidor. J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 314 Analisando o código da aplicação cliente podemos notar que é necessário criar um objeto da classe DatagramPacket para representar os Datagrama onde os dados são armazenados. No nosso exemplo colocamos também os dados do Host e a porta, porém estes dados poderiam ser omitidos na construção do Datagrama e serem passados somente no envio/recepção do pacote ou na construção do DatagramSocket. O Datagrama deverá armazenar os dados enviados pelo servidor e foi dimensionado para conter 64 bytes. O método receive() do objeto DatagramSocket aguarda o recebimento do pacote, tendo como argumento o objeto da classe DatagramPacket. Após o recebimento do pacote os dados são convertidos para String e exibidos na saída padrão. O servidor, diferentemente do servidor TCP, precisa saber a quem deve enviar os pacotes, uma vez que não é estabelecida uma conexão. Podíamos simplesmente passar o nome do host pela linha de comando, mas resolvemos adotar uma estratégia de Broadcasting. Nesta abordagem os datagramas são enviados a vários computadores e não apenas um. Isto é feito passando-se como argumento para o método InetAddress.getByName() o endereço de Broadcast da rede. import java.io.*; import java.net.*; public class DataClienteUDP { public static void main(String args[]) throws Exception { if (args.length != 1) { System.err.println("Uso: java DataClienteUDP host"); System.exit(1); } byte [] buff = new byte[64]; DatagramSocket ds = new DatagramSocket(); DatagramPacket dp = new DatagramPacket(buff, buff.length, InetAddress.getByName(args[0]),5013); ds.receive(dp); String s = new String(dp.getData()); System.out.println("Data e hora recebida de”+dp.getAddress()+ " : "+s); } } J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 315 Exemplo XI.4 – Cliente Daytime UDP. import java.io.*; import java.net.*; public class DataServerUDP { public static void main(String args[]) throws Exception { DatagramSocket ds; DatagramPacket dp; InetAddress addr = InetAddress.getByName(“255.255.255.0”); ds = new DatagramSocket(); byte [] buff; for (;;) { Thread.sleep(1000); String s = (new Date()).toString(); buff = s.getBytes(); dp = new DatagramPacket(buff, buff.length, addr, 5013); ds.send(dp); } } } Exemplo XI.5 – Servidor Daytime UDP. O tipo de endereço de Broadcast depende da classe de endereçamento IP da rede. O endereço usado no exemplo IX.5 funciona para a classe de endereçamento C. Para descobrir que tipo de classe pertence a rede onde está seu computador olhe o primeiro byte do endereço IP de sua máquina e verifique junto a tabela XI.3. Primeiro byte do endereço IP Classe Endereço de Broadcast 0 a 126 A 255.0.0.0 128 a 191 B 255.255.0.0 192 a 223 C 255.255.255.0 Tabela XI.3 – Classes para comunicação básica. J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 316 O envio dos dados é feito pelo método send() do objeto DatagramSocket, tendo como argumento um objeto da classe DatagramPacket. Note que não é necessário um “Socket servidor” uma vez que o servidor envia os pacotes independentemente de existir clientes solicitando-os. Comunicação por meio de URL URLs Um URL (Uniform Resource Locator) é uma referência (um endereço) a um recurso na Internet. O URL é dividido em partes, sendo que apenas a primeira parte é obrigatória. A maioria das URLs é dividida em três partes: Informação sobre o Host http://dpi.ufv.br/professores.html A parte do protocolo define o protocolo que deve ser usado para acessar o recurso. Os protocolos mais comuns são FTP, HTTP e file, este último indicando que o recurso se encontra no sistema de arquivos local. O protocolo é seguido do caractere “:”. A parte com informação sobre o Host fornece a informação necessária para acessar o Host onde está localizado o recurso. Esta parte é omitida caso o recurso esteja no sistema de arquivos local. A informação sobre o Host é precedida por duas barras (“//”), no caso de aplicação na Internet e apenas por uma barra (“/”), caso contrário. A informação sobre o Host também pode ser dividida em três partes: a) o nome do domínio do Host; b) o nome e senha do usuário para login; e c) o número da porta caso necessário, após o nome do host, precedida pelo caractere “:”. Exemplos: http://java.sun.com:80/doc/tutorial.html http://infax.com.br."claudio"."1234"/base/vendas 1 11 12 22 23 33 3 6 66 67 77 78 88 8 123 endereço do recurso no Host Protocolo J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 317 A última parte de uma URL representa o caminho até o recurso no sistema de arquivos do Host. Esta seção é separada da seção anterior por uma barra simples (“/”). Manipulando URLs em Java A linguagem J ava fornece as seguintes classes para manipulação de URLs: Classe Descrição URL Representa um URL URLConnection Classe abstrata que representa uma conexão entre uma aplicação e um URL. Instâncias desta classe podem ser usadas para ler e escrever no recurso referenciado pela URL. URLEncoder Usada para lidar com o formato MIME. Tabela XI.3 – Classes para manipulação de URLs. Um objeto URL é criado passando como parâmetro para o construtor o URL na forma de String: URL dpi = new URL("http://www.dpi.ufv.br"); Um objeto URL pode ser construído passando como parâmetro outro URL para servir de endereço base. Por exemplo URL profs = new URL (dpi,”professores.html”); Isto tem o mesmo efeito que gerar uma instância da classe URL com primeiro construtor, passando como parâmetro o endereço: http://www.dpi.ufv.br/professores.html Os construtores geram a exceção MalformedURLException, se o URL é inválido. Portanto, o programador deve providenciar o código para a captura e tratamento desta exceção. J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 318 De posse de um objeto URL é possível obter um objeto InputStream para ler os dados endereçados pelo URL, utilizando o método openStream() do objeto URL. O Exemplo XI.4 mostra o código de um programa que pode ser usado para listar na saída padrão o conteúdo de um URL passado pela linha de comando. import java.net.*; import java.io.*; public class LeURL { public static void main(String[] args) throws Exception { if (args.length < 1) { System.err.println("uso: java LeURL <URL>..."); System.exit(1); } URL url = new URL(args[0]); BufferedReader in = new BufferedReader( new InputStreamReader(url.openStream())); String linha; while ((linha = in.readLine()) != null) System.out.println(linha); in.close(); } } Exemplo XI.4 – Leitor de URL. Comunicando por meio de URLConnection O programador pode utilizar o método openConnection() do objeto URL para obter uma conexão entre a aplicação é o recurso referenciado pelo o URL. O método openConnection() retorna um objeto URLConnection, que permite que a aplicação escreva e leia através da conexão. Alguns URLs, como os conectados à scripts CGI 5 (Common-Gateway Interface), permitem que 5 Common-Gateway Interface (CGI) é um mecanismo para gerar páginas Web dinamicamente. Os dados são obtidos de fórmulários HTML e submetidos a um programa binário no servidor J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 319 aplicação cliente escreva informação no URL. A saída do programa CGI pode ser interceptada pelo programa J ava de modo que possa ser exibida para o usuário. Esta forma de comunicação com scripts CGI é melhor do que por meio de formulários HTML, uma vez que o usuário não precisa navegar entre páginas para visualizar os formulários e a resposta retornada. O Applet J ava se encarrega de enviar os dados e exibir o resultado na mesma página. Para ilustrar esta forma de comunicação mostraremos um programa que submete uma cadeia de caracteres a um URL para que seja invertido e enviado de volta. O script CGI utilizado, escrito em Perl é exibido no exemplo XI.5 e foi escrito por Hassan Schroeder, um membro da equipe de desenvolvimento da linguagem J ava. Este CGI pode ser acessado no URL http://java.sun.com/cgi- bin/backwards. #!/opt/internet/bin/perl read(STDIN, $buffer, $ENV{'CONTENT_LENGTH'}); @pairs = split(/&/, $buffer); foreach $pair (@pairs) { ($name, $value) = split(/=/, $pair); $value =~ tr/+/ /; $value =~ s/%([a-fA-F0-9][a-fA-F0-9])/pack("C", hex($1))/eg; # Stop people from using subshells to execute commands $value =~ s/~!/ ~!/g; $FORM{$name} = $value; } print "Content-type: text/plain\n\n"; print "$FORM{'string'} reversed is: "; $foo=reverse($FORM{'string'}); print "$foo\n"; exit 0; Exemplo XI.5 – Script backwards para inverter uma cadeia de caracteres. O exemplo XI.6 contém o programa que envia uma cadeia de caracteres ao URL e recebe de volta outra cadeia de caracteres que é a inversão da primeira. A ação necessária é codificar a cadeia de caracteres por meio do método estático encode() da classe URLEncoder: que gera a resposta na forma de uma página Web. O programa pode ser escrito em uma variadade delinguagens, como Perl e C. J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 320 String string = URLEncoder.encode(args[0]); Isto é necessário porque a string enviada a um URL necessita de uma codificação particular, como por exemplo, os espaços em branco são substituídos pelo caractere “+”, os campos são separados pelo caractere “&”e valor do campo é separado do nome do campo pelo caracteres “=”. Em seguida o programa cria um objeto URL relacionado com o endereço onde se encontra o script, abre uma conexão e define que será usada para entrada e saída. URL url = new URL("http://java.sun.com/cgi- bin/backwards"); URLConnection c = url.openConnection(); c.setDoOutput(true); Neste momento o programa está preparado para trabalhar com o URL como se fosse um Stream. O Stream para escrever no URL é obtido do objeto URLConnection por meio do método getOutputStream() e o Stream para ler do URL é obtido do objeto URLConnection por meio do método getInputStream(). Primeiro o programa submete a cadeia de caracteres a ser invertida precedida pelo nome do campo e pelo caractere “=”: out.println("string=" + string); import java.io.*; import java.net.*; public class Inverte { public static void main(String[] args) throws Exception { if (args.length != 1) { System.err.println("Uso: java Inverte string"); System.exit(1); } String string = URLEncoder.encode(args[0]); URL url = new URL("http://java.sun.com/cgi- J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 321 bin/backwards"); URLConnection c = url.openConnection(); c.setDoOutput(true); PrintWriter out = new PrintWriter(c.getOutputStream()); out.println("string=" + string); out.close(); BufferedReader in = new BufferedReader(new InputStreamReader( c.getInputStream())); String retorno; while ((retorno = in.readLine()) != null) System.out.println(retorno); in.close(); } } Exemplo XI.6 – Programa que escreve em um URL. Após isso o Stream de saída é fechado e o Stream de entrada é aberto. A cadeia de caracteres invertida é lida e exibida no dispositivo de saída padrão. No exemplo apresentado o script CGI usa o POST METHOD para ler dados enviados pelo cliente. Alguns scripts CGI usam o GET METHOD para ler dados do cliente, no entanto, este último está ficando rapidamente obsoleto devido a maior versatilidade do primeiro. J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 322 Capítulo XIV – Computaç ão Distribuída (RMI) A RMI (Invocação de métodos remotos) é uma tecnologia que coloca a programação com rede em um nível mais alto. RMI torna possível que objetos distribuídos em uma rede se comuniquem de forma transparente para o programador utilizando chamadas de procedimentos remotos. O principal objetivo da tecnologia RMI é permitir que programadores desenvolvam programas distribuídos utilizando a mesma sintaxe e semântica de programas J ava convencionais. Antes da introdução da RMI no mundo J ava, no J DK 1.1, para fazer com que dois objetos em máquinas diferentes se comunicassem o programador deveria definir um protocolo de comunicação e escrever código utilizando socket para implementar este protocolo. Com RMI a maior parte do trabalho quem realiza é a máquina virtual J ava. Existem outras tecnologias, como CORBA (Common Object Request Brocker Architecture), que também tem como objetivo fazer com que objetos distribuídos em uma rede se comuniquem. J ava também tem suporte a CORBA, mas para projetos em um ambiente J ava puro, a RMI é consideravelmente mais simples que a CORBA. Criando nossa agenda distribuída De modo exemplificar o uso de RMI modificaremos a agenda distribuída fazendo uso desta tecnologia. Os passos a serem seguidos são: 1. Escrever e compilar a interface que descreve a como serão as chamadas do cliente ao servidor; 2. Escrever e compilar a classe que implementa a interface do passo 1 (objeto servidor); 3. Gerar Stubs e Skeleton do objeto distribuído; 4. Desenvolver o código que disponibiliza o objeto; 5. Escrever e compilar o código para o cliente RMI; e 6. Testar. Implementar interface do objeto remoto J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 323 O primeiro passo quando se deseja criar um objeto Remoto com RMI é implementar uma interface para este Objeto. Essa interface deve herdar a interface Remote. É através dessa interface Remote, que não tem métodos, que a máquina virtual J ava sabe qual objeto pode ser disponibilizado para acesso remoto. Abaixo temos o exemplo da interface do nosso objeto: 1 2 3 4 5 6 7 8 public interface Agenda extends java.rmi.Remote{ public void inserir(Pessoa p) throws java.rmi.RemoteException; public Pessoa getPessoa(String nome) throws java.rmi.RemoteException; public java.util.Enumeration getPessoas() throws java.rmi.RemoteException; } Exemplo XX.XX – Interface do objeto remoto. A interface Remote deve ser herdada pela nossa interface Agenda. J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 324 Capítulo XV - Acesso a B anco de Dados No dias de hoje, uma linguagem sem recursos para acesso a sistemas de Banco de Dados está fadada ao fracasso. Pensando nisso a Sun incluiu como parte do núcleo de bibliotecas de classes da linguagem J ava uma API com o objetivo de preencher esta função, chamada de J DBC (segundo a Sun J DBC é apenas um acronismo, no entanto, muitas pessoas acreditam que é uma sigla para J ava Database Connectivity). J DBC é uma API baseada no X/Open SQL Call Level Interface, tendo sido desenvolvida originalmente como um pacote separado, porém a partir do J DK1.1 passou a fazer parte do núcleo básico de pacotes. Utilizando a API J DBC é possível conectar um programa J ava com servidores de Banco de Dados e executar comandos SQL (Structure Query Language). Sendo uma API independente do Sistema Gerenciador de Banco de Dados, não é necessário escrever uma aplicação para acessar um Banco de Dados Oracle, outra para uma Base de Dados Sybase, outra para o DB2, e assim por diante. A idéia de se usar uma camada intermediária entre o Banco de Dados e a aplicação, com o objetivo de isolá-la das particularidades do SGBD, não é nova. O exemplo mais popular deste enfoque é a API ODBC (Open DataBase Connectivity), proposta pela Microsoft. O leitor pode estar se perguntando porque a Sun resolveu propor mais uma API em vez de adotar a ODBC. Existem vários motivos, porém o principal é que a API ODBC simplesmente não é adequada para a linguagem J ava. Isto ocorre porque ODBC foi desenvolvida para ser usada na linguagem C e é baseada fortemente no uso de ponteiros, estrutura que não existe em J ava. Modelos de Acesso a Servidores O modelo mais simples de aplicação Cliente/Servidor é o chamado de modelo de duas camadas, onde a aplicação acessa diretamente o Banco de Dados. A figura XIII.1 mostra o esquema para uma aplicação que acessa um Banco de Dados usando o modelo de duas camadas. J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 325 Figura XIII.1 – Modelo de acesso a Banco de Dados em duas camadas. Figura XIII.2 – Modelo de acesso a Banco de Dados em três camadas. Tipos de Drivers JDBC Os drivers J DBC devem suportar o nível de entrada do padrão ANSI SQL-2. No momento, os drivers J DBC existentes se encaixam em um dos quatro tipos abaixo: 1. Ponte JDBC-ODBC com driver ODBC – o driver J DBC acessa o banco de dados via drivers ODBC. Como ODBC é um código binário e, em alguns casos, compõe o código do cliente, é necessário instalar em cada máquina cliente que usa o driver. Essa é uma solução adequada somente para uma Aplicação J ava DBMS Aplicação J ava ou Applet DBMS Aplicação Servidora J ava J DBC H HT TT TP P, , R RM MI I, , C CO OR RB BA A J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 326 interna corporativa, ou em aplicações que adotam o modelo de três camadas sendo a camada intermediária um servidor J ava. SGBD1 Cliente Ponte SGBD2 J ava J DBC-ODBC SGBD3 2. Driver Java parcial e Api Nativa – neste caso as chamadas J DBC são convertidas para as chamadas às APIs nativas do SGBD. Como o driver possui uma parte em código binário é necessário instalar algum código na máquina cliente, como é feito nos drivers do tipo 1. Protocolo do SGBD Cliente J DBC J ava & SGBD J ava Código Binário 3. Driver puro Java e protocolo de rede – neste caso as chamadas J DBC são convertidas para um protocolo de rede independente do SGBD que é depois traduzido para as chamadas às APIs nativas do SGBD por um servidor. Esta é uma arquitetura em três camadas, onde o servidor middleware é capaz de conectar seus clientes J ava puros com vários SGBDs. Esta solução permite o desenvolvimento de clientes 100% J ava, tendo como consequência a não necessidade de instalação de qualquer código na máquina cliente. SGBD1 Cliente Servidor de SGBD2 J ava acesso SGBD3 4. Driver Java Puro e protocolo nativo - neste caso as chamadas J DBC são convertidas para as chamadas às APIs nativas do SGBD pelo driver, que foi escrito totalmente em J ava. Protocolo do SGBD Cliente J DBC J ava SGBD J ava (100% J ava) J DBC DRIVER (100% Java) J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 327 Atualmente existe uma maior disponibilidade dos drivers tipo 1 e 2, mas a tendência é que estes desapareçam, sendo substituídos pelos drivers do tipo 3 e 4. Obtendo os Drivers JDBC Informações sobre como obter drivers J DBC podem ser obtidas no site http://www.javasoft.com/products/jdbc. Outra alternativa é acessar as páginas dos fabricantes de SGBD, para verificar se existe driver disponível. Preparando um Banco de Dados Os exemplos deste livro usam a ponte J DBC-ODBC para conectar com o Banco de Dados. Isto facilita para os usuários que possuem um gerenciador Banco de Dados pessoal como o Access, Paradox, e outros semelhantes. Além disso, como driver J DBC-ODBC está incorporado ao SDK, o usuário não necessita procurar um driver para testar os exemplos. O lado negativo desta abordagem está na necessidade de configurar o ODBC e no fato de que as aplicações remotas deverão ser desenvolvidas em três camadas. No entanto, nada impede que o leitor use outro driver para rodar os exemplos, bastando para isso alterar a chamada da carga do driver. Primeiramente é necessário criar uma base de dados em algum SGBD. Nos exemplos deste livro será usada uma base contendo dados sobre livros, alunos e empréstimos de livros aos alunos. Não trataremos neste livro dos conceitos relacionados com banco de dados relacionais nem sobre a linguagem de consulta SQL. Existem vários textos sobre o assunto onde o leitor pode buscar informação. As figuras XIII.3 a XII.5 mostram as tabelas que formam a base de dados usada nos exemplos. O banco de dados é formado por três tabelas. Uma para armazenar os dados dos alunos, outra para receber os dados dos livros e uma terceira para conter os dados dos empréstimos. alunos matricula nome 1 Railer Costa Freire 2 Alexandre Altoé J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 328 3 André M. A. Landro 4 Ana Maria Freitas 5 Claudia Maria 6 Alexandra Moreira Figura XIII.3 – Tabela de alunos livros codlivro titulo volume 1 Curso Pratico de J ava 1 2 Curso Pratico de J ava 2 3 Introdução a Compiladores 1 4 Fundamentos de Banco de Dados 1 5 Redes de Computadores 1 6 Redes de Computadores Fácil 2 7 Lógica matemática 1 8 Engenharia de Software para Leigos 1 9 Aprenda Computação Gráfica em duas 1 10 Aprenda Inteligência Artificial em 5 1 Figura XIII.4 – Tabela de livros. emprestimos codlivr matricu data_empresti data_devoluc 1 1 01/01/99 10/01/99 7 3 03/01/99 13/01/99 9 6 12/01/99 22/01/99 1 3 20/01/99 30/01/99 4 2 03/02/99 13/02/99 10 2 12/02/99 22/02/99 Figura XIII.5 – Tabela de empréstimos. Em um Banco de Dados Relacional cada tabela representa um conjunto de entidades ou relacionamentos entre entidades, e cada linha da tabela representa uma entidade particular ou um relacionamento entre entidades. Assim, cada linha das tabelas alunos e livros representa um aluno e um livro respectivamente. J á na tabela empréstimos cada linha representa o relacionamento por empréstimo de um livro a um aluno. Para estabelecer este J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 329 tipo de relacionamento em um Banco de Dados Relacional é preciso colocar na tabela que representa o relacionamento os atributos chaves de cada entidade que participa da relação. Atributos chaves são os atributos que identificam cada entidade. No caso dos alunos é seu número de matrícula, uma vez que pode existir dois alunos com o mesmo nome. J á no caso de um livro o seu atributo chave é o código do livro. Um Banco de Dados Relacional pode ser representado pelo diagrama de classes da UML, como mostrado pela figura XIII.6, onde cada tabela é vista como uma classe. Figura XIII.6 – Diagrama de Classes do Banco de Dados. Para criação das tabelas em banco de dados relacional deve-se usar comandos DDL (Data Definition Language). O exemplo XX.XX mostra os comandos em DDL para a criação das tabelas do exemplo: CREATE TABLE ALUNOS (MATRICULA INT PRIMARY KEY, NOME VARCHAR(50) NOT NULL); CREATE TABLE LIVROS (CODLIVRO INT PRIMARY KEY, TITULO VARCHAR(50) NOT NULL, VOLUME INT NOT NULL); CREATE TABLE EMPRESTIMOS ( CODLIVRO INT NOT NULL, MATRICULA INT NOT NULL, DATAEMP DATE NOT NULL, DATADEV DATE NOT NULL, CONSTRAINT PK_EMP PRIMARY KEY (CODLIVRO, MATRICULA, DATAEMP), CONSTRAINT FK_EMP1 FOREIGN KEY (CODLIVRO ) REFERENCES LIVROS (CODLIVRO ), Alunos matricula nome livros codlivro titulo volume emprestimos data_emprestimo data_devolução J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 330 CONSTRAINT FK_EMP2 FOREIGN KEY (MATRICULA) REFERENCES ALUNOS (MATRICULA )); Exemplo XX.XX – Comandos em DDL para criação das tabelas em um SGBD relacional. Configurando o ODBC Como utilizamos nos exemplos a ponte J DBC-ODBC é necessário configurar o ODBC para acessar a base de dados acima. Na plataforma Windows 98 isso é feito da seguinte forma: 1. Execute o programa de configuração do ODBC por meio do ícone “ODBC de 32bits” do painel de controle. 2. Clique na pasta “NFD do sistema” e em seguida no botão de “adicionar...”. O NFD do sistema é escolhido no lugar da opção “NFD do usuário” porque permite o compartilhado à base de dados. 3. Selecione o driver do banco de dados e pressione o botão de concluir. Por exemplo, se você estiver usando o Access selecione a opção “Driver para o Microsoft Access (*.mdb)”. J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 331 Figura XIII.7 – Seleção do driver ODBC no Windows. 4. Ao surgir a tela para a entrada seleção do Banco de Dados, utilize a tecla “Selecionar...” localizar o arquivo onde está a base de dados e preencha a janela de texto “Nome da fonte de dados” com o nome que será usado para referenciar a base. 5. Feche o programa de configuração. A configuração de uma base de dados para ser acessada via ODBC possui vários outros detalhes que consideramos não ser relevantes para os propósitos deste livro. A configuração como apresentada acima é a suficiente para se executar os exemplos deste livro. Exemplo Inicial O pacote java.sql fornece as classes e interfaces necessárias para a conexão com uma base de dados e a posterior manipulação dos dados. As etapas para se criar uma aplicação cliente de um SGBD em J ava são as seguintes: 1. Carregar o driver J DBC. 2. Estabelecer a conexão. 3. Criar um objeto Statement. 4. Executar o comando SQL por meio de um método do objeto Statement. 5. Receber o resultado, se for o caso. Para comentar cada uma destas etapas utilizaremos o exemplo XII.1 que mostra o código de uma aplicação que lista no dispositivo de saída padrão o nome de todos os alunos. import java.sql.*; import java.net.URL; class jdbc { public static void main(String a[]) J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 332 { try { Class.forName("sun.jdbc.odbc.JdbcOdbcDriver"); Connection con = DriverManager.getConnection("jdbc:odbc:biblioteca"); Statement stmt = con.createStatement(); ResultSet rs = stmt.executeQuery("SELECT NOME FROM alunos"); System.out.println("Nome"); while(rs.next()) System.out.println(rs.getString("nome")); stmt.close(); con.close(); } catch(Exception e) {System.out.println(e.getMessage()); e.printStackTrace();} } } Exemplo XX.XX – Código para listar o nome dos alunos. Carregando o Driver A primeira etapa é carregar o driver J DBC. Para isso é usado o método estático forName() da classe Class. Em caso de erro este método lança a exceção ClassNotFoundException. O método cria uma instância do driver e o registra junto ao DriverManager. No exemplo XIII.1 é carregado o driver J DBC-ODBC que vem junto com o SDK. Estabelecendo a conexão A segunda etapa é realizada por meio do método estático getConnection() da classe DriverManager. Este método, na sua forma mais simples, recebe como parâmetro um URL que faz referência a base de dados e retorna um objeto da classe Connection, que representa a conexão com a base de dados. J á discutimos sobre URLs no capítulo XI. No entanto, existem algumas particularidades no que refere a URLs que fazem referência à Banco de Dados. O formato padrão deste tipo de URL é o seguinte: jdbc:<subprotocolo>:<identificador> onde: J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 333 1. jdbc representa o protocolo; 2. <subprotocolo>se refere ao driver ou ao mecanismo de conexão com o Banco de Dados, que pode ser suportado por um ou mais drivers. No exemplo XII.1 o nome é utilizada a ponte J DBC-ODBC, representada pela palavra odbc no subprotocolo. 3. <identificador>é a parte onde se identifica o Banco de Dados. A forma de identificação varia de acordo com o subprotocolo. No nosso exemplo é colocado o mesmo nome usado para identificar a fonte de dados na configuração do ODBC. A sintaxe para o subprotocolo odbc é a seguinte: jdbc:odbc:<fonte de dados>[;<atributo>=<valor>]* onde <atributo>e <valor>representam parâmetros a serem passados para o gerente de conexão do Banco de Dados. Um banco de dados acessado remotamente requer maiores informações, como o nome do host e a porta. Tanto o uso local como remoto pode requerer a identificação do usuário, assim como uma senha. Estes dados podem ser passados como parâmetro no método getConnection(): getConnection("jdbc:odbc:contas",”ana”,”sght”); ou como parte do URL. Alguns exemplos de URLs estão descritos na tabela XIII.1 URL Descrição jdbc:odbc:biblioteca Referencia fonte de dados biblioteca via ponte J DBC-ODBC. jdbc:odbc:bd1;CacheSize=20 Referencia fonte de dados bd1 via ponte J DBC-ODBC. É definido o tamanho do cache. jdbc:odbc:contas;UID=ana;PWD=sght Referencia fonte de dados contas via ponte J DBC-ODBC. É passado também o nome do usuário e a senha. jdbc:oracle:thin:@sap.dpi.ufv.br:1521:agenda Referencia fonte de dados agenda no host remoto sap.dpi.ufv.br via subprotocolo oracle. É passado também o número da porta usada no acesso. J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 334 Tabela.1 – Exemplos de URLs JDBC. Criando e Executando Comandos É necessário cria um ou mais objetos da classe Statement, que possui os métodos necessários para manipular a base de dados. Este objeto é criado por meio do método createStatement() do objeto da classe Connection. Statement stmt = con.createStatement(); Podemos então usar o objeto Statement para executar comandos de manipulação do Banco de Dados. No exemplo XIII.1 o objetivo é recuperar s nomes dos alunos. Este objetivo é atingido por meio da execução do comando SQL SELECT nome FROM alunos passado como parâmetro para o método executeQuery() do objeto Statement. Este método retorna um objeto que implementa a interface ResultSet, e que fornece os meios de acesso ao resultado da consulta. Muitas vezes, como no exemplo, o resultado de uma consulta é uma tabela com várias linhas. O programador pode utilizar o objeto ResultSet para acessar cada linha da tabela resultante em sequência. Para isso o objeto mantém um apontador para a linha corrente, chamado de cursor. Inicialmente o cursor é posicionado antes da primeira linha, movimentado para próxima linha por meio de chamadas ao método next() do objeto ResultSet. O método executeQuery() é usado apenas para consultas. Além desse método, a classe Statement possui o método execute() que retorna múltiplos ResultSets e o método executeUpdate(), para atualização (comandos INSERT, DELETE e UPDATE da linguagem SQL), criação de tabelas (comandos CREATE TABLE) e remoção (DROP TABLE). O valor de retorno do método executeUpdate() é um valor inteiro indicando o número de linhas afetadas ou zero no caso do DROP TABLE. Um exemplo de um comando para inserir um uma novo aluno na tabela seria: stmt.executeUpdate("INSERT INTO alunos VALUES(7, 'Ana mia')"); J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 335 Recuperando Valores O objeto ResultSet possui os métodos necessários para recuperar os valores de cada coluna da tabela, bastando passar o nome da coluna como parâmetro. No exemplo XIII.1 é utilizado método getString() para recuperar o valor da coluna “nome”. Os métodos para recuperação possuem o formato geral getXXX(), onde XXX é o nome de um tipo. A tabela XIII.2 mostra qual o método mais indicado para cada tipo SQL. T I N Y I N T S M A L L I N T I N T E G E R B I G I N T R E A L F L O A T D O U B L E D E C I M A L N U M E R I C B I T C H A R V A R C H A R L O N G V A R C H A R B I N A R Y V A R B I N A R Y L O N G V A R B I N A R Y D A T E T I M E T I M E S T A M P getByte X x x x x x x x x x x x x getShort x X x x x x x x x x x x x getInt x x X x x x x x x x x x x getLong x x x X x x x x x x x x x getFloat x x x x X x x x x x x x x getDouble x x x x x X X x x x x x x getBigDecimal x x x x x x x X X x x x x getBoolean x x x x x x x x x X x x x getString x x x x x x x x x x X X x x x x x x x getBytes X X x getDate x x x X x getTime x x x X x getTimestamp x x x x X getAsciiStream x x X x x x getUnicodeStre am x x X x x x J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 336 getBinaryStrea m x x X getObject x x x x x x x x x x x x x x x x x x x “x” indica que o método pode ser usado para recuperar o valor no tipo SQL especificado. “X” indica que o método é recomendado para ser usado na recuperação do valor no tipo SQL especificado Tabela XIII.2 – Tabelas com os métodos indicados para recuperação de valores. É possível recuperar o valor da coluna passando como parâmetro o número da coluna no lugar de seu nome. Neste caso a recuperação no nome do aluno no exemplo XIII.1 ficaria na seguinte forma: rs.getString(1); Transações e Nível de Isolamento Transação Uma Transação é um conjunto de operações realizadas sobre um banco de dados tratadas atomicamente, em outras palavras, ou todas operações são realizadas e o seu resultado registrado permanentemente na base de dados ou nenhuma operação é realizada. Por default, o banco de dados trata cada operação como uma transação, realizando implicitamente uma operação de commit ao fim de cada uma delas. A operação de commit registra permanentemente o resultado da transação na tabela. No entanto, existem situações onde é necessário tratar como uma transação um conjunto de operações, e não apenas uma transação. Por exemplo, suponha que em um Banco de Dados de uma agência bancária exista uma tabela com informações sobre a conta de corrente e outra com informações sobre contas de poupança. Suponha também que um cliente deseje transferir o dinheiro da conta corrente para uma conta de poupança. Essa transação é constituída pelas seguintes operações: 1. Caso exista saldo suficiente, subtração do montante da transferência do saldo da conta corrente. 2. Adição do montante da transferência ao saldo da conta de poupança. J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 337 As operações acima precisam ocorrer totalmente ou o efeito de nenhuma delas deve ser registrado na base de dados. Caso contrário podemos ter uma situação onde o dinheiro sai da conta corrente mas não entra na conta da poupança. Este estado, onde as informações do banco de dados não reflete a realidade, é chamado de estado inconsistente. De modo a obter esse controle sobre as transações é necessário desabilitar o modo de auto-commit. Isto é feito por meio método setAutoCommit() do objeto Connection. con.setAutoCommit(false); A partir do momento em que é executado o comando acima, o programador é responsável pela indicação do final da transação, por meio da execução do método commit() do objeto Connection. con.commit(); Se alguma exceção for levantada durante a execução de qualquer operação da transação, o programador pode usar o método rollback() para desfazer as operações já realizadas após o último commit(). con.setAutoCommit(false); try { Statement stmt = con.createStatement(); stmt.executeUpdate(“UPDATE ...” ); stmt.executeUpdate(“UPDATE ...” ); con.commit(); stmt.close(); } catch(Exception e){con.rollback();} finally { try{ con.setAutoCommit(true);} catch(SQLException sqle) {System.out.prinln(sql.getMessage());} } Exemplo XX.X – Uso dos métodos commit() e rollback(). J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 338 Níveis de isolamento Além da atomicidade outra propriedade desejável em uma transação é o isolamento. A propriedade de isolamento implica que uma transação não é afetada pelas operações realizadas por outras transações que estão sendo realizadas concorrentemente. O isolamento completo entre transações prejudica muito a execução concorrente de transações e pode ser desnecessário em determinados tipos de aplicações. Por isso os SGBDs permitem que o programador defina o nível de isolamento entre as transações. De acordo com o relaxamento do isolamento certos problemas devido a interferência entre as transações podem ocorrer e o programador deve estar ciente disso. O número de níveis de isolamento, sua nomenclatura e características depende do SGBD utilizado. Descreveremos os níveis de isolamento definidos no pacote java.sql. Para exemplificar os problemas que podem ocorrer devido a interferência entre transações utilizaremos um banco de dados exemplo com a seguinte tabela: NumCC Saldo 10189-9 20645-7 200,00 300,00 • Read uncommitted - É o nível menos restritivo. Pode ocorrer leituras de registros não committed (Dirty reads). Usados em onde não existe concorrência ou não existem alterações em registros ou quando essas alterações não são relevantes. Exemplo de problema: Uma transação deve transferir R$50,00 da conta 10189-9 para a conta 20645-7 e uma segunda transação deve somar R$70,00 à conta 10189-9. A figura abaixo mostra o estado inicial e o estado final desejado da tabela: NumCC Saldo NumCC Saldo 10189-9 20645-7 200,00 300,00 10189-9 20645-7 220,00 350,00 Estado desejado após as transações Estado antes das transações J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 339 Cada transação é divida em operações de leitura e escrita. Suponha que o intercalamento das operações seja feito como mostrado abaixo: Transação 1 Transação 2 leitura do saldo 10189 Escrita do Saldo-50,00 leitura do saldo 20645 (falha na transação, realizado rollback) leitura do saldo 10189 Escrita do Saldo+70,00 Como a transação 1 falhou o valor lido pela transação 2 é um valor que não foi tornado permanente na tabela. Isto faz com que a transação 2 opere sobre um resultado desfeito. A tabela resultante, mostrada abaixo estará em um estado inconsistente. NumCC Saldo 10189-9 20645-7 220,00 300,00 • Read committed - Somente registros committed podem ser lidos. Evita o problema de Dirty reads, no entanto duas leituras de um mesmo item em uma mesma transação podem possuir valores diferentes, uma vez que o valor pode ser mudado por outra transação entre duas leituras. • Repeatable Read - Somente registros committed podem ser lidos, além disso impede a alteração de um item lido pela transação. Evita o problema de Dirty reads e o problema do non-repeatable Read . • Serializable - É o nível mais restritivo. Impede Dirty reads e non-repeatable reads. Além disso impede o problema de phantom reads onde um conjunto de registros satisfazendo a condição WHERE é lido enquanto outra transação insere novos registros que satisfazem a condição. Para se definir o nível de isolamento na linguagem J ava usa-se um objeto DatabaseMetaData que é obtido por meio do objeto getMetaData() do Connection. Primeiro é preciso saber se o SGBD suporta o nível de J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 340 isolamento desejado para depois definir o nível. O exemplo XX.X mostra uma sequência típica comandos. DatabaseMetaData meta=con.getMetaData(); if(meta.supportsTransactionIsolationLevel( con.TRANSACTION_READ_COMMITTED)) { con.setTransactionIsolation( con.TRANSACTION_READ_COMMITTED); } else return; Exemplo XX.X – Exemplo do estabelecimento do nível de isolamento. A tabela abaixo mostra as constantes relacionadas com os níveis de isolamento da linguagem J ava: Constante TRANSACTION_NONE TRANSACTION_READ_UNCOMMITTED TRANSACTION_READ_COMMITTED TRANSACTION_REPEATABLE_READ TRANSACTION_SERIALIZABLE Tabela XX.X – Tabela com as constantes dos níveis de isolamento. Prepared Statements Cada vez que se executa um comando SQL passado por meio de uma String. Este String deve ser analisado pelo processador de SQL do SGBD que irá, no caso da String estar sintaticamente correta, gerar um código binário que será executado para atender à solicitação. Todo esse processo é caro e sua execução repetidas vezes terá um impacto significativo sobre o desempenho da aplicação e do SGBD como um todo. Existem duas abordagens para tentar solucionar esse problema: Comandos preparados (prepared statements) e procedimentos armazenados (stored procedures). Discutiremos primeiramente os prepared statements. Prepared Statement é indicado nos casos onde um comando será executado várias vezes em uma aplicação. Neste caso é melhor compilar o comando uma única vez e toda vez que for necessário executá-lo basta enviar o J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 341 comando compilado. Além disso, o comando pré-compilado pode ser parametrizado, tornando-o mais genérico e, portanto, apto a expressar um maior número de consultas. Para criar um Prepared Statement é necessário obter um objeto PreparedStatement por meio do método prepareStatement() do objeto Connection, passando como argumento um comando SQL. PreparedStatement pstmt = con.prepareStatement( “INSERT INTO alunos(matricula,nome) VALUES(?, ? )”); O comando anterior insere uma nova linha na tabela alunos com os valores das colunas matricula e nome passados por parâmetro. O caractere ‘?’ representa o parâmetro. Este tipo de comando só possui valor tendo parâmetros, caso contrário teria pouca chance de ser reutilizado. Para executar o comando devemos especificar o valor dos parâmetros e executar o comando, como mostrado no exemplo abaixo: pstmt.clearParameters(); pstmt.setInt(1,8); pstmt.setString(2,”Clara Maria”); pstmt.executeUpdate(); Antes de especificar os parâmetros é necessário limpar qualquer outro parâmetro previamente especificado. Para especificar os parâmetros são utilizados um conjunto de métodos com o nome no formato setXXX(), onde XXX é o tipo sendo passado. O primeiro parâmetro do método setXXX() é o índice da ocorrência do caractere ‘?’ que será substituído pelo valor. O segundo parâmetro é o valor que será transmitido. Procedimentos Armazenados (Stored Procedures) A maioria dos SGBDs possuem algum tipo de linguagem de programação interna, como por exemplo a PL/SQL do Oracle ou mesmo J ava e C/C++. Estas linguagens permitem que os desenvolvedores insiram parte do código da aplicação diretamente no banco de dados e invoquem este código a partir da aplicação. Esta abordagem possui as seguintes vantagens: J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 342 • Reuso de código – o código precisa ser escrito apenas uma vez e usado em várias aplicações, comunicando com várias linguagens. • Independencia entre a aplicação e o esquema do BD – se o esquema mudar, provavelmente apenas os procedimentos armazenados. • Desempenho – os procedimentos são previamente compilados, eliminando esta etapa. • Segurança – as aplicações possuem privilégio apenas para execução de procedimentos armazenados, evitando assim acessos não autorizados. A sintaxe dos procedimentos armazenados depende do SGBD em questão. Utilizaremos um exemplo em PL/SQL. No exemplo abaixo o procedimento retorna o nome do aluno a partir de sua matricula. CREATE OR REPLACE PROCEDURE sp_obtem_nome (id IN INTEGER, Nome_aluno out VARCHAR2)IS BEGIN SELECT nome INTO Nome_aluno FROM alunos WHERE matricula = id; END; / Para invocar o procedimento anterior de dentro de uma aplicação J ava é necessário obter um objeto CallableStatement por meio do método prepareCall() do objeto Connection, passando como argumento um comando SQL. CallableStatement cstmt = con.prepareCall("{ CALL sp_obtem_nome(?,?)}"); cstmt.registerOutParameter(2, Types.VARCHAR); cstmt.setInt(1, 3); cstmt.execute(); System.out.prinln(“O nome do aluno numero 3 :” +cstmt.getString(2); J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 343 Agenda Eletrônica versão JDBC Pessoa import java.io.*; public class pessoa implements Serializable { String Nome; String Tel; // Construtor public pessoa(String n, String t) {Nome = n; Tel = t;} public String getNome(){return Nome;} public String getTel(){return Tel;} } agenda import java.util.*; import java.io.*; import java.sql.*; import java.net.URL; public class agenda { Connection con=null; // Construtor public agenda()throws Exception { Class.forName("sun.jdbc.odbc.JdbcOdbcDriver"); con=DriverManager.getConnection("jdbc:odbc:agenda"); } /** CloseAgenda */ public void CloseAgenda() { if (con != null) try{con.close();}catch(Exception e){}; } /** inserir J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 344 */ public void inserir(pessoa p) { if (con == null) return; try { Statement stmt = con.createStatement(); stmt.executeUpdate("INSERT INTO pessoas(nome,telefone) "+ "values('"+p.getNome()+"','"+p.getTel()+"')"); stmt.close(); }catch(Exception e) {System.err.println(e);} } /** Consultar */ /** listar */ public Enumeration getLista() { if (con == null) return null; Vector pessoas = new Vector(); try { Statement stmt = con.createStatement(); ResultSet rs = stmt.executeQuery ("SELECT Nome, Telefone FROM pessoas"); while(rs.next()) pessoas.addElement(new pessoa(rs.getString("Nome"), rs.getString("Telefone"))); stmt.close(); } catch(Exception e) {System.out.println(e.getMessage()); e.printStackTrace();} return pessoas.elements(); } } Servidor import java.util.*; import java.net.*; import java.io.*; J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 345 /** CLASS server */ class server { public static void main(String args[]) { String oHost="localhost"; ServerSocket ssocket=null; serversec oServersec; int pt = 4444; agenda ag; try {ag = new agenda();} catch(Exception e){System.err.println(e); return;}; if (args.length > 0) pt = Integer.parseInt(args[0]); try { ssocket = new ServerSocket(pt); pt = ssocket.getLocalPort(); oHost = ssocket.getInetAddress().getHostName().trim(); } catch(Exception e) {System.err.println(e); System.exit(1);} System.out.println("Porta:"+pt+" Host: "+oHost); while(true) { try { Socket clisocket = ssocket.accept(); oServersec = new serversec(ag,clisocket); oServersec.start(); } catch(Exception e) {System.err.println(e);} } } } /** CLASS serversec */ class serversec extends Thread { Socket oSocket; BufferedWriter soutput; J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 346 BufferedReader cinput; agenda ag; public serversec(agenda ag, Socket aoSocket) { this.ag = ag; oSocket = aoSocket; } public void run() { try { soutput = new BufferedWriter(new OutputStreamWriter(oSocket.getOutputStream())); cinput = new BufferedReader(new InputStreamReader(oSocket.getInputStream())); String losLinha = cinput.readLine(); switch(losLinha.charAt(0)) { case 'i': String Nome = cinput.readLine(); String Tel = cinput.readLine(); ag.inserir(new pessoa(Nome,Tel)); soutput.write("OK\n#\n"); break; case 'l': pessoa p; for (Enumeration e = ag.getLista(); e.hasMoreElements() ;) { p = (pessoa) e.nextElement(); soutput.write(p.getNome()+"\n"+p.getTel()+"\n"); } soutput.write("#\n"); break; } soutput.flush(); Thread.yield(); soutput.close(); oSocket.close(); } catch(Exception e) {System.err.println(e);} } } J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 347 Applet import java.awt.*; import java.applet.*; import java.util.*; import java.net.*; import java.io.*; public class agendaapplet extends Applet { int port = 4444; TextField txtNome = new TextField(); TextField txtTel = new TextField(); Label label1 = new Label(); Label label2 = new Label(); Button btIns = new Button(); Button btList = new Button(); Button btCons = new Button(); Button btSair = new Button(); TextArea Saida = new TextArea(); Button btLimpar = new Button(); public void init() { setLayout(null); setSize(376,224); add(txtNome); txtNome.setBounds(108,48,232,24); add(txtTel); txtTel.setBounds(108,84,232,24); label1.setText("Nome"); add(label1); label1.setBounds(24,48,60,26); label2.setText("Telefone"); add(label2); label2.setBounds(24,84,60,26); btIns.setActionCommand("button"); btIns.setLabel("Inserir"); add(btIns); btIns.setBackground(java.awt.Color.lightGray); btIns.setBounds(12,12,49,23); btList.setActionCommand("button"); btList.setLabel("Listar"); add(btList); btList.setBackground(java.awt.Color.lightGray); btList.setBounds(149,12,60,23); btCons.setActionCommand("button"); btCons.setLabel("Consultar"); add(btCons); J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 348 btCons.setBackground(java.awt.Color.lightGray); btCons.setBounds(75,12,60,23); btSair.setActionCommand("button"); btSair.setLabel("Sair"); add(btSair); btSair.setBackground(java.awt.Color.lightGray); btSair.setBounds(297,12,60,23); add(Saida); Saida.setBounds(24,120,338,90); btLimpar.setActionCommand("button"); btLimpar.setLabel("Limpar"); add(btLimpar); btLimpar.setBackground(java.awt.Color.lightGray); btLimpar.setBounds(223,12,60,23); SymMouse aSymMouse = new SymMouse(); btSair.addMouseListener(aSymMouse); btIns.addMouseListener(aSymMouse); btList.addMouseListener(aSymMouse); btLimpar.addMouseListener(aSymMouse); } public void transmit(int port,String mensagem) { BufferedWriter soutput; BufferedReader cinput; Socket clisoc=null; try { if (clisoc != null) clisoc.close(); clisoc = new Socket(InetAddress.getByName(getCodeBase().getHost()),port); soutput = new BufferedWriter (new OutputStreamWriter(clisoc.getOutputStream())); cinput = new BufferedReader(new InputStreamReader(clisoc.getInputStream())); soutput.write(mensagem+"\n"); soutput.flush(); String losRet = cinput.readLine(); while (losRet.charAt(0)!='#') { Saida.setText(Saida.getText()+losRet+"\n"); losRet = cinput.readLine(); } Thread.sleep(500); J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 349 soutput.close(); clisoc.close(); } catch(Exception e) {System.err.println(e);} } class SymMouse extends java.awt.event.MouseAdapter { public void mouseClicked(java.awt.event.MouseEvent event) { Object object = event.getSource(); if (object == btSair) btSair_MouseClick(event); else if (object == btIns) btIns_MouseClick(event); else if (object == btList) btList_MouseClick(event); else if (object == btLimpar) btLimpar_MouseClick(event); } } void btSair_MouseClick(java.awt.event.MouseEvent event) { System.exit(0); } void btIns_MouseClick(java.awt.event.MouseEvent event) { String nome = txtNome.getText(); String tel = txtTel.getText(); if (nome.length()>0 && tel.length()>0) transmit(port,"i\n"+nome+"\n"+tel+"\n"); } void btList_MouseClick(java.awt.event.MouseEvent event) { transmit(port,"l\n"); } void btLimpar_MouseClick(java.awt.event.MouseEvent event) { Saida.setText(""); } } J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 350 Capítulo XVI Servlets e JSP Servlets e J SP são duas tecnologias desenvolvidas pela Sun para desenvolvimento de aplicações na Web a partir de componentes J ava que executem no lado servidor. Essas duas tecnologias fazem parte da plataforma J 2EE (J ava 2 Platform Enterprise Edition) que fornece um conjunto de tecnologias para o desenvolvimento de soluções escaláveis e robustas para a Web. Neste livro abordaremos apenas as tecnologias Servlets e J SP, sendo o suficiente para o desenvolvimento de sites dinâmicos de razoável complexidade. Se a aplicação exigir uma grande robustez e escalabilidade o leitor deve considerar o uso em conjunto de outras tecnologias da plataforma J 2EE. Servlets Servlets são classes J ava que são instanciadas e executadas em associação com servidores Web, atendendo requisições realizadas por meio do protocolo HTTP. Ao serem acionados, os objetos Servlets podem enviar a resposta na forma de uma página HTML ou qualquer outro conteúdo MIME. Na verdade os Servlets podem trabalhar com vários tipos de servidores e não só servidores Web, uma vez que a API dos Servlets não assume nada a respeito do ambiente do servidor, sendo independentes de protocolos e plataformas. Em outras palavras Servlets é uma API para construção de componentes do lado servidor com o objetivo de fornecer um padrão para comunicação entre clientes e servidores. Os Servlets são tipicamente usados no desenvolvimento de sites dinâmicos. Sites dinâmicos são sites onde algumas de suas páginas são construídas no momento do atendimento de uma requisição HTTP. Assim é possível criar páginas com conteúdo variável, de acordo com o usuário, tempo, ou informações armazenadas em um banco de dados. Servlets não possuem interface gráfica e suas instâncias são executadas dentro de um ambiente J ava denominado de Container. O container gerencia as instâncias dos Servlets e provê os serviços de rede necessários para as requisições e respostas. O container atua em associação com servidores Web recebendo as requisições reencaminhada por eles. Tipicamente existe apenas uma instância de cada Servlet, no entanto, o container pode criar vários threads de modo a permitir que uma única instância Servlet atenda mais de uma J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 351 requisição simultaneamente A figura XX fornece uma visão do relacionamento destes componentes. Figura XVI-1. Relacionamento entre Servlets, container e servidor Web Servlets provêem uma solução interessante para o relacionamento cliente/servidor na Internet, tornando-se uma alternativa para a implantação de sistemas para a Web. Antes de entrarmos em detalhes na construção de Servlets, compararemos esta solução com outras duas soluções possíveis para implantação de aplicações na Internet. Applets X Servlets Apesar de ser uma solução robusta existem problemas no uso de Applets para validação de dados e envio para o servidor. O programador precisa contar com o fato do usuário possuir um navegador com suporte a J ava e na versão apropriada. Você não pode contar com isso na Internet, principalmente se você deseja estender a um grande número de usuário o acesso às suas páginas. Em se tratando de Servlets, no lado do cliente pode existir apenas páginas HTML, evitando restrições de acesso às páginas. Em resumo, o uso de Applets não é recomendado para ambientes com múltiplos navegadores ou quando a semântica da aplicação possa ser expressa por componentes HTML. Servidor Web Requisições Máquina Virtual J ava Container Instâncias de Servlets Respostas J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 352 CGI X Servlets Como visto no Capítulo IX, scripts CGI (Common Gateway Interface), acionam programas no servidor. O uso de CGI sobrecarrega o servidor uma vez cada requisição de serviço acarreta a execução de um programa executável (que pode ser escrito em com qualquer linguagem que suporte o padrão CGI) no servidor, além disso, todo o processamento é realizado pelo CGI no servidor. Se houver algum erro na entrada de dados o CGI tem que produzir uma página HTML explicando o problema. J á os Servlets são carregados apenas uma vez e como são executados de forma multi-thread podem atender mais de uma mesma solicitação por simultaneamente. Versões posteriores de CGI contornam este tipo de problema, mas permanecem outros como a falta de portabilidade e a insegurança na execução de código escrito em uma linguagem como C/C++. A API Servlet A API Servlet é composta por um conjunto de interfaces e Classes. O componente mais básico da API é interface Servlet. Ela define o comportamento básico de um Servlet. A figura XX.XX mostra a interface Servlet. public interface Servlet { public void init(ServletConfig config) throws ServletException; public ServletConfig getServletConfig(); public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException; public String getServletInfo(); public void destroy(); } Figura XV.XX. Interface Servlet. O método service() é responsável pelo tratamento de todas das requisições dos clientes. J á os métodos init() e destroy() são chamados quando o Servlet é carregado e descarregado do container, respectivamente. O método getServletConfig() retorna um objeto ServletConfig que contém os parâmetros de inicialização do Servlet. O método J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 353 getServletInfo() retorna um String contendo informações sobre o Servlet, como versão e autor. Tendo como base a interface Servlet o restante da API Servlet se organiza hierarquicamente como mostra a figura XV.XX. Figura XV.XX. Hierarquia de classes da API Servlet. A classe GenericServlet implementa um servidor genérico e geralmente não é usada. A classe HttpServlet é a mais utilizada e foi especialmente projetada para lidar com o protocolo HTTP. A figura XX.XX mostra a definição da classe interface HttpServlet. HttpServlet public abstract class HttpServlet extends GenericServlet implements java.io.Serializable Figura XV.XX. Definição da classe HttpServlet. Note que a classe HttpServlet é uma classe abstrata. Para criar um Servlet que atenda requisições HTTP o programador deve criar uma classe derivada da HttpServlet e sobrescrever pelo menos um dos métodos abaixo: Servlet GenericServlet HttpServlet J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 354 doGet Trata as requisições HTTP GET. doPost Trata as requisições HTTP POST. doPut Trata as requisições HTTP PUT. doDelete Trata as requisições HTTP DELETE. Tabela XV.XX. Métodos da classe HttpServlet que devem ser sobrescritos para tratar requisições HTTP. Todos esses métodos são invocados pelo servidor por meio do método service(). O método doGet() trata as requisições GET. Este tipo de requisição pode ser enviada várias vezes, permitindo que seja colocada em um bookmark. O método doPost() trata as requisições POST que permitem que o cliente envie dados de tamanho ilimitado para o servidor Web uma única vez, sendo útil para enviar informações tais como o número do cartão de crédito. O método doPut() trata as requisições PUT. Este tipo de requisição permite que o cliente envie um arquivo para o servidor à semelhança de como é feito via FTP. O método doPut() trata as requisições DELETE, permitindo que o cliente remova um documento ou uma página do servidor. O método service(), que recebe todas as requisições, em geral não é sobrescrito, sendo sua tarefa direcionar a requisição para o método adequado. Exemplo de Servlet Para entendermos o que é um Servlet nada melhor que um exemplo simples. O exemplo XV.XX gera uma página HTML em resposta a uma requisição GET. A página HTML gerada contém simplesmente a frase Ola mundo!!!. Este é um Servlet bem simples que ilustra as funcionalidades básicas da classe. import javax.servlet.*; import javax.servlet.http.*; public class Ola extends HttpServlet { public String getServletInfo() {return "Ola versão 0.1";} public void doGet(HttpServletRequest req, HttpServletResponse res) throws IOException, ServletException { J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 355 res.setContentType("text/html"); java.io.PrintWriter out =res.getWriter(); out.println("<html>"); out.println("<head>"); out.println("<title>Servlet</title>"); out.println("</head>"); out.println("<body>Ola mundo!!!"); out.println("</body>"); out.println("</html>"); out.close(); } } Exemplo XV.XX. Servlet Ola. O método doGet() recebe dois objetos: um da classe HttpServletRequest e outro da classe HttpServletResponse. O HttpServletRequest é responsável pela comunicação do cliente para o servidor e o HttpServletResponse é responsável pela comunicação do servidor para o cliente. Sendo o exemplo XV.XX apenas um exemplo simples ele ignora o que foi enviado pelo cliente, tratando apenas de enviar uma página HTML como resposta. Para isso é utilizado o objeto da classe HttpServletResponse. Primeiramente é usado o método setContentType() para definir o tipo do conteúdo a ser enviado ao cliente. Esse método deve ser usado apenas uma vez e antes de se obter um objeto do tipo PrintWriter ou ServletOutputStream para a resposta. Após isso é usado o método getWriter() para se obter um objeto do tipo PrintWriter que é usado para escrever a resposta. Neste caso os dados da resposta são baseados em caracteres. Se o programador desejar enviar a resposta em bytes deve usar o método getOutputStream() para obter um objeto OutputStream. A partir de então o programa passa usar o objeto PrintWriter para enviar a página HTML. Compilando o Servlet A API Servlet ainda não foi incorporado ao SDK, portanto, para compilar um Servlet é preciso adicionar a API Servlet ao pacote SDK. Existem várias formas de se fazer isso. A Sun fornece a especificação da API e diversos produtores de software executam a implementação. Atualmente, a especificação J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 356 da API Servlet está na versão 2.XX. Uma das implementações da API que pode ser baixada gratuitamente pela Internet é a fornecida pelo projeto J akarta (http://jakarta.apache.org) denominada de Tomcat. A implementação da API Servlet feita pelo projeto J akarta é a implementação de referência indicada pela Sun. Ou seja, é a implementação que os outros fabricantes devem seguir para garantir a conformidade com a especificação da API. No entanto, uma vez que o Tomcat é a implementação mais atualizada da API, é também a menos testada e, por consequência, pode não ser a mais estável e com melhor desempenho. Instalando o Tomcat Assim como para se executar um Applet era preciso de um navegador Web com J ava habilitado no caso de Servlets é preciso de servidor Web que execute J ava ou que passe as requisições feitas a Servlets para programas que executem os Servlets. O Tomcat é tanto a implementação da API Servlet como a implementação de um container, que pode trabalhar em associação com um servidor Web como o Apache ou o IIS, ou pode também trabalhar isoladamente, desempenhando também o papel de um servidor Web. Nos exemplos aqui mostrados usaremos o Tomcat isoladamente. Em um ambiente de produção esta configuração não é a mais adequada, uma vez que os servidores Web possuem um melhor desempenho no despacho de páginas estáticas. As instruções para configurar o Tomcat para trabalhar em conjunto com um servidor Web podem ser encontradas junto às instruções gerais do programa. As figuras XV.XX ilustram essas duas situações. Figura XV.XX. Servidor Web habilitado para Servlet. Servidor Web Servlet habilitado Internet J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 357 Figura XV.XX. Servidor Web reencaminhando as requisições para o Servlet container. A versão estável do Tomcat é a 3.2.3.XX é após baixá-la do site do projeto J akarta o usuário deve descomprimir o arquivo. Por exemplo, no ambiente Windows o usuário pode descomprimir o arquivo na raiz do disco C:, o que gerará a seguinte árvore de diretórios: C:\ jakarta-tomcat-3.2.3 | |______bin |______conf |______doc |______lib |______logs |______src |______webapps No diretório bin encontram-se os programas execução e interrupção do container Tomcat. No diretório conf encontram-se os arquivos de configuração. No diretório doc encontram-se os arquivos de documentação. No diretório lib encontram-se os bytecodes do container e da implementação da API. No diretório logs são registradas as mensagens da geradas durante a execução do sistema. No diretório src encontram-se os arquivos fontes do container e da implementação da API de configuração. Finalmente, No diretório webapps encontram-se as páginas e códigos das aplicações dos usuários. No ambiente MS-Windows aconselhamos usar um nome dentro do formato 8.3 (oito caracteres para o nome e três para o tipo). Assim o diretório jakarta-tomcat-3.2.3 poderia ser mudado para simplesmente tomcat. Internet Servidor Web Servlet Container J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 358 Antes de executar o Tomcat é necessário definir duas variáveis de ambiente. Por exemplo, supondo que no MS-Windows o Tomcat foi instalado no diretório c:\tomcat e que o SDK está instalado no diretório c:\jdk1.3 então as seguintes variáveis de ambiente devem ser definidas: set JAVA_HOME=C:\jdk1.3 set TOMCAT_HOME=C:\tomcat Agora é possível executar o Tomcat por meio do seguinte comando: C:\tomcat\bin\startup.bat Para interromper a execução servidor basta executar o arquivo c:\tomcat\bin\shutdown.bat Falta de espaço para variáveis de ambiente Caso ao iniciar o servidor apareça a mensagem “sem espaço de ambiente” clique com o botão direito do mouse no arquivo .bat e edite as propriedades definindo o ambiente inicial com 4096. Feche o arquivo é execute novamente. Ao entrar em execução o servidor lê as configurações constantes no arquivo server.xml e, por default, se anexa à porta 8080. Para verificar se o programa está funcionando corretamente execute um navegador como o Netscape ou o Internet Explorer e digite a seguinte URL: http://127.0.0.1:8080/index.html A figura XX.XX mostra a tela principal do Tomcat. J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 359 Figura XV.XX. Tela inicial do Tomcat. A número porta default para recebimento das requisições HTTP pode ser alterada por meio da edição do arquivo server.xml do diretório conf como mostrado abaixo: <Connector className="org.apache.tomcat.service.PoolTcpConnector"> <Parameter name="handler" value="org.apache.tomcat.service.http.HttpConnectionHandler"/> <Parameter name="port" value="Número da porta"/> </Connector> No entanto, caso o Tomcat esteja operando em conjunto com um servidor, o ideal é que o Tomcat não responda requisições diretamente. Preparando para executar o Servlet Compilando o Servlet J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 360 Antes de executar o Servlet e preciso compilá-lo. Para compilá-lo é preciso que as classes que implementam a API Servlet estejam no classpath. Para isso é preciso definir a variável de ambiente. No ambiente MS-Windows seria set CLASSPATH=%CLASSPATH%;%TOMCAT_HOME%\lib\servlet.jar e no ambiente Unix seria CLASSPATH=${CLASSPATH}:${TOMCAT_HOME}/lib/servlet.jar Alternativamente, é possível indicar o classpath na própria linha de execução do compilador J ava. Por exemplo, No ambiente MS-Windows ficaria na seguinte forma: javac -classpath "%CLASSPATH%;c:\tomcat\lib\servlet.jar Ola.java Criando uma aplicação no Tomcat Agora é preciso definir onde deve ser colocado o arquivo compilado. Para isso é preciso criar uma aplicação no Tomcat ou usar uma das aplicações já existentes. Vamos aprender como criar uma aplicação no Tomcat. Para isso é preciso cria a seguinte estrutura de diretórios abaixo do diretório webapps do Tomcat: webapps |_____ Nome aplicação |_____ Web-inf |_____classes Diretório de Aplicações Na verdade é possível definir outro diretório para colocar as aplicações do Tomcat. Para indicar outro diretório é preciso editar o arquivo server.xml e indicar o diretório por meio da diretiva home do tag ContextManager. O diretório de uma aplicação é denominado de contexto da aplicação. É preciso também editar o arquivo server.xml do diretório conf, incluindo as linhas: J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 361 <Context path="/nome aplicação" docBase="webapps/ nome aplicação" debug="0” reloadable="true" > </Context> Finalmente, é preciso criar (ou copiar de outra aplicação) um arquivo web.xml no diretório Web-inf com o seguinte conteúdo: <?xml version="1.0" encoding="ISO-8859-1"?> <!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.2//EN" "http://java.sun.com/j2ee/dtds/web-app_2.2.dtd"> <web-app> </web-app> Copie então o arquivo compilado Ola.class para o subdiretório /webapps/nome aplicação/Web-inf/classes do Tomcat. Executando o Servlet Invocando diretamente pelo Navegador Podemos executar um Servlet diretamente digitando a URL do Servlet no navegador. A URL em geral possui o seguinte formato: http://máquina:porta/nome aplicação/servlet/nome servlet A palavra servlet que aparece na URL não indica um subdiretório no servidor. Ela indica que esta é uma requisição para um Servlet. Por exemplo, suponha que o nome da aplicação criada no Tomcat seja teste. Então a URL para a invocação do Servlet do exemplo XX.XX teria a seguinte forma: http://localhost:8080/teste/servlet/Ola A URL para a chamada do Servlet pode ser alterada de modo a ocultar qualquer referência à diretórios ou a tecnologias de implementação. No caso do Tomcat essa configuração é no arquivo web.xml do diretório Web-inf da aplicação. Por exemplo, para eliminar a palavra servlet da URL poderíamos J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 362 inserir as seguintes linhas no arquivo web.xml entre os tags <web-app> e </web-app>: <servlet> <servlet-name> Ola </servlet-name> <servlet-class> Ola </servlet-class> </servlet> <servlet-mapping> <servlet-name> Ola </servlet-name> <url-pattern> /Ola </url-pattern> </servlet-mapping> Invocando em uma página HTML No caso de uma página HTML basta colocar a URL na forma de link. Por exemplo, <a href="http://localhost:8080/teste/servlet/Ola>Servlet Ola</a> Neste caso o Servlet Ola será solicitado quando o link associado ao texto “Servlet Ola” for acionado. Diferenças entre as requisições GET e POST Os dois métodos mais comuns, definidos pelo protocolo HTTP, de se enviar uma requisições a um servidor Web são os métodos POST e GET. Apesar de aparentemente cumprirem a mesma função, existem diferenças importantes entre estes dois métodos. O método GET tem por objetivo enviar uma requisição por um recurso. As informações necessárias para a obtenção do recurso (como informações digitadas em formulários HTML) são adicionadas à URL e, por consequência, não são permitidos caracteres inválidos na formação de URLs, J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 363 como por espaços em branco e caracteres especiais. J á na requisição POST os dados são enviados no corpo da mensagem. O método GET possui a vantagem de ser idempotente, ou seja, os servidores Web podem assumir que a requisição pode ser repetida, sendo possível adicionar à URL ao bookmark. Isto é muito útil quando o usuário deseja manter a URL resultante de uma pesquisa. Como desvantagem as informações passadas via GET não podem ser muito longas, um vez o número de caracteres permitidos é por volta de 2K. J á as requisições POST a princípio podem ter tamanho ilimitado. No entanto, elas não são idempotente, o que as tornam ideais para formulários onde os usuários precisam digitar informações confidenciais, como número de cartão de crédito. Desta forma o usuário é obrigado a digitar a informação toda vez que for enviar a requisição, não sendo possível registrar a requisição em um bookmark. Concorrência Uma vez carregado o Servlet não é mais descarregado, a não ser que o servidor Web tenha sua execução interrompida. De modo geral, cada requisição que deve ser direcionada a determinada instância de Servlet é tratada por um thread sobre a instância de Servlet. Isto significa que se existirem duas requisições simultâneas que devem ser direcionadas para um mesmo objeto o container criará dois threads sobre o mesmo objeto Servlet para tratar as requisições. A figura XX.XX ilustra esta situação. Figura XV.XX. Relacionamento entre as instâncias dos Servlets e os threads. Servlet2 thread1 Servlet1 thread2 thread1 usuário 1 usuário 2 usuário 3 J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 364 Em conseqüência disto temos o benefícios de uma sobrecarga para servidor, uma vez que a criação de threads é menos onerosa do que a criação de processos, e uma aparente melhora no tempo de resposta. Por outro lado, o fato dos Servlets operarem em modo multi-thread aumenta a complexidade das aplicações e cuidados especiais, como visto no capítulo sobre concorrência, devem tomados para evitar comportamentos erráticos. Por exemplo, suponha um Servlet que receba um conjunto de números inteiros e retorne uma página contendo a soma dos números. A exemplo XX.XX mostra o código do Servlet. O leitor pode imaginar um código muito mais eficiente para computar a soma de números, mas o objetivo do código do exemplo é ilustrar o problema da concorrência em Servlets. O exemplo contém também um trecho de código para recebimento de valores de formulários, o que será discutido mais adiante. import java.util.*; import javax.servlet.*; import javax.servlet.http.*; public class Soma extends HttpServlet { Vector v =new Vector(5); protected void doPost(HttpServletRequest req, HttpServletResponse res) throws ServletException, java.io.IOException { v.clear(); Enumeration e =req.getParameterNames(); while (e.hasMoreElements()) { String name =(String)e.nextElement(); String value =req.getParameter(name); if (value !=null) v.add(value); } res.setContentType("text/html"); java.io.PrintWriter out =res.getWriter(); out.println("<html>"); out.println("<head><title>Servlet</title></head>"); out.println("<body>"); out.println("<h1>A soma e'"); int soma =0; for(int i =0; i<v.size() ; i++) { soma +=Integer.parseInt((String)v.get(i)); } out.println(soma); out.println("<h1>"); out.println("</body>"); J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 365 out.println("</html>"); out.close(); } } Exemplo XX.XX- Servlet com problemas de concorrência. Note que o Servlet utiliza uma variável de instância para referenciar o Vector que armazena os valores. Se não forem usadas primitivas de sincronização (como no código do exemplo) e duas requisições simultâneas chegarem ao Servlet o resultado pode ser inconsistente, uma vez que o Vector poderá conter parte dos valores de uma requisição e parte dos valores de outra requisição. Neste caso, para corrigir esse problema basta declarar a variável como local ao método doPost() ou usar primitivas de sincronização. Obtendo Informações sobre a Requisição O objeto HttpServletRequest passado para o Servlet contém várias informações importantes relacionadas com a requisição, como por exemplo o método empregado (POST ou GET), o protocolo utilizado, o endereço remoto, informações contidas no cabeçalho e muitas outras. O Servlet do exemplo XX.XX retorna uma página contendo informações sobre a requisição e sobre o cabeçalho da requisição. import java.io.*; import java.util.*; import javax.servlet.*; import javax.servlet.http.*; public class RequestInfo extends HttpServlet { public void doGet(HttpServletRequest req, HttpServletResponse res) throws IOException, ServletException { res.setContentType("text/html"); PrintWriter out =res.getWriter(); out.println("<html><head>"); out.println("<title>Exemplo sobre Requisicao de Info </title>"); out.println("</head><body>"); out.println("<h3>Exemplo sobre Requisicao de Info </h3>"); out.println("Metodo: " +req.getMethod()+”<br>”); out.println("Request URI: " +req.getRequestURI()+”<br>”); out.println("Protocolo: " +req.getProtocol()+”<br>”); J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 366 out.println("PathInfo: " +req.getPathInfo()+”<br>”); out.println("Endereco remoto: " +req.getRemoteAddr()+”<br><br>”); Enumeration e =req.getHeaderNames(); while (e.hasMoreElements()) { String name =(String)e.nextElement(); String value =req.getHeader(name); out.println(name +" =" +value+"<br>"); } out.println("</body></html>"); } public void doPost(HttpServletRequest req, HttpServletResponse res) throws IOException, ServletException { doGet(req, res); } } Exemplo XX.XX- Servlet que retorna as informações sobre a requisição. Note que o método doPost() chama o método doGet(), de modo que o Servlet pode receber os dois tipos de requisição. A figura XX.XX mostra o resultado de uma execução do Servlet do exemplo XX.XX. Exemplo sobre Requisicao de Info Metodo: GET Request URI: /servlet/RequestInfo Protocolo: HTTP/1.0 PathInfo: null Endereco remoto: 127.0.0.1 Connection =Keep-Alive User-Agent =Mozilla/4.7 [en] (Win95; I) Pragma =no-cache Host =localhost:8080 Accept =image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, image/png, */* Accept-Encoding =gzip Accept-Language =en Accept-Charset =iso-8859-1,*,utf-8 Figura XX.XX- Saída da execução do Servlet que exibe as informações sobre a requisição. J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 367 Lidando com Formulários Ser capaz de lidar com as informações contidas em formulários HTML é fundamental para qualquer tecnologia de desenvolvimento de aplicações para Web. É por meio de formulários que os usuários fornecem dados, preenchem pedidos de compra e (ainda mais importante) digitam o número do cartão de crédito. As informações digitadas no formulário chegam até o Servlet por meio do objeto HttpServletRequest e são recuperadas por meio do método getParameter() deste objeto. Todo item de formulário HTML possui um nome e esse nome é passado como argumento para o método getParameter() que retorna na forma de String o valor do item de formulário. O Servlet do exemplo XX.XX exibe o valor de dois itens de formulários do tipo text. Um denominado nome e o outro denominado de sobrenome. Em seguida o Servlet cria um formulário contendo os mesmos itens de formulário. Note que um formulário é criado por meio do tag <form>. Como parâmetros opcionais deste tag temos método da requisição (method), é a URL para onde será submetida a requisição (action). No caso do exemplo, o método adotado é o POST e a requisição será submetida ao próprio Servlet Form. import java.io.*; import java.util.*; import javax.servlet.*; import javax.servlet.http.*; public class Form extends HttpServlet { public void doGet(HttpServletRequest req, HttpServletResponse res) throws IOException, ServletException { res.setContentType("text/html"); PrintWriter out =res.getWriter(); out.println("<html>"); out.println("<head><title>Trata formulario</title></head>"); out.println("<body bgcolor=\"white\">"); out.println("<h3>Trata formulario</h3>"); String nome =req.getParameter("nome"); J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 368 String sobreNome =req.getParameter("sobrenome"); if (nome !=null || sobreNome !=null) { out.println("Nome =" +nome +"<br>"); out.println("Sobrenome =" +sobreNome); } out.println("<P>"); out.print("<form action=\"Form\" method=POST>"); out.println("Nome : <input type=text size=20 name=nome><br>"); out.println("Sobrenome: <input type=text size=20 name=sobrenome><br>"); out.println("<input type=submit>"); out.println("</form>"); out.println("</body></html>"); } public void doPost(HttpServletRequest req, HttpServletResponse res) throws IOException, ServletException { doGet(req, res); } } Exemplo XX.XX- Servlet para lidar com um formulário simples. Lidando com Cookies Um cookie nada mais é que um bloco de informação que é enviado do servidor para o navegador no cabeçalho página. A partir de então, dependendo do tempo de validade do cookie, o navegador reenvia essa informação para o servidor a cada nova requisição. Dependo do caso o cookie é também armazenado no disco da máquina cliente e quando o site é novamente visitado o cookie enviado novamente para o servidor, fornecendo a informação desejada. Os cookies foram a solução adotada pelos desenvolvedores do Netscape para implementar a identificação de clientes sobre um protocolo HTTP que não é orientado à conexão. Esta solução, apesar das controvérsias sobre a possibilidade de quebra de privacidade, passou ser amplamente adotada e hoje os cookies são parte integrante do padrão Internet, normalizados pela norma RFC 2109. A necessidade da identificação do cliente de onde partiu a requisição e o monitoramento de sua interação com o site (denominada de sessão) é importante para o desenvolvimento de sistemas para a Web pelas seguintes razões: J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 369 • É necessário associar os itens selecionados para compra com o usuário que deseja adquiri-los. Na maioria da vezes a seleção dos itens e compra é feita por meio da navegação de várias páginas do site e a todo instante é necessário distinguir os usuários que estão realizando as requisições. • É necessário acompanhar as interação do usuário com o site para observar seu comportamento e, a partir dessas informações, realizar adaptações no site para atrair um maior número de usuários ou realizar campanhas de marketing. • É necessário saber que usuário está acessando o site para, de acordo com o seu perfil, fornecer uma visualização e um conjunto de funcionalidades adequadas às suas preferências. Todas essas necessidades não podem ser atendidas com o uso básico do protocolo HTTP, uma vez que ele não é orientado à sessão ou conexão. Com os cookies é possível contornar essa deficiência, uma vez que as informações que são neles armazenadas podem ser usadas para identificar os clientes. Existem outras formas de contornar a deficiência do protocolo de HTTP, como a codificação de URL e o uso de campos escondidos nas páginas HTML, mas o uso de cookies é a técnica mais utiliza, por ser mais simples e padronizada. No entanto, o usuário pode impedir que o navegador aceite cookies, o que torna o ato de navegar pela Web muito desagradável. Neste caso, é necessário utilizar as outras técnicas para controle de sessão. A API Servlet permite a manipulação explicita de cookies. Para controle de sessão o programador pode manipular diretamente os cookies, ou usar uma abstração de nível mais alto, implementada por meio do objeto HttpSession. Se o cliente não permitir o uso de cookies a API Servlet fornece métodos para a codificação de URL. O exemplo XX.XX mostra o uso de cookies para armazenar as informações digitadas em um formulário. import java.io.*; import javax.servlet.*; import javax.servlet.http.*; public class CookieTeste extends HttpServlet { public void doGet(HttpServletRequest req, HttpServletResponse res) throws IOException, ServletException { J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 370 res.setContentType("text/html"); PrintWriter out =res.getWriter(); out.println("<html>"); out.println("<body bgcolor=\"white\">"); out.println("<head><title>Teste de Cookies</title></head>"); out.println("<body>"); out.println("<h3>Teste de Cookies</h3>"); Cookie[] cookies =req.getCookies(); if (cookies.length >0) { for (int i =0; i <cookies.length; i++) { Cookie cookie =cookies[i]; out.print("Cookie Nome: " + cookie.getName() + "<br>"); out.println(" Cookie Valor: " +cookie.getValue() +"<br><br>"); } } String cName =req.getParameter("cookienome"); String cValor =req.getParameter("cookievalor"); if (cName !=null && cValor !=null) { Cookie cookie =new Cookie(cName ,cValor); res.addCookie(cookie); out.println("<P>"); out.println("<br>"); out.print("Nome : "+cName +"<br>"); out.print("Valor : "+cValor); } out.println("<P>"); out.print("<form action=\"CookieTeste\" method=POST>"); out.println("Nome : <input type=text length=20 name=cookienome><br>"); out.println("Valor : <input type=text length=20 name=cookievalor><br>"); out.println("<input type=submit></form>"); out.println("</body>"); out.println("</html>"); } public void doPost(HttpServletRequest req, HttpServletResponse res) throws IOException, ServletException { doGet(req, res); } } J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 371 Exemplo XX.XX- Servlet para lidar com Cookies. Para se criar um cookie é necessário criar um objeto Cookie, passando para o construtor um nome e um valor, sendo ambos instâncias de String. O cookie é enviado para o navegador por meio do método addCookie() do objeto HttpServletResponse. Um vez que os cookies são enviados no cabeçalho da página, o método addCookie() deve ser chamado antes do envio de qualquer conteúdo para o navegador. Para recuperar os cookies enviados pelo navegador usa-se o método getCookies() do objeto HttpServletRequest que retorna um array de Cookie. Os métodos getName() e getvalue() do objeto Cookie são utilizados para recuperar o nome o valor da informação associada ao cookie. Os objetos da classe Cookie possuem vários métodos para controle do uso de cookies. É possível definir tempo de vida máximo do cookie, os domínios que devem receber o cookie (por default o domínio que deve receber o cookie é o que o criou), o diretório da página que deve receber o cookie, se o cookie deve ser enviado somente sob um protocolo seguro e etc. Por exemplo, para definir a idade máxima de um cookie devemos utilizar o método setMaxAge(), passando um inteiro como parâmetro. Se o inteiro for positivo indicará em segundos o tempo máximo de vida do cookie. Um valor negativo indica que o cookie deve apagado quando o navegador terminar. O valor zero indica que o cookie deve ser apagado imediatamente. O trecho de código exemplo XX.XX mostra algumas alterações no comportamento default de um cookie. ... Cookie cookie =new Cookie(cName ,cValor); cookie.setDomain(“*.uvf.br”); // todos os domínios como dpi.ufv.br mas não *.dpi.ufv.br cookie.setMaxAge (3600); // uma hora de tempo de vida ... Exemplo XX.XX- Mudanças no comportamento default do cookie. Lidando com Sessões A manipulação direta de cookies para controle de sessão é um tanto baixo nível, uma vez que o usuário deve se preocupar com a identificação, tempo de vida e outros detalhes. Por isso a API Servlet fornece um objeto com controles J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 372 de nível mais alto para monitorar a sessão, o HttpSession. O objeto HttpSession monitora a sessão utilizando cookies de forma transparente. No entanto, se o cliente não aceitar o uso de cookies é possível utilizar como alternativa a codificação de URL para adicionar o identificador da sessão. Essa opção, apesar de ser mais genérica, não á primeira opção devido a possibilidade de criação de gargalos pela necessidade da análise prévia de todas requisições que chegam ao servidor. O exemplo XX.XX mostra o uso de um objeto HttpSession para armazenar as informações digitadas em um formulário. import java.io.*; import java.util.*; import javax.servlet.*; import javax.servlet.http.*; public class SessionTeste extends HttpServlet { public void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException, ServletException { resp.setContentType("text/html"); PrintWriter out =resp.getWriter(); out.println("<html><head>"); out.println("<title>Teste de Sessao</title>"); out.println("</head>"); out.println("<body>"); out.println("<h3>Teste de Sessao</h3>"); HttpSession session =req.getSession(true); out.println("Identificador: " +session.getId()); out.println("<br>"); out.println("Data: "); out.println(new Date(session.getCreationTime()) +"<br>"); out.println("Ultimo acesso: "); out.println(new Date(session.getLastAccessedTime())); String nomedado =req.getParameter("nomedado"); String valordado =req.getParameter("valordado"); if (nomedado !=null && valordado !=null) { session.setAttribute(nomedado, valordado); } out.println("<P>"); out.println("Dados da Sessao:" +"<br>"); Enumeration valueNames =session.getAttributeNames(); J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 373 while (valueNames.hasMoreElements()) { String name =(String)valueNames.nextElement(); String value =(String) session.getAttribute(name); out.println(name +" =" +value+"<br>"); } out.println("<P>"); out.print("<form action=\"SessionTeste\" method=POST>"); out.println("Nome: <input type=text size=20 name=nomedado><br>"); out.println("Valor: <input type=text size=20 name=valordado><br>"); out.println("<input type=submit>"); out.println("</form>"); out.println("</body></html>"); } public void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException, ServletException { doGet(req, resp); } } Exemplo XX.XX- Servlet para lidar com Sessões. Para controlar a sessão é necessário obter um objeto HttpSession por meio do método getSession() do objeto HttpServletRequest. Opcionalmente, o método getSession() recebe como argumento um valor booleano que indica se é para criar o objeto HttpSession se ele não existir (argumento true) ou se é para retorna null caso ele não exista (argumento false). Para se associar um objeto ou informação à sessão usa-se o método setAttribute() do objeto HttpSession, passando para o método um String e um objeto que será identificado pelo String. Note que o método aceita qualquer objeto e, portanto, qualquer objeto pode ser associado à sessão. Os objetos associados a uma sessão são recuperados com o uso método getAttribute() do objeto HttpSession, que recebe como argumento o nome associado ao objeto. Para se obter uma enumeração do nomes associados à sessão usa-se o método getAttributeNames() do objeto HttpSession. A figura XX.XX mostra o resultado da execução do exemplo XX.XX. J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 374 Teste de Sessao Identificador: session3 Data: Sun May 28 15:19:15 GMT-03:00 2000 Ultimo acesso: Sun May 28 15:19:43 GMT-03:00 2000 Dados da Sessao: Alcione =4 Alexandra =6 Nome Valor Figura XVI-1 Saída resultante da execução do Servlet que lida com Sessões. Enviar Consulta J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 375 JSP Servlets é uma boa idéia, mas você se imaginou montando uma página complexa usando println()? Muitas vezes o desenvolvimento de um site é uma tarefa complexa que envolve vários profissionais. A tarefa de projeto do layout da página fica a cargo do Web Designer, incluindo a diagramação dos textos e imagens, aplicação de cores, tratamento das imagens, definição da estrutura da informação apresentada no site e dos links para navegação pela mesma. J á o Desenvolvedor Web é responsável pela criação das aplicações que vão executar em um site. O trabalho destes dois profissionais é somado na criação de um único produto, mas durante o desenvolvimento a interferência mutua deve ser a mínima possível. Ou seja, um profissional não deve precisar alterar o que é foi feito pelo outro profissional para cumprir sua tarefa. A tecnologia Servlet não nos permite atingir esse ideal. Por exemplo, suponha que um Web Designer terminou o desenvolvimento de uma página e a entregou para o Desenvolvedor Web codificar em um Servlet. Se após a codificação o Web Designer desejar realizar uma alteração na página será necessário que ele altere o código do Servlet (do qual ele nada entende) ou entregar uma nova página para o Desenvolvedor Web para que ele a codifique totalmente mais uma vez. Qualquer uma dessas alternativas são indesejáveis e foi devido a esse problema a Sun desenvolveu uma tecnologia baseada em Servlets chamada de J SP. J ava Server Pages (J SP) são páginas HTML que incluem código J ava e outros tags especiais. Desta forma as partes estáticas da página não precisam ser geradas por println(). Elas são fixadas na própria página. A parte dinâmica é gerada pelo código J SP. Assim a parte estática da página pode ser projetada por um Web Designer que nada sabe de J ava. A primeira vez que uma página J SP é carregada pelo container J SP o código J ava é compilado gerando um Servlet que é executado, gerando uma página HTML que é enviada para o navegador. As chamadas subsequentes são enviadas diretamente ao Servlet gerado na primeira requisição, não ocorrendo mais as etapas de geração e compilação do Servlet. A figura XX.XX mostra um esquema das etapas de execução de uma página J SP na primeira vez que é requisitada. Na etapa (1) a requisição é enviada para um servidor Web que reencaminha a requisição (etapa 2) para o container Servlet/J SP. Na etapa (3) o container verifica que não existe nenhuma instância de Servlet correspondente à página J SP. Neste caso, a página J SP é traduzida para código fonte de uma classe Servlet que será usada na resposta à requisição. Na etapa (4) o código fonte do Servlet é compilado, e na etapa (5) é criada uma J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 376 instância da classe. Finalmente, na etapa (6) é invocado o método service() da instância Servlet para gerar a resposta à requisição. Figura XVI-1 Etapas da primeira execução de uma página J SP. A idéia de se usar scripts de linguagens de programação em páginas HTML que são processados no lado servidor para gerar conteúdo dinâmico não é restrita à linguagem J ava. Existem várias soluções desse tipo fornecida por outros fabricantes. Abaixo segue uma comparação de duas das tecnologias mais populares com J SP. PHP X JSP PHP (Personal Home Pages) é uma linguagem script para ser executada no lado servidor criada em 1994 como um projeto pessoal de Rasmus Lerdorf. Atualmente encontra-se na versão 4. A sintaxe é fortemente baseada em C mas possui elementos de C++, J ava e Perl. Possui suporte à programação OO por meio de classes e objetos. Possui também suporte extensivo à Banco de dados ODBC, MySql, Sybase, Oracle e outros. PHP é uma linguagem mais fácil no desenvolvimento de pequenas aplicações para Web em relação à J SP, uma vez Servidor Http (1) Requisição de página J SP Container Servlet/J SP Página jsp Fonte Servlet (3) Traduz (2) Encaminha a requisição (5) Instancia e executa (6) Resposta à requisição Navegador Bytecode Servlet (4) Compila J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 377 que é uma linguagem mais simples e menos rígida do que J SP. No entanto, a medida que passamos para aplicações de maior porte, o uso de PHP não é indicado, uma vez que necessário o uso de linguagens com checagem mais rígidas e com maior suporte à escalabilidade, como é o caso de J ava. ASP X JSP ASP (Active Server Pages) é a solução desenvolvida pela Microsoft® para atender as requisições feitas à servidores Web. Incorporada inicialmente apenas ao Internet Information Server (IIS), no entanto, atualmente já é suportada por outros servidores populares, como o Apache. O desenvolvimento de páginas que usam ASP envolve a produção de um script contendo HTML misturado com blocos de código de controle ASP. Este código de controle pode conter scripts em J avaScript ou VBScript. A primeira vantagem de J SP sobre ASP é que a parte dinâmica é escrita em J ava e não Visual Basic ou outra linguagem proprietária da Microsoft, portanto J SP é mais poderoso e fácil de usar. Em segundo lugar J SP é mais portável para outros sistemas operacionais e servidores WEB que não sejam Microsoft. Primeiro exemplo em JSP Para que o leitor possa ter uma idéia geral da tecnologia J SP apresentaremos agora a versão J SP do Olá mundo. O exemplo XX.XX mostra o código da página. <html> <head> <title>Exemplo J SP</title> </head> <body> <% String x = " Ol&aacute; Mundo!" ; %> <%=x%> </body> </html> Exemplo XX.XX- Versão J SP do Olá mundo. J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 378 Quem está habituado aos tags HTML notará que se trata basicamente de uma página HTML contendo código J ava delimitado pelos símbolos “<%” e “%>”. Para facilitar a visualização destacamos os scripts J ava com negrito. No primeiro trecho de script é declarada uma variável x com o valor “Olá mundo” (a seqüência &acute; é denota ‘á’ em HTML). No segundo trecho de script o conteúdo da variável x é extraído e colocado na página resultante da execução do Servlet correspondente. Em seguida mostraremos como executar o exemplo XX.XX. Executando o arquivo JSP Para executar o exemplo XX.XX salve-o com a extensão .jsp. Por exemplo ola.jsp. Se você estiver usando o servidor Tomcat, coloque-o arquivo no subdiretório /webapps/examples/jsp do Tomcat. Por exemplo examples/jsp/teste. Para invocar o arquivo J SP basta embutir a URL em uma página ou digitar diretamente a seguinte URL no navegador. http://localhost:8080/examples/jsp/ola.jsp Usamos o diretório /webapps/examples/jsp para testar rapidamente o exemplo. Para desenvolver uma aplicação é aconselhável criar um diretório apropriado como mostrado na seção que tratou de Servlets. O Servlet criado a partir da página J SP é colocado em um diretório de trabalho. No caso do Tomcat o Servlet é colocado em subdiretório associado à aplicação subordinado ao diretório /work do Tomcat. O exemplo XX.XX mostra os principais trechos do Servlet criado a partir da tradução do arquivo ola.jsp pelo tradutor do Tomcat. Note que o Servlet é subclasse de uma classe HttpJspBase e não da HttpServlet. Além disso, o método que executado em resposta à requisição é o método _jspService() e não o método service(). Note também que todas as partes estáticas da página J SP são colocadas como argumentos do método write() do objeto referenciado out. J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 379 public class _0002fjsp_0002fola_00032_0002ejspola_jsp_0 extends HttpJ spBase { .... public void _jspService(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { .... PageContext pageContext =null; HttpSession session =null; ServletContext application =null; ServletConfig config =null; J spWriter out =null; ... try { ... out.write("<html>\r\n <head>\r\n <title>Exemplo J SP</title>\r\n </head>\r\n <body>\r\n"); String x ="Ol&aacute; Mundo!"; out.write("\r\n"); out.print(x); out.write("\r\n </body>\r\n</html>\r\n"); ... }catch (Exception ex) { ... } } } Exemplo XX.XX- Servlet correspondente à página J SP do Olá mundo. Objetos implícitos No exemplo XX.XX pode-se ver a declaração de variáveis que referenciam a alguns objetos importantes. Estas variáveis estão disponíveis para o projetista da página J SP. As variáveis mais importantes são: Classe Variável HttpServletRequest HttpServletResponse PageContext ServletContext HttpSession J spWriter request response pageContext application session out J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 380 Os objetos referenciados pelas variáveis request e response já tiveram seu uso esclarecido na seção sobre Servlets. O objeto do tipo JspWriter tem a mesma função do PrinterWriter do Servlet. Os outros objetos terão sua função esclarecida mais adiante. Tags JSP Os tags J SP possuem a seguinte forma geral: <% Código JSP %> O primeiro caractere % pode ser seguido de outros caracteres que determinam o significado preciso do código dentro do tag. Os tags J SP possuem correspondência com os tags XML. Existem cinco categorias de tags J SP: Expressões Scriptlets Declarações Diretivas Comentários Em seguida comentaremos cada uma dessas categorias. Expressões <%= expressões %> Expressões são avaliadas, convertidas para String e colocadas na página enviada. A avaliação é realizada em tempo de execução, quando a página é requisitada. Exemplos: <%= new java.util.Date() %> <%= request.getMethod() %> No primeiro exemplo será colocado na página a data corrente em milésimo de segundos e no segundo será colocado o método usado na J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 381 requisição. Note que cada expressão contém apenas um comando J ava. Note também que o comando J ava não é terminado pelo caractere ‘;’. Scriptlets <% código Java %> Quando é necessário mais de um comando J ava ou o resultado da computação não é para ser colocado na página de resposta é preciso usar outra categoria de tags J SP: os Scriptlets . Os Scriptlets permitem inserir trechos de código em J ava na página J SP. O exemplo XX.XX mostra uma página J SP contendo um Scriptlet que transforma a temperatura digitada em celcius para o equivalente em Fahrenheit. <html> <head><title>Conversao Celcius Fahrenheit </title></head> <body> <% String valor = request.getParameter(" celcius" ); if (valor != null ) { double f = Double.parseDouble(valor)*9/5 +32; out.println(" <P>" ); out.println(" <h2>Valor em Fahrenheit:" +f +" <h2><br>" ); } %> <form action=conversao.jsp method=POST> Celcius: <input type=text size=20 name=celcius><br> <input type=submit> </form> </body> </html> Exemplo XX.XX- Página J SP que converte graus Celcius para Fahrenheit. Note o uso das variáveis request e out sem a necessidade de declaração. Todo o código digitado é inserido no método _jspService(). A figura XX.XX mostra o resultado da requisição após a digitação do valor 30 na caixa de texto do formulário. J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 382 Figura XVI-XX Resultado da conversão de 30 graus celcius. O código dentro do scriptlet é inserido da mesma forma que é escrito e todo o texto HTML estático antes e após ou um scriptlet é convertido para comandos print(). Desta forma o scriptlets não precisa conter comandos para código estático e blocos de controle abertos afetam o código HTML envolvidos por scriptlets. O exemplo XX.XX mostra dois formas de se produzir o mesmo efeito. No código da esquerda os Scriplets se intercalam com código HTML. O código HTML, quando da tradução da página J SP para Servlet é inserido como argumentos de métodos println() gerando o código da direita. Ambas as formas podem ser usadas em páginas J SP e produzem o mesmo efeito. Previs&atilde;o do Tempo <% if (Math.random() < 0.5) { %> Hoje vai <B>fazer sol</B>! <% } else { %> Hoje vai <B>chover</B>! <% } %> out.println("Previs&atilde;o do Tempo"); if (Math.random() <0.5) { out.println(" Hoje vai <B>fazer sol</B>!"); }else { out.println(" Hoje vai <B>chover</B>!"); } Exemplo XX.XX- Dois códigos equivalentes. Declarações <%! Código Java %> Valor em Fahrenheit:86.0 Celcius: Enviar Consulta J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 383 Uma declaração J SP permite definir variáveis ou métodos que são inseridos no corpo do Servlet. Como as declarações não geram saída, elas são normalmente usadas em combinação com expressões e scriptlets. O Exemplo XX.XX mostra a declaração de uma variável que é usada para contar o número de vezes que a página corrente foi requisitada desde que foi carregada. <%! Private int numAcesso = 0; %> Acessos desde carregada: <%= ++ numAcesso %> Exemplo XX.XX- Declaração de uma variável usando o tag de declaração. As variáveis declaradas desta forma serão variáveis de instância. J á as variáveis declaradas em Scriptlets são variáveis locais ao método _jspService(). Por isso é possível contar o número de requisições com o exemplo XX.XX. Se variável fosse declarada em um Scriptlet a variável seria local ao método _jspService() e, portanto, teria seu valor reinicializado a cada chamada. Como já foi dito, os tags de declarações permitem a declaração de métodos. O Exemplo XX.XX mostra a declaração de um método que converte celcius para Fahrenheit. <%! private double converte(double c) { return c*9/5 +32; } %> Exemplo XX.XX- Declaração de um método para a conversão de celcius para Fahrenheit. Comentários J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 384 Existem dois tipos de comentários utilizados em páginas J SP. O primeiro exclui todo o bloco comentado da saída gerada pelo processamento da página. A forma geral deste tipo de comentário é a seguinte: <%--comentário --%> O segundo tipo de comentário é o utilizado em páginas HTML. Neste caso o comentário é enviado dentro da página de resposta. A forma geral deste tipo de comentário é a seguinte: <!—comentário --> Diretivas Diretivas são mensagens para J SP container. Elas não enviam nada para a página mas são importantes para definir atributos J SP e dependências com o J SP container. A forma geral da diretivas é a seguinte: <%@ Diretiva atributo="valor" %> ou <%@ Diretiva atributo 1 ="valor 1 " atributo 2 ="valor 2 " ... atributo N =" valor N " %> Em seguida comentaremos as principais diretivas. Diretiva page <%@ page atributo 1 ="valor 1 " ... atributo N =" valor N " %> A diretiva page permite a definição dos seguintes atributos: import contentType isThreadSafe J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 385 session buffer autoflush info errorPage isErrorPage language Segue a descrição de cada um desses atributos. Atributo e Forma Geral Descrição import="package.class" ou import="package.class 1 ,.. .,package.class N " Permite especificar os pacotes que devem ser importados para serem usados na página J SP. Exemplo: <%@ page import="java.util.*" %> contentType="MIME-Type" Especifica o tipo MIME da saída. O default é text/html. Exemplo: <%@ page contentType="text/plain" %> possui o mesmo efeito do scriptlet <% response.setContentType("text/plain") ; %> isThreadSafe="true|false" Um valor true (default) indica um processamento normal do Servlet, onde múltiplas requisições são processadas simultaneamente. Um valor false indica que o processamento deve ser feito por instancias separadas do Servlet ou serialmente. session="true|false” Um valor true (default) indica que a variável predefinida session (HttpSession) deve ser associada à sessão, se existir, caso contrário uma nova sessão deve ser criada e associada a ela. Um valor false indica que nenhuma sessão será usada. buffer="sizekb|none" Especifica o tamanho do buffer para escrita usado pelo objeto J spWriter. O tamanho default não é menor que 8k.. autoflush="true|false” Um valor true (default) indica que o buffer deve ser J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 386 esvaziado quando estiver cheio. info="mensagem" Define uma cadeia de caracteres que pode ser recuperada via getServletInfo(). errorPage="url” Especifica a página J SP que deve ser processada em caso de exceções não capturadas. isErrorPage="true|false” Indica se a página corrente pode atuar como página de erro para outra página J SP. O default é false. Language="java” Possibilita definir a linguagem que está sendo usada. No momento a única possibilidade é J ava. Tabela XVI.XX –Atributos da diretiva page. Diretiva include <%@ include file="relative url" %> Permite incluir arquivos no momento em que a página J SP é traduzida em um Servlet. Exemplo: <%@ include file="/meuarq.html" %> Extraindo Valores de Formulários Uma página J SP, da mesma forma que um Servlet, pode usar o objeto referenciado pela variável request para obter os valores dos parâmetros de um formulário. O exemplo XX.XX usado para converter graus Celcius em Fahrenheit fez uso deste recurso. O exemplo XX.XX mostra outra página J SP com formulário. Note que o scriptlet é usado para obter o nome e os valores de todos os parâmetros contidos no formulário. Como o método getParameterNames() retorna uma referência a um objeto Enumeration é preciso importar o pacote java.util, por meio da diretiva page. <%@ page import="java.util.*" %> <html><body> <H1>Formulário</H1> <% J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 387 Enumeration campos = request.getParameterNames(); While(campos.hasMoreElements()) { String campo = (String)campos.nextElement(); String valor = request.getParameter(campo); %> <li><%= campo %> = <%= valor %></li> <% } %> <form method="POST" action="form.jsp"> Nome: <input type="text" size="20" name="nome" ><br> Telefone: <input type="text" size="20" name="telefone"><br> <INPUT TYPE=submit name=submit value="envie"> </form> </body></html> Exemplo XVI.XX – Página JSP com formulário. A figura XX.XX mostra o resultado da requisição após a digitação dos valores Alcione e 333-3333 nas caixas de texto do formulário. Figura XVI.XX- Saída do exemplo XX.XX. Criando e Modificando Cookies Formulário • telefone =333-3333 • nome =Alcione • submit =envie Nome: Telefone: envie J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 388 Da mesma for que em Servlets os cookies em J SP são tratados por meio da classe Cookie. Para recuperar os cookies enviados pelo navegador usa-se o método getCookies() do objeto HttpServletRequest que retorna um arranjo de Cookie. Os métodos getName() e getvalue() do objeto Cookie são utilizados para recuperar o nome o valor da informação associada ao cookie. O cookie é enviado para o navegador por meio do método addCookie() do objeto HttpServletResponse. O exemplo XX.XX mostra uma página J SP que exibe todos os cookies recebidos em uma requisição e adiciona mais um na resposta. <html><body> <H1>Session id: <%= session.getId() %></H1> <% Cookie[] cookies = request.getCookies(); For(int i = 0; i < cookies.length; i++) { %> Cookie name: <%= cookies[i].getName() %> <br> Value: <%= cookies[i].getValue() %><br> antiga idade máxima em segundos: <%= cookies[i].getMaxAge() %><br> <% cookies[i].setMaxAge(5); %> nova idade máxima em segundos: <%= cookies[i].getMaxAge() %><br> <% } %> <%! Int count = 0; int dcount = 0; %> <% response.addCookie(new Cookie( ”Cookie " + count++, ”Valor " + dcount++)); %> </body></html> Exemplo XVI.XX – Página JSP que exibe os cookies recebidos. A figura XX.XX mostra o resultado após três acessos seguidos à página J SP. Note que existe um cookie a mais com o nome JSESSIONID e valor igual à sessão. Este cookie é o usado pelo container para controlar a sessão. Session id: 9ppfv0lsl1 Cookie name: Cookie 0 value: Valor 0 antiga idade máxima em segundos: -1 nova idade máxima em segundos: 5 Cookie name: Cookie 1 J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 389 value: Valor 1 antiga idade máxima em segundos: -1 nova idade máxima em segundos: 5 Cookie name: J SESSIONID value: 9ppfv0lsl1 antiga idade máxima em segundos: -1 nova idade máxima em segundos: 5 Figura XVI.XX- Saída do exemplo XX.XX após três acessos. Lidando com sessões O atributos de uma sessão são mantidos em um objeto HttpSession referenciado pela variável session. Pode-se armazenar valores em uma sessão por meio do método setAttribute() e recuperá-los por meio do método getAttribute(). O tempo de duração default de uma sessão inativa (sem o recebimento de requisições do usuário) é 30 minutos mas esse valor pode ser alterado por meio do método setMaxInactiveInterval(). O exemplo XX.XX mostra duas páginas J SP. A primeira apresenta um formulário onde podem ser digitados dois valores recebe dois valores de digitados em um formulário e define o intervalo máximo de inatividade de uma sessão em 10 segundos. A segunda página recebe a submissão do formulário, insere os valores na sessão e apresenta os valores relacionados com a sessão assim como a identificação da sessão. <%@ page import="java.util.*" %> <html><body> <H1>Formulário</H1> <H1>Id da sess&atilde;o: <%= session.getId() %></H1> <H3><li>Essa sess&atilde;o foi criada em <%= session.getCreationTime() %></li></H3> <H3><li>Antigo intervalo de inatividade = <%= session.getMaxInactiveInterval() %></li> <% session.setMaxInactiveInterval(10); %> <li>Novo intervalo de inatividade= <%= session.getMaxInactiveInterval() %></li> </H3> J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 390 <% Enumeration atribs = session.getAttributeNames(); while(atribs.hasMoreElements()) { String atrib = (String)atribs.nextElement(); String valor = (String)session.getAttribute(atrib); %> <li><%= atrib %> = <%= valor %></li> <% } %> <form method="POST" action="sessao2.jsp"> Nome: <input type="text" size="20" name="nome" ><br> Telefone: <input type="text" size="20" name="telefone" > <br> <INPUT TYPE=submit name=submit value="envie"> </form> </body></html> <html><body> <H1>Id da sess&atilde;o: <%= session.getId() %></H1> <% String nome = request.getParameter("nome"); String telefone = request.getParameter("telefone"); if (nome !=null && nome.length()>0) session.setAttribute("nome",nome); if (telefone !=null &&telefone.length()>0) session.setAttribute("telefone",telefone); %> <FORM TYPE=POST ACTION=sessao1.jsp> <INPUT TYPE=submit name=submit Value="Retorna"> </FORM> </body></html> Exemplo XVI.XX – Exemplo do uso de sessão. O exemplo XX.XX mostra que a sessão é mantida mesmo quando o usuário muda de página. As figura XX.XX e XX.XX mostram o resultado da requisição após a digitação dos valores Alcione e 333-3333 nas caixas de texto do formulário, à submissão para página sessao2.jsp e o retorno à página sessao1.jsp. J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 391 Figura XVI.XX- Tela da página sessao1.jsp. Figura XVI.XX- Tela da página sessao2.jsp. O Uso de JavaBeans Formulário Id da sessão: soo8utc4m1 Essa sessão foi criada em 1002202317590 Antigo intervalo de inatividade = 1800 Novo intervalo de inatividade= 10 telefone =333-3333 nome =Alcione Nome: Telefone: envie Id da sessão: soo8utc4m1 Retorna J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 392 A medida que o código J ava dentro do HTML torna-se cada vez mais complexo o desenvolvedor pode-se perguntar: J ava em HTML não é o problema invertido do HTML em Servlet? O resultado não será tão complexo quanto produzir uma página usando println()? Em outras palavras, estou novamente misturando conteúdo com forma? Para solucionar esse problema a especificação de J SP permite o uso de J avaBeans para manipular a parte dinâmica em J ava. J avaBeans já foram descritos detalhadamente em um capítulo anterior, mas podemos encarar um J avaBean como sendo apenas uma classe J ava que obedece a uma certa padronização de nomeação de métodos, formando o que é denominado de propriedade. As propriedades de um bean são acessadas por meio de métodos que obedecem a convenção getXxxx e setXxxx. , onde Xxxx é o nome da propriedade. Por exemplo, getItem() é o método usado para retornar o valor da propriedade item. A sintaxe para o uso de um bean em uma página J SP é: <jsp:useBean id="nome" class="package.class" /> Onde nome é o identificador da variável que conterá uma referência para uma instância do J avaBean. Você também pode modificar o atributo scope para estabelecer o escopo do bean além da página corrente. <jsp:useBean id="nome" scope="session" class="package.class" /> Para modificar as propriedades de um J avaBean você pode usar o jsp:setProperty ou chamar um método explicitamente em um scriptlet. Para recuperar o valor de uma propriedade de um J avaBean você pode usar o jsp:getProperty ou chamar um método explicitamente em um scriptlet. Quando é dito que um bean tem uma propriedade prop do tipo T significa que o bean deve prover um método getProp() e um método do tipo setProp(T). O exemplo XX.XX mostra uma página J SP e um J avaBean. A página instancia o J avaBean, altera a propriedade mensagem e recupera o valor da propriedade, colocando-o na página. Página bean.jsp <HTML><HEAD> <TITLE>Uso de beans</TITLE> </HEAD><BODY><CENTER> J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 393 <TABLE BORDER=5><TR><TH CLASS="TITLE">Uso de J avaBeans </TABLE> </CENTER><P> <jsp:useBean id=" teste" cl ass=” curso.BeanSimples" /> <jsp:setProperty name=" teste" property=" mensagem" value=” Ola mundo!" /> <H1>Mensagem: <I> <jsp:getProperty name=" teste" property=" mensagem" /> </I></H1> </BODY></HTML> Arquivo Curso/BeanSimples.java package curso; public class BeanSimples { private String men = "Nenhuma mensagem"; public String getMensagem() { return(men); } public void setMensagem(String men) { this.men = men; } } Exemplo XVI.XX – Exemplo do uso de JavaBean. A figura XX.XX mostra o resultado da requisição dirigida à página bean.jsp. Figura XVI.XX- Resultado da requisição à página bean.jsp. Se no tag setProperty usarmos o valor “*” para o atributo property então todos os valores de elementos de formulários que possuírem nomes iguais à propriedades serão transferidos para as respectivas propriedades Mensagem: Ola mundo! Uso de JavaBeans J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 394 no momento do processamento da requisição. Por exemplo, seja uma página jsp contendo um formulário com uma caixa de texto com nome mensagem, como mostrado no exemplo XX.XX. Note que, neste caso, a propriedade mensagem do J avaBean tem seu valor atualizado para o valor digitado na caixa de texto, sem a necessidade de uma chamada explícita no tag setProperty. Os valores são automaticamente convertidos para o tipo correto no bean. <HTML><HEAD><TITLE>Uso de beans</TITLE></HEAD> <BODY><CENTER> <TABLE BORDER=5><TR><TH CLASS="TITLE">Uso de J avaBeans </TABLE> </CENTER><P> <jsp:useBean id=" teste" cl ass=" curso.BeanSimpl es" /> <jsp:setProperty name=" teste" property=" *" /> <H1>Mensagem: <I> <jsp:getProperty name=" teste" property=" mensagem" /> </I></H1> <form method="POST" action="bean2.jsp"> Texto: <input type="text" size="20" name="mensagem" ><br> <INPUT TYPE=submit name=submit value="envie"> </form> </BODY></HTML> Exemplo XVI.XX – Exemplo de atualização automática da propriedade. A figura XX.XX mostra o resultado da requisição dirigida à página bean2.jsp após a digitação do texto Olá! Figura XVI.XX- Resultado da requisição à página bean2.jsp. Mensagem: Ola! Uso de JavaBeans envie Texto: J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 395 Escopo Existem quatro valores possíveis para o escopo de um objeto: page, request, session e application. O default é page. A tabela XX.XX descreve cada tipo de escopo. Escopo Descrição page Objetos declarados com nesse escopo são válidos até a resposta ser enviada ou a requisição ser encaminhada para outro programa no mesmo ambiente, ou seja, só podem ser referenciados nas páginas onde forem declarados. Objetos declarados com escopo page são referenciados pelo objeto pagecontext. request Objetos declarados com nesse escopo são válidos durante a requisição e são acessíveis mesmo quando a requisição é encaminhada para outro programa no mesmo ambiente. Objetos declarados com escopo request são referenciados pelo objeto request. session Objetos declarados com nesse escopo são válidos durante a sessão desde que a página seja definida para funcionar em uma sessão. Objetos declarados com escopo session são referenciados pelo objeto session. application Objetos declarados com nesse escopo são acessíveis por páginas no mesmo servidor de aplicação. Objetos declarados com escopo application são referenciados pelo objeto application. Tabela XVI.XX –Escopo dos objetos nas páginas JSP. Implementação de um Carrinho de compras O exemplo abaixo ilustra o uso de J SP para implementar um carrinho de compras virtual. O carrinho de compras virtual simula um carrinho de compras de supermercado, onde o cliente vai colocando os produtos selecionados para compra até se dirigir para o caixa para fazer o pagamento. No carrinho de compras virtual os itens selecionados pelo usuário são armazenados em uma estrutura de dados até que o usuário efetue o pagamento. Esse tipo de exemplo J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 396 exige que a página J SP funcione com o escopo session para manter o carrinho de compras durante a sessão. O exemplo XX.XX mostra um exemplo simples de implementação de carrinho de compras. O exemplo é composto por dois arquivos: um para a página J SP e um para o JavaBean que armazena os itens selecionados. Página compras.jsp <html> <jsp:useBean id=" carrinho" scope=" session" cl ass=" compra.Carrinho" /> <jsp:setProperty name=" carrinho" property=" *" /> <body bgcolor="#FFFFFF"> <% carrinho.processRequest(request); String[] items = carrinho.getItems(); if (items.length>0) { %> <font size=+2 color="#3333FF">Voc&ecirc; comprou os seguintes itens:</font> <ol> <% for (int i=0; i<items.length; i++) { out.println(" <li >" +items[i]); } } %> </ol> <hr> <form type=POST action=compras.jsp> <br><font color="#3333FF" size=+2>Entre um item para adicionar ou remover: </font><br> <select NAME="item"> <option>Televis&atilde;o <option>R&aacute;dio <option>Computador <option>V&iacute;deo Cassete </select> <p><input TYPE=submit name="submit" value="adicione"> <input TYPE=submit name="submit" value="remova"></form> </body> </html> J avaBean compra/Carrinho.java package compra; J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 397 import javax.servlet.http.*; import java.util.Vector; import java.util.Enumeration; public class Carrinho { Vector v =new Vector(); String submit =null; String item =null; private void addItem(String name) {v.addElement(name); } private void removeItem(String name) {v.removeElement(name); } public void setItem(String name) {item =name; } public void setSubmit(String s) {submit =s; } public String[] getItems() { String[] s =new String[v.size()]; v.copyInto(s); return s; } private void reset() { submit =null; item =null; } public void processRequest(HttpServletRequest request) { if (submit ==null) return; if (submit.equals("adicione")) addItem(item); el se if (submit.equals("remova")) removeItem(item); reset(); } } Exemplo XVI.XX – Implementação de um carrinho de compras Virtual. O exemplo XX.XX implementa apenas o carrinho de compras, deixando de fora o pagamento dos itens, uma vez que esta etapa depende de cada sistema. Geralmente o que é feito é direcionar o usuário para outra página onde ele digitará o número do cartão de crédito que será transmitido por meio de uma conexão segura para o servidor. Existem outras formas de pagamento, como J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 398 boleto bancário e dinheiro virtual. O próprio carrinho de compras geralmente é mais complexo, uma vez que os para compra devem ser obtidos dinamicamente de um banco de dados. A figura XX.XX mostra a tela resultante de algumas interações com o carrinho de compras. Figura XVI.XX- Carrinho de compras virtual. J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 399 Reencaminhando ou Redirecionando requisições Existem algumas situações onde pode ser desejável transferir uma requisição para outra URL. Isto é feito com frequência em sistemas que combinam o uso de Servlets juntamente com J SP. No entanto, a transferência pode ser para qualquer recurso. Assim, podemos transferir uma requisição de um Servlet para uma página J SP, HTML ou um Servlet. Da mesma forma uma página J SP pode transferir uma requisição para uma página J SP, HTML ou um Servlet. Existem dois tipos de transferência de requisição: o redirecionamento e o reencaminhamento. O redirecionamento é obtido usando o método sendRedirect() de uma instância HttpServletResponse, passando como argumento a URL de destino. O exemplo XX.XX mostra o código de um Servlet redirecionando para uma página HTML. import javax.servlet.*; import javax.servlet.http.*; import java.io.*; public class Redireciona extends HttpServlet { public void doGet(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException { res.sendRedirect("/test/index.html"); } } Exemplo XVI.XX – Redirecionamento de requisição. Note pelo exemplo que é preciso passar o contexto do recurso (/teste). No caso de redirecionamento o a requisição corrente é perdida e uma nova requisição é feita para a URL de destino. Por isso não se deve associar nenhum objeto à requisição, uma vez que o objeto HttpServletRequest corrente será perdido. O que ocorre na prática é que o servidor envia uma mensagem HTTP 302 de volta para o cliente informando que o recurso foi transferido para outra URL e o cliente envia uma nova requisição para a URL informada. J á no caso de reencaminhamento a requisição é encaminhada diretamente para a nova URL mantendo todos os objetos associados e evitando uma nova ida J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 400 ao cliente. Portanto, o uso de reencaminhamento é mais eficiente do que o uso de redirecionamento. O reencaminhamento é obtido usando o método forward() de uma instância RequestDispatcher, passando como argumento os objetos HttpServletRequest e HttpServletResponse para a URL de destino. Uma instância RequestDispatcher é obtida por meio do método getRequestDispatcher()de uma instância ServletContext , que é obtido, por sua vez, por meio do método getServletContext() do Servlet. O exemplo XX.XX mostra o código de um Servlet reencaminhando a requisição para uma página J SP. import javax.servlet.*; import javax.servlet.http.*; public class Reencaminha extends HttpServlet { public void doGet(HttpServletRequest request, HttpServletResponse response) { try { getServletContext().getRequestDispatcher("/index.html"). forward(request,response); }catch (Exception e) { System.out.println("Servlet falhou: "); e.printStackTrace(); } } } Exemplo XVI.XX – Reencaminhamento de requisição. Note que não é necessário passar o contexto na URL, como é feito no redirecionamento, uma vez que a requisição é encaminhada no contexto corrente. J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 401 Uma Arquitetura para comércio eletrônico O projeto de uma solução para comércio eletrônico é uma tarefa complexa e deve atender diversos requisitos. Nesta seção mostraremos uma modelo de arquitetura básico para comércio eletrônico que pode ser adaptado para soluções mais específicas. Este modelo implementa o padrão de projeto MVC, procurando, desta forma, isolar esses aspectos de um sistema de computação. Tipos de aplicações na WEB Podemos enquadra as aplicações na Web em um dos seguintes tipos: • Business-to-consumer (B2C) – entre empresa e consumidor. Exemplo: uma pessoa compra um livro na Internet. • Business-to-business (B2B) – Troca de informações e serviços entre empresas. Exemplo: o sistema de estoque de uma empresa de automóveis detecta que um item de estoque precisa ser resposta e faz o pedido diretamente ao sistema de produção do fornecedor de autopeças. Neste tipo de aplicação a linguagem XML possui um papel muito importante, uma vez que existe a necessidade de uma padronização dos tags para comunicação de conteúdo. • User-to-data – acesso à bases de informação. Exemplo: uma usuário consulta uma base de informação. • User-to-user – chat, e troca de informações entre usuários (Morpheus). O exemplo que mostraremos é tipicamente um caso de User-to-data, (agenda eletrônica na Web) mas possui a mesma estrutura de um B2C. Arquitetura MVC para a Web A figura XX.XX contém um diagrama de blocos que mostra a participação de Servlets, J SP e J avaBeans na arquitetura proposta. A idéia é isolar cada aspecto do modelo MVC com a tecnologia mais adequada. A página J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 402 J SP é ótima para fazer o papel da visão, uma vez que possui facilidades para a inserção de componentes visuais e para a apresentação de informação. No entanto, é um pouco estranho usar uma página J SP para receber e tratar uma requisição. Esta tarefa, que se enquadra no aspecto de controle do modelo MVC é mais adequada a um Servlet, uma vez que neste momento componentes de apresentação são indesejáveis. Finalmente, é desejável que a modelagem do negócio fique isolada dos aspectos de interação. A proposta é que a modelagem do negócio fique contida em classes de J avaBeans. Em aplicações mais sofisticadas a modelagem do negócio deve ser implementada por classes de Enterprise J avaBeans (EJ B), no entanto esta forma de implementação foge ao escopos deste livro. Cada componente participa da seguinte forma: • Servlets – Atuam como controladores, recebendo as requisições dos usuários. Após a realização das análises necessária sobre a requisição, instancia o J avaBean e o armazena no escopo adequado (ou não caso o bean já tenha sido criado no escopo) e encaminha a requisição para a página J SP. • J avaBeans – Atuam como o modelo da solução, independente da requisição e da forma de apresentação. Comunicam-se com a camada intermediária que encapsula a lógica do problema. • J SP – Atuam na camada de apresentação utilizando os J avaBeans para obtenção dos dados a serem exibidos, isolando-se assim de como os dados são obtidos. O objetivo é minimizar a quantidade de código colocado na página. • Camada Intermediária (Middleware) – Incorporam a lógica de acesso aos dados. Permitem isolar os outros módulos de problemas como estratégias de acesso aos dados e desempenho. O uso de EJ B (Enterprise J avaBeans) é recomendado para a implementação do Middleware, uma vez que os EJ Bs possuem capacidades para gerência de transações e persistência. Isto implica na adoção de um servidor de aplicação habilitado para EJ B. A figura XX.XX mostra a interação entre os componentes. J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 403 Figura XV.XX. Arquitetura de uma aplicação para Comércio Eletrônico. Essa arquitetura possui as seguintes vantagens: 1. Facilidade de manutenção: a distribuição lógica das funções entre os módulos do sistema isola o impacto das modificações. 2. Escalabilidade: Modificações necessária para acompanhar o aumento da demanda de serviços (database pooling, clustering, etc) ficam concentradas na camada intermediária. A figura abaixo mostra a arquitetura física de uma aplicação de comércio eletrônico. Navegador Web JSP (Apresentação) Requisição Servlet (controlador) Resposta JavaBean (modelo) Cria uma instância Servidor de Aplicação 1 5 4 3 2 SGBD J DBC MiddleWare J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 404 Figura XV.XX. Arquitetura física de uma aplicação para Comércio Eletrônico. Demilitarized Zone (DMZ) é onde os servidores HTTP são instalados. A DMZ é protegida da rede púbica por um firewall, também chamado de firewall de protocolo. O firewall de protocolo deve ser configurado para permitir tráfego apenas através da porta 80. Um segundo firewall, também chamado de firewall de domínio separa a DMZ da rede interna. O firewall de domínio deve ser configurado para permitir comunicação apenas por meio das portas do servidor de aplicação Agenda Web: Um Exemplo de uma aplicação Web usando a arquitetura MVC O exemplo a seguir mostra o desenvolvimento da agenda eletrônica para o funcionamento na Web. A arquitetura adotada é uma implementação do modelo MVC. Apenas, para simplificar a solução, a camada intermediária foi simplificada e é implementada por um J avaBean que tem a função de gerenciar a conexão com o banco de dados. O banco de dados será composto por duas tabelas, uma para armazenar os usuários autorizados a usar a tabela e outra para armazenar os itens da agenda. A figura XX.XX mostra o esquema conceitual do banco de dados e a figura XX.XX mostra o comando para a criação das tabelas. Note que existe um relacionamento entre a tabela USUARIO e a tabela PESSOA, mostrando que os dados pessoais sobre o usuário ficam armazenados na agenda. USUARIO PESSOA J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 405 Figura XV.XX. Esquema conceitual do banco de dados para a agenda. As tabelas do BD devem ser criadas de acordo com o seguinte script: CREATE TABLE PESSOA (ID INT PRIMARY KEY, NOME VARCHAR(50) NOT NULL, TELEFONE VARCHAR(50), ENDERECO VARCHAR(80), EMAIL VARCHAR(50), HP VARCHAR(50), CELULAR VARCHAR(20), DESCRICAO VARCHAR(80)); CREATE TABLE USUARIO (ID INT PRIMARY KEY, LOGIN VARCHAR(20) NOT NULL, SENHA VARCHAR(20) NOT NULL, CONSTRAINT FK_USU FOREIGN KEY (ID) REFERENCES PESSOA(ID)); Figura XV.XX. Script para criação das tabelas. Para se usar a agenda é necessário que exista pelo menos um usuário cadastrado. Como no exemplo não vamos apresentar uma tela para cadastro de usuários será preciso cadastrá-los por meio comandos SQL. Os comandos da figura XX.XX mostram como cadastrar um usuário. INSERT INTO PESSOA(ID,NOME,TELEFONE,ENDERECO,EMAIL) VALUES(0,'Alcione de Paiva Oliveira','3899-1769', 'PH Rolfs','[email protected]'); INSERT INTO USUARIO(ID,LOGIN,SENHA) VALUES(0,'Alcione','senha'); Figura XV.XX. Script para cadastra um usuário. O sistema e-agenda é composta pelos seguintes arquivos: 1:1 1:1 J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 406 Arquivo Descrição agenda.html Página inicial do site, contendo o formulário para a entrada do login e senha para entrar no restante do site. principal.jsp Página J SP contendo o formulário para entrada de dados para inserção, remoção ou consulta de itens da agenda. LoginBean.java J avaBean responsável por verificar se o usuário está autorizado a acessar a agenda. AgendaServlet.java Servlet responsável pelo tratamento de requisições sobre alguma função da agenda (consulta, inserção e remoção) AcaoBean.java J avaBean responsável pela execução da ação solicitada pelo usuário. ConnectionBean.java J avaBean responsável pelo acesso ao DB e controle das conexões. Tabela XV.XX. Arquivos do sistema e-agenda. O diagrama de colaboração abaixo mostra as interação entre os componentes do sistema. Figura XV.XX. Interação entre os componentes do sistema. 1 e 4 – Requisições 2 e 6 – instanciações 4 – reencaminhamento de requisições 3,5,7 e 8 – Chamadas de métodos agenda.html AgendaServlet ConnectionBean LoginBean AcaoBean principal.jsp 1 2 4 5 7 8 3 6 J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 407 Descreveremos agora cada componente da aplicação. O exemplo XX.XX mostra código HTML da página agenda.html. Esta é a página inicial da aplicação. Ela contém o formulário para a entrada do login e senha para entrar no restante do site. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 20 31 32 33 34 35 36 37 38 39 <HTML> <HEAD> <TITLE>Agenda</TITLE> </HEAD> <BODY BGCOLOR="#FFFFFF"> <P align="center"><IMG src="tit.gif" width="350" height="100" border="0"></P> <BR> <CENTER> <FORM method="POST" name="TesteSub" onsubmit="return TestaVal()" action="/agenda/agenda"><BR> Login:<INPUT size="20" type="text" name="login"><BR><BR> Senha:<INPUT size="20" type="password" name="senha"><BR><BR><BR> <INPUT type="submit" name="envia" value="Enviar"> <INPUT size="3" type="Hidden" name="corrente" value="0"><BR> </FORM> </CENTER> <SCRIPT language="J avaScript"> <!-- function TestaVal() { if (document.TesteSub.login.value == " " ) { alert (" Campo Login nao Preenchido...Form nao Submetido" ) return false } else if (document.TesteSub.senha.value == " " ) { alert (" Campo Senha nao Preenchido...Form nao Submetido" ) return false } else { return true } } //--></SCRIPT> </BODY></HTML> J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 408 Exemplo XVI.XX – agenda.html. O formulário está definido nas linha 11 a 17. Na linha 12 o parâmetro action indica a URL que dever receber a requisição. A URL é virtual e sua associação com o Servlet AgendaServlet será definida no arquivo web.xml. Na linha 16 é definido um campo oculto (Hidden) como o nome de corrente e valor 0. Ele será usado pelo AgendaServlet reconhecer a página de onde saiu a requisição. As linha 19 a 31 definem uma função em J avaScript que será usada para verificar se o usuário digitou o nome e a senha antes de enviar a requisição ao usuário. O uso de J avaScript no lado cliente para criticar a entrada do usuário é muito comum pois diminui a sobrecarga do servidor. O exemplo XX.XX mostra código da página principal.jsp. Esta página contém o formulário para entrada de dados para inserção, remoção ou consulta de itens da agenda. Na linha 4 a diretiva page define que o servidor deve acompanhar a sessão do usuário e importa o pacote agenda. Na linha 7 um objeto da classe agenda.LoginBean é recuperado da sessão por meio do método getAttribute(). Para recuperar o objeto é preciso passar para o método o nome que está associado ao objeto na sessão. De forma semelhante, na linha 8 um objeto da classe agenda.AcaoBean é recuperado da requisição por meio do método getAttribute(). Este objeto é recuperado da requisição porque cada requisição possui uma ação diferente associada. Na linha 9 é verificado se objeto agenda.LoginBean foi recuperado e se o retorno do método getStatus() é true. Se o objeto agenda.LoginBean não foi recuperado significa que existe uma tentativa de acesso direto à página principal.jsp sem passar primeiro pela página agenda.html ou que a sessão se esgotou. Se o método getStatus() retornar false significa que o usuário não está autorizado a acessar essa página. Nestes casos é processado o código associado ao comando else da linha 51 que apaga a sessão por meio do método invalidate() do objeto HttpSession (linha 53) e mostra a mensagem “Usuário não autorizado” (linha 55). Caso o objeto indique que o usuário está autorizado os comandos internos ao if são executados. Na linha 11 é mostrada uma mensagem com o nome do usuário obtido por meio do método getNome() do objeto agenda.LoginBean . Na linha 13 é mostrado o resultado da ação anterior por meio do método toString() do objeto agenda.AcaoBean. A ação pode ter sido de consulta, inserção de um novo item na agenda e remoção de um item na agenda. No primeiro caso é mostrado uma lista dos itens que satisfizeram a consulta. No segundo e terceiro casos é exibida uma mensagem indicado se a operação foi bem sucedida. J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 409 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 <HTML><HEAD> <TITLE>Tela da Agenda </TITLE> </HEAD><BODY bgcolor="#FFFFFF"> <%@ page session=" true" import=" agenda.*" %> <% agenda.LoginBean lb = (agenda.LoginBean) session.getAttribute(" loginbean" ); agenda.AcaoBean ab = (agenda.AcaoBean) request.getAttribute(" acaobean" ); if (lb != null && lb.getStatus()) { %> <H2>Sess&atilde;o do <%=lb.getNome() %></H2> <% if (ab!=null) out.println(ab.toString()); %> <P><BR></P> <FORM method="POST" name="formprin" onsubmit="return TestaVal()" action="/agenda/agenda"> Nome: <INPUT size="50" type="text" name="nome"><BR> Telefone: <INPUT size="20" type="text" name="telefone"><BR> Endere&ccedil;o: <INPUT size="50" type="text" name="endereco"><BR> Email: <INPUT size="50" type="text" name="email"><BR><BR> P&aacute;gina: <INPUT size="50" type="text" name="pagina"><BR> Celular: <INPUT size="20" type="text" name="celular"><BR> Descri&ccedil;&atilde;o: <INPUT size="20" type="text" name="descricao"> <BR><CENTER> <INPUT type="submit" name="acao" value="Consulta"> <INPUT type="submit" name="acao" value="Insere"> <INPUT type="submit" name="acao" value="Apaga"></CENTER> <INPUT size="3" type="Hidden" name="corrente" value="1"> </FORM> <SCRIPT language="J avaScript"><!-- function TestaVal() { if (document.formprin.nome.value == " " && document.formprin.descricao.value== " " ) { alert (" Campo Nome ou Descricao devem ser Preenchidos!" ) return false } else { return true } } //--></SCRIPT> <% } else { session.invalidate(); J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 410 54 55 56 57 58 59 %> <H1>Usu&aacute;rio n&atilde;o autorizado</H1> <% } %> </BODY></HTML> Exemplo XVI.XX – principal.jsp. As linhas 17 a 31 definem o código do formulário de entrada. Nas linhas 17 e 18 são definidos os atributos do formulário. O atributo method indica a requisição será enviada por meio do método POST. O atributo name define o nome do formulário como sendo formprin. O atributo onsubmit define que a função javaSript TestaVal() deve ser executada quando o formulário for submetido. Finalmente, o atributo action define a URL para onde a requisição deve ser enviada. Neste caso a URL é agenda/agenda que está mapeada para o Servlet AgendaServlet. O mapeamento é feito no arquivo web.xml do diretório web-inf do contexto agenda, como mostrado na figura XX.XX. As linhas 19 a 25 definem os campos de texto para entrada dos valores. As linhas 27 a 29 definem os botões de submit. Todos possuem o mesmo nome, de forma que o Servlet precisa apenas examinar o valor do parâmetro acao para determinar qual ação foi solicitada Na linha 30 é definido um campo oculto (Hidden) como o nome de corrente e valor 0. Ele será usado pelo AgendaServlet reconhecer a página de onde saiu a requisição. As linha 33 a 47 definem uma função em J avaScript que será usada para verificar se o usuário entrou com valores nos campos de texto nome ou decricao. No mínimo um desses campos deve ser preenchido para que uma consulta possa ser realizada. O exemplo XX.XX mostra código do J avaBean usado para intermediar a conexão com o banco de dados. O J avaBean ConnectionBean tem a responsabilidade de abrir uma conexão com o banco de dados, retornar uma referência desta conexão quando solicitado e registrar se a conexão esta livre ou ocupada. Neste exemplo o se encarrega apenas de obter a conexão e fechá-la, no entanto, em aplicação com maior número de acessos ao banco de dados pode ser necessário um maior controle sobre o uso das conexões, mantendo-as em uma estrutura de dados denominada de pool de conexões. Na linha 12 podemos observar que o construtor da classe foi declarado com o modificador de acesso private. Isto significa que não é possível invocar o construtor por meio de um objeto de outra classe. Isto é feito para que se possa ter um controle sobre a criação de instâncias da classe. No nosso caso permitiremos apenas que uma instância da classe seja criada, de modo que todas as referências apontem para J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 411 esse objeto. Esta técnica de programação, onde se permite uma única instância de uma classe é denominada de padrão de projeto Singleton. O objetivo de utilizarmos este padrão é porque desejamos que apenas um objeto controle a conexão com o banco de dados. Ma se o construtor não pode ser chamado internamente como uma instância da classe é criada e sua referência é passada para outros objetos? Esta é a tarefa do método estático getInstance() (linhas 14 a 19). Este método verifica se já existe a instância e retorna a referência. Caso a instância não exista ela é criada antes de se retornar a referência. O método init() (linhas 21 a 27) é chamado pelo construtor para estabelecer a conexão com o SGBD. Ele carrega o driver J DBC do tipo 4 para HSQLDB e obtém uma conexão com o SGBD. O método devolveConnection() (linhas 29 a 34) é chamado quando se deseja devolver a conexão. Finalmente, o método getConnection() (linhas 36 a 46) é chamado quando se deseja obter a conexão. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 package agenda; import java.sql.*; import java.lang.*; import java.util.*; public class ConnectionBean { private Connection con=null; private static int clients=0; static private ConnectionBean instance=null; private ConnectionBean() { init(); } static synchronized public ConnectionBean getInstance() { if (instance ==null) { instance =new ConnectionBean(); } return instance; } private void init() { try { Class.forName("org.hsqldb.jdbcDriver"); con= DriverManager.getConnection("jdbc:hsqldb:hsql://localhost","sa",""); }catch(Exception e){System.out.println(e.getMessage());}; } public synchronized void devolveConnection(Connection con) { J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 412 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 if (this.con==con) { clients--; notify(); } } public synchronized Connection getConnection() { if(clients>0) { try { wait(5000); } catch (InterruptedException e) {}; if(clients>0) return null; } clients ++; return con; } } Exemplo XVI.XX – ConnectionBean.java. O exemplo XX.XX mostra código do J avaBean usado para verificar se o usuário está autorizado a usar a agenda. O J avaBean LoginBean recebe o nome e a senha do usuário, obtém a conexão com o SGBD e verifica se o usuário está autorizado, registrando o resultado da consulta na variável status (linha 10). Tudo isso é feito no construtor da classe (linhas 12 a 35). Note que na construção do comando SQL (linhas 17 a 20) é inserido uma junção entre as tabelas PESSOA e USUARIO de modo a ser possível recuperar os dados relacionados armazenados em ambas as tabelas. Os métodos getLogin(), getNome() e getStatus() (linhas 36 a 38) são responsáveis pelo retorno do login, nome e status da consulta respectivamente. 1 2 3 4 5 6 7 8 9 10 11 package agenda; import java.sql.*; import java.lang.*; import java.util.*; public class LoginBean { protected String nome =null; protected String login=null; protected boolean status=false; J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 413 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 public LoginBean(String login, String senha) { this.login =login; Connection con=null; Statement stmt =null; String consulta ="SELECT NOME FROM PESSOA, USUARIO "+ "WHERE USUARIO.ID =PESSOA.ID AND "+ "USUARIO.SENHA ='"+senha+"' AND "+ "USUARIO.LOGIN ='"+login+"'"; try { con=ConnectionBean.getInstance().getConnection(); stmt =con.createStatement(); ResultSet rs =stmt.executeQuery(consulta); if(rs.next()) { status =true; nome =rs.getString("NOME"); } }catch(Exception e){System.out.println(e.getMessage());} finall y { ConnectionBean.getInstance().devolveConnection(con); try{stmt.close();}catch(Exception ee){}; } } public String getLogin(){return login;} public String getNome(){return nome;} public boolean getStatus(){return status;} } Exemplo XVI.XX – LoginBean.java. O exemplo XX.XX mostra código do Servlet que implementa a camada de controle do modelo MVC. O Servlet AgendaServlet recebe as requisições e, de acordo com os parâmetros, instância os J avaBeans apropriados e reencaminha as requisições para as páginas corretas. Tanto o método doGet() (linhas 9 a 12) quanto o método doPost()(linhas 13 a 17) invocam o método performTask()(linhas 19 a 61) que realiza o tratamento da requisição. Na linhas 24 a 26 do método performTask() é obtido o valor do parâmetro corrente que determina a página que originou a requisição. Se o valor for nulo é assumido o valor default zero. Na linha 30 é executado um comando switch sobre esse valor, de modo a desviar para bloco de comandos adequado. O bloco que vai da linha 32 até a linha 43 trata a requisição originada em uma página com a identificação 0 (página agenda.html). Nas linhas 32 e 33 são recuperados o valor de login e senha digitados pelo usuário. Se algum J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 414 desses valores for nulo então a requisição deve ser reencaminhada para a página de login (agenda.html) novamente (linha 35). Caso contrário é instanciado um objeto LoginBean, inserido na sessão corrente e definida a página principal.jsp como a página para o reencaminhamento da requisição (linhas 38 a 41). J á o bloco que vai da linha 44 até a linha 54 trata a requisição originada em uma página com a identificação 1 (página principal.jsp). Na linha 44 é recuperado o objeto HttpSession corrente. O argumento false é utilizado para impedir a criação de um novo objeto HttpSession caso não exista um corrente. Se o valor do objeto for null, então a requisição deve ser reencaminhada para a página de login (linha 47). Caso contrário é instanciado um objeto AcaoBean, inserido na requisição corrente e definida a página principal.jsp como a página para o reencaminhamento da requisição (linhas 50 a 52). Na linha 56 a requisição é reencaminhada para a página definida (página agenda.html ou principal.jsp). 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 package agenda; import javax.servlet.*; import javax.servlet.http.*; import agenda.*; public class AgendaServlet extends HttpServlet { public void doGet(HttpServletRequest request, HttpServletResponse response) { performTask(request,response); } public void doPost(HttpServletRequest request, HttpServletResponse response) { performTask(request,response); } public void performTask(HttpServletRequest request, HttpServletResponse response) { String url; HttpSession sessao; String corrente =request.getParameter("corrente"); int icorr=0; if (corrente !=null) icorr =Integer.parseInt(corrente); try J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 415 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 { switch(icorr) { case 0: String login =request.getParameter("login"); String senha =request.getParameter("senha"); if (login ==nul l||senha ==null) url="/agenda.html"; el se { sessao =request.getSession(true); sessao.setAttribute("loginbean", new agenda.LoginBean(login,senha)); url="/principal.jsp"; }; break; case 1: sessao =request.getSession(false); if (sessao ==null) url="/agenda.html"; el se { request.setAttribute("acaobean", new agenda.AcaoBean(request)); url="/principal.jsp"; }; break; } getServletContext().getRequestDispatcher(url).forward(request,response); }catch (Exception e) { System.out.println("AgendaServlet falhou: "); e.printStackTrace(); } } } Exemplo XVI.XX – AgendaServlet.java. O exemplo XX.XX mostra código do J avaBean usado para realizar a manutenção da agenda. O J avaBean AcaoBean é responsável pela consulta, remoção e inserção de novos itens na agenda. Um objeto StringBuffer referenciado pela variável retorno é utilizado pelo J avaBean para montar o resultado da execução. O construtor (linhas 16 a 27) verifica o tipo de requisição e invoca o método apropriado. O método consulta() (linhas 29 a 77) é responsável pela realização de consultas. As consultas podem ser realizadas sobre o campo nome ou J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 416 descrição e os casamentos podem ser parciais, uma vez que é usado o operador LIKE. A consulta SQL é montada nas linhas 40 a 47. Na linha 50 é obtida uma conexão com SGBD por meio do objeto ConnectionBean. Na linha 57 o comando SQL é executado e as linhas 59 a 72 montam o resultado da consulta. O método insere() (linhas 79 a 148) é responsável por inserir um item na agenda. Na linha 95 é obtida uma conexão com SGBD por meio do objeto ConnectionBean. Para inserir um novo item é preciso obter o número do último identificador usado, incrementar o identificador e inserir na base o item com o identificador incrementado. Esta operação requer que não seja acrescentado nenhum identificador entre a operação de leitura do último identificador e a inserção de um novo item. Ou seja, é necessário que essas operações sejam tratadas como uma única transação e o isolamento entre as transações sejam do nível Repeatable Read. A definição do inicio da transação é feita no comando da linha 102. A mudança do nível de isolamento é feita pelos comandos codificados nas linha 103 a 109. Na linha 112 é invocado o método obtemUltimo() (linhas 150 a 171) para obter o último identificador utilizado. As linhas 114 a 128 montam o comando SQL para a execução. O comando SQL é executado na linha 131. O fim da transação é definido pelo comando da linha 132. Ao fim da transação, de forma a não prejudicar a concorrência, o nível de isolamento deve retornar para um valor mais baixo. Isto é feito pelos comandos das linhas 133 a 137. O método apaga() (linhas 173 a 201) é responsável por remover um item da agenda. As linhas 175 a 180 contém o código para verificar se o usuário digitou o nome associado ao item que deve ser removido. A linha 181 montam o comando SQL para a execução. Na linha 184 é obtida uma conexão com SGBD por meio do objeto ConnectionBean. O comando SQL é executado na linha 191. 1 2 3 4 5 6 7 8 9 10 11 12 13 package agenda; import java.lang.*; import java.util.*; import java.sql.*; public class AcaoBean { private Connection con=null; private StringBuffer retorno =null; private Statement stmt=null; private String [] legenda={"C&oacute;digo","Nome","Telefone", "Endere&ccedil;o", "email","hp", J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 417 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 "celular","Descri&ccedil;&atilde;o"}; public AcaoBean(javax.servlet.http.HttpServletRequest request) { String acao =request.getParameter("acao"); if (acao.equals("Consulta")) { String nome =request.getParameter("nome"); String descri =request.getParameter("descricao"); consulta(nome,descri); } else if (acao.equals("Insere")) insere(request); else if (acao.equals("Apaga")) apaga(request); } private void consulta(String nome,String descri) { String consulta =null; if ((nome ==null||nome.length()<1) && (descri ==nul l|| descri.length()<1)) { retorno =new StringBuffer("Digite o nome ou descricao!"); return; } if (descri ==null|| descri.length()<1) consulta ="SELECT * FROM PESSOA WHERE NOME LIKE '%"+ nome+"%'"+" ORDER BY NOME"; else if (nome ==null|| nome.length()<1) consulta ="SELECT * FROM PESSOA WHERE DESCRICAO LIKE '%"+ descri+"%'"+" ORDER BY NOME"; else consulta="SELECT * FROM PESSOA WHERE DESCRICAO LIKE '%"+ descri+"%' AND NOME LIKE '%"+nome+"%' ORDER BY NOME"; try { con=ConnectionBean.getInstance().getConnection(); if (con ==null) { retorno =new StringBuffer("Servidor ocupado. Tente mais tarde.!"); return; } stmt =con.createStatement(); ResultSet rs =stmt.executeQuery(consulta); retorno =new StringBuffer(); retorno.append("<br><h3>Resultado</h3><br>"); while(rs.next()) { J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 418 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 retorno.append("ID:").append(rs.getString("id")); retorno.append("<br>Nome:").append(rs.getString("Nome")); retorno.append("<br>Telefone:").append(rs.getString("Telefone")); retorno.append("<br>Endereco:").append(rs.getString("Endereco")); retorno.append("<br>email:").append(rs.getString("email")); retorno.append("<br>hp:").append(rs.getString("hp")); retorno.append("<br>celular:").append(rs.getString("celular")); retorno.append("<br>descricao:").append(rs.getString("descricao")); retorno.append("<br><br>"); } } catch(Exception e){System.out.println(e.getMessage());} finall y {ConnectionBean.getInstance().devolveConnection(con); try{stmt.close();}catch(Exception ee){}; } } private void insere(javax.servlet.http.HttpServletRequest request) { String[] par ={"telefone","endereco","email","hp","celular","descricao"}; StringBuffer comando =new StringBuffer("INSERT INTO PESSOA("); StringBuffer values =new StringBuffer(" VALUES("); String aux =request.getParameter("nome"); if (aux ==null || aux.length()<1) { retorno =new StringBuffer("<br><h3>Digite o nome!</h3><br>"); return; } try { con=ConnectionBean.getInstance().getConnection(); if (con ==null) { retorno =new StringBuffer("Servidor ocupado. Tente mais tarde!"); return; } con.setAutoCommit(false); DatabaseMetaData meta=con.getMetaData(); if(meta.supportsTransactionIsolationLevel( con.TRANSACTION_REPEATABLE_READ)) { con.setTransactionIsolation( con.TRANSACTION_REPEATABLE_READ); } J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 419 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 int ultimo =obtemUltimo(con); if (ultimo==-1) return; ultimo++; comando.append("id,nome"); values.append(ultimo+",'").append(aux).append("'"); for(int i=0;i<par.length;i++) { aux =request.getParameter(par[i]); if (aux !=null && aux.length()>0) { comando.append(",").append(par[i]); values.append(",'").append(aux).append("'"); } } comando.append(")"); values.append(")"); aux =comando.toString()+values.toString(); stmt = con.createStatement(); stmt.executeUpdate(aux); con.setAutoCommit(true); if(meta.supportsTransactionIsolationLevel( con.TRANSACTION_READ_COMMITTED)) { con.setTransactionIsolation( con.TRANSACTION_READ_COMMITTED); } retorno =new StringBuffer("<br><h3>Inserido!</h3><br>"); return; } catch(Exception e) {retorno = new StringBuffer("<br><h3>Erro:"+e.getMessage()+"!</h3><br>"); } finall y { ConnectionBean.getInstance().devolveConnection(con); try{stmt.close();}catch(Exception ee){}; } } private int obtemUltimo(Connection con) { String consulta ="SELECT MAX(ID) AS maior FROM PESSOA"; try { if (con ==null) { retorno =new StringBuffer("Servidor ocupado. Tente mais tarde.!"); return -1; } J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 420 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 stmt =con.createStatement(); ResultSet rs =stmt.executeQuery(consulta); if(rs.next()) return Integer.parseInt(rs.getString("maior")); else return 0; }catch(Exception e) { retorno = new StringBuffer("<br><h3>Erro:"+e.getMessage()+"!</h3><br>"); return -1; } finall y {try{stmt.close();}catch(Exception ee){};} } private void apaga(javax.servlet.http.HttpServletRequest request) { String aux =request.getParameter("nome"); if (aux ==null || aux.length()<1) { retorno =new StringBuffer("<br><h3>Digite o nome!</h3><br>"); return; } String consulta ="DELETE FROM PESSOA WHERE NOME ='"+aux+"'"; try { con=ConnectionBean.getInstance().getConnection(); if (con ==null) { retorno =new StringBuffer("Servidor ocupado. Tente mais tarde.!"); return; } stmt = con.createStatement(); stmt.executeUpdate(consulta); retorno =new StringBuffer("<br><h3>Removido!</h3><br>"); return; }catch(Exception e){ retorno =new StringBuffer("<br><h3>Erro:"+e.getMessage()+"!</h3><br>"); } finall y { ConnectionBean.getInstance().devolveConnection(con); try{stmt.close();}catch(Exception ee){};} } public String[] getLeg(){return legenda;} public String toString(){return retorno.toString();} } Exemplo XVI.XX – AcaoBean.java. J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 421 Instalação do SGBD O HSQLDB (www.hsqldb.org) é um SGBD de código aberto desenvolvido em J ava. Ele obedece os padrões SQL e J DBC. Possui as seguintes características: • Tamanho pequeno (≅ 100KB). • Funciona como servidor, standalone e in-memory. • Suporta transação. • Integridade referencial. • Procedimentos Armazenados em J ava. • Direitos de acessos Para instalá-lo em no MS-Windows execute as seguintes etapas: 1) Descompacte o arquivo hsqldb_v.1.61.zip em um diretório qualquer. Por exemplo : c:\sgbd 2) Coloque o seguinte comando em seu autoexec.bat SET CLASSPATH=%CLASSPATH%;c:\sgbd\hsqldb_v.1.61\lib\hsqldb.jar Execução em modo servidor c:\sgbd\hsqldb_v.1.61\demo\runServer.bat Execução do gerenciador gráfico c:\sgbd\hsqldb_v.1.61\demo\runManager.bat Instalação da Aplicação Para instalar crie a seguinte estrutura de diretório abaixo do diretório webapps do Tomcat: Servlets e J avaBeans páginas HTML e J SP arquivo web.xml J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 422 webapps |_____ agenda |_____ Web-inf |_____classes |_______agenda Figura XV.XX. Estrutura de diretórios para a aplicação agenda. O arquivo web.xml deve ser alterado para conter mapeamento entre a URL agenda e o Servlet AgendaServlet. ... <web-app> <servlet> <servlet-name> agenda </servlet-name> <servlet-class> agenda.AgendaServlet </servlet-class> </servlet> <servlet-mapping> <servlet-name> agenda </servlet-name> <url-pattern> /agenda </url-pattern> </servlet-mapping> ... </web-app> Figura XV.XX. Arquivo web.xml para a agenda. Considerações sobre a solução J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 423 A aplicação acima implementa uma agenda que pode ser acessada por meio da Internet, no entanto, devido à falta de espaço e à necessidade de destacarmos os pontos principais, alguns detalhes foram deixados de lado, como por exemplo uma melhor interface com o usuário. Abaixo seguem alguns comentários sobre algumas particularidades da aplicação: 1. O J avaBean da classe LoginBean é armazenado na sessão para permitir a verificação se o acesso ao site é autorizado. Isto impede que os usuários tentem acessar diretamente a página principal.jsp da agenda. Caso tentem fazer isso, a sessão não conterá um objeto LoginBean associado e, portanto, o acesso será recusado. 2. O J avaBean da classe AcaoBean é armazenado no objeto request uma vez que sua informações são alteradas a cada requisição. Uma forma mais eficiente seria manter o objeto AcaoBean na sessão e cada novo requisição invocar um método do AcaoBean para gerar os resultados. No entanto, o objetivo da nossa implementação não é fazer a aplicação mais eficiente possível, e sim mostrar para o leitor uma aplicação com variadas técnicas. 3. Apesar de termos adotado o padrão MVC de desenvolvimento a aplicação não exibe uma separação total da camada de apresentação (Visão) em relação à camada do modelo. Parte do código HTML da visão é inserido pelo AcaoBean no momento da construção da String contendo o resultado da ação. Isto foi feito para minimizar a quantidade de código J ava na página J SP. Pode-se argumentar que neste caso a promessa da separação entre as camadas não é cumprida totalmente. Uma solução para o problema seria gerar o conteúdo em XML e utilizar um analisador de XML para gerar a página de apresentação. No entanto, o uso da tecnologia XML foge ao escopo deste livro. 4. A solução apresenta código redundante para criticar as entradas do usuário. Existe código J avaScript nas páginas, e código J ava no Servlet e J avaBeans. O uso de código J avaScript nas páginas para críticas de entrada é indispensável para aliviarmos a carga sobre o servidor. J á o código para crítica no servidor não causa impacto perceptível e útil para evitar tentativas de violação. J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 424 Capítulo XVII Perguntas Frequentes Como executar um programa a partir de uma aplicação em Java? Resposta: isso pode ser feito com o método de instância exec da classe Runtime. Como criar um TextField que não exiba o que está sendo digitado para se usado como campo de entrada de senhas? Resposta: use o método setEchoChar() do TextField para definir qual caractere que deve ser ecoado. Como aumentar a área de ambiente do DOS para caber o CLASSPATH? Resposta: coloque a seguinte linha no autoexec.bat set shell=c:\command.com /e:4096 J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 425 Bibliografia Eckel B. Thinking in Java. 2 nd Ed. New J ersey : Prentice Hall, 2000. Gosling J ., J oy W., Steele G. The Java Language Specification. Massachusetts : Addison-Wesley, 1996. Oaks S. Java Security. California : O’Reilly & Associates, Inc, 1998. Oaks S., Wong H. Java Threads. 2 ª Ed. California : O’Reilly & Associates, Inc, 1999. Watt D. A. Programming Language Concepts and Paradigms. Great Britain : Prentice Hall, 1990. Ethan H., Lycklama E. How do you Plug Java Memory Leaks? Dr. Dobb´s J ournal, San Francisco, CA, No. 309, February 2000. Wahli U. e outros. Servlet and JSP Programming with IBM WebSphere Studio and VisualAge for J ava, IBM RedBooks, California, May 2000. Sadtler C. e outros. Patterns for e-business: User-to-Business Patterns for Topology 1 and 2 using WebSphere Advanced Edition, IBM RedBooks, California, April 2000. Bagwel D. e outros. An Approach to Designing e-business Solutions, IBM RedBooks, California, December 1998. J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 426 Links Revistas http://www.javaworld.com/ Revista online sobre J ava. Livros http://www.eckelobjects.com/ Página do autor do livro Thinking in Java, atualmente em segunda edição. O livro pode ser baixado gratuitamente no site. http://www.redbooks.ibm.com/booklist.html Livros da IBM Servidores http://jakarta.apache.org Página do projeto J akarta que desenvolveu o Tomcat. http://www.metronet.com/~wjm/tomcat Lista Tomcat http://www.jboss.org Servidor de aplicação gratuito habilitado para EJ B Dicas J ava e recursos http://java.sun.com/ Página da Sun com informações, tutoriais e produtos J ava. http://gamelan.earthweb.com/ Página da com informações, Applets, Lista de discussão, tutoriais. http://www.inquiry.com/techtips/java_pro Ask the J ava Pro http://www.jguru.com/ jGuru.com(Home): Your view of the J ava universe http://www.soujava.org.br J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 427 Bem Vindo ao SouJ ava! Servlets e J SP http://www.servlet.com/srvdev.jhtml Servlet Inc : Developers Forum http://www.servlets.com Servlets.com http://www.jspin.com/home J spin.com - The J SP Resource Index http://www.burridge.net/jsp/jspinfo.html Web Development with J SP: J SP, J ava Servlet, and J ava Bean Information http://www.apl.jhu.edu/~hall/java/Servlet- Tutorial A Tutorial on J ava Servlets and J ava Server Pages (J SP) J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 428 Índice A Acesso Direto.........................................128 Acesso Sequencial .................................123 Agregação................................................25 Arrays .............................................37, 142 ASP........................................................378 Assert...................................................63 Assertivas......................Consulte Assert Assinatura dos métodos.....................18, 21 Associação comum..................................23 Atribuição...............................................49 atributos...................................................67 Atributos..................................................11 AWT ......................................................165 B Bloco........................................................52 boolean.....................................................33 booleanos boolean................................................37 break .....................................................60 Button.................................................201 Bytecodes.............................................7, 28 C cadeias de caracteres......... Consulte Strings Calendar ................................................1 Caractere char......................................................37 cardinalidade da relação...........................23 casting............ Consulte Conversão de tipos CGI ........................................................353 char ..........................................................33 Classe.................................................11, 66 classe anônima.......................................180 clone.........................................................81 Coleta de Lixo............................................8 Color ...................................................197 Comentários ...........................................51 Component..........................................199 Construtor ................................................16 Construtores ...........................................68 Containers..............................................206 contexto da aplicação............................ 361 continue.............................................. 60 Convenções............................................. 10 Conversão de Tipos ............................... 41 Cookies.................................................. 369 Cópia profunda...........Consulte deep copy Cópia rasa ..............Consulte Shallow copy CORBA................................................. 323 Criptografia............................................... 9 D Date ..................................................... 145 deep copy................................................. 81 Demilitarized Zone................................ 405 Diagrama de Classes................................ 20 DMZ.............. Consulte Demilitarized Zone double...................................................... 32 do-while .................................................. 58 E EJ B........................................................ 403 Enterprise J avaBeans..............Consulte EJ B escape...................................................... 34 Escopo..................................................... 52 Estruturas de Controle.......................... 53 extends..................................................... 17 F final..................................................... 76 finally.............................................. 112 float.......................................................... 32 for............................................................ 59 Frame................................................... 209 G Garbage Collection..Consulte coleta de lixo Generalização.......................................... 25 H Hashtable ......................................... 139 Herança.............................................. 12, 14 hexadecimal............................................. 31 J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 1 I Identificadores........................................29 if.............................................................53 Instância...................................................11 Inteiros int36 J J 2EE.........................................................10 J 2ME........................................................10 J AR........................................................234 java.io...............................................123 J avaBean................................................239 J CE.............................................................9 J DK..........................................................10 J SP.................................................351, 376 L Label ...................................................202 linha de comando ...................................61 List......................................................203 Literais ....................................................31 M main.........................................................28 Máquina Virtual .........................................7 método de classe......................................71 método de instância..................................71 métodos....................................................67 Métodos...................................................15 Modificadores de acesso ........................72 Mudança de Contexto..........................259 MVC......................................147, 402, 405 N native...................................................78 Níveis de isolamento..............................339 Nível de Isolamento...............................337 null........................................................34 O Object.......................................................19 Objeto Comportamento...................................12 Objetos...............................................11, 69 Observable .......................................147 Observer ....................................148, 150 octal ......................................................... 31 Ocultação de informação................... 14, 16 Olá mundo............................................... 27 Operadores............................................. 43 P Palavras Reservadas.............................. 30 Panel................................................... 208 PHP ....................................................... 377 Polimorfismo........................................... 19 Ponteiro..................................................... 7 Ponto Flutuante float, double........................................ 36 pool de conexões ................................... 411 Precedência entre Operadores ............. 50 Prepared Statements.............................. 341 private...................................................... 16 Procedimentos Armazenados................ 342 Propriedades............................................ 11 R RandomAccessFile......................... 128 recursão................................................... 57 Referências Compartilhadas ................ 78 return .................................................. 69 RMI ....................................................... 323 S separadores.............................................. 34 Servlets.................................................. 351 shallow copy............................................ 81 Singleton................................................ 412 sites dinâmicos ...................................... 351 Smalltalk.................................................... 7 Sobrecarga............................................... 18 Sobrescrita............................................... 18 Stack................................................... 137 static .................................................. 74 Stored Procedures.................................. 342 String....................................................... 33 StringBuffer.................................... 79 Strings..................................................... 39 StringTokenizer........................... 152 super..................................................... 73 Superclasse.............................................. 12 switch .................................................. 55 synchronized.................................... 78 J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 2 T TextArea ....................................178, 205 TextField..................................178, 204 this........................................................82 Tipos de dados........................................35 toString()..................................................42 Transação...............................................337 Tratamento de Eventos...........................168 U UML.........................................................20 Unicode................................................... 29 Unidade de Compilação.......................... 67 V Vector ................................................ 134 Visibilidade............................................. 22 void ....................................................... 69 W while..................................................... 57 widgets................................................... 165 J ava na Prática Alcione de P. Oliveira, Vinícius V. Maciel - UFV 1
Copyright © 2024 DOKUMEN.SITE Inc.