Docsity
Docsity

Prepare-se para as provas
Prepare-se para as provas

Estude fácil! Tem muito documento disponível na Docsity


Ganhe pontos para baixar
Ganhe pontos para baixar

Ganhe pontos ajudando outros esrudantes ou compre um plano Premium


Guias e Dicas
Guias e Dicas

Java - Introdução, Notas de estudo de Engenharia Informática

- - - - - - -

Tipologia: Notas de estudo

Antes de 2010

Compartilhado em 23/09/2008

robson-cesar-f-leite-3
robson-cesar-f-leite-3 🇧🇷

4

(2)

4 documentos

1 / 196

Documentos relacionados


Pré-visualização parcial do texto

Baixe Java - Introdução e outras Notas de estudo em PDF para Engenharia Informática, somente na Docsity! Universidade São Francisco Prof. Peter Jandl Junior Núcleo de Educação a Distância Universidade São Francisco 1999 Prof. Peter Jandl Jr. 2 Jandl, Peter Jr. Introdução ao Java, Apostila. p. cm. Inclui índice e referências bibliográficas. 1. Java (linguagem de programação de computadores) 2. Programação para Internet. 1999. Marcas Registradas Java e JavaScript são marcas comerciais da Sun Microsystems, Inc. Sun, Sun Microsystems, o logotipo da Sun, Sun Microsystems, Solaris, HotJava e Java são marcas registradas de Sun Microsystems, Inc. nos Estados Unidos e em alguns outros países. O personagem “Duke” é marca registrada de Sun Microsystems. UNIX é marca registrada nos Estados Unidos e outros países, exclusivamente licenciada por X/Open Company, Ltd.. Netscape Communicator é marca registrada de: “Netscape Communications Corporation”. Microsoft, Windows95/98/NT, Internet Explorer, VisualBasic são marcas registradas de Microsoft Corp. Todas as demais marcas, produtos e logotipos citados e usados são propriedade de seus respectivos fabricantes. Introdução ao Java 5 8.1.5 O Método destroy...........................................................................................................158 8.2 A Classe java.applet.Applet .....................................................................................................159 8.3 Restrições das Applets..............................................................................................................164 8.4 Parametrizando Applets...........................................................................................................165 8.5 Applets que são Aplicações.....................................................................................................167 8.6 O Contexto das Applets...........................................................................................................168 8.6.1 Mudança Automática de Páginas com uma Applet.................................................170 8.6.2 Uma Applet Menu.........................................................................................................172 8.7 Empacotando Applets..............................................................................................................176 9 Arquivos ....................................................................................................................178 9.1 O pacote java.io .........................................................................................................................178 9.2 Entrada........................................................................................................................................179 9.2.1 A Família java.io.InputStream....................................................................................179 9.2.2 A Família java.io.Reader..............................................................................................181 9.3 Saída.............................................................................................................................................184 9.3.1 A Família java.io.OutputStream ................................................................................184 9.3.2 A Família java.io.Writer ..............................................................................................187 9.4 Acesso Aleatório........................................................................................................................189 9.5 Obtendo Informações de Arquivos e Diretórios ................................................................191 10 Bibliografia ...............................................................................................................195 Prof. Peter Jandl Jr. 6 “Qual o absurdo de hoje que será a verdade do amanhã?” Alfred Whitehead Introdução ao Java 7 Prefácio A linguagem Java e sua plataforma de programação constituem um fascinante objeto de estudo: ao mesmo tempo que seduzem pelas promessas de portabilidade, exibem um conjunto rico de bibliotecas que realmente facilitam o desenvolvimento de novas aplicações e utiliza como paradigma central a orientação à objetos. Embora a mídia, grandes empresas e inúmeros especialistas vejam o Java como uma ferramenta indispensável para um futuro bastante próximo, tais previsões podem, obviamente, mudar em função de novas propostas e descobertas que hoje acontecem num ritmo quase inacreditável. De qualquer forma o estudo Java é uma opção conveniente pois mesmo que o futuro desta linguagem não seja tão brilhante como parece, torna-se um meio eficiente para o aprendizado da orientação à objetos que é incontestavelmente importante. Este material pretende apresentar de forma introdutória os conceitos essenciais da linguagem de programação Java e para tanto trata, sem profundidade, as questões fundamentais da orientação à objetos. Iniciando pelo ambiente de programação Java e a sua sintaxe, discute a construção de aplicações de console, aplicações gráficas e a construção de applets, procurando cobrir os elementos de maior uso da vasta biblioteca de programação oferecida por esta plataforma. São quase uma centena de exemplos, vários diagramas de classe e ilustrações distribuídos ao longo deste texto que colocam de maneira prática como aplicar os conceitos discutidos. Espera-se assim que o leitor encontre aqui um guia para um primeiro contato com a plataforma Java, percebendo as possibilidades de sua utilização e ganhando motivação para a continuidade de seus estudos. O Autor. Prof. Peter Jandl Jr. 10 efetuar o download e a execução de código Java via Internet. Apresentado formalmente pela Sun como o navegador HotJava e a linguagem Java no SunWorld'95, o interesse pela solução se mostrou explosivo. Poucos meses depois a Netscape Corp. lança uma nova versão de seu navegador Navigator também capaz de efetuar o download e a execução de pequenas aplicações Java então chamadas applets. Assim se inicia a história de sucesso do Java. Numa iniciativa também inédita a Sun decide disponibilizar o Java gratuitamente para a comunidade de desenvolvimento de software, embora detenha todos os direitos relativos à linguagem e as ferramentas de sua autoria. Surge assim o Java Developer’s Kit 1.0 (JDK 1.0). As plataformas inicialmente atendidas foram: Sun Solaris e Microsoft Windows 95/NT. Progressivamente foram disponibilizados kits para outras plataformas tais como IBM OS/2, Linux e Applet Macintosh. Em 1997, surge o JDK 1.1 que incorpora grandes melhorias para o desenvolvimento de aplicações gráficas e distribuídas e no início de 1999 é lançado o JDK 1.2, contendo muitas outras melhorias, de forma que também seja conhecido como Java 2. Atualmente a Sun vem liberando novas versões ou correções a cada nove meses bem como novas API (Application Program Interface) para desenvolvimento de aplicações específicas. A última versão disponível é o JDK 1.3. 1.3 Características Importantes A linguagem Java exibe importantes características que, em conjunto, diferenciam- na de outras linguagens de programação: • Orientada à Objetos Java é uma linguagem puramente orientada à objetos pois, com exceção de seus tipos primitivos de dados, tudo em Java são classes ou instância de uma classe. Java atende todos os requisitos necessários para uma linguagem ser considerada orientada à objetos que resumidamente são oferecer mecanismos de abstração, encapsulamento e hereditariedade. • Independente de Plataforma Java é uma linguagem independente de plataforma pois os programas Java são compilados para uma forma intermediária de código denominada bytecodes que utiliza instruções e tipos primitivos de tamanho fixo, ordenação big-endian e um biblioteca de classes padronizada. Os bytecodes são como uma linguagem de máquina destinada a uma única plataforma, a máquina virtual Java (JVM – Java Virtual Machine), um interpretador de bytecodes. Pode-se implementar uma JVM para qualquer plataforma assim temos que um mesmo programa Java pode ser executado em qualquer arquitetura que disponha de uma JVM. • Sem Ponteiros Java não possui ponteiros, isto é, Java não permite a manipulação direta de endereços de memória nem exige que o objetos criados seja destruídos livrando os programadores de uma tarefa complexa. Além disso a JVM possui um mecanismo automático de gerenciamento de memória conhecido como garbage collector, que recupera a memória alocada para objetos não mais referenciados pelo programa. • Performance Java foi projetada para ser compacta, independente de plataforma e para utilização em rede o que levou a decisão de ser interpretada através dos esquema de bytecodes. Como uma linguagem interpretada a performance é razoável, não podendo ser comparada a velocidade de execução de código nativo. Para superar esta limitação várias JVM dispõe de compiladores just in time (JIT) que compilam os bytecodes para código nativo durante a execução Introdução ao Java 11 otimizando a execução, que nestes casos melhora significativamente a performance de programas Java. • Segurança Considerando a possibilidade de aplicações obtidas através de uma rede, a linguagem Java possui mecanismos de segurança que podem, no caso de applets, evitar qualquer operação no sistema de arquivos da máquina-alvo, minimizando problemas de segurança. Tal mecanismo é flexível o suficiente para determinar se uma applet é considerada segura especificando nesta situação diferentes níveis de acesso ao sistema-alvo. • Permite Multithreading Java oferece recursos para o desenvolvimento de aplicações capazes de executar múltiplas rotinas concorrentemente bem dispõe de elementos para a sincronização destas várias rotinas. Cada um destes fluxos de execução é o que se denomina thread, um importante recurso de programação de aplicações mais sofisticadas. Além disso, o Java é uma linguagem bastante robusta, oferece tipos inteiros e ponto flutuante compatíveis com as especificações IEEE, suporte para caracteres UNICODE, é extensível dinamicamente além de ser naturalmente voltada para o desenvolvimento de aplicações em rede ou aplicações distribuídas. Tudo isto torna o Java uma linguagem de programação única. 1.4 Recursos Necessários Para trabalharmos com o ambiente Java recomendamos o uso do Java Developer’s Kit em versão superior à JDK 1.1.7 e um navegador compatível com o Java tais como o Netscape Communicator 4.5 ou o Microsoft Internet Explorer 4 ou versões superiores. O JDK, em diversas versões e plataformas oficialmente suportadas pela Sun, pode ser obtido gratuitamente no site: http://www.javasoft.com/products/jdk/ Versões de demonstração dos navegadores da Microsoft, Netscape e Sun (HotJava) podem ser obtidas nos seguintes sites: http://www.microsoft.com/ http://www.netscape.com/ http://java.sun.com/products/hotjava/ Outras informações sobre a plataforma Java, artigos, dicas e exemplos podem ser obtidas nos sites: http://www.javasoft.com/ Site oficial da Sun sobre o Java http://www.javasoft.com/tutorial/ Tutoriais sobre o Java http://java.sun.com/docs/books/tutorial/ Outros tutoriais sobre o Java http://www.javaworld.com/ Revista online http://www.javareport.com/ Revista online http://www.jars.com/ Applets, exemplos e outros recursos http://www.gamelan.com/ Diversos recursos e exemplos Java http://www.internet.com/ Diversos recursos e exemplos Java http://www.javalobby.org/ Revista online http://www.sys-con.com/java Revista online http://sunsite.unc.edu/javafaq/javafaq.html Respostas de dúvidas comuns sobre Java http://www.acm.org/crossroads/ Revista online da ACM http://www.december.com/works/java.html Diversos recursos e exemplos Java Prof. Peter Jandl Jr. 12 1.5 O Sun Java Developer’s Kit O JDK é composto basicamente por: • Um compilador (javac) • Uma máquina virtual Java (java) • Um visualizador de applets (appletviewer) • Bibliotecas de desenvolvimento (os packages java) • Um programa para composição de documentação (javadoc) • Um depurador básico de programas (jdb) • Versão run-time do ambiente de execução (jre) Deve ser observado que o JDK não é um ambiente visual de desenvolvimento, embora mesmo assim seja possível o desenvolvimento de aplicações gráficas complexas apenas com o uso do JDK que é, de fato, o padrão em termos da tecnologia Java. Outros fabricantes de software tais como Microsoft, Borland, Symantec e IBM oferecem comercialmente ambientes visuais de desenvolvimento Java, respectivamente, Visual J++, JBuilder, VisualCafe e VisualAge for Java. 1.6 Um Primeiro Exemplo Com o JDK adequadamente instalado num computador, apresentaremos um pequeno exemplo de aplicação para ilustrarmos a utilização básica das ferramentas do JDK. Observe o exemplo a seguir, sem necessidade de compreendermos em detalhe o que cada uma de suas partes representa: //Eco.java import java.io.*; public class Eco { public static void main(String[] args) { for (int i=0; i < args.length; i++) System.out.print(args[i] + " "); System.out.println(); } } Exemplo 1 Primeiro Exemplo de Aplicação Java Utilize um editor de textos qualquer para digitar o exemplo, garantindo que o arquivo será salvo em formato de texto simples, sem qualquer formatação e que o nome do arquivo será Eco.java (utilize letras maiúsculas e minúsculas exatamente como indicado!). Para podermos executar a aplicação exemplificada devemos primeiramente compilar o programa. Para tanto devemos acionar o compilador Java, ou seja, o programa javac, como indicado abaixo: javac nome_do_arquivo.java O compilador Java exige que os arquivos de programa tenham a extensão “.java” e que o arquivo esteja presente no diretório corrente, assim, numa linha de comando, dentro do diretório onde foi salvo o arquivo Eco.java digite: javac Eco.java Isto aciona o compilador Java transformando o código digitado no seu correspondente em bytecodes, produzindo um arquivo Eco.class. Não existindo erros o javac não exibe qualquer mensagem, caso contrário serão exibidas na tela uma mensagem para cada erro encontrado, indicando em que linha e que posição desta foi detectado o erro. Introdução ao Java 15 2 Java: Variáveis, Operadores e Estruturas de Controle Apresentaremos agora uma pequena discussão sobre a sintaxe da linguagem Java, abordando os tipos de dados existentes, as regras para declaração de variáveis, as recomendações gerais para nomenclatura, os operadores, sua precedência e as estruturas de controle disponíveis. Como será notado, a sintaxe da linguagem Java é muito semelhante àquela usada pela linguagem C/C++. 2.1 Tipos de Dados Primitivos A linguagem Java possui oito tipos básicos de dados, denominados tipos primitivos, que podem agrupados em quatro categorias: Tipos Inteiros Tipos Ponto Flutuante Tipo Caractere Byte Ponto Flutuante Simples Caractere Inteiro Curto Ponto Flutuante Duplo Inteiro Tipo Lógico Inteiro Longo Boleano Tabela 1 Tipos de Dados Primitivos Como pode ser facilmente observado, os tipos primitivos do Java são os mesmos encontrados na maioria das linguagens de programação e permitem a representação adequada de valores numéricos. A representação de outros tipos de dados utiliza objetos específicos assim como existem classes denominadas wrappers que encapsulam os tipos primitivos como objetos da linguagem. 2.1.1 Tipos de Dados Inteiros Existem quatro diferentes tipos de dados inteiros byte (8 bits), short (inteiro curto – 16 bits), int (inteiro – 32 bits) e long (inteiro longo – 64 bits) cuja representação interna é feita através de complemento de 2 e que podem armazenar valores dentro dos seguintes intervalos numéricos: Tipo Valor Mínimo Valor Máximo byte -128 +127 short -32.768 +32.767 int -2.147.483.648 +2.147.483.647 long -9.223.372.036.854.775.808 +9.223.372.036.854.775.807 Tabela 2 Tipos de Dados Inteiros Por default os valores literais são tratados como inteiros simples (int) ou seja valores de 32 bits. Não existe em Java o modificador unsigned disponível em outras linguagens assim os tipos inteiros são sempre capazes de representar tanto valores positivos como negativos. Prof. Peter Jandl Jr. 16 2.1.2 Tipo de Dados em Ponto Flutuante No Java existem duas representações para números em ponto flutuante que se diferenciam pela precisão oferecida: o tipo float permite representar valores reais com precisão simples (representação interna de 32 bits) enquanto o tipo double oferece dupla precisão (representação interna de 64 bits). Os valores m ponto flutuante do Java estão em conformidade com o padrão IEEE 754: Tipo Valor Mínimo Valor Máximo float double Tabela 3 Tipos de Dados em Ponto Flutuante Deve ser utilizado o ponto como separador de casas decimais. Quando necessário, expoentes podem ser escritos usando o caractere ‘e’ ou ‘E’, como nos seguintes valores: 1.44E6 (= 1.44 x 106 = 1,440,000) ou 3.4254e-2 (= 3.4254 x 10-2 = 0.034254). 2.1.3 Tipo de Dados Caractere O tipo char permite a representação de caracteres individuais. Como o Java utiliza uma representação interna no padrão UNICODE, cada caractere ocupa 16 bits (2 bytes) sem sinal, o que permite representar até 32.768 caracteres diferentes, teoricamente facilitando o trabalho de internacionalização de aplicações Java. Na prática o suporte oferecido ao UNICODE ainda é bastante limitado embora permita a internacionalização do código Java. Alguns caracteres são considerados especiais pois não possuem uma representação visual, sendo a maioria caracteres de controle e outros caracteres cujo uso é reservado pela linguagem. Tais caracteres podem ser especificados dentro dos programas como indicado na tabela abaixo, ou seja, precedidos por uma barra invertida (‘\’): Representação Significado \n Pula linha (newline ou linefeed) \r Retorno de carro (carriage return) \b Retrocesso (backspace) \t Tabulação (horizontal tabulation) \f Nova página (formfeed) \’ Apóstrofe \” Aspas \\ Barra invertida \u223d Caractere UNICODE 233d \g37 Octal \fca Hexadecimal Tabela 4 Representação de Caracteres Especiais O valor literal de caracteres deve estar delimitado por aspas simples (‘ ’). 2.1.4 Tipo de Dados Lógico Em Java dispõe-se do tipo lógico boolean capaz de assumir os valores false (falso) ou true (verdadeiro) que equivalem aos estados off (desligado) e on (ligado) ou no (não) e yes (sim). Deve ser destacado que não existem equivalência entre os valores do tipo lógico e valores inteiros tal como usualmente definido na linguagem C/C++. Introdução ao Java 17 2.2 Declaração de Variáveis Uma variável é um nome definido pelo programador ao qual pode ser associado um valor pertencente a um certo tipo de dados. Em outras palavras, uma variável é como uma memória, capaz de armazenar um valor de um certo tipo, para a qual se dá um nome que usualmente descreve seu significado ou propósito. Desta forma toda variável possui um nome, um tipo e um conteúdo. O nome de uma variável em Java pode ser uma sequência de um ou mais caracteres alfabéticos e numéricos, iniciados por uma letra ou ainda pelos caracteres ‘_’ (underscore) ou ‘$’ (cifrão). Os nomes não podem conter outros símbolos gráficos, operadores ou espaços em branco, podendo ser arbitrariamente longos embora apenas os primeiros 32 caracteres serão utilizados para distinguir nomes de diferentes variáveis. É importante ressaltar que as letras minúsculas são consideradas diferentes das letras maiúsculas, ou seja, a linguagem Java é sensível ao caixa empregado, assim temos como exemplos válidos: a total x2 $mine _especial TOT Maximo ExpData Segundo as mesmas regras temos abaixo exemplos inválidos de nomes de variáveis: 1x Total geral numero-minimo void A razão destes nomes serem inválidos é simples: o primeiro começa com um algarismo numérico, o segundo possui um espaço em branco, o terceiro contêm o operador menos mas por que o quarto nome é inválido? Porque além das regras de formação do nome em si, uma variável não pode utilizar como nome uma palavra reservada da linguagem. As palavras reservadas são os comandos, nomes dos tipos primitivos, especificadores e modificadores pertencentes a sintaxe de uma linguagem. As palavras reservadas da linguagem Java, que portanto não podem ser utilizadas como nome de variáveis ou outros elementos, são: abstract continue finally interface public throw boolean default float long return throws break do for native short transient byte double if new static true case else implements null super try catch extends import package switch void char false instanceof private synchronized while class final int protected this Além destas existem outras que embora reservadas não são utilizadas pela linguagem: const future generic goto inner operator outer rest var volatile Algumas destas, tal como o goto, faziam parte da especificação preliminar do Oak, antes de sua formalização como Java. Recomenda-se não utilizá-las qualquer que seja o propósito. Desta forma para declararmos uma variável devemos seguir a seguinte sintaxe: Tipo nome1 [, nome2 [, nome3 [..., nomeN]]]; Ou seja, primeiro indicamos um tipo, depois declaramos uma lista contendo um ou mais nomes de variáveis desejadas deste tipo, onde nesta lista os nomes são separados por vírgulas e a declaração terminada por ‘;’ (ponto e vírgula). Exemplos: int i; float total, preco; byte mascara; double valorMedio; As variáveis podem ser declaradas individualmente ou em conjunto: char opcao1, opcao2; Prof. Peter Jandl Jr. 20 System.out.println("a % b = " + (a % b)); System.out.println("a++ = " + (a++)); System.out.println("--b = " + (--b)); System.out.println("a = " + a); System.out.println("b = " + b); } } Exemplo 2 Aplicação Aritmetica (operadores aritméticos) Compilando e executando o código fornecido teríamos o resultado ilustrado a seguir: Figura 4 Resultado da Aplicação Aritmetica Embora o exemplo só tenha utilizado variáveis e valores inteiros, o mesmo pode ser realizado com variáveis do tipo ponto flutuante (float ou double) 2.4.2 Operadores Relacionais Além dos operadores aritméticos o Java possui operadores relacionais, isto é, operadores que permitem comparar valores literais, variáveis ou o resultado de expressões retornando um resultado do tipo lógico, isto é, um resultado falso ou verdadeiro. Os operadores relacionais disponíveis são: Operador Significado Exemplo == Igual a == b != Diferente a != b > Maior que a > b >= Maior ou igual a a >= b < Menor que a < b <= Menor ou igual a a >= b Tabela 6 Operadores Relacionais Note que o operador igualdade é definido como sendo um duplo sinal de igual (==) que não deve ser confundido com o operador de atribuição, um sinal simples de igual (=). Como é possível realizar-se uma atribuição como parte de uma expressão condicional, o compilador não faz nenhuma menção sobre seu uso, permitindo que uma atribuição seja Introdução ao Java 21 escrita no lugar de uma comparação por igualdade, constituindo um erro comum mesmo para programadores mais experientes. O operador de desigualdade é semelhante ao existente na linguagem C, ou seja, é representado por “!=”. Os demais são idênticos a grande maioria das linguagens de programação em uso. É importante ressaltar também que os operadores relacionais duplos, isto é, aqueles definidos através de dois caracteres, não podem conter espaços em branco. A seguir um outro exemplo simples de aplicação envolvendo os operadores relacionais. Como para os exemplos anteriores, sugere-se que esta aplicação seja testada como forma de se observar seu comportamento e os resultados obtidos. // Relacional.java import java.io.*; public class Relacional { static public void main (String args[]) { int a = 15; int b = 12; System.out.println("a = " + a); System.out.println("b = " + b); System.out.println("a == b -> " + (a == b)); System.out.println("a != b -> " + (a != b)); System.out.println("a < b -> " + (a < b)); System.out.println("a > b -> " + (a > b)); System.out.println("a <= b -> " + (a <= b)); System.out.println("a >= b -> " + (a >= b)); } } Exemplo 3 Aplicação Relacional (operadores relacionais) 2.4.3 Operadores Lógicos Como seria esperado o Java também possui operadores lógicos, isto é, operadores que permitem conectar logicamente o resultado de diferentes expressões aritméticas ou relacionais construindo assim uma expressão resultante composta de várias partes e portanto mais complexa. Operador Significado Exemplo && E lógico (and) a && b || Ou Lógico (or) a || b ! Negação (not) !a Tabela 7 Operadores Lógicos Os operadores lógicos duplos, isto é, definidos por dois caracteres, também não podem conter espaços em branco. 2.4.4 Operador de Atribuição Atribuição é a operação que permite definir o valor de uma variável através de uma constante ou através do resultado de uma expressão envolvendo operações diversas. boolean result = false; i = 0; y = a*x + b; Em Java é válido o encadeamento de atribuições, tal como abaixo, onde todas as variáveis são inicializadas com o mesmo valor: Prof. Peter Jandl Jr. 22 byte m, n, p, q; M m = n = p = q = 0; // equivale a m = (n = (p = (q = 0))); 2.4.5 Precedência de Avaliação de Operadores Numa expressão onde existam diversos operadores é necessário um critério para determinar-se qual destes operadores será primeiramente processado, tal como ocorre em expressões matemáticas comuns. A precedência é este critério que especifica a ordem de avaliação dos operadores de um expressão qualquer. Na Tabela 8 temos relacionados os níveis de precedência organizados do maior (Nível 1) para o menor (Nível 15). Alguns dos operadores colocados na tabela não estão descritos neste texto. Nível Operadores 1 . (seletor) [ ] ( ) 2 ++ -- ~ instanceof new clone - (unário) 3 * / % 4 + - 5 << >> >>> 6 < > <= >= 7 == != 8 & 9 ^ 10 | 11 && 12 || 13 ?: 14 = op= 15 , Tabela 8 Precedência dos Operadores em Java Note que algumas palavras reservadas (instanceof, new e clone) se comportam como operadores. Operadores de um mesmo nível de precedência são avaliados conforme a ordem em que são encontrados, usualmente da esquerda para direita. Para modificarmos a ordem natural de avaliação dos operadores é necessário utilizarmos os parêntesis, cujo nível de avaliação é o mais alto, para especificar-se a ordem de avaliação desejada. 2.5 Estruturas de Controle Um programa de computador é uma sequência de instruções organizadas de forma tal a produzir a solução de um determinado problema. Naturalmente tais instruções são executadas em sequência, o que se denomina fluxo sequencial de execução. Em inúmeras circunstâncias é necessário executar as instruções de um programa em uma ordem diferente da estritamente sequencial. Tais situações são caracterizadas pela necessidade da repetição de instruções individuais ou de grupos de instruções e também pelo desvio do fluxo de execução. As linguagens de programação tipicamente possuem diversas estruturas de programação destinadas ao controle do fluxo de execução, isto é, estruturas que permitem a repetição e o desvio do fluxo de execução. Geralmente as estruturas de controle de execução são divididas em: • Estruturas de repetição simples Destinadas a repetição de um ou mais comandos, criando o que se denomina laços. Geralmente o número de repetições é pré-definido ou pode ser Introdução ao Java 25 Após a execução da seção de inicialização ocorre a avaliação da expressão lógica. Se a expressão é avaliada como verdadeira, a diretiva associada é executada, caso contrário o comando for é encerrado e a execução do programa prossegue com o próximo comando após o for. O terceiro campo determina como a variável de controle será modificada a cada iteração do for. Considera-se como iteração a execução completa da diretiva associada, fazendo que ocorra o incremento ou decremento da variável de controle. A seguir um exemplo de utilização da diretiva for: import java.io.*; public class exemploFor { public static void main (String args[]) { int j; for (j=0; j<10; j++) { System.out.println(“”+j); } } } Exemplo 4 Utilização da Diretiva for Se executado, o exemplo acima deverá exibir uma contagem de 0 até 9 onde cada valor é exibido numa linha do console. 2.5.2 Estruturas de desvio de fluxo Existem várias estruturas de desvio de fluxo que podem provocar a modificação da maneira com que as diretivas de um programa são executadas conforme a avaliação de uma condição. O Java dispõe de duas destas estruturas: if e switch. O if é uma estrutura simples de desvio de fluxo de execução, isto é, é uma diretiva que permite a seleção entre dois caminhos distintos para execução dependendo do resultado falso ou verdadeiro resultante de uma expressão lógica. if (expressão_lógica) diretiva1; else diretiva2; A diretiva if permite duas construções possíveis: a primeira, utilizando a parte obrigatória, condiciona a execução da diretiva1 a um resultado verdadeiro oriundo da avaliação da expressão lógica associada; a segunda, usando opcionalmente o else, permite que seja executada a diretiva1 caso o resultado da expressão seja verdadeiro ou que seja executada a diretiva2 caso tal resultado seja falso. Isto caracteriza o comportamento ilustrado pela Figura 7. A seguir um exemplo de uso da diretiva if. import java.io.*; public class exemploIf { public static void main (String args[]) { if (args.length > 0) { for (int j=0; j<Integer.parseInt(args[0]); j++) { System.out.print("" + j + " "); } System.out.println("\nFim da Contagem"); } System.out.println("Fim do Programa"); } } Exemplo 5 Utilização da Diretiva if Prof. Peter Jandl Jr. 26 Ao executar-se ente programa podem ocorrer duas situações distintas como resultado: se foi fornecido algum argumento na linha de comando (args.length > 0), o programa o converte para um número inteiro (Integer.parseInt(args[0])) e exibe uma contagem de 0 até o número fornecido e depois é exibida uma mensagem final, senão apenas a mensagem final é exibida. Caso se forneça um argumento que não seja um valor inteiro válido ocorre um erro que é sinalizado como uma exceção java.lang.NumberFormatException que interrompe a execução do programa. Já o switch é uma diretiva de desvio múltiplo de fluxo, isto é, baseado na avaliação de uma expressão ordinal é escolhido um caminho de execução dentre vários possíveis. Um resultado ordinal é aquele pertencente a um conjunto onde se conhecem precisamente o elemento anterior e o posterior, por exemplo o conjunto dos números inteiros ou dos caracteres, ou seja, um valor dentro de um conjunto cujos valores podem ser claramente ordenados (0, 1, 2 .... no caso dos inteiros e ‘A’, ‘B’, ‘C’... no caso dos caracteres). Expressão Lógica Diretiva 1 falso verdadeiro Expressão Lógica Diretiva 1 falsoverdadeiro Diretiva 2 Figura 7 Comportamento da Diretiva if O switch equivale logicamente a um conjunto de diretivas if encadeadas, embora seja usualmente mais eficiente durante a execução. Na Figura 8 temos ilustrado o comportamento da diretiva switch. A sintaxe desta diretiva é a seguinte: switch (expressão_ordinal) { case ordinal1: diretiva3; break; case ordinal2: diretiva2; break; default: diretiva_default; } A expressão utilizada pelo switch deve necessariamente retornar um resultado ordinal. Conforme o resultado é selecionado um dos casos indicados pela construção case ordinal. As diretivas encontradas a partir do caso escolhido são executadas até o final da diretiva switch ou até uma diretiva break que encerra o switch. Se o valor resultante não possuir um caso específico são executadas as diretivas default colocadas, opcionalmente, ao final da diretiva switch. Introdução ao Java 27 Expressão Ordinal Diretiva 1 caso1 Diretiva 2 Diretiva n caso2 default Figura 8 Comportamento da Diretiva switch Note que o ponto de início de execução é um caso (case) cujo valor ordinal é aquele resultante da expressão avaliada. Após iniciada a execução do conjunto de diretivas identificadas por um certo caso, tais ações só são interrompidas com a execução de uma diretiva break ou com o final da diretiva switch. A seguir temos uma aplicação simples que exemplifica a utilização das diretivas switch e break. // exemploSwitch.java import java.io.*; public class exemploSwitch { public static void main (String args[]) { if (args.length > 0) { switch(args[0].charAt(0)) { case 'a': case 'A': System.out.println("Vogal A"); break; case 'e': case 'E': System.out.println("Vogal E"); break; case 'i': case 'I': System.out.println("Vogal I"); break; case 'o': case 'O': System.out.println("Vogal O"); break; case 'u': case 'U': System.out.println("Vogal U"); break; default: System.out.println("Não é uma vogal"); } } else { System.out.println("Não foi fornecido argumento"); } } } Exemplo 6 Utilização da Diretiva Switch Prof. Peter Jandl Jr. 30 2.5.4 Estruturas de controle de erros O Java oferece duas importantes estruturas para o controle de erros muito semelhantes as estruturas existentes na linguagem C++: try catch e try finally. Ambas tem o propósito de evitar que o programador tenha que realizar testes de verificação e avaliação antes da realização de certas operações, desviando automaticamente o fluxo de execução para rotinas de tratamento de erro. Através destas diretivas, delimita-se um trecho de código que será monitorado automaticamente pelo sistema. A ocorrência de erros no Java é sinalizada através de exceções, isto é, objetos especiais que carregam informação sobre o tipo de erro detectado. Existem várias classes de exceção adequadas para o tratamento do problemas mais comuns em Java, usualmente inclusas nos respectivos pacotes. Exceções especiais podem ser criadas em adição às existentes ampliando as possibilidades de tratamento de erro. Com o try catch a ocorrência de erros de um ou mais tipos dentro do trecho de código delimitado desvia a execução automaticamente para uma rotina designada para o tratamento específico deste erro. A sintaxe do try catch é a seguinte: try { diretiva_normal; } catch (exception1) { diretiva_de_tratamento_de erro1; } catch (exception2) { diretiva_de_tratamento_de erro2; } No Exemplo 7, a aplicação exibe uma contagem regressiva baseada num valor inteiro fornecido pelo primeiro argumento (args[0]). Se tal argumento não é fornecido, ocorre uma exceção da classe java.lang.ArrayIndexOutOfBoundsException pois o código tenta inadvertidamente usar um argumento que não existe (a mensagem exibida segue abaixo). java.lang.ArrayIndexOutOfBoundsException at exemploWhile.main(Compiled Code) Para evitar esse erro o programador poderia testar de forma convencional se foram fornecidos argumentos à aplicação, como no Exemplo 6 (args.length > 0). A seguir temos uma nova versão do Exemplo 7 que contêm uma estrutura convencional de teste do número de argumentos fornecido. // exemploTeste.java import java.io.*; public class exemploTeste { public static void main (String args[]) { int j = 10; if (args.length > 0) { while (j > Integer.parseInt(args[0])) { System.out.println(""+j); j--; } } else { System.out.println("Não foi fornecido um argumento inteiro"); } } } Exemplo 9 Aplicação com Teste Convencional Introdução ao Java 31 Caso não se forneçam argumentos não ocorrerá erro mas se o primeiro argumento fornecido não for numérico acontecerá a sinalização de uma exceção devido a tentativa conversão efetuada para determinação do limite da contagem a se exibida. Outra alternativa é a utilização de uma diretiva try catch como abaixo: // exemploTryCatch1.java import java.io.*; public class exemploTryCatch1 { public static void main (String args[]) { int j = 10; try { while (j > Integer.parseInt(args[0])) { System.out.println(""+j); j--; } } catch (ArrayIndexOutOfBoundsException e) { System.out.println("Não foi fornecido um argumento."); } } } Exemplo 10 Aplicação da Diretiva try catch Desta forma o código e a rotina de tratamento de erros ficam mais claramente isolados, aumentando a legibilidade e facilidade de modificação do programa. Considerando que o argumento fornecido pelo usuário pode não ser um inteiro válido ocorre uma outra exceção: java.lang.NumberFormatException pois a conversão se torna impossível. Contornar esse erro testando a validade do argumento requereria uma rotina extra no modo convencional. Com o uso da diretiva try catch teríamos: // exemploTryCatch2.java import java.io.*; public class exemploTryCatch2 { public static void main (String args[]) { int j = 10; try { while (j > Integer.parseInt(args[0])) { System.out.println(""+j); j--; } } catch (ArrayIndexOutOfBoundsException e) { System.out.println("Não foi fornecido um argumento."); } catch (java.lang.NumberFormatException e) { System.out.println("Não foi fornecido um inteiro válido."); } } } Exemplo 11 Utilização da Diretiva try catch com 2 Exceções Com o try finally temos um comportamento bastante diferente: uma rotina de finalização é garantidamente executada, isto é, o trecho particular de código contido na cláusula finally é sempre executado ocorrendo ou não erros dentro do trecho delimitado pela cláusula try. A sintaxe do try finally é colocada a seguir: Prof. Peter Jandl Jr. 32 try { diretiva_normal; } finally { diretiva_de_tratamento_de erro; } Isto é particularmente interessante quando certos recursos do sistema ou estruturas de dados devem ser liberadas, independentemente de sua utilização. Introdução ao Java 35 O essencial desta abordagem é que podemos (e devemos) criar novos modelos de dados, as classes, para melhor representar os dados que serão utilizados pela aplicação, isto é, para melhor representarmos os objetos reais a serem tratados pela aplicação. Note que um mesmo objeto pode ser descrito de diferentes maneiras, sendo cada uma destas maneiras mais ou menos adequada para representar tal objeto dentro de um determinado contexto, ou seja, dentro um de um certo problema. A definição de uma nova classe em Java é um processo muito simples desde que saibamos como descrever um objeto, sendo assim a definição de um objeto é realmente uma tarefa mais complexa do que sua programação como uma classe. Uma classe em Java pode ser definido através da seguinte estrutura de código: qualificador_de_acesso class Nome_Da_Classe { // atributos da classe M // métodos da classe M } Os qualificadores de acesso indicam como a classe pode ser utilizada por outras classes e, consequentemente, outras aplicações. Existem dois especificadores de acesso básicos: Qualificador Significado public Indica que o conteúdo público da classe pode ser utilizado livremente por outras classes do mesmo pacote ou de outro pacote. package Indica que o conteúdo público da classe pode ser utilizado livremente por outras classes pertencentes ao mesmo pacote. Para compreendemos melhor o significado destes qualificadores devemos entender primeiramente o que é um pacote para o Java. Quando declaramos uma classe como pública (public) estamos indicando ao compilador que todo o conteúdo da classe indicado como público pode ser irrestritamente utilizado por classes que pertençam ao mesmo pacote, isto é, que esteja localizadas no mesmo diretório, ou que pertençam a outros pacotes (outros diretórios). O segundo caso é quando indicamos que a classe é do tipo pacote (package) o que corresponde a situação padrão, que não precisa ser informada. Uma classe do tipo package pode ser utilizada como sendo pública para classes contidas no mesmo diretório, o mesmo não acontecendo para classes de outros pacotes ou diretórios. Isto serve para restringir o uso de classes “de serviço” ao próprio pacote. Por definição todas as classes de um diretório são do tipo pacote, exceto as explicitamente informadas como públicas. 3.2.1 Pacotes (Packages) Um pacote (package) em Java nada mais é do que um diretório onde residem uma ou mais classes ou seja, é um conjunto de classes. Usualmente colocam-se num package classes relacionadas construídas com um certo propósito. Sob certos aspectos os packages reproduzem a idéias das libraries (bibliotecas) de outras linguagens de programação. Para facilitar o uso de classes existentes em diferentes diretórios, ou seja, a construção e utilização de pacotes, utiliza-se uma denominação especial para os pacotes, cuja sintaxe é: import nome_do_pacote.Nome_da_classe; Seguem alguns exemplos do uso de pacotes: import java.io.*; import java.awt.Button; import br.usf.toolbox.ImageButton; No primeiro indicamos que queremos utilizar qualquer classe do pacote java.io, um pacote padrão que acompanha o JDK. No segundo indicamos uma classe específica Prof. Peter Jandl Jr. 36 (Button) do pacote java.awt que também acompanha o JDK. Nestes casos, tanto o pacote java.io como o java.awt devem estar localizados diretamente num dos diretórios especificados como diretórios de classes do JDK (contida na variável de ambiente classpath cujo valor default é \jdk_path\lib\classes.zip). No terceiro indicamos o uso da classe ImageButton do pacote toolbox cuja origem é a empresa de domínio www.usf.br (o uso dos nomes de domínio na denominação de pacotes é uma recomendação da Sun para que sejam evitadas ambigüidades) ou seja, deve existir um diretório \br\usf\toolbox\ a partir de um dos diretórios de classes do JDK. Para que uma aplicação ou applet Java utilize classes de diferentes pacotes é necessário portanto incluir uma ou mais diretivas import que especifiquem tais pacotes. Por default apenas as classes do pacote java.lang, que contêm as definições de tipos e outros elementos básicos, são importadas. Para definirmos classes de um pacote devemos adicionar a diretiva package com o nome do pacote no início de cada um dos arquivos de suas classes: package br.usf.toolbox; public class ImageButton { M } 3.2.2 Estrutura das Classes Um classe basicamente possui dois grupos de elementos: a declaração de seus atributos e a implementação de seus métodos. Se desejássemos construir uma nova classe pública denominada TheFirstClass estruturalmente deveríamos escrever o seguinte código: // TheFirstClass.java public class TheFirstClass { // declaração de atributos M // implementação de métodos M } Exemplo 12 Uma Primeira Classe Ao salvarmos o código correspondente a uma classe em um arquivo devemos tomar os seguintes cuidados: 1. Em um único arquivo Java podem existir várias diferentes definições de classes mas apenas uma delas pode ser pública (se deseja-se várias classes públicas então cada uma delas deve ser salva em arquivos separados). 2. O nome do arquivo deve ser o nome da classe pública, observando-se cuidadosamente o uso do mesmo caixa tanto para o nome da classe como para o nome do arquivo (Java é uma linguagem sensível ao caixa, isto é, diferencia letras maiúsculas de minúsculas em tudo). Portanto o nome do arquivo para conter a classe pública TheFirstClass deve ser TheFirstClass.java. O que inicialmente pode parecer uma restrição é na verdade um mecanismo inteligente e simples para manter nomes realmente representativos para os arquivos de um projeto. Graficamente representaremos nossas classes como indicado na ilustração a seguir, isto é, utilizaremos a notação proposta pela UML (Unified Modeling Language) bastante semelhante a OMT (Object Modeling Techinique). Na notação UML classes são representadas por retângulos contendo o nome da classe. O nome que figura entre parêntesis abaixo do nome da classe é uma extensão da Introdução ao Java 37 notação para a linguagem Java destinada a indicar o nome do pacote ao qual pertence a classe. A expressão “from default” indica que a classe não pertence explicitamente a qualquer pacote, sendo assim considerada do pacote default subentendido pelas classes Java presentes no diretório onde se localiza. Figura 11 Representação de Classe (Notação UML) Apesar de estar correta a definição de FirstClass, nossa classe é inútil pois não contêm nenhum elemento interno. Para que esta classe possa representar algo é necessário que possua atributos ou métodos como veremos nas próximas seções. 3.2.3 Regras para Denominação de Classes Em Java recomenda-se que a declaração de classes utilize nomes iniciados com letras maiúsculas, diferenciando-se do nomes de variáveis ou instâncias de objetos. Caso o nome seja composto de mais de uma palavras, as demais também deveriam ser iniciadas com letras maiúsculas tal como nos exemplos: Bola Socket Filter BolaFutebol ServerSocket DataChangeObserver A utilização de caracteres numéricos no nome também é livre enquanto o uso do traço de sublinhar (underscore ‘_’) não é recomendado. 3.3 Atributos Um atributo (attribute) de uma classe é uma variável pertencente a esta classe que é destinada a armazenar alguma informação que está intrinsecamente associada a classe. Por exemplo, ao falarmos de uma bola qualquer, o tamanho de uma bola, isto é, seu raio, está associado a sua forma geométrica de forma natural pois toda bola deve possuir um tamanho qualquer que seja. Assim sendo é bastante natural definirmos uma classe Bola que possua como um atributo uma variável destinada a armazenar seu tamanho ou raio, como abaixo: // Bola.java public class Bola { // Atributos float raio; } Como mostra o trecho de código, a adição de um atributo a uma classe corresponde a simples declaração de uma variável de um certo tipo dentro de sua definição cujo nome deveria indicar seu propósito. Muitas vezes nos referimos aos atributos de uma classe como sendo seus campos (fields) ou também como suas variáveis-membro (members). Nossa classe Bola permite agora representar objetos do tipo bola através de um atributo ou campo raio. Além deste atributo, outros poderiam ser adicionados para melhor modelarmos um objeto desta categoria: // Bola.java public class Bola { // Atributos float raio; boolean oca; int material; int cor; } Exemplo 13 Classe Bola Prof. Peter Jandl Jr. 40 Desta forma objetos diferentes podem ser criados e referenciados através de variáveis objeto com nomes distintos: Bola bolaDoJoao = new Bola(); Bola minhaBola = new Bola(); Bola outraBola = new Bola(); Bola bola1 = new Bola(); Portanto um mesmo programa pode utilizar vários diferentes objetos de uma mesma classe, tal qual podemos manusear diferentes objetos do mesmo tipo, mantendo-se apenas uma referência distinta para cada objeto (uma variável objeto diferente para cada um deles). A classe Bola define atributos raio, oca, material e cor para seus objetos. Para indicarmos qual destes atributos desejamos utilizar utilizamos um outro operador denominado seletor (selector) indicado por um caractere ponto (“.”) como segue: nomeDoObjeto.nomeDoAtributo Esta construção pode ser lida como: selecione o atributo nomeDoAtributo do objeto nomeDoObjeto. Com o nome da variável objeto selecionamos o objeto em si e através do seletor indicamos qual atributo deste objeto que desejamos utilizar. Usar um atributo pode significar uma de duas operações: (i) consultar seu conteúdo, ou seja, ler ou obter seu valor e (ii) determinar seu conteúdo, isto é, escrever ou armazenar um certo valor neste atributo. A primeira situação envolve o atributo numa expressão qualquer enquanto na segunda situação temos o atributo recebendo o resultado de uma expressão qualquer através de uma operação de atribuição. Observe o exemplo a seguir onde utilizamos a classe Bola num pequeno programa que instancia dois objetos diferentes e utiliza os atributos destes objetos: // DuasBolas.java public class DuasBolas { public static void main(String args[]) { // Instanciando um objeto Bola bola1 = new Bola(); // Armazenando valores em alguns dos atributos deste objeto bola1.raio = 0.34; bola1.oca = false; bola1.cor = 10; // Instanciando um outro objeto Bola bola2 = new Bola(); // Armazenando valores em alguns atributos do outro objeto bola2.oca = true; bola2.material = 1324; // Usando valores armazenados bola2.raio = 5 * bola1.raio; bola2.cor = bola1.cor; System.out.println(“Bola1:”); System.out.println(“ raio = “ + bola1.raio); System.out.println(“ oca = “ + bola1.oca); System.out.println(“ cor = “ + bola1.cor); System.out.println(“Bola2:”); System.out.println(“ raio = “ + bola2.raio); System.out.println(“ oca = “ + bola2.oca); System.out.println(“ cor = “ + bola2.cor); } } Exemplo 15 Instanciação de Objetos Compilando e executando este programa temos que são exibidos os diferentes valores armazenados nos atributos dos dois objetos Bola criados. Introdução ao Java 41 A combinação objeto.atributo pode ser utilizada como uma variável comum do mesmo tipo definido para o atributo na classe objeto, permitindo a fácil manipulação do dados armazenados internamente no objeto. Como visto, valores dos atributos de um objeto podem ser utilizados diretamente em qualquer expressão e operação válida para seu tipo. Assim variáveis comuns podem receber valores de atributos de objetos e vice-versa, desde que de tipos compatíveis, como exemplificado: int a = 7, b; bola1.cor = a; b = bola2.material; bola1.material = b – 2; bola2.cor = bola1.cor + 3; if (bola2.cor == 0) System.out.println(“Bola branca”); 3.5 Métodos Enquanto os atributos permitem armazenar dados associados aos objetos, ou seja, valores que descrevem a aparência ou estado de um certo objeto, os métodos (methods) são capazes de realizar operações sobre os atributos de uma classe ou capazes de especificar ações ou transformações possíveis para um objeto. Isto significa que os métodos conferem um caráter dinâmico aos objetos pois permitem que os objetos exibam um comportamento que, em muitos casos, pode mimetizar (imitar) o comportamento de um objeto real. Outra forma de entender o que são os métodos é imaginar que os objetos são capazes de enviar e receber mensagens, de forma tal que possamos construir programas onde os objetos trocam mensagens proporcionando o comportamento desejado. A idéia central contida na mensagem que pode ser enviada ao objeto é mesma contida quando ligamos um liquidificar: acionar o botão que liga este aparelho numa de suas velocidades corresponde a enviar uma mensagem ao liquidificar “ligue na velocidade x”. Quando fazemos isto não é necessário compreender em detalhe como funciona o liquidificador mas apenas entender superficialmente como operá-lo. Imaginemos uma lâmpada incandescente comum. A despeito de detalhes técnicos (tensão de operação, potência, frequência etc.) podemos assumir que uma lâmpada pode estar acesa ou apagada, o que caracteriza um estado lógico acesa (acesa = true ou acesa = false). Nós não fazemos uma lâmpada acender diretamente mas acionamos algo que faz com que ela funcione como desejado, assim, usualmente enviamos mensagens do tipo ligar e desligar para a lâmpada. Para construirmos uma classe para modelar uma lâmpada e o comportamento descrito poderíamos ter: // Classe Lampada public class Lampada { // atributos boolean acesa; // metodos public void ligar() { acesa = true; } public void desligar() { acesa = false; } } Exemplo 16 Classe Lampada A classe Lampada modelada possui um atributo lógico acesa que identifica o estado atual da lâmpada. Além deste atributo podemos identificar dois métodos ligar e Prof. Peter Jandl Jr. 42 desligar que fazem com que o estado da lâmpada seja adequadamente modificado para verdadeiro e falso conforme a mensagem enviada a lâmpada. Notamos que um método nada mais é do que um bloco de código para o qual se atribui um nome como o método ligar destacado abaixo: public void ligar() { acesa = true; } O método é declarado como público pois desejamos que seja utilizado externamente a classe Lampada pelas classes que criem instâncias do objeto Lâmpada. A palavra reservada void indica que não existe um valor de retorno, isto é, o método ligar não devolve uma mensagem informando o resultado da operação (neste caso não era necessário). Os parêntesis após o nome ligar tem duplo propósito: diferenciam a construção de um método da declaração de uma variável e permitem que sejam especificados valores auxiliares que podem ser enviados em anexo a mensagem para informar mais precisamente a forma com que a ação deve ser realizada (neste caso nada é especificado indicando que nada mais é necessário além da própria mensagem “ligar”). A especificação dos valores que um método recebe é denominada lista de parâmetros formais ou simplesmente lista de parâmetros. Os valores que são passados para o método quando de seu uso são chamados argumentos. A representação da classe Lampada através da notação UML é dada a seguir. Note que as operações definidas para a classe são colocadas numa outra divisão. Figura 14 Classe Lâmpada (Notação UML) Ao declararmos uma classe é comum a colocação da declaração de todos os seus métodos após a declaração dos seus atributos, porém podemos intercalar a declaração de métodos e atributos ou usar qualquer outra ordem que nos convenha. Existem autores e programadores que preferem concentrar a declaração dos atributos no final da classe, mas é apenas uma questão de estilo de escrita de código. Recomenda-se de qualquer forma que sejam utilizadas linhas de comentários para delimitar as áreas código das classes, ou seja, comentários separando a seção dos atributos da seção dos métodos ou mesmo pequenos comentários para cada declaração contida na classe. Para utilizarmos um método de uma classe procedemos de forma análoga ao uso de atributos, isto é, escolhemos o objeto através de seu nome e com o operador de seleção (selector) indicamos qual o método deve ser utilizado. Se o método recebe argumentos, os mesmo devem colocados no interior dos parêntesis separados por vírgulas, caso contrário os parêntesis permanecem vazios. nomeDoObjeto.nomeDoMetodo(argumentos) Assim para invocar o método ligar ou desligar devemos usar um dos trechos de código exemplificados abaixo, onde objeto é o nome da variável objeto que se refere a uma instância da classe Lampada: objeto.ligar(); objeto.desligar(); A seguir um programa exemplo onde instanciamos um objeto Lampada e utilizamos seus métodos: Introdução ao Java 45 liquidificador. Da mesma forma é igualmente simples adicionar-se novas velocidades ao código. O diagrama UML da classe Liquidificador é ilustrado a seguir. Figura 15 Diagrama UML da Classe Liquidificador Finalmente o método obterVelocidade, que também não recebe argumentos, retorna o valor do atributo velocidade, pois este não pode ser diretamente lido por qualquer outra classe. Como este método devolve um valor seu uso somente é efetivo quando o resultado for empregado num expressão qualquer. A devolução de valores emprega a diretiva return. No diagrama UML da Figura 15 é indicado o tipo de retorno do método obterVelocidade. O exemplo a seguir demonstra a funcionalidade da classe liquidificador. // LiquiTest.java public class LiquiTest { public static void main(String args[]) { Liquidificador l = new Liquidificador(); System.out.println("Liquidificador"); System.out.println("> Vinicial = " + l.obterVelocidade()); for(int v=0; v<5; v++) { System.out.print("> aumentando velocidade..."); l.aumentarVelocidade(); System.out.println("Vatual = " + l.obterVelocidade()); } for(int v=0; v<5; v++) { System.out.print("> diminuindo velocidade..."); l.diminuirVelocidade(); System.out.println("Vatual = " + l.obterVelocidade()); } } } Exemplo 19 Teste da Classe Liquidificador O programa tenta aumentar e diminuir a velocidade do liquidificador instanciado mais do que realmente é possível, mas como antecipado, a classe não admite valores inconsistentes mantendo a velocidade dentro dos limites estabelecidos. Imaginando agora um liquidificador de funcionalidade idêntica mas com controle digital de velocidades, ou seja, botões para velocidade 0 (desligado), velocidade baixa e velocidade alta. Agora é possível passar de qualquer velocidade para qualquer outra, não existindo uma sequência de estados como no liquidificador analógico, assim não temos um comportamento distinto para o aumento ou diminuição de velocidades apenas uma troca de velocidades. Poderíamos definir esta classe como segue: // Liquidificador2.java public class Liquidificador2 { // atributos Prof. Peter Jandl Jr. 46 protected int velocidade; // metodos public void trocarVelocidade(int v) { // verifica se velocidade informada valida if (v>=0 && v<=2) { velocidade = v; } } public int obterVelocidade() { return velocidade; } } Exemplo 20 Classe Liquidificador2 (controle digital) Nesta nova classe o atributo velocidade é definido como protegido (protected) de forma que possa ser eventualmente utilizado por classes baseadas na classe Liquidificador2 (os conceitos de herança serão vistos na seção 5.3). O novo diagrama UML para esta classe seria como abaixo. Note a semelhança com o mostrado para o exemplo anterior e a indicação de tipo para argumento de métodos e valor de retorno. Figura 16 Diagrama UML da Classe Liquidificador2 O método trocarVelocidade é agora o único método responsável pela troca de velocidades do liquidificador. Tal como o uso dos botões, onde cada um corresponde ao acionamento do liquidificador numa dada velocidade, seu uso depende de um argumento que indicará qual a velocidade com que o liquidificador deve funcionar. Obviamente, se for especificada uma velocidade inconsistente não ocorre mudança alguma no estado do liquidificador (você não pode acionar um botão que não existe e portanto tal atitude não tem efeito prático). Para acionarmos este método com seu argumento devemos escrever: objeto.trocarVelocidade(expressão) Onde como expressão entende-se qualquer expressão que produza um resultado inteiro, ou seja, um resultado do mesmo tipo esperado como argumento do método em questão. Desta forma consegue-se associar a troca de velocidades do liquidificador com o resultado numérico de uma expressão aritmética. A seguir um exemplo de aplicação que pode testar a classe Liquidificador2 como anteriormente realizado: // LiquiTest2.java public class LiquiTest2 { public static void main(String args[]) { Liquidificador2 l = new Liquidificador2(); System.out.println("Liquidificador2"); System.out.println("> Vinicial = " + l.obterVelocidade()); for(int v=0; v<5; v++) { System.out.print("> aumentando velocidade..."); l.trocarVelocidade(v); Introdução ao Java 47 System.out.println("Vatual = " + l.obterVelocidade()); } for(int v=4; v>=0; v--) { System.out.print("> diminuindo velocidade..."); l.trocarVelocidade(v); System.out.println("Vatual = " + l.obterVelocidade()); } } } Exemplo 21 Teste da Classe Liquidificador2 Embora o resultado da execução desta aplicação seja o mesmo da classe anterior, ficam caracterizados métodos capazes de: (i) apenas efetuar operações, (ii) efetuar operações retornando um resultado e (iii) receber valores efetuando operações. Combinando-se o recebimento de argumentos com o retorno de valores teríamos o último tipo de método, que será exemplificado mais a frente. 3.6 Acessibilidade A acessibilidade de uma classe, método ou atributo de uma classe é a forma com que tal elemento pode ser visto e utilizado por outras classes. Este conceito é mais conhecido como encapsulamento ou data hiding (ocultamento de dados) sendo muito importante dentro da orientação à objetos. A determinação da acessibilidade de uma classe ou membro de classe é feita pelos qualificadores de acesso (access qualifiers) ou seja, por palavras reservadas da linguagem que definem o grau de encapsulamento exibido por uma classe e seus elementos, isto é, são especificações para restringir-se o acesso as declarações pertencentes a uma classe ou a própria classe como um todo. Como visto em parte, isto pode ser feito através do uso das palavras reservadas public (público), private (privado), package (pacote) e protected (protegido). Restringir o acesso a certas partes de uma classe equivale a controlar seu uso, o que oferece várias vantagens dentre elas: (i) o usuário da classe passa a conhecer apenas aquilo que é necessário para o uso da classe, (ii) detalhes da forma de implementação são ocultos dos usuários da classe, (iii) pode-se assegurar que certos atributos tenham valores restritos a um conjunto mantendo-se a consistência e a integridade dos dados armazenados na classe e (iv) pode-se garantir que determinadas operações sejam realizadas numa certa sequência ou tenham seu funcionamento condicionado a estados da classe. Isto é possível porque através dos qualificadores de acesso existentes podemos ocultar partes da classe, fazendo apenas que outras partes selecionadas sejam visíveis aos usuários desta classe. Como no caso do Exemplo 18, onde o atributo velocidade da classe Liquidificador não era visível externamente, impedindo que valores inconsistentes fossem armazenados nele, pois seu uso só podia ser indiretamente realizado através de métodos pertencentes a classe programados de forma a garantir a consistência deste atributo. Se instanciássemos um objeto Liquidificador e tentássemos usar diretamente o atributo velocidade: Liquidificador l = new Liquidificador(); l.velocidade = 3; Obteríamos o seguinte erro durante a compilação que indica que o atributo existe mas não pode ser utilizado: Prof. Peter Jandl Jr. 50 Um construtor obrigatoriamente tem sempre o mesmo nome da classe a qual pertence. Outro ponto importante é que os construtores sempre devem ser especificados como públicos (public) e nunca podem ser declarados como abstratos (abstract). Tomando como exemplo a classe String, que está definida dentro do package java.lang, temos que existe um construtor String(), que não recebe argumentos, tal como nos exemplos vistos anteriormente (classe Lampada e seu construtor Lampada(), classe Liquidificador e seu construtor Liquidificador()). Inspecionando as classes construídas notamos que não existe menção a um construtor, assim como estamos usando um método que não foi declarado? Para classes onde não exista um construtor explicitamente definido, o Java assume a existência de um construtor denominado default. O construtor default é um método de mesmo nome que a classe, sem argumentos, que inicializa as variáveis membro existentes na classe da seguinte forma: variáveis inteiras e reais recebem zero, variáveis lógicas recebem false e variáveis objeto recebem o valor null (sem referência). Isto é uma das facilidades oferecidas pelo Java: construtores default e inicialização automática de variáveis. Em todos os exemplos anteriores usamos os construtores default das classes construídas. Para a classe Lampada (Exemplo 16) poderíamos ter definido explicitamente o seguinte construtor, que substitui o construtor default, possuindo funcionalidade equivalente: // Cria um objeto Lampada no estado apagada public Lampada() { acesa = false; } Exemplo 22 Um Construtor para Classe Lampada Da mesma forma a classe Liquidificador (Exemplo 18) poderia ter sido definida contendo o seguinte construtor: // Cria um objeto Liquidificador no estado desligado public Liquidificador() { velocidade = 0; } Exemplo 23 Um Construtor para Classe Liquidificador Um construtor semelhante também poderia ser adicionado a classe Liquidificador2 (Exemplo 20). A adição destes construtores ao código não implica na necessidade de qualquer modificação nos programas de teste já elaborados para estas classes dado que tais construtores apenas substituem o construtor default utilizado. Nos diagramas UML da Figura 15 e da Figura 16 temos a indicação dos construtores destas classes. Tal como para métodos comuns, podemos criar construtores que recebem argumentos, de forma que possamos inicializar o objeto de uma maneira particular. Estes construtores também são chamados de construtores parametrizados. Retomando a classe java.lang.String, outro construtor existente é um que aceita uma string literal (uma cadeia de caracteres literal) como argumento. Veja o exemplo a seguir: // UseStrings.java public class UseStrings { public static void main(String args[]) { String s0 = null; String s1 = new String(); String s2 = new String("Alo Pessoal!"); System.out.println("Testando construtores de Strings:"); System.out.println("s0 = " + s0); System.out.println("s1 = " + s1); Introdução ao Java 51 System.out.println("s2 = " + s2); } } Exemplo 24 Teste do Construtores da Classe String Executando o programa obtemos como resultado a impressão dos valores null, branco e “Alo Pessoal!” correspondentes as respectivas inicializações destas variáveis. Uma classe pode possuir vários construtores, todos com o mesmo nome obrigatório, diferenciando-se apenas pelas suas listas de argumentos (isto é o que chamamos: sobrecarga de construtor ou constructor overload, assunto que será visto mais a frente na seção 5.2). A possibilidade de uma classe possuir diversos construtores facilita seu uso em diferentes circunstâncias. Assim poderíamos adicionar o seguinte construtor a classe Lampada do Exemplo 16 que faz com que os novos objetos sejam criados num dado estado inicial. // Cria um objeto Lampada no estado dado public Lampada(boolean estado) { acesa = estado; } Exemplo 25 Um Construtor Adicional para Classe Lampada 3.8 Destrutores e a Coleta de Lixo Os destrutores (destructors) também são métodos especiais que liberam as porções de memória utilizada por um objeto, ou seja, enquanto os construtores são responsáveis pela alocação de memória inicial e também preparo do objeto, os destrutores encerram as operações em andamento, fechando arquivos abertos, encerrando comunicação e liberando todos os recursos alocados do sistema, “apagando” todos os vestígios da existência prévia do objeto. Na linguagem C++ os destrutores são acionados através de operações de eliminação de objetos acionadas pelo comando delete. Assim como os construtores, os destrutores do C++ possuem o mesmo nome da classe embora precedidos pelo caractere til (‘~’). A importância dos destrutores reside no fato de que devemos devolver ao sistema os recursos utilizados pois caso contrário tais recursos (memória, handlers para arquivos etc.) podem se esgotar impedindo que este programa e os demais existentes completem suas tarefas. Um dos maiores problemas do desenvolvimento de aplicações é justamente garantir a correta devolução dos recursos alocados do sistema, concentrando-se este problema na devolução da memória ocupada. Quando um programa não devolve ao sistema a quantidade integral de memória alocada e como se o sistema estivesse “perdendo” sua memória, termo conhecido como memory leakage. Um exemplo disto em C++ seria: Lampada l; // variável objeto l = new Lampada(); // l recebe a referencia de um novo objeto l = new Lampada(); // l recebe uma outra referencia de um objeto // a referência anterior é perdida, ou seja, // o primeiro objeto alocado fica perdido na // memória Exemplo 26 Simulação de Memory Leakage Ciente destes problemas, os desenvolvedores do Java incorporaram ao projeto da máquina virtual um gerenciador automático de memória que é executado paralelamente ao programa Java. Cada vez que um objeto deixa de ser referenciado, isto é, sai do escopo do programa ficando perdido na memória, ele é marcado para eliminação futura. Quando o Prof. Peter Jandl Jr. 52 programa fica ocioso, esperando por entrada de dados por exemplo, o gerenciador automático de memória destroi os objetos perdidos, recuperando a memória perdida. Se a quantidade de memória também se esgota, este mecanismo procura por objetos perdidos para recuperar memória de imediato. Este inteligente mecanismo de eliminação de objetos perdidos e consequente recuperação de memória do sistema é conhecido como coletor automático de lixo (automatic garbage collector ou garbage collector). Desta forma não é necessário liberar memória explicitamente nos programas em Java, libertando os programadores desta tarefa usualmente complexa e problemática. Para eliminar um objeto em Java basta perder sua referência o que pode ser feito através de uma nova inicialização da variável objeto que mantêm a referência ou atribuindo-se o valor null para tal variável. O mesmo código C++ exemplificado anteriormente funciona perfeitamente em Java pois ao perder-se a referência do primeiro objeto este se torna alvo do coletor de lixo (alvo do gc) tendo a memória associada marcada e recuperada assim que possível, de forma automática e transparente pela máquina virtual Java. Lampada l; // variável objeto l = new Lampada(); // l recebe a referencia de um novo objeto l = new Lampada(); // l recebe uma outra referencia de um objeto : // a referência anterior será eliminada pelo gc l = null; // a referência anterior será eliminada pelo gc Exemplo 27 Exemplos de Destruição de um Objeto em Java A presença da coleta automática de lixo no Java torna o conceito de destrutores um pouco diferente de seus equivalentes em outras linguagens orientadas à objetos. Para todos os objetos existem destrutores default tais como os construtores default. Como o conceito e propósito destes destrutores são ligeiramente diferentes em Java, tais destrutores são denominados finalizadores (finalizers). Estes métodos especiais são acionados pelo sistema quando um objeto perdido é efetivamente selecionado para destruição. Assim como para os construtores, é possível explicitamente criar-se os finalizadores para realização de tarefas mais sofisticadas numa estrutura como a abaixo: protected void finalize() { // codigo para “preparar a casa” antes da efetiva destruição do objeto } Exemplo 28 Estrutura de um Finalizador (finalizer) Os finalizadores não possuem argumento e não tem valor de retorno (void) podendo ser explicitamente acionados, embora isto não garanta que o objeto será imediatamente eliminado, mas apenas marcado para eliminação futura. A coleta de lixo pode ser forçada programaticamente, isto é, pode ser acionada por meio de programas, através de uma chamada explicita a um método estático existente na classe java.lang.System: System.gc(); //ativa a coleta automática de lixo É importante ressaltar que um objeto não é imediatamente destruído ao sair do escopo do programa. O coletor de lixo apenas efetua uma marcação para eliminação futura, isto é, marca o objeto fora de escopo para ser eliminado quando se torna necessário recuperar memória para o sistema, evidenciando o caráter assíncrono da coleta de lixo. Outra forma de forçar a destruição efetiva dos objetos é desativar o funcionamento assíncrono da coleta de lixo, ou seja, indicar para a máquina virtual que o objeto deve ser destruído assim que sair do escopo. Isto pode ser feito invocando-se a JVM como abaixo: java –noasyncgc NomeDaClasse Introdução ao Java 55 public class PontoGeografico { private float latitude; private float longitude; public float getLatitude() { return latitude; } public void setLatitude(float l) { latitude = l; } public float getLongitude() { return longitude; } public void setLongitude (float l) { longitude = l; } } Assim a declaração inicial dos campos latitude e longitude pode ser trocada por: PontoGeografico pg; Desta forma as idéias contidas nos atributos individualmente declarados são preservadas e podem ser utilizadas por outras classes. Além disso torna-se mais fácil modificar-se esta estrutura ou adicionar-se novas regras de tratamento. 5. Nem todos os atributos necessitam de métodos de acesso Mesmo sendo privados, nem todos os atributos necessitam de métodos de acesso para leitura e escrita de dados. Avalie caso a caso e implemente apenas os métodos de acesso necessário. Se os dados só são modificados na criação do objeto, crie construtores parametrizados para realizar tal tarefa. Isto reduz a quantidade de código associada a uma classe. 6. Padronize a estrutura de suas classes Recomenda-se que as classes sejam escritas da mesma forma, ou seja, que seja utilizado um estilo consistente de escrita de programas para todas as classes, incluindo-se nisso o formato dos comentários e a distribuição destes dentro do código, bem como o posicionamento da declaração de atributos e de métodos. A declaração dos atributos pode ser colocada no início ou fim da classe. Os métodos podem ser ordenados alfabeticamente ou, como é mais útil, divididos em categorias: primeiro os públicos, depois os protegidos e finalmente os privados. 7. Divida classes com muitas responsabilidades Se uma classe possui muitos atributos ou métodos, estude sempre a possibilidade de dividir esta classe em duas ou três, tornando cada uma destas mais simples e ao mesmo tempo mais especializada. 8. Utilize nomes significativos na denominação de classes, métodos e atributos Ao escolher um nome para uma classe, método ou atributo, procure um que represente o mais precisamente possível o propósito deste elemento. Evite nomes sem significado ou abreviaturas não usuais. As classes e atributos devem utilizar substantivos como nomes simples ou substantivos e adjetivos como nomes duplos. Métodos devem dar preferência para verbos no infinitivo ou substantivos e verbos no infinitivo. Estas dicas não são as únicas que poderiam ser dadas nem tão pouco definitivas, mas podem auxiliá-lo num bom projeto e redação de suas classes. Utilizando os comentários padronizados propostos pela ferramenta javadoc, contida no JDK, pode-se produzir com relativa facilidade uma documentação simples mas bastante consistente e de fácil manutenção. Quando possível faça diagramas OMT (Object Modeling Technique) ou UML (Unified Modeling Language) para suas classes mantendo-os junto com a documentação das mesmas. Prof. Peter Jandl Jr. 56 4 Aplicações de Console Através da linguagem Java é possível construirmos aplicações de console, isto é, aplicações que utilizam os consoles de operação dos sistemas operacionais para a interação com o usuário, ou seja, para a realização das operações de entrada de dados fornecidos pelo usuário e a exibição de mensagens do programa. Como console de operação ou console entendemos as seções ou janelas em modo texto (que capazes de exibir apenas texto) de sistemas Unix, as janelas “Prompt do MS- DOS” existentes no Windows e outras semelhantes existentes nos demais sistemas operacionais. As aplicações de console, embora em desuso para a construção de sistemas mais sofisticados, podem ser utilizadas para a criação de programas utilitários do sistema, tal como certos comandos do sistema operacional que na verdade são programas específicos disponíveis apenas para utilização nos consoles. 4.1 Estrutura das Aplicações de Console Para construir-se aplicações de console basta uma classe onde seja definido o método estático main. Este método é o primeiro a ser executado numa aplicação, daí ser estático, ou seja, não pode depender de ninguém mais para ser instanciado, existindo por completo dentro da classe, como uma espécie de objeto permanente. Sendo um método estático, main não pode acessar variáveis-membro que não sejam estáticas, embora possa internamente instanciar objetos de classes acessíveis. Assim, usualmente no método main ocorre a declaração e a instanciação dos objetos que serão utilizados pela aplicação de console. Abaixo temos a estrutura básica de uma aplicação de console: // Classe Pública contendo método main public class Aplicacao { // declaração do método main public static void main (String args[]) { // código correspondente ao main } } // Outras classes no mesmo arquivo Exemplo 30 Estrutura de Uma Aplicação de Console O método main também deve ser obrigatoriamente público, não retornando valores e recebendo um vetor de java.lang.String como argumento. Em algumas circunstâncias, o método main instancia objetos de sua própria classe como veremos em exemplos mais a frente. Criemos então uma aplicação simples capaz de gerar uma certa quantidade de números aleatórios sendo esta quantidade especificada pelo usuário através de um argumento passado ao programa. Para isto devemos usar o método gerador de números aleatórios (random) disponível na classe java.Math sob o nome random. Introdução ao Java 57 // RandomNumbers.java public class RandomNumbers { public static void main (String args[]) { for (int i=0; i<Integer.parseInt(args[0]); i++) { System.out.println("" + Math.random()); } } } Exemplo 31 Aplicação RandomNumbers Este programa, quando executado, exibe tantos números aleatórios no intervalo [0, 1] quantos solicitados através do primeiro argumento passado ao programa. Uma pequena modificação adicional pode fazer com que os número produzidos estejam dentro do intervalo [0, n], onde n também pode ser fornecido como um segundo argumento. // RandomInts.java public class RandomInts { public static void main (String args[]) { int multiplicador = 1; if (args.length == 2) { multiplicador = Integer.parseInt(args[1]); for (int i=0; i<Integer.parseInt(args[0]); i++) { System.out.println("" + Math.round( Math.random()*multiplicador)); } } else { for (int i=0; i<Integer.parseInt(args[0]); i++) { System.out.println("" + Math.random() ); } } } } Exemplo 32 Aplicação RandomInts Outras modificações desejáveis para esta aplicação seriam: (i) o controle de erros provocados por argumentos inválidos ou pelo fornecimento de um número insuficiente de argumentos com a consequente exibição de mensagens de erro.; (ii) a geração de números aleatórios dentro de um intervalo [m, n]; (iii) melhor apresentação dos resultados de saída pois apenas os 24 últimos números solicitados podem ser visualizados; etc. 4.2 Saída de Dados As aplicações de console utilizam como padrão a stream de dados out, disponível estaticamente através da classe java.lang.System. Uma stream pode ser entendida como um duto capaz de transportar dados de um lugar (um arquivo ou dispositivo) para um outro lugar diferente. O conceito de stream é extremamente importante pois é utilizado tanto para manipulação de dados existentes em arquivos como também para comunicação em rede e outros dispositivos. A stream de saída padrão é aberta automaticamente pela máquina virtual Java ao iniciarmos uma aplicação Java e permanece pronta para enviar dados. No caso a saída padrão está tipicamente associada ao dispositivo de saída (display) ou seja, a janela de console utilizada pela aplicação conforme designado pelo sistema operacional. Enquanto a stream de saída out é um objeto da classe java.io.PrintStream a stream de entrada in é um objeto da classe java.io.InputStream. Prof. Peter Jandl Jr. 60 A aplicação não trata quaisquer tipos de erros, nem sequer o uso de caracteres minúsculos ao invés de maiúsculos (uma boa sugestão para um exercício adicional). A dificuldade de tratar a entrada de dados em aplicações de console é uma clara indicação de que os projetistas do Java concentraram seus esforços para tornar esta plataforma mais adaptada para o desenvolvimento de aplicações gráficas destinadas as GUIs (Graphical User Interfaces) dos diversos sistemas operacionais existentes. Para contornar os problemas de leitura de valores inteiros, em ponto flutuante e também strings, apresentamos a classe Console que contém três métodos para leitura de valores destes tipos. Método Descrição readDouble() Lê um valor double da entrada padrão. readInteger() Lê um valor inteiro da entrada padrão. readString() Lê uma string da entrada padrão. Tabela 13 Métodos da Classe Console Abaixo o código da classe Console onde foi utilizado a classe java.io.BufferedReader para prover a facilidade da leitura de uma linha da entrada de cada vez. Todos os métodos oferecidos fazem um tratamento mínimo de erros de forma que valores inválidos são retornados como valores nulos. // Console.java import java.io.*; public final class Console { public static double readDouble() { try { BufferedReader br = new BufferedReader( new InputStreamReader(System.in)); String s = br.readLine(); Double d = new Double(s); return d.doubleValue(); } catch (IOException e) { return 0; } catch (NumberFormatException e) { return 0; } } public static int readInteger() { try { BufferedReader br = new BufferedReader( new InputStreamReader(System.in)); String s = br.readLine(); return Integer.parseInt(s); } catch (IOException e) { return 0; } catch (NumberFormatException e) { return 0; } } public static String readString() { try { BufferedReader br = new BufferedReader( new InputStreamReader(System.in)); String s = br.readLine(); return s; Introdução ao Java 61 } catch (IOException e) { return ""; } } } Exemplo 34 Classe Console Como todos os métodos da classe Console são estáticos, não é necessário instanciar-se um objeto para fazer uso destes métodos, como é ilustrado no exemplo a seguir, que lê um número real, um inteiro e uma string mas de forma bastante robusta. // ConsoleTeste.java public class ConsoleTest { public static void main(String args[]){ System.out.print("Forneca um numero real: "); double x = Console.readDouble(); System.out.println("Numero Fornecido: " + x); System.out.print("Forneca um numero inteiro: "); int y = Console.readInteger(); System.out.println("Numero Fornecido: " + y); System.out.print("Forneca uma string: "); String s = Console.readString(); System.out.println("String Fornecida: " + s); } } Exemplo 35 Aplicação da Classe Console 4.4 Uma Aplicação Mais Interessante Apesar das aplicações de console não terem o apelo atrativo das interfaces gráficas, ainda assim é possível construir-se aplicações interessantes. Propõe-se como um exemplo mais longo um jogo de cartas popular: o “Vinte e Um”. O projeto do jogo segue a lógica comum: para jogar-se “Vinte e Um” é necessário conhecer-se as regras do jogo que são simples e dispor-se de um baralho comum. As regras do jogo são as seguintes: (i) Utilizam-se as 52 cartas do baralho comum (de ás ao rei de cada um dos quatro naipes embaralhadas. (ii) Jogam dois adversários de cada vez: o jogador (usuário) e o croupier (computador). (iii) O objetivo é fazer 21 pontos ou pelo menos mais pontos que o adversário embora que obter mais de 21 pontos (estourar) perde imediatamente. Em caso de empate o croupier vence. (iv) O ás vale 1 ponto, dois vale 2 pontos e assim por diante até o dez. Valete, dama e rei valem apenas 1 ponto. (v) O jogador sempre começa recebendo uma carta que é exibida para todos. O jogador pode solicitar tantas cartas quanto desejar. (vi) Se o jogador parar com 21 pontos ou menos joga o croupier até que obtenha pontuação igual ou superior ao jogador, perdendo apenas se estourar. Para implementar-se o jogo podemos utilizar a seguinte estratégia: criamos um objeto Carta para representar individualmente as cartas de um baralho; criamos um objeto Baralho que contêm 52 cartas e depois uma classe VinteUm que implemente as regras do jogo “Vinte e Um” utilizando os objetos pré-definidos. Prof. Peter Jandl Jr. 62 A classe Carta deve definir um objeto capaz de armazenar duas informações: o número da carta e seu naipe, ambas podendo ser representadas por valores inteiros. Como não existe carta sem naipe e vice-versa, o construtor deve receber dois valores inteiros um para cada informação. A classe poderia ainda oferecer métodos para obter-se o número (valor) da carta e seu naipe, bem como um método para descrever a carta (ás de paus, quatro de ouros, etc.). A classe também poderia conter a definição de algumas constantes para os naipes e também valores das cartas. Esta classe poderia ser utilizada para a criação de uma classe Baralho ou outra que necessitasse manusear cartas. Segue no Exemplo 36 o código sugerido para implementação da classe Carta. // Carta.java public class Carta { public static final int PAUS = 1; public static final int OUROS = 2; public static final int ESPADAS = 3; public static final int COPAS = 4; private int naipe; private int valor; public Carta(int n, int v) { if (n >= PAUS && n <= COPAS) naipe = n; else naipe = 1; if (v >= 1 && n <= 13) valor = v; else valor = 1; } public int getNaipe() { return naipe; } public int getValor() { return valor; } public String toString() { String tmp = new String(""); switch(valor) { case 1: tmp = "as"; break; case 2: tmp = "dois"; break; case 3: tmp = "tres"; break; case 4: tmp = "quatro"; break; case 5: tmp = "cinco"; break; case 6: tmp = "seis"; break; case 7: tmp = "sete"; break; case 8: tmp = "oito"; break; case 9: tmp = "nove"; break; case 10: tmp = "dez"; break; case 11: tmp = "valete"; break; case 12: tmp = "dama"; break; case 13: tmp = "rei"; break; } switch(naipe) { case PAUS: tmp = tmp + " de paus"; break; Introdução ao Java 65 Figura 17 Diagrama de Classe do VinteUm Experimente agora executar a aplicação e jogar uma partida do jogo. Prof. Peter Jandl Jr. 66 5 Sobrecarga, Herança e Polimorfismo A construção de classes, embora de fundamental importância, não representa o mecanismo mais importante da orientação à objetos. A grande contribuição da orientação à objetos para o projeto e desenvolvimento de sistemas é o polimorfismo. A palavra polimorfismo vem do grego poli morfos e significa muitas formas. Na orientação à objetos representa uma característica onde se admite tratamento idêntico para formas diferentes baseado em relações de semelhança, isto é entidades diferentes podem ser tratadas de forma semelhante conferindo grande versatilidade aos programas e classes que se beneficiam destas características. 5.1 Sobrecarga de Métodos A forma mais simples de polimorfismo oferecido pela linguagem Java é a sobrecarga de métodos (method overload) ou seja é a possibilidade de existirem numa mesma classe vários métodos com o mesmo nome. Para que estes métodos de mesmo nome possam ser distinguidos eles devem possuir uma assinatura diferente. A assinatura (signature) de um método é uma lista que indica os tipos de todos os seus argumentos, sendo assim métodos com mesmo nome são considerados diferentes se recebem um diferente número ou tipo de argumentos e tem, portanto, uma assinatura diferente. Um método que não recebe argumentos tem como assinatura o tipo void enquanto um outro método que recebe dois inteiros como argumentos tem como assinatura os tipos int, int como no exemplo abaixo: // Sobrecarga.java public class Sobrecarga { public long twice (int x) { return 2*(long)x; } public long twice (long x) { return 2*x; } public long twice (String x) { return 2*(long)Integer.parseInt(x); } } Exemplo 39 Sobrecarga de Métodos No exemplo temos na classe Sobrecarga a implementação de três métodos denominados twice que tomam um único argumento retornando um valor que é o dobro do valor do argumento recebido. Através da sobrecarga foram implementadas três versões do método twice, cada uma capaz de receber um argumento de tipo diferente. Na prática é Introdução ao Java 67 como se o método twice fosse capaz de processar argumentos de tipos diferentes, o que para os usuários da classe Sobrecarga. Outra situação possível é a implementação de métodos com mesmo nome e diferentes listas de argumentos, possibilitando uma utilização mais flexível das idéias centrais contidas nestes métodos como a seguir: // Sobrecarga2.java public class Sobrecarga2 { public long somatorio (int max) { int total=0; for (int i=1; i<=max; i++) total += i; return total; } public long somatorio (int max, int incr) { int total=0; for (int i=1; i<=x; i += incr) total += i; return total; } } Exemplo 40 Sobrecarga de Métodos No Exemplo 40 temos o método somatorio efetua a soma dos primeiros max números naturais ou efetua a soma dos primeiro max números naturais espaçados de incr e que a seleção de um método ou outro se dá pelo diferente número de argumentos utilizados pelo método somatorio. A API do Java utiliza intensivamente o mecanismo de sobrecarga, por exemplo a classe java.lang.String onde o método indexOf possui várias implementações. O método indexOf se destina a localizar algo dentro de uma string, isto é, a determinar a posição (índice) de um caractere ou substring, o que caracteriza diferentes possibilidades para seu uso, conforme a tabela a seguir: Método Descrição indexOf(int) Retorna a posição, dentro da string, da primeira ocorrência do caractere especificado. indexOf(int, int) Retorna a posição, dentro da string, da primeira ocorrência do caractere especificado a partir da posição dada. indexOf(String) Retorna a posição, dentro da string, da primeira ocorrência da substring especificada. indexOf(String, int) Retorna a posição, dentro da string, da primeira ocorrência da substring especificada a partir da posição dada. Figura 18 Sobrecarga do Método indexOf da Classe java.lang.String Desta forma, fica transparente para o usuário da classe a existência destes quatro métodos ao mesmo tempo que disponíveis tais alternativas para localização de caracteres simples ou substrings dentro de strings. Numa classe onde são implementados vários métodos com o mesmo nome fica a cargo do compilador determinar qual o método apropriado em função da lista de argumento indicada pelo método efetivamente utilizado (dinamic binding). O valor de retorno não faz parte da assinatura pois admite-se a conversão automática de tipos impedindo o compilador de identificar o método adequando. Um outro ponto importante é que a linguagem Java não fornece recursos para sobrecarga de operadores tal como existe na linguagem C++. Apesar disto ser um aspecto restritivo da linguagem, é perfeitamente condizente com a filosofia que orientou o Prof. Peter Jandl Jr. 70 Num diagrama UML teríamos a seguinte representação para classes hierarquicamente relacionadas: Figura 19 Representação de Herança (Notação UML) Em princípio, todos atributos e operações definidos para uma certa classe base são aplicáveis para seus descendentes que, por sua vez, não podem omitir ou suprimir tais caraterísticas pois não seriam verdadeiros descendentes se fizessem isto. Por outro lado uma subclasse (um descendente de uma classe base) pode modificar a implementação de alguma operação (reimplementar) por questões de eficiência sem modificar a interface externa da classe. Além disso as subclasses podem adicionar novos métodos e atributos não existentes na classe base, criando uma versão mais específica da classe base, isto é, especializando-a. Na Figura 20 temos um exemplo de hierarquia de classes onde são indicados os atributos e operações adicionados em cada classe. Note que os atributos e operações adicionados em uma dada classe base não são indicados explicitamente na classe derivada mas implicitamente pela relação de herança. A herança é portanto um mecanismo de especialização pois uma dada subclasse possui tudo aquilo definido pela sua superclasse além de atributos e operações localmente adicionados. Por outro lado a herança oferece um mecanismo para a generalização pois uma instância de uma classe é uma instância de todos os ancestrais desta classe, isto é, um objeto de uma classe derivada pode ser tratado polimorficamente como um objeto da superclasse, como veremos na seção 5.3. No mundo real alguns objetos e classes podem ser descritos como casos especiais, especializações ou generalizações de outros objetos e classes. Por exemplo, a bola de futebol de salão é uma especialização da classe bola de futebol que por sua vez é uma especialização da classe bola. Aquilo que foi descrito para uma certa classe não precisa ser repetido para uma classe mais especializada originada na primeira. Atributos e operações definidos na classe base não precisam ser repetidos numa classe derivada, desta forma a orientação à objetos auxilia a reduzir a repetição de código dentro de um programa ao mesmo tempo que possibilita que classes mais genéricas sejam reaproveitadas em outros projetos (reusabilidade de código). Outro aspecto bastante importante é que se o projeto de uma hierarquia de classe é suficientemente genérico e amplo temos o desenvolvimento de uma solução generalizada e com isto torna-se possível mais do que a reutilização de código, mas passa a ser possível o que denomina reutilização do projeto. Uma classe ou hierarquia de classes que pode ser genericamente utilizada em outros projetos é que se chama de padrão de projeto ou design pattern. Enquanto mecanismo de especialização, a herança nos oferece uma relação “é um” entre classes (“is a” relationship): uma bola de futebol de salão é uma bola de futebol que por sua vez é uma bola. Uma sentença é uma parte de um parágrafo que é uma parte de um documento. Inúmeros outros exemplos poderiam ser dados. A descoberta de relações do tipo “é um” é a chave para determinarmos quando novas classes devem ser ou não derivadas de outras existentes. Introdução ao Java 71 Figura 20 Exemplo de Hierarquia de Classes Desta forma o mecanismo de especialização pode conduzir a duas situações distintas: (i) Extensão A herança pode ser utilizada para a construção de novas classes que ampliam de forma especializada as operações e atributos existentes na classe base. Com isto temos a adição de novos elementos a uma classe. (ii) Restrição As subclasses podem ocultar ou alterar consistentemente operações e atributos da superclasse (overhiding) sem modificar a interface proposta por estas. Com isto temo a modificação de elementos já existentes numa classe para adequação com os novos elementos adicionados. O acesso à atributos e operações declarados em uma classe base por parte das suas classes derivadas não é irrestrito. Tal acessibilidade depende dos nível de acesso permitido através dos especificadores private (privado), protected (protegido), package (pacote) e public (público) conforme a Tabela 15. Métodos e Atributos da Classe Implemen- tação da Classe Instância da Classe SubClasse da Classe Instância da SubClasse privados (private) sim não não não protegidos (protected) sim não sim não pacote (package) sim sim (no mesmo pacote) sim (no mesmo pacote) sim (no mesmo pacote) públicos (public) sim sim sim sim Tabela 15 Especificadores de Acesso do Java Prof. Peter Jandl Jr. 72 Ao especificarmos acesso privado (private) indicamos que o atributo ou operação da classe é de uso interno e exclusivo da implementação da própria classe, não pertencendo a sua interface de usuário tão pouco a sua interface de programação. Membros privados não podem ser utilizados externamente as classes que os declaram O acesso protegido (protected) e público (public) servem, respectivamente, para definir membros destinado a interface de programação e a interface de usuário. Os membros públicos podem, sem restrição ser utilizados por instâncias da classe, por subclasses e por instâncias das subclasses. Membros protegidos podem ser utilizados apenas na implementação de subclasses, não sendo acessíveis por instâncias da classe ou por instâncias das subclasses. Finalmente membros que não recebem as especificações private (privado), protected (protegido) ou public (público) são considerados como do caso especial package (pacote), isto é, são como membros públicos para classes do mesmo pacote mas comportam-se como membros privados para classes de outros pacotes. Esta modalidade de acesso visa facilitar a construção de conjuntos de classes correlacionadas, isto é, de pacotes. O acesso package é o considerado default pelo Java. Desta forma é possível evitar que pequenas alterações propaguem-se provocando grandes efeitos colaterais pois alterações na implementação que não alteram a interface existente do objeto podem ser realizadas sem que seja necessário modificar-se as aplicações deste objeto. Ao invés de termos criado duas classes Liquidificador e Liquidificador2 para representar liquidificadores com controle de velocidade analógico e digital (Exemplo 18 e Exemplo 20) poderíamos ter criado uma classe base genérica LiquidificadorGenerico contendo as características comuns destes aparelhos e duas classes derivadas LiquidificadorAnalogico e LiquidificadorDigital que implementassem as características particulares conforme ilustrado no diagrama UML da Figura 21. Figura 21 Hierarquia de Classes: Liquidificador Seguem implementações possíveis para estas classes. // LiquidificadorGenerico.java public class LiquidificadorGenerico { // atributos protected int velocidade; protected int velocidadeMaxima; // construtores public LiquidificadorGenerico() { velocidade = 0; velocidadeMaxima = 2; } public LiquidificadorGenerico(int v) { this() ajustarVelocidadeMaxima(v); } Introdução ao Java 75 tratamento genérico para as classse LiquidificadorDigital e LiquidificadorAnalogico bem como para quaisquer novas classes que sejam derivadas destas últimas ou da própria superclasse. Figura 22 Classes Liquidificador O tratamento generalizado de classes permite escrever programas que podem ser mais facilmente extensíveis, isto é, que podem acompanhar a evolução de uma hierarquia de classe sem necessariamente serem modificados. Enquanto a herança oferece um poderoso mecanismo de especialização, o polimorfismo oferece um igualmente poderoso mecanismo de generalização, constituindo uma outra dimensão da separação da interface de uma classe de sua efetiva implementação. Imaginemos que seja necessário implementar numa classe LiquidificadorInfo um método que seja capaz de imprimir a velocidade atual de objetos liquidificador os quais podem ser tanto do tipo digital como analógico. Poderíamos, utilizando a sobrecarga, criar um método de mesmo nome que recebesse ou um LiquidificadorAnalogico ou um LiquidificadorDigital: public class LiquidificadorInfo { public void velocidadeAtual(LiquidificadorAnalogico l) { System.out.println("Veloc. Atual: "+l.obterVelocidade()); } public void velocidadeAtual(LiquidificadorDigital l) { System.out.println("Veloc. Atual: "+l.obterVelocidade()); } } Embora correto, existem duas grandes desvantagens nesta aproximação: (i) para cada tipo de Liquidificador existente teríamos um método sobrecarregado avolumando o código que deveria ser escrito (e depois mantido) além disso (ii) a adição de novas subclasse que representassem outros tipos de liquidificador exigiria a implementação adicional de outros métodos semelhantes. Através do polimorfismo pode-se chegar ao mesmo resultado mas de forma mais simples pois a classe LiquidificadorGenerico permite o tratamento generalizado de todas as suas subclasses. Prof. Peter Jandl Jr. 76 public class LiquidificadorInfo { public void velocidadeAtual(LiquidificadorGenerico l) { System.out.println("Veloc. Atual: "+l.obterVelocidade()); } } Nesta situação o polimorfismo permite que um simples trecho de código seja utilizado para tratar objetos diferentes relacionados através de seu ancestral comum, simplificando a implementação, melhorando a legibilidade do programa e também aumentando sua flexibilidade. Outra situação possível é a seguinte: como qualquer método de uma superclasse pode ser sobreposto (overhided) em uma subclasse, temos que o comportamento deste método pode ser diferente em cada uma das subclasses, no entanto através da superclasse podemos genericamente usar tal método, que produz um resultado distinto para cada classe utilizada. Assim o polimorfismo também significa que uma mesma operação pode se comportar de forma diferente em classes diferentes. Para a orientação à objetos uma operação é uma ação ou transformação que um objeto pode realizar ou está sujeito e, desta forma, os métodos são implementações específicas das operações desejadas para uma certa classe. Conclui-se através destes exemplos que o polimorfismo é uma característica importantíssima oferecida pela orientação à objetos que se coloca ao lado dos mecanismos de abstração de dados, herança e encapsulamento. 5.5 Composição O termo composição significa a construção de novas classes através do uso de outras classes existentes, isto é, a nova classe possui internamente atributos que representam objetos de outras classes. No código apresentado no Exemplo 37 Classe Baralho, temos que um dos atributos da classe Baralho é um vetor de objetos Carta. A implementação ocorreu desta forma porque um baralho é um conjunto bem definido de cartas, onde podemos dizer que o baralho “tem” cartas. Uma classe Cidade poderia conter como atributos o seu nome, número de habitantes e suas coordenadas representadas através de uma classe PontoGeográfico (veja 3.9 Dicas para o Projeto de Classes) como abaixo: public class Cidade { public string nome; public int populacao; public PontoGeográfico coordenadas; public Cidade() { nome = new String(""); populacao = 0; coordenadas = new PontoGeografico(); } } Enquanto o campo populacao é representado por um tipo primitivo, tanto o campo nome como o campo coordenadas são representados por outros objetos, isto é, são campos cujo tipo é uma outra classe, no caso, String e PontoGeografico respectivamente. Isto significa que um objeto pode ser construído através da associação ou agregação de outros objetos, o que se denomina composição. A composição ou agregação é uma característica frequentemente utilizada quando se deseja representar uma relação do tipo “tem um” (“has a” relationship) indicando que um objeto tem ou outro objeto como parte de si. O baralho tem cartas assim como a cidade tem um nome e tem uma população. Desta forma é igualmente comum que nas classes que Introdução ao Java 77 se utilizem de composição ocorra no construtor da classe ou em outro ponto do código a instanciação dos atributos do tipo objeto ou o recebimento de objetos construídos em outras partes do código. Note que a relação definida para herança é bastante diferente, pois indica que um objeto é algo de um certo tipo (relação “é um” – “is a” relatioship), tal como o liquidificador analógico é um (é do tipo) liquidificador genérico. 5.6 Classes Abstratas Em algumas circunstâncias desejamos orientar como uma classe deve ser implementada, ou melhor, como deve ser a interface de uma certa classe. Em outros casos, o modelo representado é tão amplo que certas classes tornam-se por demais gerais, não sendo possível ou razoável que possuam instâncias. Para estes casos dispomos das classes abstratas (abtract classes). Tais classes são assim denominadas por não permitirem a instanciação, isto é, por não permitirem a criação de objetos do seu tipo. Sendo assim seu uso é dirigido para a construção de classes modelo, ou seja, de especificações básicas de classes através do mecanismo de herança. Uma classe abstrata deve ser estendida, ou seja, deve ser a classe base de outra, mais específica que contenha os detalhes que não puderam ser incluídos na superclasse (abstrata). Outra possível aplicação das classes abstratas é a criação de um ancestral comum para um conjunto de classes que, se originados desta classe abstrata, poderão ser tratados genericamente através do polimorfismo. Um classe abstrata, como qualquer outra, pode conter métodos mas também pode adicionalmente conter métodos abstratos, isto é, métodos que deverão ser implementados em suas subclasses não abstratas. Vejamos um exemplo: ao invés de criarmos classes isoladas para representar quadrados, retângulos e círculos, poderíamos definir uma classe abstrata Forma que contivesse um vetor de pontos, um método comum que retornasse a quantidade de pontos da forma e métodos abstratos para desenhar e calcular a área desta forma. Note que seria impossível implementar métodos para o cálculo da área ou para o desenho de uma forma genérica, dirigindo-nos para uma classe abstrata como a exemplificado a seguir: // Forma.java public abstract class Forma { // atributos public java.awt.Point pontos[]; // metodos comuns public int contagemPontos() { if (pontos != null) return pontos.length; else return 0; } // metodos abstratos public abstract void plotar(java.awt.Graphics g); public abstract double area(); } Exemplo 46 Classe Abstrata Forma A partir desta classe abstrata podem ser criadas outras classes derivadas que contenham os detalhes específicos de cada forma pretendida, tal como nos exemplos simplificados a seguir. Prof. Peter Jandl Jr. 80 A classe java.lang.Class oferece vários métodos que permite relacionar quais métodos e construtores existem dentro de uma dada classe bem como a identificação de sua superclasse. Os principais métodos desta classe estão listados na Tabela 16. Método Descrição forName(String) Retorna um objeto tipo Class para a string dada. getClassLoader() Retorna o carregador da classe. getConstructors() Retorna um vetor contendo todos construtores da classe. getDeclaredConstructors() Retorna um vetor contendo apenas os construtores declarados na classe. getDeclaredMethods() Retorna um vetor contendo apenas os métodos declarados na classe. getMethods() Retorna um vetor contendo todos métodos da classe incluindo os métodos compartilhados com as superclasses. getName() Obtêm no nome completamente qualificado da classe. getSuperclass() Obtêm o nome da superclasse. Tabela 16 Métodos da Classe java.lang.Class Os métodos da classe java.lang.Class são tipicamente utilizados em associação ao pacote java.lang.reflect, que permite obter informações mais detalhadas sobre os campos, construtores e métodos de uma classe bem como de seus argumentos, valores de retorno e modificadores. O pacote java.lang.reflect contêm as seguintes classes: Classe Descrição Array Provê métodos estáticos capazes de obter informações para criar e acessar dinamicamente arrays. Constructor Provê métodos estáticos capazes de obter informações sobre construtores de uma dada classe em tempo de execução. Field Provê métodos estáticos capazes de obter informações sobre campos (atributos) de uma dada classe em tempo de execução. Method Provê métodos estáticos capazes de obter informações sobre métodos (de instância, classe e abstratos) de uma dada classe em tempo de execução. Modifier Provê métodos estáticos capazes de obter informações sobre ao modificadores de uma classe. Tabela 17 Classes do Pacote java.lang.reflect A seguir temos o código de uma pequena aplicação que extrai informações de classes existentes, isto é, retorna uma relação dos construtores e métodos disponíveis em uma dada classe. // ClassInfo.java import java.lang.reflect.*; public class ClassInfo { static final String usage = "Uso:\n\tClassInfo [+] pacote.class [palavra]\n" + "Exibe relacao de metodos da classe 'pacote.class' " + "ou apenas\naqueles envolvendo 'palavra' (opcional).\n" + "A opcao '+' indica que tambem devem ser listados os " + "metodos\n e construtores das superclasses.\n"; Introdução ao Java 81 public static void main(String args[]) { Class classe; Method metodos[]; Constructor construtores[]; boolean incluirHeranca; int argBusca = 0; try { incluirHeranca = args[0].equals("+"); if (incluirHeranca) { argBusca = (args.length>2 ? 2 : 0); classe = Class.forName(args[1]); metodos = classe.getMethods(); construtores = classe.getConstructors(); } else { argBusca = (args.length>1 ? 1 : 0); classe = Class.forName(args[0]); metodos = classe.getDeclaredMethods(); construtores = classe.getDeclaredConstructors(); } if (argBusca==0) { for (int i=0; i<construtores.length; i++) System.out.println(construtores[i].toString()); for (int i=0; i<metodos.length; i++) System.out.println(metodos[i].toString()); } else { for (int i=0; i<construtores.length; i++) if (construtores[i].toString().indexOf(args[argBusca]) != -1) System.out.println(construtores[i].toString()); for (int i=0; i<metodos.length; i++) if (metodos[i].toString().indexOf(args[argBusca])!=-1) System.out.println(metodos[i].toString()); } } catch (ClassNotFoundException e) { System.out.println("Classe não encontrada: " + e); } catch (ArrayIndexOutOfBoundsException e) { System.out.println(usage); } System.exit(0); } } Exemplo 49 Classe ClassInfo Executando-se esta aplicação obtêm-se uma listagem dos métodos contidos em uma classe. Caso tal relação seja muito extensa, pode-se redirecionar o seu resultado para um arquivo auxiliar que pode ser posteriormente consultado através de um editor de textos qualquer como indicado: java ClassInfo java.lang.String > aux notepad aux Este utilitário simples permite substituir a documentação da API nos casos onde se deseje apenas saber quais métodos estão disponíveis e quais seus argumentos Prof. Peter Jandl Jr. 82 6 Aplicações Gráficas com a AWT Aplicações gráficas são aquelas destinadas a execução dentro dos ambientes gráficos oferecidos por vários sistemas operacionais. Uma GUI (Graphical User Interface) é um ambiente pictórico que oferece uma interface mais simples e intuitiva para os usuários. Cada sistema operacional pode oferecer GUIs com aparências (look and feel) distintas tais como o modelo COM da Microsoft, o Presentation Manager da IBM, o NeWS da Sun ou o X-Window System do MIT. A linguagem Java oferece capacidades únicas no desenvolvimento de aplicações gráficas que, sem modificação ou recompilação, podem ser executadas em diferentes ambientes gráficos, uma vantagem significativa além do próprio desenvolvimento de aplicações gráficas. 6.1 Componentes e a AWT Um dos elementos chave do sucesso das GUIs é a utilização de componentes padronizados para representação e operação da interface em si. Um componente GUI é um objeto visual com o qual o usuário pode interagir através do teclado ou mouse, permitindo realizar uma ou algumas dentre diversas operações: a entrada de valores numéricos ou de texto, a seleção de itens em listas, a marcação de opções, o desenho livre, o acionamento de operações etc. Os componentes GUI (GUI components), também chamados de widgets (window gadgets – dispositivos de janela) são os elementos construtivos das interfaces GUI, isto é botões, caixas de entrada de texto, caixas de lista, caixas de seleção, botões de seleção única, botões de seleção múltipla, barras de rolagem etc. O Java oferece uma ampla biblioteca de componentes GUI e de capacidades gráficas na forma de classe pertencentes ao seu pacote java.awt, mais conhecido como AWT (Abstract Window Toolkit). A AWT oferece classes comuns e abstratas relacionadas a renderização e obtenção de informações do sistema gráfico oferecido pela plataforma sobre a qual opera a máquina virtual Java. Uma hierarquia parcial das classes da AWT pode ser vista na Figura 24. Figura 24 Hierarquia Parcial de Classes java.awt Todas as classes básicas da AWT são extensões da classe java.lang.Object que é em última análise a superclasse de todas as classes Java. A classe java.awt.Font trata dos fontes, de suas características e representação. A classe java.awt.Color oferece meios para Introdução ao Java 85 Figura 25 Hierarquia Parcial de Classes java.awt (Componentes GUI) A classe java.awt.Component contêm portanto métodos comuns a todos os demais componentes da AWT. Destacamos na Tabela 18 e Tabela 19 apenas alguns dos muitos métodos disponíveis. Método Descrição add(PopupMenu) Adiciona um menu popup ao componente. addKeyListener(KeyListener) Adiciona o processador de eventos de teclas. addMouseListener(MouseListener) Adiciona o processador de eventos do mouse. getBackground() Obtêm a cor do segundo plano do componente. getBounds() Obtêm os limites deste componente como um objeto Rectangle. getComponentAt(int, int) Retorna o componente contido na posição dada. getCursor() Obtêm o cursor atual do componente. getFont() Obtêm o fonte atual do componente. getGraphics() Obtêm o contexto gráfico do componente. getLocation() Obtêm a posição (esquerda, topo) do componente. getSize() Obtêm o tamanho do componente como um objeto Dimension. isEnabled() Determina se o componente está habilitado. isVisible() Determina se o componente está visível. Tabela 18 Métodos da classe java.awt.Component (Parte A) Prof. Peter Jandl Jr. 86 Método Descrição paint(Graphics) Renderiza o componente. repaint() Atualiza a renderização do componente. setBounds(int, int, int, int) Move e redimensiona o componente. setCursor(Cursor) Especifica o cursor para o componente. setEnabled(Boolean) Habilita ou desabilita o componente. setFont(Font) Especifica o fonte para o componente. setLocation(int, int) Move o componente para a posição dada. setSize(int, int) Especifica o tamanho para o componente. setVisible(Boolean) Especifica se o componente é ou não visível. update(Graphics) Atualiza a exibição do componente. Tabela 19 Métodos da classe java.awt.Component (Parte B) 6.3.1 Frame O componente java.awt.Frame é uma janela do sistema gráfico que possui uma barra de título e bordas, comportando-se como uma janela normal da GUI. Como é uma subclasse do java.awt.Container (veja Figura 25) pode conter outros componentes sendo esta sua finalidade principal. O alinhamento default para componentes adicionados a um Frame é o java.awt.BorderLayout, como será discutido mais a frente. Além disso, implementa a interface java.awt.MenuContainer de forma que também possa conter uma barra de menus e seus menus associados. A seguir temos a Tabela 20 que lista os construtores e alguns dos métodos desta classe: Método Descrição Frame() Constrói uma nova instância, invisível e sem título. Frame(String) Constrói uma nova instância, invisível e com o título dado. dispose() Libera os recursos utilizados por este componente. getTitle() Obtêm o título da janela. isResizable() Determina se a janela é ou não dimensionável. setMenuBa(MenuBar) Adiciona a barra de menu especificada a janela. setResizable(Boolean) Especifica se a janela é ou não dimensionável. setTitle(String) Especifica o título da janela. Tabela 20 Métodos da Classe java.awt.Frame Nesta classe ainda existem constantes1 para especificação de diversos cursores como exemplificado na Tabela 21. Verifique a documentação do Java a existência de outros cursores cujo formato deve ser consultado na documentação do sistema operacional. A utilização do componente java.awt.Frame determina a estratégia básica para criação de aplicações gráficas. Usualmente cria-se uma nova classe que estende a classe java.awt.Frame e com isto obtêm-se duas facilidades: (i) uma janela para apresentação gráfica da aplicação e (ii) um container para disposição de outros componentes GUI da aplicação. Na classe do primeiro Frame que desejamos exibir adiciona-se o método main responsável por instanciar e exibir o Frame através do método show (que pertence a classe java.awt.Window). 1 Como será notado, a atual API do Java peca na denominação de constantes. Embora a recomendação seja de nomes escritos exclusivamente com maiúsculas, constantes presentes em outras classes nem sempre respeitam esta regra. Introdução ao Java 87 Constante Descrição CROSSHAIR_CURSOR Cursor em forma de cruz. DEFAULT_CURSOR Cursor default. HAND_CURSOR Cursor tipo apontador. MOVE_CURSOR Cursor para indicação de possibilidade de movimento. TEXT_CURSOR Cursor de edição de texto. WAIT_CURSOR Cursor de sinalização de espera. Tabela 21 Cursores da classe java.awt.Frame Observe o exemplo dado a seguir: // FrameDemo1.java import java.awt.*; public class FrameDemo1 extends Frame{ // construtor public FrameDemo1() { super("Frame Demo 1"); setSize(320, 240); setLocation(50, 50); } // main static public void main (String args[]) { FrameDemo1 f = new FrameDemo1(); f.show(); } } Exemplo 50 Classe FrameDemo1 (Uso Básico de Frames) Compilando e executando a aplicação obtemos o resultado ilustrado na Figura 26. Figura 26 Aplicação FrameDemo1 Apesar da aparência esperada, das capacidades de dimensionamento e iconização, a janela não é fechada quando acionamos a opção “Fechar”. Isto ocorre por não haver métodos processadores de eventos capazes de receber tal mensagem encerrando a aplicação. Para encerrar a aplicação devemos encerrar a JVM com um CTRL+C na janela de console exibida. Para que uma janela tenha seus eventos processados é necessário implementarmos um processador de eventos específico. Existem basicamente duas opções para a solução desta questão: (i) utilizar a classe java.awt.event.WindowAdapter ou (ii) utilizar a interface java.awt.event.WindowListener. Prof. Peter Jandl Jr. 90 um construtor que aceita três valores inteiros entre 0 e 255 para cada uma das componentes da cor, possibilitando a especificação de uma dentre 65.536 cores diferentes. Consulte a documentação da API do Java para conhecer as constantes existentes para cores padronizadas, tais como Color.black, Color.white, Color.red, Color.blue, Color.yellow, Color.magenta, Color.green, Color.gray etc. Além destas constantes de cor existem outras associadas ao sistema, isto é, as cores utilizadas pela GUI do sistema, na classe java.awt.SystemColor que indicam as cores utilizadas por controles (SystemColor.control), sombras (SystemColor.controlShadow), barras de título (SystemColor.activeCaption), menus (SystemColor.menu) etc. // FrameDemo4.java import java.awt.*; public class FrameDemo4 extends Frame { public FrameDemo4() { super("Frame Demo 4"); Dimension d = Toolkit.getDefaultToolkit().getScreenSize(); setSize(d.width/2, d.height/2); setLocation(d.width/2, d.height/2); setBackground(new Color(0,128,128)); } static public void main (String args[]) { FrameDemo4 f = new FrameDemo4(); f.addWindowListener(new CloseWindowAndExit()); f.show(); } } Exemplo 54 Classe FrameDemo4 6.3.2 Label O componente java.awt.Label, conhecido também como rótulo de texto ou simplesmente rótulo, é destinado a exibição de texto dentro de quaisquer componentes do tipo container. Os rótulos podem exibir uma única linha de texto a qual não pode ser modificada nem editada pelo usuário, caracterizando um componente passivo. Métodos apropriados são fornecidos para que o texto de um rótulo possa ser programaticamente obtido e modificado bem como seu alinhamento. Na Tabela 22 temos os construtores e os principais métodos desta classe. O alinhamento pode ser especificado ou determinado através de três constantes disponíveis nesta classe: Label.LEFT, Label.CENTER e Label.RIGHT. Além destes métodos estão disponíveis outros, pertencentes as classes ancestrais, tal como ilustra a Figura 25. Método Descrição Label() Constrói um novo rótulo sem texto. Label(String) Constrói um novo rótulo com o texto dado. Label(String, int) Constrói um novo rótulo com o texto e alinhamento dados. getAlignment() Obtêm o alinhamento do rótulo. getText() Obtêm o texto do rótulo. setAlignment(int) Especifica o alinhamento do rótulo. setText(String) Especifica o texto do rótulo. Tabela 22 Métodos da Classe java.awt.Label Introdução ao Java 91 A seguir o exemplo de uma aplicação muito simples que instancia e adiciona alguns rótulos a sua interface. Note que é possível adicionar-se rótulos a interface sem manter-se uma referência para o mesmo o que é útil quando seu conteúdo é sabidamente fixo, embora tal componente se torne inacessível programaticamente. // LabelDemo.java import java.awt.*; public class LabelDemo extends Frame{ private Label l1; private Label l2; public LabelDemo() { super("Label Demo"); setSize(100, 200); setLocation(50, 50); // instanciacao l1 = new Label("Java:"); l2 = new Label("Write Once...", Label.CENTER); // alteracao do layout do Frame setLayout(new FlowLayout()); // adicao dos componentes add(l1); add(l2); add(new Label("...Run Everywhere!", Label.RIGHT)); } static public void main (String args[]) { LabelDemo f = new LabelDemo(); f.addWindowListener(new CloseWindowAndExit()); f.show(); } } Exemplo 55 Classe LabelDemo Observe que o layout do Frame é modificado para java.awt.FlowLayout (por questões de simplificação) ao invés do default java.awt.BorderLayout. O resultado desta aplicação é ilustrado a seguir: Figura 27 Aplicação LabelDemo Se a janela for redimensionada, os rótulos são automaticamente reposicionados, o que em algumas situações anula o efeito pretendido com o alinhamento individual de cada texto associado aos componentes java.awt.Label. Prof. Peter Jandl Jr. 92 6.3.3 Button Os botões, componentes encapsulados na classe java.awt.Button, são como painéis rotulados com um texto que, quando acionados, podem provocar a execução de alguma rotina ou sequência de comandos. O acionamento dos botões é visualmente indicado através de um efeito de afundamento de sua superfície. O texto associado ao botão, tal como nos rótulos, não pode ser alterado pelo usuário embora isto possa ser realizado através da aplicação. Os principais construtores e métodos desta classe são listados na tabela a seguir: Método Descrição Button() Constrói um novo botão sem texto. Button (String) Constrói um novo botão com o texto dado. addActionListener(ActionListener) Registra uma classe listener (processadora de eventos) ActionListener para o botão. getLabel() Obtêm o texto do botão. setLabel(String) Especifica o texto do botão. Tabela 23 Métodos da Classe java.awt.Button Sendo um componente ativo, para que o botão responda ao seu acionamento pelo usuário é necessário registrar-se uma classe que processe o evento especificamente gerado por tal ação. Estas classes devem implementar a interface java.awt.event.ActionListener, que exige a presença de um único método, o actionPerformed como exemplificado a seguir. // ButtonDemo.java import java.awt.*; import java.awt.event.*; public class ButtonDemo extends Frame implements ActionListener{ private Label label1; private Button button1; private int count; public ButtonDemo() { super("Button Demo"); setSize(280, 70); setLayout(new FlowLayout()); // instanciacao, registro e adicao do botao button1 = new Button("Clique Aqui"); button1.addActionListener(this); add(button1); // instanciacao e adicao do label label1 = new Label("Botão não foi usado."); add(label1); count=0; } public void actionPerformed(ActionEvent e) { if (e.getSource()==button1) { count++; label1.setText("Botão já foi usado " + count + " vez(es)."); } } static public void main (String args[]) { ButtonDemo f = new ButtonDemo(); f.addWindowListener(new CloseWindowAndExit()); Introdução ao Java 95 componente. Note que podem existir dois listeners associados a este componente: um ActionListener, que percebe quando o usuário aciona a tecla “Enter” quando o foco esta no componente TextField, e um TextListener que percebe eventos associados a edição do texto. No exemplo a seguir demostraremos o uso básico do componente TextField e seu comportamento com relação ao acionamento da tecla “Enter”: // TextFieldDemo.java import java.awt.*; import java.awt.event.*; public class TextFieldDemo extends Frame implements ActionListener{ private TextField tf1, tf2, tf3, tf4; private Button button1; public TextFieldDemo() { super("TextField Demo"); setSize(250, 150); setLayout(new FlowLayout()); // instanciacao, registro e adicao dos textfields tf1 = new TextField(); // textfield vazio add(tf1); tf2 = new TextField("", 20); // textfield vazio de 20 colunas add(tf2); tf3 = new TextField("Hello!"); // textfield com texto add(tf3); tf4 = new TextField("Edite-me!", 30);// textfield com texto e 30 cols add(tf4); tf4.addActionListener(this); // instanciacao, registro e adicao do botao button1 = new Button("tf3->tf2"); button1.addActionListener(this); add(button1); } public void actionPerformed(ActionEvent e) { if (e.getSource()==button1) { tf2.setText(tf3.getText()); tf3.setText(""); } if (e.getSource()==tf4) { tf3.setText(tf4.getText()); tf4.setText(""); } } static public void main (String args[]) { TextFieldDemo f = new TextFieldDemo(); f.addWindowListener(new CloseWindowAndExit()); f.show(); } } Exemplo 58 Classe TextFieldDemo O texto digitado na última caixa de entrada de texto é automaticamente transferido para a terceira caixa de entrada quando acionamos a tecla “Enter” durante a edição do texto. Acionando-se o botão existente transferimos o texto da terceira caixa de entrada para Prof. Peter Jandl Jr. 96 a segunda, mostrando uma outra possibilidade. Em ambos os casos a caixa de texto de origem é limpa. Experimente compilar, executar e testar a aplicação, que exibe uma janela semelhante a ilustrada na Figura 29. Figura 29 Aplicação TextFieldDemo 6.3.5 Panel O componente java.awt.Panel ou Painel é um componente tipo container, ou seja, é um componente que permite dispor outros componente inclusive outros painéis., oferendo grande versatilidade na disposição de elementos na interface da aplicação. Como será visto em 6.4 Os Gerenciadores de Layout, cada painel pode assumir uma diferente disposição para seus componentes, ampliando as possibilidades de seu uso. A classe java.awt.Panel possui dois construtores: Método Descrição Panel() Constrói um painel com o alinhamento default (FlowLayout). Panel(LayoutManager) Constrói um painel com o alinhamento especificado. Tabela 26 Métodos da Classe java.awt.Panel Além destes construtores, a classe java.awt.Panel compartilha os seguintes métodos com sua classe ancestral java.awt.Container. Método Descrição add(Component) Adiciona o componente especificado ao container. add(Component, int) Adiciona o componente especificado ao container na posição indicada. doLayout() Causa o rearranjo dos componentes do container segundo seu layout. getComponent(int) Obtêm uma referência para o componente indicado. getComponentsCount() Retorna o número de componentes do container. getComponentAt(int, int) Obtêm uma referência para o componente existente na posição dada. getLayout() Obtêm o gerenciador de layout para o container. paint(Graphics) Renderiza o container. remove(int) Remove o componente indicado. removeAll() Remove todo os componentes do container. setLayout(LayoutManager) Especifica o gerenciador de layout para o container. updates(Graphics) Atualiza a renderização do container. Tabela 27 Métodos da Classe java.awt.Container Da mesma forma que adicionamos componentes a um objeto java.awt.Frame, faremos isto aos java.awt.Panel pois ambas as classes são derivadas de Introdução ao Java 97 java.awt.Container. A seguir um exemplo de aplicação onde adicionamos três painéis a um Frame e componentes diferentes em cada um. // PanelDemo.java import java.awt.*; import java.awt.event.*; public class PanelDemo extends Frame implements ActionListener { private Label l1, l2; private TextField entrada; private Button bLimpar, bTransf, bOk; private Panel pTop, pBot, pRight; // método main public static void main(String args[]) { PanelDemo f = new PanelDemo(); f.addWindowListener(new CloseWindowAndExit()); f.show(); } // construtor public PanelDemo() { super("Panel Demo"); setSize(400, 120); // instanciacao dos componentes l1 = new Label("Entrada"); l2 = new Label("Saída"); entrada = new TextField(20); bLimpar = new Button("Limpar"); bLimpar.addActionListener(this); bTransf = new Button("Transferir"); bTransf.addActionListener(this); bOk = new Button("Ok"); bOk.addActionListener(this); pTop = new Panel(new FlowLayout(FlowLayout.LEFT)); pTop.setBackground(Color.lightGray); pBot = new Panel(new GridLayout(1,2)); pRight = new Panel(); pRight.setBackground(Color.gray); // adicao dos componentes pTop.add(l1); pTop.add(entrada); add(pTop, BorderLayout.CENTER); pRight.add(l2); add(pRight, BorderLayout.EAST); pBot.add(bLimpar); pBot.add(BTransf); pBot.add(bOk); add(pBot, BorderLayout.SOUTH); } // interface ActionListener public void actionPerformed(ActionEvent e) { if (e.getSource()==bLimpar) { // limpa entrada entrada.setText(""); } else if (e.getSource()==bTransf) { // entrada p/ saida
Docsity logo



Copyright © 2024 Ladybird Srl - Via Leonardo da Vinci 16, 10126, Torino, Italy - VAT 10816460017 - All rights reserved