Programação Java
Programação Java
1 – Operações Iniciais 3
4 – Matrizes 33
5 – Objetos e Classes 37
14 – Threads 153
Operações Iniciais
Objetivos
A arquitetura da linguagem Java foi desenvolvida para atingir os objetivos mencionados acima. Os
seguintes recursos atingem esses objetivos:
• A máquina virtual Java (JVM)
• A coleta de lixo (garbage collector)
• A segurança de códigos
Qualquer interpretador compatível com Java deve ser capaz de executar qualquer programa com os
arquivos de classes que estão em conformidade com o formato de arquivo de classes descrito na
Especificação JVM.
Garbage Collector
Quando a memória alocada não for mais necessária (o ponteiro que faz referência à memória fica
sem escopo), é aconselhável que o programa ou ambiente de tempo de execução desaloque a
memória para evitar que o programa fique sem memória.
Ao utilizar o Java, o programador não mais terá a responsabilidade de liberar a memória, pois essa
linguagem fornece um thread de segundo plano, em nível de sistema, que registra toda a alocação
de memória e mantém uma contagem do número de referências feitas a cada ponteiro de memória.
Durante os ciclos de inatividade no tempo de execução da Máquina Virtual Java, o thread de coleta
de lixo verifica se há algum ponteiro de memória em que o número de referências tenha sido
reduzido a zero (0). Se houver, a memória “marcada” pelo thread de coleta de lixo é “limpa”
(desalocada).
A coleta de lixo ocorre automaticamente durante a vida útil de um programa Java elimina a
necessidade de desalocar a memória e perdas de memória.
Segurança de códigos
A figura abaixo ilustra o ambiente do programa Java. Utilizaremos esta figura para descrever como a
linguagem Java impõe a segurança de códigos.
Os arquivos da linguagem Java são “compilados” para que sejam convertidos do formato de texto
em que os programadores o criam para um conjunto de códigos de bytes independente de máquina.
Em tempo de execução, os códigos de bytes que constituem o programa Java são carregados,
verificados e, em seguida, executados em um interpretador. O interpretador tem duas funções:
executar o bytecode Java e fazer as chamadas apropriadas, através de um aplicativo em tempo de
execução do sistema, para o hardware subjacente.
Em alguns ambientes run-time Java, uma parte dos bytecodes verificados é compilada no código
original da máquina e executada diretamente na plataforma de hardware.
O interpretador Java
O interpretador Java deve executar o código compilado para a Máquina Virtual Java. Esse
interpretador é responsável por três tarefas principais:
Carregador de classes
Uma vez carregadas todas as classes, o layout da memória do arquivo executável é determinado.
Nesse ponto, endereços específicos de memória são atribuídos a referências simbólicas e a tabela
de pesquisa é criada. Como o layout de memória ocorre em tempo de execução, um interpretador
Java adiciona proteção contra o endereçamento inválido de código, que possa enviar um programa
que cause danos ao sistema operacional.
Verificador de bytecodes
O código Java passa por vários testes antes de ser efetivamente executado na máquina. O
programa executa o código através de um verificador de bytecodes que testa o formato dos
fragmentos de código e aplica um provador de teoremas para verificar a existência de códigos
ilegais, os quais falsificam ponteiros, violam os direitos de acesso a objetos ou tentam alterar o tipo
ou a classe do objeto.
Processo de verificação
O verificador de bytecodes faz quatro passagens no código de um programa. Além disso, assegura
que o código esteja em conformidade com as especificações JVM e que não viole a integridade do
sistema. Se o verificador concluir todas as quatro passagens sem retornar uma mensagem de erro,
você poderá ter certeza do seguinte:
• As classes estão de acordo com o formato de arquivo de classe contido nas especificações JVM.
• Não há violações de restrição de acesso.
• O código não causa estouros nem estouros negativos em pilhas de operandos.
• Os tipos de parâmetros referentes a todos os códigos operacionais são sempre considerados
como corretos, é uma linguagem fortemente tipada.
• Não ocorreu nenhuma conversão de dados inválida, como a conversão de inteiros em ponteiros.
• Os acessos a campos de objeto são considerados como válidos, dependendo do modificador.
OlaMundo.java
1. // Exemplo de aplicativo
2. //
3. public class OlaMundo {
4. public static void main (String args[]) {
5. System.out.println(“Ola mundo!”);
6. }
7. }
Essas linhas reúnem os componentes mínimos necessários para imprimir Ola mundo! na tela.
A próxima linha declara o nome da classe como OlaMundo . O nome da classe especificado no
arquivo de origem cria um arquivo nomedaclasse.class no diretório em que você executou a
compilação. Nesse caso, o compilador cria um arquivo chamado OlaMundo.class .
A próxima linha do programa é o local onde inicia a execução do programa. O interpretador Java
deve encontrá-la exatamente como foi definida ou ele se recusará a executar o programa.
Se o programa receber argumentos em sua linha de comando, eles serão passados para o método
main(), em uma matriz de Strings. Nesse exemplo, não são esperados ou tratados argumentos.
• public – O método main()
• static – Uma keyword que informa ao compilador que o método main() pode ser utilizado no
contexto da classe, não exigindo uma instância para sua execução.
• void – Indica que o método main() não retorna nada. Isso é importante porque a linguagem
Java executa uma verificação de tipos cuidadosa, que inclui verificar se os métodos chamados
retornam os tipos com os quais foram declarados.
• String args[ ] - A declaração de um array de Strings. Estes são os argumentos digitados
na linha de comandos após o nome da classe. O nome da classe não é incluída nos argumentos.
Java OlaMundo arg1 arg2 . . .
O método println recebe um argumento String e passa-o para o fluxo de saída padrão.
Por fim as duas linhas finais fecham os blocos com chaves, de maneira semelhante a C e C++.
Executar
Para executar o aplicativo OlaMundo, utilize o interpretador Java, java, também localizado no
diretório bin.
Observação – O sistema anexa o local das classes do sistema ao fim do caminho da classe, a
menos você utilize a opção –classpath para especificar um caminho.
A variável de caminho não foi definida corretamente para incluir o compilador javac. O compilador
está localizado no diretório bin, abaixo do JDK (Java Developers Kit).
Ocorre esse erro porque a palavra static ou public não foi inserida na linha que contém o
método main.
Em geral, isso significa que o nome da classe foi digitado de forma diferente do nome do arquivo de
origem e que um arquivo nomedoarquivo.class foi criado com a mesma ortografia (inclusive
maiúsculas e minúsculas) que a definição da classe.
Por exemplo,
class OlaMundo {
Cria uma classe OlaMundo que não tem o nome de classe esperado pelo compilador.
• Atribuição de nomes
Se o arquivo .java contiver uma classe pública, seu nome deverá ser o mesmo que o desta classe.
• Contagem de classes
Uma regra na linguagem Java estabelece que no máximo uma classe pública pode ser definida em
um arquivo fonte.
• Layout
São eles:
Esses itens devem aparecer nessa ordem. Isto é, as instruções de importação devem preceder
todas as declarações de classe e, se uma declaração de package for utilizada, deverá preceder as
classes e as importações.
O fato de um Button ser herdado de um Component é indicado pela a hierarquia de classes. Por
enquanto, você precisa saber que vários recursos de uma classe talvez não estejam documentadas
na página referente a ela e, em vez disso, estarão descritos na página selecionada à classe "pai".
Portanto para localizar o método setColor() de um Button, você deverá procurar em
Component na documentação.
Objetivos
Um bloco é marcado com a inclusão de chave ao seu redor - {e}, constituindo uma instrução
composta.
Permite-se um espaço em branco entre os elementos do código fonte e, sempre que for permitido,
você poderá utilizar quantos desejar. O espaço em branco abrange espaços, tabulações e novas
linhas. portanto, poderá ser utilizado para melhorar a aparência do código de origem.
{
int x;
x = 23 * 54;
}
Identificadores
Na linguagem Java, os identificadores iniciam com uma letra, um caracter de sublinhado
( _ ) ou um símbolo de dólar ($). Os caracteres subsequentes também podem conter dígitos. Os
identificadores fazem distinção entre maiúsculas e minúsculas e não têm limite de comprimento.
Identificadores válidos:
• identifier
• username
• User_name
• _sys_var1
• $change
Tenha cuidado com caracteres que não sejam ASCII, pois o Unicode pode suportar diferentes
caracteres que possuem a mesma aparência.
Observação - os identificadores que contém um símbolo de dólar ($) geralmente são raros, embora
linguagens como os shells BASIC e UNIX e também os sistemas VAX VMS os utilizem com
frequência. Como esses identificadores não são comuns, provavelmente é melhor evitá-los, a menos
que haja uma convenção local ou outra razão importante para se incluir esse símbolo no
identificador.
Observação - Não existem operadores sizeof. o tamanho e a representação de todos os tipos são
fixos e não dependem de implementação.
Observação - goto e const são palavras-chaves, mas não são utilizados na linguagem Java.
Lógica - boolean
Os valores lógicos possuem dois estados. Em geral, são indicados como "ativado" e "desativado",
"verdadeiro" e "falso" ou "sim" e "não". Na linguagem Java, esse valor é representado pelo tipo
boolean. O boolean contém dois valores literais: true e false.
Observação - Não há conversão entre tipos de inteiro e o tipo boolean. Algumas linguagens,
especialmente C e C++, permitem que valores numéricos sejam interpretados como valores lógicos,
normalmente considerando o zero como falso e o valor diferente de zero como verdadeiro. Isso não
é permitido na linguagem Java, pois sempre que um valor boolean for necessário, nenhum outro
poderá ser utilizado.
Os caracteres simples são representados através do tipo char. O char representa um caracter
Unicode que utiliza um número de 16 bits sem sinal, em uma faixa de 0 a 2 16 -1. O tipo char deve
ser delimitado por aspas simples. (‘ ‘).
‘a’ a
‘\t’ tab
‘\u????’ um caracter Unicode específico, ???? . Deve ser substituído exatamente por quatro
dígitos hexadecimais.
O tipo String, que não é primitivo, é utilizado para representar a seqüência de caracteres. Os
caracteres baseados em Unicode, e os estilos de seqüência de escape mostrados para o tipo char
também funcionam em uma String. Um literal String é colocado entre aspas duplas, como a
seguir:
“abcdefghijklmnopqrstuvwxyz 1234567890”
Há quatro tipos inteiros na linguagem Java. Cada tipo é declarado através de uma das palavras-
chave byte, short, int ou long. Os literais de um tipo inteiro podem ser representados
através de formas decimais, octais ou hexadecimais, como a seguir:
Observação - todos os tipos inteiros na linguagem Java são números com sinais.
Os literais inteiros pertencem ao tipo int, a menos que explicitamente seguidos da letra “L” indica
um valor long. Observe que, na linguagem Java, é válido utilizar o L maiúsculo ou minúsculo. No
entanto, letra minúscula não é recomendável, pois, na maioria das vezes, ela não pode ser
distinguida do dígito um. Estas são versões longas dos literais mostrados acima:
Uma variável de ponto flutuante pode ser declarada através da palavra-chave float ou double.
Um literal será um ponto flutuante se incluir um decimal, uma parte exponencial (a letra E) ou for
seguido da letra F ou D.
Observação - os literais de ponto flutuante são double, a menos que explicitamente declarados
como float.
O formato de um número de ponto flutuante é definido pela especificação Java como uma
representação IEEE 754, usando os tamanhos mostrados na tabela acima. Esse formato independe
de plataforma.
Interfaces - os nomes de interface devem ser escritos em letras maiúsculas e minúsculas, como nos
nomes de classe.
Constantes - as constantes primitivas devem ser descritas totalmente em letras maiúsculas, com as
palavras separadas por caracteres de sublinhado. As constantes de objetos podem usar maiúsculas
e minúsculas.
Variáveis - todas as variáveis globais, de classe e de instância devem ser descritas em maiúsculas e
minúsculas, com a primeira letra minúscula. As palavras são separadas por letras maiúsculas. Em
geral, você não deve utilizar o caracter de sublinhado em nomes de variáveis. Além disso, evite
utilizar o símbolo de dólar ($), pois esse caracter possui um significado especial em inner classes.
Os nomes das variáveis devem ter significado. Esse nome deve indicar a função da variável ao leitor
casual. Você deve evitar utilizar nomes de um único caracter, exceto no caso de variáveis
"descartáveis" temporárias (por exemplo, i, j, k, para uma variável de controle que não é utilizada
fora do loop).
Espaçamento - coloque apenas uma única instrução em qualquer linha e utilize um recuo de quatro
espaços para que seu código fique legível.
Comentários - Deve-se utilizar comentários para explicar os segmentos de código que não sejam
óbvios. Utilize delimitador de comentários // para comentário normal, de modo que as seções de
código possam ser comentadas através dos delimitadores /*...*/. Utilize o comentário de
documentação /**...*/ para fornecer uma API através do javadoc à pessoa que realizará a
manutenção posteriormente.
Essa declaração tem duas funções. Ela informa que quando nos referirmos ao dia, mês ou ano,
estaremos manipulando um inteiro, em vez de qualquer outro item da memória. Ela também aloca a
área de armazenamento para esses inteiros.
Embora essa abordagem seja de fácil compreensão, possui duas desvantagens significativas.
Primeiro, se o programa precisar controlar várias datas, mais declarações serão necessárias. Para
armazenar duas datas de aniversário, podemos utilizar:
Segunda é que cada um desses valores consiste em uma variável independente. Em um nível
conceitual, e existe uma associação entre um dia, um mês e um ano. Eles fazem parte de um
mesmo item, neste caso, uma data.
A maioria das linguagens de programação, até mesmo as mais antigas, suporta o conceito de
variáveis como tipos. Isto é, um valor pode ser um inteiro, um número de ponto flutuante ou talvez
um caracter. Embora essas linguagens contenham vários tipos e embutidos, o ideal é haver a
possibilidade de definir novos tipos para representar outras idéias, como uma "data".
Essas dificuldades são abordadas em diversas linguagens, através da inclusão de tipos agregados
de dados. Os tipos agregados de dados são mencionados em algumas linguagens como tipos
estruturados ou tipos de registro.
Os tipos agregados de dados são definidos pelos programadores no programa fonte. Uma vez feita
a definição, o tipo poderá ser utilizado como qualquer outro. Para definir um tipo, o programador
deve descrever os elementos que constituem uma instância na nova variável.
int dia;
Java sabe que determinada área de armazenamento deve ser alocada e também pode interpretar o
conteúdo desse armazenamento. Portanto, para definir um novo tipo, devemos e especificar a área
de armazenamento necessária e como interpretar o conteúdo. Em geral, isso não é feito com base
nos números de bytes, nem na ordem e no significado dos bits, mas sim com base em outros tipos
que já foram compreendidos.
Por exemplo, para definir um tipo que represente uma data, precisamos de um armazenamento
suficiente para três inteiros, além de tratar o espaço como três inteiros separados. Além disso,
quando os utilizamos, atribuímos o significado do dia, mês e ano, respectivamente a esses inteiros.
Na linguagem Java, isso é feito da seguinte maneira:
A palavra class faz parte da linguagem Java e deve ser totalmente escrito em letras minúsculas. O
nome Date é escrito com a primeira letra maiúscula em virtude de convenção; não é um requisito da
linguagem.
Observação - Uma classe representa bem mais que um simples tipo de dados agregado. Porém,
abordaremos essas questões adicionais mais adiante.
Agora, uma variável pode ser declarada como sendo do tipo Date, que as partes que constituem o
dia, ou mês e o ano serão indicadas por essa declaração da seguinte maneira:
Utilizando essa declaração, Java permitem que as partes (day, month e year) das variáveis sejam
acessadas através do operador ponto (.), como mostrado a seguir:
minhaData.dia = 26;
minhaData.mes = 11;
minhaData.ano = 1960;
Os tipos agregados de dados fornecem uma maneira de associar as partes componentes do modelo
de uma data, tratando-as não como itens isolados e sim como elementos de um todo maior. Além
disso, reduzem a carga de nomes, já que, em vez de três nomes, apenas um é necessário para a
cada data.
Os elementos que constituem a classe, isto é, as partes de dia, mês e ano, são normalmente
chamados campos, mas na terminologia do orientada a objetos recebem o nome de variáveis
membro. Como a linguagem Java é orientada a objetos, utilizaremos o termo variável membro ou
apenas membro.
Criar um objeto
Quando declaramos variáveis de qualquer tipo primitivo, isto é, seja boolean, byte, short, char,
int, long float ou double, um espaço da memória é alocado como parte da operação. A
declaração de uma variável através de um tipo de classe como String ou de qualquer tipo definido
pelo ou usuário não aloca memória.
Na verdade, uma variável que declarada com um em tipo de classe não consiste nos dados, e sim
em uma referência aos dados.
Observação – A referência também pode ser considerado como um ponteiro, que é sua verdadeira
função na maioria das implementações. Se esse termo tiver significado para você, ele irá ajudá-lo a
compreender melhor o assunto.
Antes que você possa utilizar a variável, a área de armazenamento deve ser alocada. para isso,
utiliza-se a palavra-chave new como a seguir:
A primeira declaração aloca apenas o espaço suficiente para a referência. A segunda declaração
aloca o espaço para os três inteiros utilizados para formar a Data. A atribuição configura a variável
minhaData para se referir corretamente ao novo objeto. Após executar essas duas operações,
você poderá manipular os componentes do tipo data, como descrito anteriormente.
O armazenamento real, alocado através de new Data() é chamado objeto. Com base em uma
definição de classe referente a uma classe arbitrária Xxxx, você pode chamar a new Xxxx() para
criar todos os objetos necessários. Eles são separados uns dos outros e possuem sua própria
referência. Essa referência precisará ser armazenada em uma variável, de modo que você possa
utilizar a combinação “variável-ponto-membro” (como minhaData.dia) para a acessar os membros
individuais de cada um dos objetos.
Date today;
today = new Date();
today ????
Date today;
today = new Date();
today ????
day 0
month 0
year 0
Por fim, a atribuição configura variável de referência para que ela se refira corretamente ao objeto
recém-criado.
Date today;
today = new Date();
today 0x01abcdef
day 0
month 0
year 0
A linguagem Java trata as variáveis que são declaradas como contendo um tipo de classe, como
referência, o que influencia no significado da atribuição. Considere este fragmento de código:
int x = 7;
int y = x;
String s = “Olá!”;
Quatro variáveis são criadas: duas do tipo int e duas referências a String. O valor de x é 7,
sendo copiado para y. Tanto x como y são variáveis independentes e as alterações efetuadas em
uma delas não afeta a outra.
Com as variáveis s e t, e existe apenas um objeto String, que contém o texto “Olá”. Tanto s como
t referem-se a esse único objeto.
Quando você executa new a fim de alocar memória para um objeto, a linguagem Java a inicializa os
valores no espaço como zero. Em variáveis numéricas, o valor é realmente zero. Em variáveis
boolean, o valor é definido como false. Em referências, isto é, qualquer variável de um tipo de
objeto, é utilizado um valor null especial.
Na linguagem Java, um valor null indica que a referência não se refere a um objeto. Isso permite
que o sistema de tempo de execução detecte a utilização dessa referência e pare o processo
imediatamente, seja qual for a plataforma, evitando a ocorrência de danos. Mais detalhes sobre esse
comportamento serão apresentados mais adiante.
Observação - A inicialização padrão ocorre em variáveis membro, mas não de variáveis locais ou
automáticas de método. Ocorrerá um erro no tempo de compilação se você tentar utilizar uma
variável automática antes que ela tenha recebido um valor. No entanto, você pode atribuir o valor
null a uma variável.
Os programadores de C e C++ devem tomar cuidado, pois, na linguagem Java, o literal null é
escrito em letras minúsculas, ao contrário de seu equivalente naquelas linguagens.
Um nome (um tanto matemático) que um programador utiliza para criar um modelo. O modelo é
construído através da inclusão de variáveis que descrevem os vários aspectos que especificam o
estado da entidade em uma única definição. Uma vez definidas, as variáveis poderão ser criadas por
meio de um nome de tipo. Em vez disso, algumas linguagens utilizam os termos registro ou tipo
estruturado. A linguagem Java utiliza o termo classe.
Classe
Uma versão em linguagens orientadas a objetos de um tipo agregado de dados. Estritamente, uma
classe consiste em um super conjunto da idéia de um tipo agregado de dados. No entanto, os
diversos recursos adicionais que a tornam uma classe, em vez de um tipo agregado de dados, ainda
não foram abordados.
Objeto
Uma instância real de uma classe. A classe pode ser considerada com um gabarito - um modelo do
objeto que você está descrevendo. Um objeto é o que você obtém toda vez que cria uma instância
de uma classe.
Membro
Membro é um dos elementos que constituem um objeto. O termo também é utilizado para os
elementos da classe de definição. Os termos variável membro, instância de variável ou campo
também são utilizados como sinônimos.
Referência
Na linguagem Java, uma variável que é definida como contendo um tipo de classe, na verdade, não
contém os dados do objeto. em vez disso, ela consiste em um meio de identificar um objeto
específico. Esse tipo de variável é chamado referência.
Objetivos
Você já conhece as duas maneiras como as variáveis podem ser descritas: através de tipos simples
como int e float ou através de tipos de classes definidos pelo programador. Você também já
sabe que duas variáveis locais podem ser declaradas: dentro de um método ou dentro da definição
de classe como um todo.
As variáveis definidas dentro de um método (você verá mais adiante que o método main() não é o
único que pode ser definido) são chamadas variáveis automáticas, locais, temporárias ou de pilha,
dependendo da preferência do usuário.
As variáveis membro são criadas quando o objeto é construído através do método new Xxxx ().
Essas variáveis continuam a existir, enquanto o objeto seja necessário.
Observação – Parte do sistema chamada “garbage collector” destrói os objetos não mais
necessários. Em geral, isso acontece sem a intervenção do programador.
As variáveis automáticas são criadas quando a execução entra no método e são destruídas quando
saem desse método. Esse é o motivo pelo qual, às vezes, elas são chamadas “temporárias”.
Inicialização de variáveis
Nenhuma variável em um programa Java pode ter um valor indefinido. Quando um objeto é criado,
as variáveis membro são inicializadas com os seguintes valores, no momento em que o
armazenamento é alocado:
byte 0
short 0
int 0
long 0l
float 0.0f
double 0.0d
char ‘\u0000’
boolean false
Todos os tipos de referência null
Observação – Uma referência que contém o valor nulo não se refere a nenhum objeto. Uma
tentativa de utilizar o objeto ao qual se refere causará uma exceção. As exceções consistem em
erros que ocorrem em tempo de execução e serão abordadas em um módulo posterior.
As variáveis automáticas devem ser inicializadas antes de serem utilizadas. O compilador estuda o
código para determinar que cada variável seja definitivamente inicializada antes de ser utilizada pela
primeira vez. Se o compilador não puder determinar isso, ocorrerá um erro de compilação.
Operadores
Os operadores da linguagem Java são bastante semelhantes em estilo e função aos utilizados nas
linguagens C e C++. A tabela a seguir lista os operadores em ordem de precedência (“L até R”
significa associativo da esquerda para a direita; “R até L” significa associativo da direita para a
esquerda):
Separador . [] () ; ,
R até L ++ -- + - ~ ! (cast_operator)
L até R * / %
L até R + -
L até R << >> >>>
L até R < > <= >= instanceof
L até R == !=
L até R &
L até R ^
L até R |
L até R &&
L até R ||
R até L ?:
R até L = *= /= %= += -= <<= >>= >>>= &= |=
Expressões lógicas
A expressão booleana que forma o argumento para a instrução if () é válida e totalmente segura.
Isso ocorre porque a primeira subexpressão (unset != null) é falsa, o que é suficiente para
provar que toda a expressão é falsa. Por isso, o operador && ignora o cálculo desnecessário de
(unset.lenght() > 5). Como isso não é calculado, uma exceção de ponteiro nulo é evitada. Da
mesma forma, se o operador || for utilizado e a primeira expressão retornar verdadeiro, a segunda
não será calculada, pois já será considerada como totalmente verdadeira.
Observação – Para evitar o comportamento de avaliação curta, basta utilizar os operadores & e |
O operador comum >> executa um deslocamento aritmético para a direita. Em geral, o resultado
desse deslocamento é que o operando é divido pelo dobro do número especificado pelo segundo
operando. Por exemplo:
O operador de deslocamento para a direita >>> lógico ou sem sinal trabalha com o padrão de bits
de um valor, em vez de considerar seu significado aritmético, e sempre inclui zeros nos bits mais
significativos. Por exemplo:
Observação – É importante observar que o operador >>> só é permitido em tipos integrais e só terá
efeito em valores int ou long. Se for utilizado em um valor de byte ou curto, o valor será elevado,
com uma extensão de sinal, a um inteiro antes de >>> ser aplicado. Nesse ponto, o deslocamento
sem sinal normalmente se torna um deslocamento com sinal.
Cast
Onde os valores não são compatíveis em termos de atribuição, um cast pode, às vezes, ser utilizado
para persuadir o compilador a reconhecer uma atribuição. Isso pode se feito, por exemplo, para
“comprimir” um valor longo em uma variável do tipo inteiro. O cast explícito é feito da seguinte
maneira:
Observe que o tipo desejado é colocado entre parênteses e utilizado como um prefixo da expressão
que deve ser modificada. Embora não seja necessário, é geralmente aconselhável colocar
parênteses toda a expressão em que haverá o cast, pois, caso contrário, a precedência da operação
de cast poderá causar problemas.
Observação – Lembre-se de que a faixa de short é de –215 até 2 15e a faixa de char é de 0 até 216
-1. Por isso, a atribuição entre short e char sempre necessitará de um cast explícito.
Exemplo
1 int count;
2 count = getCount(); // um método definido no programa
3 if (count < 0) {
4 System.out.println(“Erro: valor de count é negativo!”);
5 }
6 else {
7 System.out.println(“Haverá “ + count +
8 “ pessoas para o almoco hoje.”);
9 }
A linguagem Java difere da C/C++ pelo fato de um método if () utilizar uma expressão boolean
em vez de um valor numérico. Lembre-se de que os tipos boolean e os tipos numéricos não podem
ser convertidos e nem sofrer cast. Portanto:
if (x) // x é inteiro
if (x!=0)
Instrução switch
Switch (expr1)
case expr2:
declarações;
break;
case expr3:
declarações;
break;
default:
declarações;
break;
}
Observação – Na declaração switch (expr 1), expr1 deve ser compatível em termos de
atribuição a um tipo int. A elevação ocorre com os tipos type, short ou char. As expressões de
ponto flutuante ou do tipo long não são permitidas.
Observação – Só é possível efetuar switch com os tipos de dados short, int, byte ou char.
1 switch (colorNum) {
2 case 0:
3 setBackground(Color.red);
4 break;
5 case 1:
6 setBackground(Color.green);
7 break;
8 default:
9 setBackground(Color.black);
10 break;
11 }
Instruções de loop
Loops for
Exemplo
Observação – A linguagem Java permite o operador vírgula em for (), mas em nenhum outro
lugar.
Loops while
while (boolean)
declaração_ou_bloco
Exemplo
1 int i=0;
2 while (i<10) {
3 System.out.println(“Você já acabou?”);
4 i++;
5 }
6 System.out.println(“Finalmente”);
Loops do
do
Linguagem de Programação Java 31
declaração_ou_bloco
while (boolean);
Exemplo
1 int i=0;
2 do {
3 System.out.println(“Você já acabou?”);
4 i++;
5 } while (i<10);
6 System.out.println(“Finalmente”);
As instruções a seguir podem ser utilizadas para controlar melhor as declarações de loop:
• break [label];
• continue [rótulo];
• label declaração; //onde declaração deve ser uma
//instrução for, while ou do.
Exemplos
Matrizes
Objetivos
char s [ ];
point p [ ];
Na linguagem Java, uma matriz é uma classe e, como em outras classes, a declaração não cria o
objeto em si. Portanto, essas declarações não criam matrizes. Apenas fazem referência a variáveis
que podem ser utilizadas para se referir a uma matriz.
O formato mostrado acima, com colchetes após o nome da variável, é o padrão das linguagens C e
C++, sendo também utilizado na linguagem Java. Esse formato gera formas complexas de
declaração, cuja leitura pode ser bem difícil. Por isso, Java permite um formato alternativo com
colchetes à esquerda:
char [ ] s;
point [ ] p;
O resultado é que a declaração passa a ter a parte do tipo à esquerda e o nome da variável à direita.
Os dois formatos serão utilizados, mas você deverá optar por um deles e continuar com ele.
Criar matrizes
Você cria matrizes, como todo os objetos, usando a palavra-chave new,da seguinte maneira :
A primeira linha cria uma matriz de 20 valores char. A segunda linha cria uma matriz de 100
variáveis do tipo Point. No entanto, ela não cria 100 objetos Point. Esses objetos devem ser
criados individualmente.
Inicializar matrizes
A linguagem Java permite uma abreviação que criará matrizes com valores iniciais:
String names[] = {
“Georgiana”,
“Jean”,
“Silvia”,
String names[];
names[0] = “Georgiana”;
names[1] = “Jean”;
names[2] = “Silvia”;
names[3] = “Carlos”;
Esta abreviação pode ser utilizada para qualquer tipo de elemento e exige uma referência a cada
elemento em cada posição no bloco de inicialização. Por exemplo:
Color palette[] = {
Color.blue,
Color.red,
Color.white
};
Matrizes multidimensionais
A linguagem Java não fornece matrizes multidimensionais, mas como uma matriz pode ser
declarada e ter qualquer tipo de base, é possível criar matrizes de matrizes (de matrizes, de
matrizes etc.)
O objeto criado através de new é simplesmente uma matriz. A matriz contém cinco elementos. Cada
um desses elementos é uma referência null a um elemento do tipo array of int.Portanto,
cada um dos quatro elementos deve ser construído separadamente. Cada um deles é um array
of int.
Como esse tipo de inicialização é tediosa e a matriz de matrizes retangular é o formato mais comum,
a linguagem Java fornece uma abreviação para criar matrizes bidimensionais. Por exemplo,
pode ser utilizado para criar uma matriz de quatro matrizes, contendo cinco inteiros cada.
Limites da matriz
O número de elementos em uma matriz é armazenado como parte do objeto de matriz. O valor
utilizado para executar a verificação de limites de todos os acessos em tempo de execução. Se
houver um acesso fora dos limites, ocorrerá uma exceção. As exceções constituem o tema de um
próximo módulo.
O tamanho de uma matriz pode ser determinado em tempo de execução através da variável
membro length. Por exemplo, para iterar uma matriz, o código deve ser utilizado desta forma:
Observe que o limite do loop é determinado através da comparação com o list.length, em vez
de com valor imediato 10. Esse método é mais eficiente em face da manutenção do programa.
Copiar matrizes
Uma vez criada, um matriz não poderá ser redimensionada. No entanto, você pode utilizar a mesma
variável de referência para se referir a uma matriz totalmente nova:
Nesse caso, a primeira matriz é efetivamente perdida, a menos que alguma outra referência a ela
seja mantida em outro local.
É possível copiar uma matriz com eficiência tolerável. A linguagem Java fornece um método
especial na classe System: o método arraycopy(), que pode ser utilizado da seguinte maneira:
// matriz original
int elements[] new int { 1, 2, 3, 4, 5, 6 };
:
// nova matriz mais extensa
int hold[] = new int { 10, 9, 8, 7, 6, 5, 4, 3, 2, 1 };
// copia tudo da matriz elements para a matriz
// hold
// iniciando com índice de ordem 0
System.arraycopy(elements, 0, hold, 0, elements.lenngth);
Objetos e classes
Objetivos
Em várias linguagens de programação, uma vez definido um tipo agregado de dados, o programador
simplesmente define funções utilitárias para operar em variáveis desse tipo, sem qualquer
associação específica entre o código agregado, exceto talvez na convenção de atribuição de nome.
Portanto, pode ser que você encontre uma função apenas chamada tomorrow(), que deve conter
um argumento e uma variável do tipo Date.
Algumas linguagens de programação, incluindo Java, permitem uma associação mais segura entre
a declaração de um tipo de dados e a declaração do código que deve operar em variáveis desse
tipo. Em geral, essa associação é descrita como um tipo abstrato de dados.
Na linguagem Java, você pode criar uma associação entre o tipo date e a operação tomorrow da
seguinte maneira:
Na linguagem Java, você pode se referir ao código denominado tomorrow como um método,
embora também possa encontrar termos função membro e função.
Observação – Não se preocupe sobre o significado da palavra private nessa declaração, pois ela
será descrita mais adiante.
Nas linguagens que não suportam a associação entre dados e código, você poderá observar que a
declaração do método tomorrow especifica a data especifica que deve ser aumentada em um dia.
Por exemplo:
As linguagens, como Java, que suportam tipos de dados criam uma associação mais segura entre
os dados e o código. Não descrevemos um método de acordo com sua operação em um fragmento
de dados.
Em vez disso, preferimos considerá-lo como se ele soubesse como se auto-modificar. Em seguida,
solicitamos que os dados executem uma operação em si próprios, da seguinte maneira:
Essa notação reflete a idéia de que o comportamento é realizado pelos dados e não de outra
maneira. Lembre-se de que você pode se referir aos campos da classe Data utilizando a notação de
pontos:
d.dia
A idéia é de que os métodos são uma propriedade dos dados em vez de construir a etapa principal
para a construção de um sistema orientado a objetos. Às vezes, você ouvirá o termo passagem de
mensagens. É utilizado em algumas linguagens para passar a noção de instruir um item de dados
para executar alguma ação em si próprio. Na verdade, em linguagens que utilizam
convencionalmente essa terminologia, ele geralmente também reflete a natureza da implementação.
Definir métodos
Na linguagem Java, os métodos são definidos através de uma abordagem bastante semelhante à
utilizada em outras linguagens, especialmente C e C++. A declaração assume a seguinte forma:
O <name> pode ser qualquer identificador válido, com algumas restrições baseadas nos nomes que
já estão sendo utilizados.
O <return_type> indica o tipo de valor retornado pelo método. Se o método não retornar um
valor, ele deve ser declarado como void. A linguagem Java é rigorosa com relação aos valores
retornados. Se a declaração indicar que o método retorna, por exemplo, um inteiro,ele deverá fazê-
lo a partir de todos os caminhos de retorno possíveis.
Instrui o corpo do método a receber um argumento que indica o número de dias a sem adicionados
à data atual. No método, é feita uma referência ao valor através do identificador days.
A linguagem Java só passa argumentos “por valor”; isto é, o argumento não pode ser alterado pelo
método chamado. Quando uma instância de objeto é passada como um argumento para um objeto,
o conteúdo do objeto pode ser alterado no método chamado, mas a referência ao objeto nunca é
alterada.
% Java PassTest
Valor Int é: 11
Valor Str: olá
PtValue atual é: 99.0
O objeto String não é alterado por changeStr(), mas o conteúdo do objeto PassTest pode ser
alterado.
A referência this
Em uma função tradicional, é feita referência aos dados a serem manipulados, através do
argumento com nome contido na função. Na linguagem Java, não há argumentos com nome. Em
vez disso, você pode fazer referência ao objeto-alvo da operação, utilizando a palavra chave this.
Por exemplo:
Há situações em que a palavra-chave this não é redundante. Por exemplo, convém chamar um
método em uma classe totalmente separada e passar a instância do objeto como um argumento.
Por exemplo:
Ocultar dados
A utilização da palavra-chave private na declaração de dia, mes e ano na classe Data torna
impossível acessar esses membros em qualquer código, exceto nos métodos da própria classe
Data. Assim, com base nessa declaração da classe Data, o seguinte código é inválido:
Pode parecer que impedir o acesso direto a essas variáveis de dados seja uma ação peculiar para
ser executada deliberadamente. Porém, na verdade, traz várias vantagens para a qualidade do
programa que utiliza a classe Data. Como os itens individuais dos dados são inacessíveis, a única
maneira de ler e gravar neles é através de métodos. Por isso, se seu programa exigir uma
consistência interna dos membros da classe, isso poderá ser gerenciado pelos métodos da própria
classe.
Considere uma classe Data que permita um acesso externo arbitrário a seus membros. Será muito
mais fácil para o código executar qualquer uma destas ações:
Se os membros de dados de uma classe não forem expostos, o usuário da classe será forçado a
modificar as variáveis membro por meio de métodos. Como esses métodos constituem código real,
podem executar verificações de validade. Considere o seguinte método como parte da classe Data:
O método verifica se o dia que ele deve definir é, de fato, válido. Se não for, ele ignorará a
solicitação e imprimirá uma mensagem. Até agora, no entanto, você pode ver que essa classe Date
é efetivamente protegida contra datas com valores excessivos.
As regras que determinam como um método pode ser chamado adequadamente (como,”o valor do
argumento deve se encontrar na faixa válida de números de dias referente ao mês do objeto”) são
denominados pré-condições. A utilização cuidadosa de testes de pré-condição pode facilitar
bastante a reutilização de uma classe, além de torna-la bem mais confiável para tal, pois, se algum
método tiver sido utilizado incorretamente, o programador que estiver trabalhando com a classe
saberá disso imediatamente.
Encapsulamento
Além de proteger os dados de um objeto contra qualquer modificação imprópria, fazer com que o
usuário acesse os dados através de um método permitirá que uma classe seja reutilizada com mais
Se os dados estiverem totalmente acessíveis, cada usuário da classe terá que incrementar o valor
do dia, compara-lo com o número de dias do mês atual e tratar as condições de fim de mês (e
possivelmente fim de ano), em que o aumento de um dia significa também haver alteração no mês
e/ou o ano. Esse procedimento é tedioso e suscetível a erros. Embora essas necessidades possam
ser bem compreendidas com relação a datas, pode ser que outros tipos de dados tenham restrições
semelhantes que não sejam tão conhecidas. Ao forçar o usuário da classe a utilizar o método
amanha() fornecido, todos poderão ter certeza de que os efeitos colaterais necessários sempre
serão tratados de forma consistente e correta.
Esse aspecto da ocultação de dados é geralmente chamado encapsulamento.
Agora, suponha que você precise de um método diferente para imprimir os tipos int, float e
String. Isso é razoável , já que os vários tipos de dados necessitarão de uma formatação diferente
e, provavelmente, de um tratamento diferente. Você pode criar três métodos, denominados
printint(), printfloat() e printString(), respectivamente. No entanto, isso é tedioso.
A linguagem Java, da mesma forma que várias outras linguagens, permite que você reutilize um
nome de método em mais de um método. Obviamente, isso só poderá funcionar se, nas
circunstâncias em que a chamada for feita, houver um meio para distinguir qual método é realmente
necessário. No caso dos três métodos de impressão, é possível fazer essa condição com base nos
argumentos. Ao reutilizar o nome do método, chegamos a estes três métodos:
Quando você cria um código para chamar um desses três métodos, o que for apropriado será
escolhido de acordo com o tipo de argumento fornecido.
Se você inclui expressões simples de atribuição nas declarações membro, poderá executar uma
inicialização explícita de membro durante a construção de seu objeto.
Construtores
O mecanismo de inicialização explícita descrita fornece uma maneira simples de definir os valores
iniciais de campos em um objeto. Ás vezes, no entanto, você realmente precisa executar um método
para realizar a inicialização. Pode ser que você precise tratar exceções possíveis com try/catch.
Convém utilizar loops ou condicionais para determinar a inicialização. Também é aconselhável
passar argumentos para o processo de construção, deforma que o código que solicita a construção
do novo objeto possa controlar o objeto criado. Objetos como botões são exemplos, pois precisam
utilizar uma seqüência de texto como rótulo.
Para criar um método que se chame construtor, você deve seguir estas duas regras:
1. O nome do método deve corresponder exatamente ao nome da classe.
2. Não deve haver um tipo de retorno declarado para o método.
Por exemplo:
O construtor padrão
Dissemos que toda classe possui pelo menos um construtor, mas antes desta seção, não criamos
construtores para nenhuma de nossas classes.
Na verdade, se você não criar nenhum construtor, a linguagem Java lhe fornecerá um. Esse
construtor não contém argumentos e possui um corpo vazio.
O construtor padrão permite que você crie instâncias de objetos com new Xxx(). Caso contrário,
você terá de fornecer um construtor para cada classe.
Observação – É importante saber que, se adicionar uma declaração de construtor com argumentos
a uma classe que não continha construtores explícitos, você perderá o construtor padrão. Nesse
ponto, se você chamar o método new Xxx(), causará erros de compilação.
Esse exemplo ilustra a duplicação entre a classe Manager e a classe Employee. Na realidade, há
diversos métodos que podem ser aplicáveis ao Employee e, provavelmente, um conjunto deles
também seria necessário ao Manager.
Portanto, o que precisamos é de uma maneira de criar uma nova classe a partir de uma existente.
Isso é chamado divisão em subclasses ou herança.
A palavra-chave extends
Em linguagens orientadas a objeto, são fornecidos mecanismos especiais para permitir que o
programador defina uma classe em termos de uma classe anteriormente definida. Na linguagem
Java, isso é feito através da palavra-chave extends, da seguinte maneira:
Nessa organização, a classe Manager é definida para ter todas as variáveis e métodos contidos em
um Employee. Todas essas variáveis e métodos são herdados da definição classe pai. Tudo o que
o programador precisa fazer é definir os recursos adicionais ou como veremos brevemente,
especificar as alterações que devem ser aplicadas.
Herança única
A linguagem Java permite que uma classe origine apenas uma outra. Essa restrição é chamada
herança única. O debate sobre os méritos relativos da herança única e múltipla constitui o assunto
de extensas discussões entre programadores de linguagens orientadas a objetos. A linguagem Java
impõe a restrição de uma única herança porque se considera que ela torne o código resultante mais
confiável, embora, às vezes, à custa de dificultar um pouco o trabalho dos programadores.
É importante saber que, embora uma subclasse herde todos os métodos e variáveis de uma classe
pai, ela não herda os construtores.
Polimorfismo
A descrição de uma classe Manager como a classe Employee não é apenas uma maneira fácil de
descrever a relação entre duas classes. Lembre-se que dissemos que a classe Manager obtém
todos os atributos, tanto membros como métodos, da classe pai Employee. Isso significa que
qualquer operação que seja legítima em uma classe Employee também será em uma classe
Manager. Se a Employee tiver os métodos raiseSalary()and fire(), a classe Manager
também os terá.
Isso leva à idéia de que os objetos são poliformicos, ou seja, possuem “várias formas”. Um objeto
específico pode ter a forma de uma classe Manager, mas também tem a forma de uma classe
Employee. Acontece que, na linguagem Java, existe uma classe que é a classe pai de todas as
outras. Essa é a classe java.lang.Object. Assim, na realidade, as definições anteriores eram
abreviações de:
A classe Object define vários métodos úteis, incluindo toString(). É por isso que todos os
elementos da linguagem Java podem ser convertidos em uma representação de seqüência, mesmo
que o resultado seja de uso limitado.
Java, como a maioria das linguagens orientadas a objeto, realmente permite que você se refira a um
objeto com uma variável cujo tipo seja um dos da classe pai. Assim, é válido dizer que:
Pode parecer irreal criar uma classe Manager e deliberadamente atribuir a referência a ela em uma
variável do tipo Employee. Isso é verdadeiro, mas há razões para que você queira atingir o mesmo
efeito.
Com esse procedimento, pode criar métodos que aceitem um objeto “genérico” , nesse caso, uma
classe Employee e trabalhar adequadamente em qualquer uma das subclasses dela. Portanto, você
pode produzir um método em uma classe de aplicativo que compare um funcionário e determinado
salário limite, para determinar os prováveis impostos relativos a esse salário. Utilizando os recursos
polimórficos, você pode tornar essa tarefa bastante simples:
e assim por diante. Podemos até criar um método de classificação que coloque os funcionários na
ordem de idade ou salário, sem termos de nos preocupar com o fato de alguns deles serem
realmente gerentes.
Observação – Como na linguagem Java toda classe é uma subclasse de Object, você pode
utilizar um array de Object como recipiente de quaisquer objetos. Você só não pode adicionar
variáveis primitivas a essa matriz. No entanto, melhor que uma matriz de Object é a classe
Vector, que foi criada para armazenar coleções heterogêneas de objetos.
O operador instanceof
Como você pode deslocar objetos utilizando referências às respectivas classes pai, ás vezes
convém saber o que você realmente conseguiu obter. Essa é a finalidade do operador instanceof.
Suponha que nossa hierarquia de classes seja ampliada da seguinte maneira:
Se você receber um objeto através de uma referência do tipo Employee, pode ser que ele venha a
ser ou não um Manager ou um Contractor. Se desejar, você poderá testá-lo utilizando o operador
instanceof, da seguinte maneira:
Em circunstâncias em que for feita referência a uma classe pai e for determinado que o objeto seja
realmente uma subclasse específica por meio do operador instanceof, você poderá restaurar a
funcionalidade total do objeto ao efetuar um cast na referência.
Se você não conseguir executar o cast, também não conseguirá fazer referência à
e.departament, já que o compilador não conhece nenhum membro chamado departament
pertencente à classe Employee.
Se você não realizar o teste utilizando o operador instanceof, pode ser que ocorra uma falha no
cast. Em geral, qualquer tentativa de efetuar coerção na referência de um objeto estará sujeita a
varias verificações:
- os casts “para cima” na hierarquia de classes são sempre válidas e, na verdade, não exigem
o operador de cast, podendo ser efetuadas através de uma simples atribuição.
- Para casts “para baixo, o compilador precisa estar certo de que o cast seja, pelo menos,
possível. Por exemplo, qualquer tentativa de efetuar coerção em uma referência de Manager
para uma referência de Contractor é definitivamente inválida, já que a classe
Contractor não é uma classe Manager. A classe para a qual a coerção está sendo
efetuada deve ser subclasse do tipo de referência atual”.
- Se o compilador permitiu a coerção, o tipo de referência será verificado no tempo de
execução . se em virtude de a verificação de instaceof ter sido omitida da origem, o objeto
que está sofrendo a coerção não é de fato um objeto do tipo da classe que irá recebê-lo,
ocorrerá uma Exceção. As exceções são uma espécie de erro execução e serão abordadas
em um próximo módulo.
Se um método for definido em uma nova classe de modo que o nome, o tipo de retorno e a lista de
argumentos correspondam exatamente aos contidos em um método de uma classe pai, significa que
o novo método terá sobrescrito o antigo.
Observação – Lembre-se de que se houver o mesmo nome e uma lista de argumentos diferente,
ocorrerá simplesmente uma sobrecarga, e o compilador terá apenas de analisar os argumentos
fornecidos para decidir qual método chamar.
Anteriormente falamos que a classe Manager possui um método getDetails(), por definição,
pois ela o herda da classe Employee. Agora, substituímos ou sobrescrevemos esse método original
e fornecemos outro em seu lugar.
Ou algum efeito semelhante, como um argumento de método geral ou um item derivado de uma
coleção heterogênea.
Observação – se você for programador de C++, haverá uma importante distinção a ser delineada
entre a linguagem Java e a C++. Em C++, você só obterá esse comportamento se marcar o método
como virtual na origem. No entanto, nas linguagens orientadas a objetos “puras”, isso não é normal.
É evidente que a C++ faz isso para aumentar a velocidade de execução.
Às vezes, você ouvirá praticantes da programação orientada a objetos falando sobre “passagem de
mensagens”. Uma maneira fácil de pensar sobre a chamada de um método virtual consiste em
imaginar que o método é uma instrução passada, quase verbalmente, para o objeto. Portanto, em
vez de informar a CPU para executar um bit de código, você instruirá um objeto: faça xxx para mim”.
Esse modelo torna fácil compreender que o comportamento sempre será adequado se associado ao
objeto real. Na verdade, isso também descreve muito bem a maneira como o sistema é
implementado em algumas outras linguagens.
A linguagem Java fornece o mecanismo de pacote como uma maneira de agrupar classes
relacionadas. Até agora, todos os exemplos pertenciam ao pacote padrão ou sem nome.
Você pode indicar que as classes de um arquivo fonte pertencem a um pacote específico, utilizando
a declaração package.
A declaração de pacote, se houver, deve se encontrar no inicio do arquivo fonte. Antes dele, você
pode incluir um espaço em branco e comentários, mas nada além disso. Apenas uma declaração de
pacote será permitida e controlará todo o arquivo de origem.
Os nomes de pacotes são hierárquicos e separados por pontos. É normal que os elementos do
nome do pacote se encontrem totalmente em letras minúsculas. A classe, no entanto, geralmente
começa com uma letra maiúscula e não pode utilizar outras maiúsculas para separar as palavras no
nome da classe.
Quando você utilizar uma declaração de pacote, não precisa importar o mesmo pacote ou qualquer
elemento dele. Lembre-se de que a declaração import é utilizada para trazer as classes de outros
pacotes para o espaço de nomes atual. O pacote atual seja explícito ou implícito, sempre fará parte
do espaço de nomes atual.
A declaração import
Na linguagem Java, para trabalhar com recursos de pacotes, você deve utilizar a declaração
import para informa ao compilador onde encontrar as classes utilizadas. Na verdade, o nome do
pacote(por exemplo, abc.FinanceDept) forma parte do nome das classes contidas no pacote.
Você pode se referir à classe Employee como abc.FinanceDept.Employee, mas para
economizar tempo, se você utilizar apenas o nome da classe básica Employee:
import abc.FinanceDept.*;
Os pacotes são “armazenados” em um diretório formado pelo nome do pacote. Por exemplo, o
arquivo Employee.class, descrito na página anterior, existiria no seguinte diretório quando
compilado:
path/abc/FinanceDept
O diretório abc que forma a base dessa hierarquia deve estar localizado em alguma parte de
CLASSPATH.
A variável CLASSPATH não foi utilizada antes, pois, como ainda não foi definida, o comportamento
padrão das ferramentas inclui automaticamente o local padrão das classes de distribuição e o
diretório de trabalho atual. Para acessar os pacotes que se encontram em outros locais, defina
explicitamente a variável CLASSPATH.
O compilador de Java criará o diretório package e moverá o arquivo de classe compilado quando a
opção –d for utilizada.
CLASSPATH = /home/anton/mypackages:.
Objetivos
Em geral, quando você sobrescreve um método, o objetivo real não é substituir o comportamento
existente e sim ampliar esse comportamento de alguma maneira. Para isso, basta utilizar a palavra
chave super.
Observe que, se você chamar o formato super.method (), todo o comportamento será ativado,
juntamente com qualquer efeito colateral do método que teria sido chamado se o objeto a que foi
feita referência realmente pertencesse à classe pai. 0 método não precisa ser definido nessa classe
pai. Ele pode ser herdado de uma classe localizada mais acima na hierarquia.
• Um método sobrescritor não pode ser menos acessível que o método que ele sobrescreve.
• Um método sobrescritor não pode lançar mais exceções que o método que ele sobrescreve.
Essas duas regras resultam da natureza do polimorfismo combinada com a necessidade de Java ser
"segura em termos de tipo". Considere este cenário inválido:
Se for permitido que o metodo() contido na classe Filha seja privado e o método sobrescrito
contido na classe Pai seja público, o código irá executar. No entanto, na execução, surgira um
public Employee(String n) {
this (n, 0) ;
}
public Employee() {
this("Unknown");
}
}
No terceiro construtor, que não possui argumentos, a chamada this ("Unknown") realmente
passa o controle para a versão do construtor que utiliza um argumento String e fornece a String.
O modelo de segurança da linguagem Java exige que os aspectos de um objeto que descreva a
classe pai sejam inicializados antes que a classe filho execute qualquer ação. Em várias
circunstâncias, o construtor padrão (isto é, o construtor sem argumentos) é utilizado para inicializar o
objeto pai.
Variáveis de classe
Às vezes, é desejável haver uma variável que seja compartilhada entre todas as instâncias de uma
classe. Por exemplo, isso pode ser utilizado como a base para a comunicação entre instâncias ou
para controlar o número de instâncias que foram criadas.
Você pode atingir esse efeito marcando a variável com a palavra-chave static. Essa variável é, às
vezes, chamada variável de classe para que seja diferenciada de uma variável membro ou de
instância.
Nesse exemplo, todos os objetos criados obtêm um número de série exclusivo, começando em um e
progredindo na ordem crescente. A contagem da variável é compartilhada por todas as instâncias.
Portanto, quando o construtor de um objeto a incrementa, o próximo objeto a ser criado vê o valor
incrementado.
Uma variável estática é semelhante, de alguma forma, a uma variável global contida em outras
linguagens. Java não contém variáveis globais desse tipo, mas uma variável estática é uma única
variável acessível a partir de qualquer instância da classe.
Se uma variável estática for marcada como pública, poderá ser acessada de fora da classe. Para
isso, não é necessário haver uma instância da classe. Você pode fazer referência a ela através do
nome da classe.
Como um método static pode ser chamado sem qualquer instância da classe à qual pertence, não
existe o valor this. Conseqüentemente, um método static não pode acessar variáveis a não ser
os próprios argumentos e variáveis static. A tentativa de acessar variáveis nãoestáticas causará
um erro de compilação.
A palavra-chave final
Classes finais
A linguagem Java permite que a palavra-chave final seja aplicada a classes. Se isso ocorrer, a
classe não poderá ser dividida em subclasses. A classe java.lang.String, por exemplo, é uma
classe final. Isso pode ser feito por razões de segurança, pois garante que, se um método
contiver uma referência a uma String, ela será definitivamente uma String, em vez de uma subclasse
que possa ter sido adicionada maliciosamente com um comportamento modificado.
Métodos finais
Os métodos individuais também podem ser marcados como final. Se forem marcados, não será
possível sobrescrever os mesmos. Mais uma vez, isso é feito por razões de segurança, de modo
que a pessoa que chamar um método possa saber que o comportamento resultante será o original e
adequado, e não um comportamento alternativo.
Os métodos declarados como final são eventualmente utilizados para otimização, já que o
compilador pode gerar um código que cause uma chamada direta ao método, em vez de utilizar a
chamada de método comum virtual que envolve uma pesquisa de tempo de execução para decidir
qual método chamar.
Variáveis finais
Se uma variável for marcada como final, ela se tornará uma constante. Qualquer tentativa de
alterar o valor de uma variável final causará um erro de compilação.
Observação - Se você marcar uma variável do tipo referência, que é de qualquer tipo de classe,
como final, essa variável não conseguira fazer referencia a qualquer outro objeto. No entanto, ainda
será possível alterar o conteúdo do objeto.
Classes abstratas
Às vezes, no desenvolvimento de bibliotecas, convém criar uma classe que represente um
comportamento fundamental e definir métodos para essa classe. Contudo, você não poderá
implementar o comportamento nela. Em vez disso, é sua intenção implementar os métodos em
subclasses.
Por exemplo, considere uma classe Drawing. A classe deve representar métodos referentes a
diversos recursos de desenho, mas eles devem ser implementados sem se considerar a plataforma.
Obviamente, não será possível acessar o hardware de vídeo de uma máquina e continuar a ser
independente de plataforma. A intenção é que a classe de desenho defina os métodos que devem
existir, mas subclasses especiais dependentes de plataforma realmente implementarão o
comportamento.
Uma classe como a Drawing, que declara a existência de métodos mas não a implementação, é
comumente denominada classe abstrata. Você pode declarar uma classe abstrata em Java,
marcando-a com a palavra-chave abstract. Cada método que não estiver sendo definido também
deve ser marcado como abstract.
Observe que uma classe abstract pode conter métodos não-abstratos e variáveis.
Você não pode construir uma instância de uma classe abstract, exceto indiretamente, construindo
uma instância de uma subclasse dela.
As subclasses da classe abstract devem fornecer implementações para todos os métodos abstratos
nas classes pai. Caso contrário, elas também serão classes abstratas.
A vantagem de uma interface é que ela pode ser utilizada para violar a regra de herança única
da linguagem Java. Embora uma definição de classe possa apenas herdar de uma única classe, ela
pode implementar todas as interfaces necessárias.
Como em classes abstract, é possível utilizar um nome interface como o tipo de uma variável de
referência. Conseqüentemente, a vinculação dinâmica comum ocorrerá. Pode haver cast de
referências para tipos de interface e a partir destes, e o operador instanceof pode ser utilizado
para determinar se um objeto implementa uma interface.
Uma variável ou um método terá acessibilidade padrão se não tiver um modificador explícito como
parte da declaração. Tal acessibilidade significa que o acesso é permitido a partir de qualquer
método em qualquer classe que seja um membro do mesmo package.
Uma variável de método marcada com o modificador protected é, na verdade, mais acessível que
um controle de acesso padrão. Um método ou uma variável protected é acessível a partir de
qualquer método em qualquer classe que seja um membro do mesmo pacote. Ele também será
acessível a partir de qualquer método em qualquer subclasse.
Deprecação
Entre o JDK versão 1.0 e o JDK versão 1.1, foi feito um grande esforço para se padronizar os nomes
dos métodos. Como resultado, um número significativo de construtores de classe e chamadas de
método tornaram-se obsoletos. Foram substituídos por nomes de métodos que seguem uma
abordagem mais orientada a objetos e, em geral, facilitam o trabalho do programador.
• setSize() e getSize()
• setBounds () e getBounds()
Atualmente no JDK 1.1.1, os métodos deprecados existem juntamente com novos métodos, mas
essa situação mudará futuramente. Sempre que você estiver movendo códigos do JDK 1.0 para o
JDK 1.1, 1.2 e 1.3 ou mesmo se você estiver utilizando os códigos que funcionavam anteriormente
com o JDK 1.0, convém compilar o código com o sinalizador -deprecation.
O indicador -deprecation relatará quaisquer métodos da classe utilizada que sejam deprecados.
Por exemplo, há uma classe de utilitário chamada DateConverter, que é utilizada para converter
uma data no formato mm/dd/yy no dia da semana:
1 package myutilities;
2 import java.util.*;
3 public final class DateConverter
4
5 private static String day_of_the_week = {"Domingo",
"Segunda", "Terça", "Quarta", "Quinta", "Sexta", "Sábado");
6
7 public static String getDayOfWeek (String theDate) {
8 int month, day, year;
9
10 StringTokenizer st =
11 new StringTokenizer (thedate, "/");
12 month = Integer.parseInt(st.nextToken ());
13 day = Integer.parseInt(st.nextTokeno);
14 year = Integer.parseInt(st.nextTokeno);
15 Date d = new Date (year, month, day);
16
17 return (day_of_the-week[d.getDay()]);
18 }
19 }
1 package myutilities;
2 import java.util.*;
3 public final class DateConverter {
4
5 private static String day__of-the week [] = {"Domingo", "Segunda",
"Terça", "Quarta", "Quinta","Sexta", "Sábado");
6
7 public static String getDayOfWeek (String thedate){
8 Date d = null;
9 SimpleDateFormat sdf =
10 new SimpleDateFormat ("MM/dd/yy");
11 try {
12 d = sdf.parse (theDate);
13 } catch (ParseException e) {
14 System.out.println (e);
15 e.printStackTrace();
16 }
17 //Criar um objeto GregorianCalendar
18 Calendar c =
19 new GregorianCalendar (TimeZone.getTimeZone("EST"), Locale.US);
20 c.settime (d);
21 return (day-of-the-week[(c.get(Calendar.DAY_OF_WEEK)-1)]);
22 }
23}
Nesse caso, a versão 1.1 utiliza duas novas classes: SimpleDateFormat, que é uma classe
utilizada para transformar qualquer formato de String em um objeto Date e a classe
GregorianCalendar, que é utilizada para criar um calendário com o fuso horário local e a
localidade.
Agora, o JDK 1.1 suporta classes que são membros de outras classes definidas localmente com um
bloco de instruções ou anonimamente em uma expressão. As classes internas possuem as
seguintes propriedades:
• O nome da classe só pode ser utilizado no escopo definido, exceto quando utilizado em um
nome qualificado. O nome da classe interna deve diferir da classe delimitadora.
• Ela também pode ser urna interface implementada por outra classe interna
• As classes internas não podem declarar membros como static. Esse procedimento só
pode ser efetuado por classes de nível superior. Para que uma classe interna utilize um
membro static, devera declará-lo na classe de nível superior.
As classes internas são melhor utilizadas como um recurso útil para a criação de adaptadores de
eventos ou em classes que compartilham dados. Será visto em eventos a recriação da classe
TwoListen, descrita no capítulo sobre Eventos, para incluir duas classes de manipuladores na
classe TwoListen.
1 import java.awt.*;
2 import java.awt.event.*;
3
4 public class TwoListenInner {
5 private Frame f;
6 private TextField tf;
7
8 public static void main(String args[]) {
9 TwoListen that = new TwoListen();
10 that.go();
11 }
12
13 public void go() {
14 f = new Frame("Exemplo de Dois Ouvidores");
15 f.add ("North", new Label ("Clique e arraste o mouse"));
16 tf = new TextField (30);
17 f.add ("South", tf);
18
19 f.addMouseMotionListener (new MouseMotionHandler());
20 f.addMouseListener (new MouseEventHandler()o);
21 f.setSize(300, 200);
22 f.setVisible(true);
23 }
24 MouseMotionHandler é uma classe interna
25 public class MouseMotionHandler extends MouseMotionAdapter {
26
27 public void mouseDragged (MouseEvent e) {
28 String s =
29 "Mouse arrastado: X = " + e.getX() + " Y = " + e. gety () ;
30 tf.settext (s);
31
32
33
34 //MouseEventHandler
35 public class MouseEventHandler extends MouseAdapter {
36
37 public void mouseEntered (MouseEvent e) {
38 String s = "O mouse entrou";
39 tf.settext (s);
40 }
Se o código acima for recriado com duas classes de nível superior adicionais para o
MouseMotionHandler e o MouseEventHandler, três classes separadas serão criadas e o
tamanho global do aplicativo será 2.864 bytes.
Classes anônimas
Também é possível incluir toda a definição de uma classe no escopo de uma expressão. Esse
procedimento define a classe anonymous e cria a instância de uma só vez. Por exemplo, é possível
recriar parte de TwoListenInner para utilizar uma classe anônima desta maneira:
A classe Vector
A classe Vector fornece métodos que permitem trabalhar com matrizes dinâmicas de variados tipos
de elementos.
java.lang.object
|
|
|
|
--|---------- java.util.Vector
Declaração
Cada vector mantém uma capacity e capacityIncrement. Como elementos são adicionados a
um vector, o armazenamento deste aumenta em partes do tamanho da variável
capacityIncrement, A capacidade de um vector é sempre, pelo menos, correspondente ao
tamanho do vector (normalmente maior).
Construtores
Variáveis
Métodos
Veja a seguir alguns dos métodos da classe vector. Consulte a API da linguagem Java para obter
uma descrição de todos os métodos dessa classe.
public final int size () - Retorna o número de elementos no vector. (Não é o mesmo que a
capacidade do vector.)
public final boolean contains (Object elem) - Retorna verdadeiro quando o objeto
especificado e um valor da coleção.
public final int indexOf (Object elem) - Procura o objeto especificado, a partir da
primeira posição, e retorna um índice para ele (ou -1 se o elemento não for encontrado). Utiliza o
método equals() do objeto, de modo que se esse objeto não sobrescrever o método equals() de
Object, apenas comparará referências a objetos e não o conteúdo destes.
Exemplo de Vector
O exemplo a seguir pode ser utilizado para adicionar diferentes tipos de elementos a um vector e
imprimir elementos de Vector.
Observação - O programa utiliza métodos das classes descritas anteriormente neste módulo.
1 import java.util.*;
2
3 public class MyVector extends Vector
4 public MyVector {
5 super(1,1); // capacidade de armazenamento e capacityIncrement
6 }
7
8 public void addInt(int i) {
9 addElement(new Integer(i)); // addelement necessita de Object
arg
10 }
11
12 public void addFloat(float f) {
13 addElement(new Float(f));
14 }
is
16 public void addString(String s) {
17 addElement(s);
18 }
19
20 public void addCharArray(char a[]) {
21 addElement (a);
22 }
23
24 public void printVector() {
25 Object o;
26 int length = size();
27
28 System.out.println("Número de elementos de vector é "
29 + length + “ e eles são:");
29 for (int i = 0; i < length; i++) {
30 o = elementAt(i);
31 if (o instanceof char[])
32 //System.out.println(o.toString()); imprime ref
33 System.out.println(String.copyValueof((char[]) o));
34
35 else
36 System.out.println(o.toString());
37 }
38 }
39 public static void main(String args[]) {
40 MyVector v = new MyVector();
41 int digit = 5;
42 float real = 3.14f;
43 char letters[] = {'a', 'b', 'c', 'd'};
44 String s = new String ("Olá!");
45
46 v.addInt(digit);
Objetivos
java.lang.Object BorderLayout
CardLayout
CheckBoxGroup
Color
Dimension
Event
Font
FlowLayout
FontMetrics
Graphics
GridBagLayout
GridLayout
Image
Insets
Point
Polygon
Rectangle
Toolkit MenuBar Menu
MenuComponent MenuItem CheckBoxMenuItem
Component
Button
Canvas
CheckBox Applet
Choice Panel
Container Window
Label Dialog
List Frame
Scrollbar
TextComponent TextArea
Textfield
Para que esses componentes sejam exibidos, devem ser "adicionados" a um container. Em geral, um
recipiente pode conter um ou mais componentes a se, for o caso, pode conter outros recipientes.
Observação - O fato de um recipiente poder armazenar não apenas componentes, mas também
recipientes é extremamente importante para a construção de layouts de complexidade realística.
Dimensionar componentes
Se você tiver de controlar o tamanho ou a posição de componentes de outra maneira que não seja
utilizando os gerenciadores de layout padrão, será possível desativar esse gerenciador, chamando
este método no containers :
setLayout(null);
Após essa etapa, você deve utilizar o método setLocation(), setSize() ou setBounds() nos
componentes para localizá-los no container.
Saiba que esse procedimento resulta em layouts que dependem de plataforma, devido as diferenças
entre os sistemas de janelas a os tamanhos de fonte. Um melhor procedimento consiste em criar
uma nova classe de LayoutManager.
Frames (moldura)
Frame é uma subclasse de Window. É uma Window com um titulo e cantos de redimensionamento.
O construtor Frame (String) da classe Frame cria um novo objeto Frame invisível com o titulo
especificado por String. Um Frame pode ser redimensionado através do método setSize ()
herdado da classe Component.
Observação - Os métodos setVisible() e setSize() devem ser chamados para que Frame se
torne visível.
O programa abaixo cria uma moldura simples com um título, tamanho e cor de fundo específicos:
1 import java.awt.*;
2 public class MyFrame extends Frame f {
3 public static void main (String args[]) {
4 MyFrame fr = new MyFrame("Ola ai fora!");
5 // método de componente setSize()
6 fr.setSize(500,500);
7 fr.setBackground(Color.blue);
8 fr.setVisible(true); // método de componente show ()
9 }
10 public MyFrame (String titulo) {
11 super(titulo);
12 }
13 ...
Executar o programa
Panels (painéis)
Os painéis, como as molduras, fornecem o espaço para que você anexe qualquer componente da
GUI, incluindo outros painéis.
Criar painéis
Os painéis são criados através do construtor Panel (). Uma vez criado um objeto Panel, ele
deverá ser adicionado a um objeto Window ou Frame para que fique visível. Isso é feito através do
método add () da classe Container.
1 import java.awt.*;
2 public class FrameWithPanel extends Frame {
3
4 // Construtor
5 public FrameWithPanel (String str) {
6 super (str);
7 }
8
9 public static void main (String args[]) {
10 FrameWithPanel fr =
11 new FrameWithPanel ("Moldura com Painel");
12 Panel pan = new Panel();
13
14 fr.setSize(200,200);
15 fr.setBackground(Color.blue);
16 fr.setLayout(null); //passa por cima do gerenciador de layout
padrao
17 pan.setSize (100,100);
18 pan.setBackground(Color.yellow);
19
20 fr.add(pan);
21 fr.setVisible(true);
22 }
Resultado:
Layouts de containers
O layout de componentes em um container pode ser controlado por um gerenciador de layout. Cada
container, como um painel ou uma moldura, tem um gerenciador de layout padrão associado, que
pode ser alterado pelo desenvolvedor de Java ao criar uma instância desse container.
Gerenciadores de layout
Um exemplo simples
Este exemplo simples de código demonstra vários pontos importantes, que serão abordados nas
próximas seções:
import java.awt.*;
O método main ( )
O método main () desse exemplo tem duas funções. A primeira a criar uma instância do objeto
ExGui. Lembre-se de que até que uma instância seja criada, não haverá itens de dados reais
chamados f, bl e b2 para serem utilizados.
Esse procedimento cria uma instância da classe java.awt.Frame. Um Frame na linguagem Java
é uma janela de nível superior que contém uma barra de título, nesse caso definida pelo argumento
de construtor "Exemplo de GUI", além de uma borda redimensionada e outros acessórios, de acordo
com as convenções locais. O tamanho da moldura equivale a zero a não esta visível no momento.
f.setLayout(new FlowLayout())
Isso cria uma instância do gerenciador de layout de fluxo e a instala na moldura. Há um gerenciador
de layout padrão para cada moldura, mas ele não se aplica à finalidade desse exemplo. O
gerenciador de layout de fluxo é o mais simples do AWT a posiciona os componentes como se
fossem palavras em uma página, linha por linha. Observe que, por padrao, o layout de fluxo
centraliza cada linha.
Isso cria uma instância da classe java.awt.Button. Um botão é o padrão extraído do kit de
ferramentas do sistemas de janelas local. O rótulo do botão é definido pelo argumento da string para
o construtor.
f.add(b1)
Isso informa à moldura f (que é um container) que ela deve conter o componente b1. Os botões são
exemplos de componentes da linguagem Java. O tamanho e a posição de b1 estão sob o controle
do gerenciador de layout da moldura, deste ponto em diante.
f.pack()
Este método informa à moldura para definir um tamanho que "acomode adequadamente" os
componentes que ela contém. Para tomar essa decisão, a moldura chama o gerenciador de layout,
que é o responsável pelo tamanho a posição de todos os elementos contidos nela.
f.setVisible(true)
Este método faz com que a moldura e todos os componentes dela se tornem visíveis ao usuário.
Gerenciadores de layout
Gerenciador de layout de fluxo
O layout de fluxo utilizado no primeiro exemplo posiciona os componentes linha por linha. Toda vez
que uma linha é preenchida, uma nova linha é iniciada.
Ao contrário de outros gerenciadores de layout, o layout de fluxo não limita o tamanho dos
componentes gerenciados, mas, em vez disso, permite que eles tenham o tamanho preferencial.
Você poderá especificar inserções se quiser criar uma área de borda maior entre cada componente.
Quando a área que esta sendo gerenciada por um layout de fluxo é redimensionada, pode ser que o
layout seja alterado.
Após o
redimensionamento
setLayout(new FlowLayout(FlowLayout.RIGHT,20,40));
setLayout(new FlowLayout(FlowLayout.LEFT));
setLayout(new FlowLayout());
1 import java.awt.*;
2
3 public class MyFlow f
4 private Frame f;
5 private Button buttonl, button2, button3;
6
Linguagem de Programação Java 77
7 public static void main (String args[]) {
8 MyFlow mflow = new MyFlow ();
9 mflow.go();
10 }
11
12 public void go() {
13 f = new Frame (" Layout de Fluxo");
14 f.setLayout(new FlowLayout());
15 button1 = new Button("Ok");
16 button2 = new Button("Abrir");
17 button3 = new Button("Fechar");
18 f.add(buttonl);
19 f.add(button2);
20 f.add(button3);
21 f.setSize (100,100);
22 f.setVisible(true);
23 }
24 }
A área North ocupa a parte superior de um painel, a East ocupa o lado direito a assim por diante.
A área Center representa todo o restante, uma vez preenchidas as áreas North, South, East
e West.
Após o redimensionamento
Observação – Quando a janela é redimensionada, os tamanhos dos botões são alterados, mas não
suas posições relativas.
1 import java.awt.*;
2
3 public class ExGui2 {
4 private Frame f;
5 private Button bn, bs, bw, be, bc;
6
7 public static void main(String args[]) {
8 ExGui2 that = new ExGui2();
10 )
11
12 public ExGui2() {
13 f = new Frame("Layout de Borda ");
14 bn = new Button("B1");
15 bs = new Button("B2");
16 bw = new Button("B3");
17 be = new Button("B4");
18 be = new Button("B5");
19
20 f.add(bn, "North");
21 f.add(bs, "South");
22 f.add(bw, "West");
23 f.add(be, "East");
24 f.add(bc, "Center");
25
26 f.setSize (200, 200);
27 f.setVisible(true);
28 }
29 }
Os componentes devem ser adicionados a regiões com nome no gerenciador de layout de borda;
caso contrário, não ficarão visíveis. Tome cuidado para digitar corretamente os nomes de regiões,
pois a ortografia e a utilização de maiúsculas são cruciais.
Você pode utilizar urn gerenciador de layout de borda para produzir layouts com elementos que se
expandam em uma direção ou em outra ou em ambas as direções, mediante redimensionamento.
Se você não utilizar uma região de um layout de borda, ela se comportará como se o tamanho
preferencial fosse zero por zero. Assim, a região central ainda aparecerá como fundo, mesmo que
ela não contenha componentes, mas as quatro regiões periféricas serão reduzidas a uma linha de
espessura zero e desaparecerão.
Você só deve adicionar um único componente a cada uma das cinco regiões do gerenciador de
layout de borda. Se você tentar adicionar mais de um, apenas um ficará visível. Voce verá, mais
adiante, como é possível utilizar recipientes intermediários para permitir que mais de um
componente seja incluído no espaço de uma única região do gerenciador de layout de borda.
O gerenciador grid layout fornece flexibilidade para o posicionamento de componentes. Você cria o
gerenciador com um número determinado de linhas a colunas. Em seguida, os componentes
preenchem as células definidas pelo gerenciador. Por exemplo, um layout degrade com tres linhas e
duas colunas criadas pela declaração new GridLayout(3,2) criaria seis células, como mostrado
abaixo.
Após o
redimensionamento
Observe que a largura de todas as células é idêntica e é determinada como uma divisão simples da
largura disponível pelo número de células. Da mesma forma, a altura de todas as células é
simplesmente determinada pela altura disponível dividida pelo número de linhas.
A ordem em que os componentes são adicionados à grade determina a célula que eles ocupam. As
linhas de células são preenchidas da esquerda para a direita como texto, e a "página" é preenchida
com linhas de cima para baixo.
1 import java.awt.*;
2 public class GridEx {
3 private Frame f;
4 private Button bl, b2, b3, b4, b5, b6;
5
6 public static void main(String args[]) {
7 GridEx grid = new GridEx();
8 grid.go();
9 }
10
11 public void go() {
12 f = new Frame("Exemplo de Grade");
13
14 f.setLayout (new GridLayout (3, 2));
15 b1 = new Button("1");
16 b2 = new Button("2");
17 b3 = new Button("3");
18 b4 = new Button("4");
19 b5 = new Button("5");
20 b6 = new Button("6");
21
22 f.add(bl);
23 f.add(b2);
24 f.add(b3);
CardLayout
O Gerenciador CardLayout permite tratar a interface como uma série de cartões, sendo que é
possível visualizar um de cada vez. Veja a seguir um exemplo de um Frame com cinco painéis. Se
você clicar no Panel do Frame, mudará para o próximo cartão.
Se você clicar neste painel, mudará para este outro e assim por diante.
1 import java.awt.*;
2 import java.awt.event.*;
3
4 public class CardTest implements MouseListener {
5
6 Panel p1 p2, p3, p4, p5;
7 Label 11, 12, 13, 14, 15;
8
9 // Declara um objeto CardLayout,
10 // de modo que seja possível chamar os respectivos métodos
11 CardLayout mycard;
12 Frame f;
13
14 public static void main (String args[]){
15 CardTest ct = new CardTest ();
16 ct.init();
17 }
18
19 public void init {
20 f = new Frame (" Teste de Cartão");
21 mycard = new CardLayout();
Além dos gerenciadores de layout de fluxo, borda, grade e cartão, o núcleo awt de java também
fornece o GridBagLayout.
O gerenciador de layout de camadas de grades fornece recursos complexos de layout com base em
uma grade, mas permite que componentes individuais assumam o tamanho preferencial em uma
célula, em vez de preencher a célula inteira. Além disso, um layout de camadas de grades permite
que um um único componente ocupe mais de uma célula.
Molduras (Frames)
Você já viu o frame utilizado nos exemplos anteriores. Ele apresenta uma janela de "nível superior"
com um título, uma borda e cantos redimensionáveis, de acordo com as convenções locais de
plataforma.
Se você não utilizar explicitamente o método setLayout() , um frame terá, por padrão, um
gerenciador de layout de borda.
A maioria dos aplicativos utilizara, pelo menos, um frame como o ponto inicial das respectivas
interfaces gráficas com o usuário (GUIs), mas é possível utilizar vários frames em um único código.
Painéis (Panels)
Os painéis são containers e praticamente não têm outra função. Eles não possuem uma aparência
própria e não podem ser utilizados como janelas autônomas. Por padrão, existe um gerenciador de
layout de fluxo associado a cada painel, mas você pode mudar isso através do método
setLayout() utilizado anteriormente em molduras.
Os painéis são criados e adicionados a outros containers da mesma maneira que outros
componentes como, por exemplo, botões. No entanto, quando um painel é adicionado a um
recipiente, há dois procedimentos cruciais que você pode executar no painel resultante. São eles:
Os painéis são criados através do construtor Panel(). Uma vez criado um objeto Panel, ele deverá
ser adicionado a outro recipiente. Para isso, basta utilizar o método add() do container.
O programa abaixo utiliza um painel para permitir que dois botões sejam posicionados na região
North de um layout de borda. Esse tipo de aninhamento é fundamental para layouts complexos.
Observe que o painel é tratado tal como outro componente, no que diz respeito à moldura.
1 import java.awt.*;
2 public class ExGui3
3 private Frame f;
4 private Panel p;
5 private Button bw, bc;
6 private Button bfile, bhelp;
7
8 public static void main(String args[]) {
9 ExGui3 gui = new ExGui3();
10 gui.go();
11 }
12 public void go() {
13 f = newFrame("Exemplo de GUI 3");
14 bw = new Button("Oeste");
Observe que a região North do layout de borda agora contém efetivamente dois botões. Na verdade,
essa região apenas acomoda o painel, pois é este que contém os dois botões.
O tamanho e a posição do painel é determinada pelo layout de borda, ao passo que o tamanho
preferencial de um painel é determinado a partir do tamanho preferencial dos componentes desse
painel. O tamanho e a posição dos botões do painel são controlados pelo layout de fluxo que, por
padrão, está associado ao painel.
Objetivos
Origens de eventos
Uma origem de evento (em nível da interface com o usuário) é o resultado de alguma ação do
usuário em um componente AWT. Por exemplo, um clique do mouse em um componente de botão
gerará (origem) um ActionEvent. O ActionEvent é um objeto (classe) que contém informações
sobre o status do evento:
Manipuladores de eventos
Quando ocorre um evento, ele é recebido pelo componente com o qual o usuário interagiu; por
exemplo, o botão, a barra deslizante, o campo de texto etc.. Um manipulador de eventos é um
método que recebe um objeto Event para que o programa possa processar a interação com o
usuário.
Entre o JDK 1.0 e o JDK 1.1, houve mudanças significativas com relação à maneira como os
eventos são recebidos e processados. Nesta seção, compararemos o modelo de evento anterior
(JDK 1.0) e o modelo de evento atual JDK 1.1 e posteriores.
Moldura
Painel
• Para tratá-los, você deve dividir o componente em subclasses que recebam o evento ou criar
um método handleEvent extenso no container básico.
O JDK 1.1 introduziu um novo modelo de evento, chamado modelo de evento de delegação. No
modelo de evento de delegação, os eventos são enviados ao componente, mas cabe a cada
componente registrar uma rotina de manipulador de eventos (chamada de listener) para receber o
evento. Dessa maneira, o manipulador de eventos pode se encontrar em uma classe separada do
componente. Em seguida, o tratamento do evento é delegado à classe separada.
ActionPerformed (ActionEvent e) {
…
}
Os eventos são objetos que só são relatados a listeners registrados. Cada evento tem uma classe
receptora correspondente (interface). A classe que implementa a interface receptora define os
métodos que podem receber um objeto Event.
import java.awt.*;
import ButtonHandler;
public class TestButton {
public static void main(String args[]) {
Frame f = new Frame("Teste");
import java.awt.event.*;
public class ButtonHandler implements ActionListener {
public void actionPerformed(ActionEvent e) {
System.out.println("Ação ocorreu");
}
}
• Quando um objeto Button é criado, ele pode registrar um receptor para o ActionEvents
através do método addActionListener, especificando o objeto de classe que implementa a
interface ActionListener.
Vale a pena considerar algumas questões/ desvantagens com relação a esse modelo:
• Embora o JDK atual suporte o modelo de evento do JDK 1.0, além do modelo de delegação, os
modelos de delegação do JDK 1.0 e JDK 1.1 não podem ser combinados.
O mecanismo geral para receber eventos de componentes foi descrito no contexto de um único tipo
de evento. Diversos eventos são definidos no pacote java.awt.event e componentes de terceiros
podem ser adicionados a essa lista.
Para cada categoria de eventos, há uma interface que deve ser definida por qualquer classe que
deseje receber os eventos. Essa interface também exige que um ou mais métodos sejam definidos.
Esses métodos serão chamados quando surgirem eventos específicos. A tabela relaciona as
categorias, atribuindo o nome da interface às categorias e aos métodos necessários. Os nomes dos
métodos são mnemônicos indicando as condições que farão com que o método seja chamado.
Agora, vamos considerar um exemplo mais complexo. Ele controlará o movimento do mouse quando
o botão for pressionado (arrasto do mouse) e, além disso, detectará esse movimento.
Os eventos causados pelo movimento do mouse com um botão pressionado poderão ser detectados
por uma classe que implementa a interface mouseMotionListener. Essa interface necessita de
dois métodos: mouseDragged() e mouseMoved(). Estamos apenas interessados no movimento
de arrasto, mas, de qualquer forma, fornecemos ambos os métodos. No entanto, o corpo do método
mouseMoved() pode estar vazio.
Quando ocorrem eventos de arrasto do mouse ou de tecla, relatamos nos campos de rótulo as
informações sobre a posição do mouse e a tecla pressionada. Este é o código:
1 import java.awt.*;
2 import java.awt.event.*;
3
4 public class TwoListen implements
5 MouseMotionListener, MouseListener {
6 private Frame f;
7 private TextField tf;
8
9 public static void main(String args[]){
10 TwoListen two = new TwoListeno;
11 two.go () ;
12 }
13 public void go() {
14 f = new Frame("Exemplo de dois ouvidores");
15 f.add (new Label ("Clique e arraste o mouse”),
16 "North");
17 tf = new TextField (30);
18 f.add (tf, "South");
19
20 f.addMouseMotionListener(this);
21 f.addMouseListener (this);
22 f.setSize(300, 200);
23 f.setVisible(true);
24 //f.requestFocus();
25 }
26
27 // Estes são eventos MouseMotionListener
28 public void mouseDragged (MouseEvent e)
29 String s =
30 "Mouse arrastado: X = " + e.getX() +
31 " Y = " + e.getY();
32 tf.setText (s);
33 }
34
35 public void mouseMoved (MouseEvent e) {
36 }
37
38 // Estes são eventos MouseListener
39 public void mouseClicked (MouseEvent e) {
Observe que você pode declarar várias interfaces simplesmente utilizando a separação por vírgulas.
f.addMouseListener(this);
f.addMouseMotionListener(this);
Os dois tipos de evento fazem com que os métodos sejam chamados na classe TwoListen. Você
pode receber todas as origens de evento desejadas, pertencentes a várias categorias e origens,
como diferentes molduras ou diferentes botões.
Quando os métodos de manipulação, como o mouseDragged (), são chamados, eles recebem um
argumento que contém informações potencialmente importantes sobre o evento original. Para
determinar os detalhes de quais informações estão disponíveis para cada categoria de evento, você
deve verificar a documentação de classe apropriada no pacote java.awt.event.
A estrutura de escuta de eventos do AWT permite que vários listeners sejam anexados ao mesmo
componente. Em geral, para criar um programa que execute várias ações com base em um único
evento, você provavelmente deve codificar esse comportamento no método do manipulador. No
entanto, as vezes, o projeto de um programa necessita de várias partes não relacionadas do mesmo
programa para reagir ao mesmo evento. Isso poderá acontecer, se, por exemplo, um sistema de
ajuda contextual estiver sendo adicionado a um programa existente.
Adaptadores de eventos
Obviamente, dá muito trabalho implementar todos os métodos em cada uma das interfaces Listener,
especialmente a MouseListener e a ComponentListener.
• mouseClicked (MouseEvent)
• mouseEntered (MouseEvent)
• mouseExited (MouseEvent)
• mousePressed (MouseEvent)
• mouseReleased (MouseEvent)
Para facilitar o trabalho, a Java fornece uma classe de adaptador para cada interface Listener que
implementa a interface Listener adequada, mas deixa os métodos implementados vazios.
Dessa forma, a rotina Listener definida pode ampliar a classe Adapter e sobrescrever apenas os
métodos necessários.
Por exemplo:
import java.awt.*;
import java.awt.event.*;
Objetivos
Primeiro, os componentes são descritos. Eles são utilizados para construir interfaces com o usuário.
É óbvio que você precisa pelo menos conhecer o conjunto completo de componentes de UI para que
possa escolher os adequados quando for construir as interfaces.
Além disso, o AWT suporta impressão. Esse é um recurso que foi adicionado com a transição para o
JDK 1.1.
Button
Você já conhece o componente Button. Ele fornece um componente básico de interface com o
usuário do tipo "pressione para ativar". Pode ser construído com um rótulo de texto que funcione
como um guia para o usuário, com relação à sua utilização.
A interface ActionListener deve ser utilizada para reagir quando botões são pressionados. O
método getActionCommand () do ActionEvent que é emitido quando o botão é pressionado é,
por padrão, a seqüência do rótulo. Isso pode ser modificado através do método
setActionCommand () do botão.
Checkbox
A caixa de seleção fornece um dispositivo simples de entrada "ativada/ desativada", com um rótulo
de texto ao lado.
CheckboxGroup
Você pode criar caixas de verificação por meio de um construtor especial que utiliza um argumento
adicional: um CheckboxGroup. Se você fizer isso, a aparência das caixas de seleção será alterada
e todas as caixas de seleção relacionadas ao mesmo grupo exibirão o comportamento de "botão de
opção".
Choice
Escolha fornece um tipo de entrada simples "selecione um item na lista".
Quando você clica na opção, ela exibe a lista de itens que foram adicionados a ela. Observe que os
itens adicionados são objetos String.
Canvas
Um objeto Canvas fornece um espaço em branco (com segundo plano colorido). Ele possui um
tamanho preferencial de zero por zero. Por isso, você geralmente deve se certificar de que o
gerenciador de layout atribuirá a ele um valor diferente de zero.
O espaço pode ser utilizado para desenhar ou digitar texto ou receber uma entrada de teclado ou
mouse.
Em geral, o Canvas é utilizado para fornecer um espaço geral de desenho ou é utilizado como a
base de desenvolvimento que fornecerá uma área de trabalho a um componente personalizado.
O Canvas pode ouvir todos os eventos que sejam aplicáveis a um componente geral. De forma
específica, convém adicionar os objetos KeyListener, MouseMotionListener ou
mouseListener a ele para permitir que responda, de alguma maneira, a entrada do usuário. Os
métodos contidos nessas interfaces recebem os objetos KeyEvent e MouseEvent,
respectivamente.
1 import java.awt.*;
2 import java.awt.event.*;
3 import java.util.*;
4
Labels normalmente não tratam eventos, mas fazem isso, de alguma forma, como um c anvas. Isto
é, as teclas pressionadas só podem ser detectadas confiavelmente através do método
requestFocus ().
TextField
O TextField é um dispositivo de texto de uma única linha.
Como apenas uma linha é possível, um ActionListener pode ser informado através do método
actionperformed (), quando a tecla Enter ou Return é pressionada. Outros listeners de
componentes poderão ser adicionados, se desejado.
Como a área de texto, eles podem ser definidos como somente leitura. Não exibem barras de
rolagem em qualquer das direções, mas podem percorrer um texto longo da esquerda para a direita,
se necessário.
É possível adicionar ouvidores gerais de componentes à área de texto, mas como o texto é
constituído de várias linhas, se você pressionar a tecla "Enter", apenas fará com que outro caractere
seja inserido no buffer. Se você precisar reconhecer a "conclusão da entrada", poderá inserir um
botão que indique "Aplicar" ou "Gravar", próximo a uma área de texto, para permitir que o usuário
indique isso.
TextComponent
A área de texto e o campo de texto são documentados em duas partes. Se você pesquisar uma
classe chamada TextComponent, encontrará vários métodos essenciais documentados nesse
local. Isso ocorre porque a área e o campo de texto são subclasses do componente de texto.
Você já viu que os construtores das classes TextArea e TextField permitem especificar várias
colunas para exibição. Lembre-se de que o tamanho de um componente exibido é responsabilidade
de um gerenciador de layout. Portanto, essas preferências podem ser ignoradas. Além disso, o
número de colunas e interpretado em termos da largura média dos caracteres contidos na fonte que
está sendo utilizada. O número de caracteres que é realmente exibido poderá variar radicalmente se
uma fonte com espaçamento proporcional for utilizada.
O argumento numérico para o construtor define a altura preferencial da lista em termos do número
de linhas visíveis. Como sempre, lembre-se de que isso pode ser sobrescrito por um gerenciador de
layout. O argumento booleano indica se a lista deve permitir que o usuário faca várias seleções.
Um ActionEvent detectado através da interface ActionListener é gerado pela lista, nos modos
de seleção única e múltipla. Os itens são selecionados na lista de acordo com as convenções de
plataforma. Em uma ambiente UNIX/Motif, isso significa que um único clique destaca uma entrada
na lista, mas um clique duplo é necessário para acionar essa lista.
Frame
Essa é a janela de "nível superior" de finalidade geral. Contém acessórios do gerenciador de janelas,
como uma barra de título e alças de redimensionamento.
O tamanho de um moldura pode ser definido através do método setsize (). No entanto, ele
geralmente deve ser definido através do método pack (). Isso faz com que o gerenciador de layout
Os eventos do frame podem ser monitorados por meio de todos os receptores aplicáveis aos
componentes gerais. O WindowListener pode ser utilizado para reconhecer, através do método
windowClosing(), que o botão Quit do menu Window Manager foi selecionado.
Você não deve tentar ouvir os eventos de teclado diretamente de um frame. Embora a técnica
descrita para os com ponentes de tela e de rótulo (isto é, chamar requestFocus() funcione
eventualmente, ela não é confiável. Se você precisar acompanhar eventos de teclado, deverá
adicionar uma tela ou um painel à moldura e, por fim, adicionar o listener a esta para desempenhar
essa função.
Panel
Esse é o recipiente de finalidade geral. Não pode ser utilizado isoladamente, ao contrário de
molduras, janelas e caixas de diálogo.
Como um objeto Panel não é exibido, não há uma figura para mostrá-lo.
Panels podem tratar eventos, com a advertência de que o foco no teclado deve ser solicitado
explicitamente, como no exemplo anterior sobre a tela.
Caixa de diálogo
Um objeto Dialog é semelhante a um Frame, no que diz respeito a ser uma janela autônoma com
alguns acessórios. Ele difere de uma moldura, pois menos acessórios são fornecidos, e você pode
solicitar urna caixa de diálogo "modal", que permitira o armazenamento de todas as formas de
entrada até que ela seja fechada.
Dialogs normalmente não ficam visíveis ao usuário da primeira vez que são criadas. Em vez disso,
normalmente "aparecem" em resposta a alguma outra ação exercida na interface com o usuário; por
exemplo, quando você pressiona um botão.
Para ocultar um objeto Dialog, você deve chamar setVisible(false) nele. Em geral, para isso,
basta adicionar um WindowListener a ele e esperar que o método windowClosing() seja
chamado nesse receptor. Essa ação é semelhante ao fechamento de uma moldura.
FileDialog
Essa é uma implementação de um dispositivo de seleção de arquivos. Ele possui uma janela
autônoma com acessórios e permite que o usuário percorra o sistema de arquivos para selecionar
um arquivo específico a ser utilizado em outras operações.
Em geral, não é necessário tratar eventos no FileDialog. O setVisible (true) chama blocos
até que o usuário selecione OK. Nesse ponto, você apenas solicita o nome do arquivo selecionado.
Isso é retornado como String.
Painel de rolagem
Fornece um recipiente geral que não pode ser utilizado de forma autônoma. Além disso, fornece
uma porta de visualização para uma região mais ampla e barras de rolagem para manipular essa
porta.
Em geral, você não trata eventos em um ScrollPane. Em vez disso, deve fazê-lo nos
componentes contidos nele.
Menus
Os menus diferem dos outros componentes em um ponto crucial. Em geral, não é possível adicionar
menus a recipientes comuns e fazer com que sejam posicionados pelo gerenciador de layout. Você
só pode adicionar menus a elementos específicos, denominados recipientes de menu.
Normalmente, você só pode iniciar uma "árvore" de menus inserindo uma barra de menus em uma
moldura, através do método setMenuBar(). Nesse ponto, é possível adicionar menus à barra de
menus e também adicionar menus ou itens a esses menus.
A exceção é que um menu instantâneo pode ser adicionado a qualquer componente mas,
obviamente, não haverá nenhum layout nesse caso.
O menu de ajuda
Um recurso específico da barra de menus é que você pode designar um menu para ser o menu de
ajuda. Isso é feito através do método setHelpMenu(Menu).O menu a ser tratado como o menu de
ajuda deve ser adicionado à barra de menus. Em seguida, ele será tratado da maneira adequada
para constituir um menu de ajuda na plataforma local. Em sistemas X/Motif esse procedimento
envolve ajustar a entrada do menu à extremidade direita da barra de menus.
Barra de menu
É o menu horizontal. Só pode ser adicionado a um objeto Frame e forma a raiz de todas as árvores
de menus.
Menu
A classe Menu fornece um menu subseqüente básico, que pode ser adicionado a um MenuBar ou a
outro Menu.
Observação - Os menus mostrados aqui estão vazios, daí a aparência do menu Arquivo.
Item de menu
Menu instantâneo
Fornece um menu autônomo que pode ser ativado em qualquer componente. Você pode adicionar
itens de menu ou menus a um menu instantâneo.
Observação - O PopupMenu deve ser adicionado a um componente "pai". Isso não equivale a
adicionar componentes comuns a recipientes. Neste exemplo, o pop-up foi adicionado à moldura
delimitadora
Para que o PopupMenu seja exibido, você deve chamar o método de exibição. A exibição exige uma
referência a um componente para funcionar como a origem das coordenadas x e y. Normalmente,
você utiliza o componente de acionamento para isso. Nesse caso, esse componente é o botão b.
Cores
• setForeground()
• setBackground()
Esses dois métodos utilizam um argumento que constitui uma instância da classe
java.awt.Color. Você pode utilizar as cores constantes, que são chamadas Color.red
Color.blue etc. O conjunto completo de cores predefinidas está relacionado na página de
documentação referente à classe Color.
Além disso, você pode criar uma cor específica como esta:
Esse tipo de construtor criar uma cor com base nas intensidades especificadas (em uma faixa de 0
até 255 para cada) de vermelho, verde e azul.
Fontes
A fonte utilizada para a exibição de texto em um componente pode ser especificada através do
método setFont(). O argumento referente a esse método deve ser uma instância da classe
java.awt.Font.
Nenhuma constante é definida para fontes, mas você pode criar uma fonte especificando o nome
dela, o estilo e o tamanho em pontos.
Uma lista completa pode ser determinada chamando-se o método getFontList() no objeto
Toolkit. O kit de ferramentas pode ser obtido através do componente, após sua exibição, por meio
do método getToolkit(). Como alternativa, utilize o kit de ferramentas padrão, que pode ser
localizado através de Tookit.getDefaultToolkit().
• Font.BOLD
• Font.ITALIC
• Font.PLAIN
• Font.BOLD + Font.ITALIC
Impressão
A impressão é tratada no Java 1.1 ou superior de uma maneira bastante semelhante à exibição em
tela. Um tipo especial de objeto j ava. awt. Graphics é obtido para que qualquer instrução de
desenho enviada a esses elementos gráficos seja realmente destinada à impressora.
O sistema de impressão do Java 1.1 permite utilizar convenções de controle de impressora local
para que seja exibida uma caixa de diálogo ao usuário quando uma operação de impressão for
iniciada. 0 usuário poderá, então, escolher opções como o tamanho do papel, a qualidade de
impressão e a impressora a ser utilizada.
Essas linhas criam um objeto Graphics que é "conectado" à impressora escolhida pelo usuário.
Obtenha um novo elemento gráfico para cada página.
f.printAll(g);
Você pode utilizar qualquer um dos métodos de desenho da classe de elemento gráfico para enviar
dados à impressora. O exemplo mostra que, como alternativa, você pode simplesmente solicitar a
um componente para que desenhe a própria imagem no elemento gráfico. O método print ()
solicita a um componente para que ele faça o desenho dessa maneira, mas ele só se relaciona com
o componente para o qual foi chamado. No caso de um recipiente, como o deste exemplo, você
pode utilizar o método printAll () para fazer com que o recipiente e todos os componentes nele
contidos sejam desenhados para a impressora.
g.dispose();
job.end();
Após criar a página de saída necessária, utilize o método dispose() para que essa página seja
submetida à impressora.
Após concluir o trabalho, chame o método end () no objeto de trabalho de impressão. Isso indica
que o trabalho de impressão foi concluído e permite que o sistema de spool da impressora execute
efetivamente o trabalho e, em seguida, libere a impressora para outros trabalhos.
Objetivos
Carregar um applet
Como o applet é executado em um navegador da Web, ele não é iniciado diretamente através de um
comando. Você deve criar um arquivo HTML que informe ao navegador o que deve ser carregado e
como deve ser executado. Em seguida, você "aponta" o navegador para a URL que especifica o
arquivo HTML.
Como os applets constituem códigos que são carregados via rede, eles representam uma
possibilidade inerentemente perigosa. O que aconteceria se alguém criasse um programa malicioso
que lesse seu arquivo de senha e o enviasse através da Internet?
Para impedir que isso ocorra, a linguagem Java fornece uma classe SecurityManager que controla o
acesso a quase toda chamada em nível de sistema na Máquina Virtual Java (JVM)> Esse modelo é
chamado modelo de segurança "sandbox". A JVM fornece uma sandbox que permite a execução de
applets. No entanto, se eles tentarem sair da sandbox, esta os impedirá.
1 //
2 // Exemplo do applet Olá mundo
3 //
4 import java.awt.Graphics;
5 import java.applet.Applet;
6
7 public class HelloWorld extends Applet {
8
9 String hw_text;
10
11 public void init () {
12 hw_text = "Olá mundo”;
13 }
14
15 public void paint(Graphics g){
16 g.drawSLring(hw_text, 25, 25);
Desenvolver um applet
import java.applet.*;
public class HelloWorld extends Applet {
Para se tornar um applet, a classe deve ser pública e o nome do arquivo deve corresponder ao
nome da classe do applet, nesse caso, Helloworld.java. Além disso, essa classe deve ser uma
subclasse da classe java.applet.Applet.
java.lang.object
java.awt.Component
Java.awt.Container
Java.awt.Button
Java.awt.Frame Java.awt.Panel
Java.awt.TextArea
Java.awt.Applet
Essa hierarquia mostra que um applet pode ser utilizado diretamente como o ponto inicial de um
layout de AWT. Como o applet é um painel, ele possui, por padrão, um gerenciador de layout de
fluxo.
Os métodos init e start são totalmente executados antes de o applet se tornar "ativo" e, por
isso, não podem ser utilizados para programar o comportamento atual em um applet. Na verdade,
ao contrário do método main() em um aplicativo simples, não existe um método que seja
executado continuamente durante o cicio de vida do applet. Você vera, mais adiante, como
conseguir isso utilizando threads, mas, por enquanto, não é possível fazer isso.
Linguagem de Programação Java 113
Criar um applet
Exibição do applet
Os applets são essencialmente gráficos. Por isso, embora você possa emitir chamadas
System.out.println(), você não irá fazê-lo normalmente. Em vez disso, você criará a exibição
em um ambiente gráfico.
Você pode desenhar em uma applet, criando um método paint(). O método paint() é chamado
pelo ambiente de navegação sempre que for necessário atualizar a exibição do applet. Isso
acontece, por exemplo, quando a janela do navegador é ativada após ter sido minimizada.
Você deve criar o método paint() de modo que ele funcione corretamente sempre que for
chamado. Isso porque sua exposição é controlada pelo ambiente e não pelo programa, além de
ocorrer de forma assíncrona.
import java.awt.*;
import java.applet.*;
O ciclo de vida do applet é um pouco mais complexo do que já vimos até agora. Há três métodos
principais que se relacionam a esse ciclo: init(), start() e stop().
init()
Essa função membro é chamada quando o applet é criado e carregado em um navegador Java
(como o AppletViewer). O applet pode utilizar esse método para inicializar valores de dados. Esse
método só é chamado na primeira vez que o navegador abre a página que contém o applet.
start()
O método start() é chamado para informar ao applet que ele deve se tornar "ativo". Isso ocorre
quando o applet é iniciado pela primeira vez, após a execução do método init(). Também ocorre
quando o navegador é restaurado após ser iconizado ou quando ele retorna à página que contém o
stop()
O método stop() é chamado quando o applet é "desativado". Isso ocorre quando o navegador é
iconizado ou segue um link para outro URL. O applet pode utilizar esse método para realizar tarefas,
como parar uma animação.
Os métodos start() e stop() formam efetivamente um par. O start() pode ser utilizado para
acionar um comportamento no applet, enquanto o stop() é utilizado para desativar esse
comportamento.
Desenho no AWT
Além dos métodos básicos ciclo de vida, um applet possui métodos importantes relacionados à
exibição. Na realidade, esses métodos são declarados e documentados para uma classe
Component do AWT, e é importante seguir o modelo correto para tratar a exibição utilizando o AWT
A atualização da exibição é feita por um thread separado, que chamaremos thread de AWT. Esse
thread pode ser chamado para tratar duas situações que se relacionam a essa atualização.
A primeira dessas condições é a exposição, onde parte da exibição tiver sido danificada e precisar
ser substituída. Isso pode ocorrer a qualquer momento e o seu programa deve ser capaz de lidar
com essa situação.
A segunda condição ocorre quando o programa decide redesenhar a exibição com novo conteúdo.
Para isso, pode ser necessário que a imagem antiga seja removida primeiro.
O método paint(Graphics g)
O método repaint()
Por outro lado, você pode avisar ao sistema que deseja alterar a exibição através do método
repaint().
O método update(Graphics g)
Repaint faz com que o thread do AWT possa chamar outro método denominado update(). O
comportamento do método update normalmente consiste em limpar a exibição atual e, em seguida,
chamar o paint().
• Manter um modelo da exibição. Isso pode ser feito através de várias formas. Você pode utilizar
um mapa dos valores de pixel ou uma técnica de nível superior, como uma matriz de formas.
• O método paint() deve renderizar a exibição com base apenas no conteúdo do modelo. Isso
permite que ele regenere a exibição de forma consistente, sempre que for chamado. Por isso, a
exposição é tratada corretamente.
• Quando o programa deseja alterar a exibição, ele deve atualizar o modelo e, em seguida,
chamar o método repaint() para que o método update() seja chamado pelo thread do AWT.
Este diagrama mostra como os métodos paint(), update() e repaint() estão relacionados.
Observação - Qualquer navegador Java executa as mesmas funções básicas que as descritas aqui
para o appletviewer.
O que é o appletviewer?
O appletviewer lê a HTML de um URL fornecido na linha de comando. Nessa HTML, ele espera
encontrar instruções para carregar e executar um ou mais applets. Ele ignora qualquer instrução
HTML que não essas instruções. Por isso, o appletviewer não exibe a HTML normal e não
incorpora applets em uma página de texto.
<html>
<applet code=HelloWorld.class width=100 height=100>
</applet>
</html>
O appletviewer cria um espaço para navegador, incluindo uma área gráfica, na qual o applet será
executado e, em seguida, chama a classe de applet apropriada. Neste exemplo, o appletviewer
carrega urna classe chamada HelloWorld e permite que ela trabalhe com o espaço gráfico.
java.lang.Object
java.lang.Component
java.lang.Container
java.lang.Panel java.lang.Window
java.lang.Frame
java.lang.Applet sun.applet.AppletViewer
Utilizar o appletviewer
Sinopse
Se o arquivo de HTML passado para o appletviewer não contiver um indicador <applet> válido,
o appletviewer não executará qualquer ação. O appletviewer não exibe outros indicadores
HTML.
A única opção válida para o appletviewer e o sinalizador -debug, que inicializa o applet no
depurador de Java jdb. Compile o código Java com a opção -g para ver o código fonte no
depurador.
Exemplo
Sintaxe
<applet
archive=archiveList
code=appletFile. class
width=pixels height=pixels
[codebase=codebase URL]
[alt=alternateText]
[name=appletInstanceName]
[align=alignment]
[vspace=pixels] [hspace=pixels]
>
[<param name=appletAttributel value=value>]
[<param name=appletAttribute2 value=value>]
...
[alternateHTML]
</applet>
Descrição
Observação - Esse arquivo refere-se ao URL básico do arquivo de HTML do qual você o está
carregando. Ele não pode conter um nome de caminho. Para alterar o URL básico do applet,
consulte o indicador codebase.
• width=pixels height = pixels - Estes atributos necessários definern a largura e a altura iniciais
(em pixels) da área de exibição do applet, sem considerar qualquer janela ou caixa de dialogo
que o applet venha a ativar.
• alt=alternateText - Este atributo opcional especifica qualquer texto que deverá ser exibido se o
navegador compreender o indicador applet, mas não conseguir executar applets Java.
Os navegadores sem Java exibirão qualquer HTML comum incluída entre o <applet> e as tags
</applet>. Os navegadores com Java ignoram a HTML comum entre esses dois indicadores.
O URL que você especifica como argumento para o appletviewer deve conter instruções
para carregar um applet. Essas instruções assumem a forma de um indicador de applet que,
em sua forma mais simples, tem esta aparência:
Observe que o formato geral desse indicador e o mesmo que o de qualquer outra HTML, utilizando
os símbolos < e > para delimitar as instruções. Como todas as partes mostradas aqui são
necessárias, você precisa ter <applet . . .> e </applet>. A parte <applet ... > deve
especificar uma entrada de código, além de uma largura e uma altura.
Observação - Em geral, você deve tratar applets como sendo de tamanho fixo e utilizar o tamanho
especificado no indicador de applet. É possível, em determinadas condições, alterar o tamanho de
um applet. Entretanto, os resultados nem sempre serão adequados
Todos os programas Java têm acesso aos recursos de rede, utilizando as classes do pacote
java.net. Além disso, os applets contêm metodos que permitem determinar informações sobre o
ambiente de navegação em que foram iniciados.
A classe java.net.URL descreve URLs e pode ser utilizada para se conectar a eles. Dois métodos
da classe Applet podem determinar o valor de URLs significativas:
Utilizando o objeto URL como ponto inicial, você pode trazer sons e imagens para o applet.
• getImage(URL base, String target) traz uma imagem do arquivo denominado target,
localizado no URL especificado por base. O valor retornado é uma instância da classe Image.
4. O objeto observer da imagem. O observador da imagem consiste na informação que deve ser
fornecida se a imagem for alterada.
Uma imagem carregada pelo getImage() será alterada após a primeira emissão da chamada. Isso
ocorre porque a carga é efetuada em segundo plano. Toda vez que mais uma parte da imagem é
carregada, o método paint() é chamado novamente. Isso ocorre porque o applet foi registrado
como um observador, tendo sido passado no quarto argumento da chamada de drawImage().
Clipes de áudio
Executar um clipe
A maneira mais fácil de ouvir um clipe de áudio é através do método play do applet:
ou simplesmente:
play(URL soundURL);
Um URL comum a ser utilizado com o método play() é o diretório em que os arquivos de HTML
estão localizados. Acesse esse local através do método getDocumentBase().
play(getDocumentBase(), "bark.au");
Para que isso funcione, o arquivo de HTML e o arquivo bark.au devem se encontrar no mesmo
diretório.
1 //
2 //olá mundo ampliado para executar áudio
3 //Pressupõe a existência do arquivo "sounds/cuco.au"
4 //
5
6 import java.awt.Graphics;
7 import java.applet.Applet;
8
9 public class HwAudio extends Applet {
10
11 public void paint(Graphics g) {
12 g.drawString("Teste de Audio", 25, 25);
13 play(getDocumentBase (),"sounds/cuco.au");
14 }
15 }
16
Você também pode tratar clipes de áudio como imagens. Você pode carregá-las e executá-Ias
posteriormente.
AudioClip sound;
Após carregar um clipe, utilize os três métodos associados a ele: play, loop ou stop.
sound.play();
Para iniciar a execução do clipe e executar um loop nele (fazê-lo repetir automaticamente), utilize o
método loop da classe java.applet.AudioClip:
sound.loop();
sound.stop();
1 //
2 // Ola mundo ampliado para executar um loop em uma trilha de audio
3 // Pressupõe a existência de "sounds/cuco.au"
4
5
6 import java.awt.Graphics;
7 import java.applet.*;
8
9 public class HwLoop extends Applet {
10 AudioClip sound;
11
12 public void init() {
13 sound = getAudioClip(getDocumentBase (),
14 "sounds/cuco.au");
15 }
16
17 public void paint(Graphics g) {
18 g.drawString("Teste de Audio", 25, 25);
19 }
20
21 public void start () {
22 sound.loop();
23 }
24
25 public void stop () {
26 sound.stop();
27 }
28 }
Um dos recursos mais úteis suportados pela linguagem Java consiste na interatividade direta. Um
aplicativo pode monitorar o mouse e reagir a alterações efetuadas neste.
O modelo de evento 1.1 suporta um tipo de evento para cada tipo de interatividade. Os eventos do
mouse são recebidos por classes que implementam a interface MouseListener, que recebe os
eventos de:
1 //
2 // olá mundo ampliado para monitorar a entrada do mouse
3 // O "Olá mundo!" é reimpresso no local do
4 // clique no mouse.
5 //
6
7 import java.awt.*;
8 import java.awt.event.*;
9 import java.applet.Applet;
10
11 public class HwMouse extends Applet
12 implements MouseListener {
13
14 int mouseX=25;
15 int mouseY=25;
16
17 //Registrar o mousePressed Event
18 public void init () {
19 addMouseListener (this);
20 }
21
22 public void paint(Graphics g) {
23 g.drawString("Olá mundo!", mouseX, mouseY);
24 }
25
26 public void mousePressed(MouseEvent evt){
27 mouseX = evt.getX ();
28 mouseY = evt.getY ();
29 repaint ();
30 }
31
32 // Não estamos utilizando os outros eventos do mouse
Ler parâmetros
Em um arquivo HTML, uma tag de applet pode passar informações de configuração para o applet.
Para isso, utilize o tag <param>. Por exemplo:
Dentro do applet, você pode utilizar o método getParameter () para ler esses valores.
import java.awt.*;
import java.applet.*;
O tipo do parâmetro é sempre String. Se você necessitar dele em outros formatos, terá de
convertê-lo. Por exemplo, para ler um parâmetro int:
Em virtude da natureza do código HTML, os parâmetros não fazem distinção entre maiúsculas e
minúsculas. No entanto, será um bom estilo adotar apenas maiúsculas ou minúsculas sempre que
aparecerem.
A partir de um único arquivo de classe, é possível criar um código que seja utilizado como um applet
Java e um aplicativo Java. Você terá um pouco mais de trabalho para compreender os requisitos do
aplicativo mas, uma vez criado, o código do applet/ aplicativo poderá ser utilizado como modelo para
programas mais complexos.
Applet/Aplicativo
Objetivos
Então, o que é um erro? Em Java, a classe Error define as condições de erro consideradas
bastante sérias, das quais você não deve tentar se recuperar. Na maioria dos casos, é aconselhável
permitir o término do programa quando ocorrer um erro, ou o programa termina sem o controle do
usuário.
A linguagem Java implementa exceções no estilo C++ para ajudá-lo a criar um código com
capacidade de recuperação rápida. Quando ocorre no programa, o código que encontra esse erro
pode “lançar” uma exceção. O processo de lançar uma exceção consiste em avisar ao processo em
execução que ocorreu um erro. Em seguida, você poderá detectar a exceção e, quando possível,
recuperar-se dela.
Exemplo de exceção
Tratamento de exceções
Normalmente, um programa termina com uma mensagem de erro quando você lança uma exceção,
como faz o programa mostrado anteriormente após o loop ter sido executado quatro vezes.
$ java OlaMundo
Olá mundo!
Não, eu digo!
OLÁ MUNDO!!
Java.lang.ArrayIndexOutOfBoundsException: 3
At OlaMundo.main(OlaMundo.java):12)
No entanto, a importância do tratamento de exceções é que você pode codificar o programa para
detectá-las, tratá-las e, em seguida, continuar a execução.
A linguagem Java possui utilitários que lhe permitem descobrir qual exceção foi lançada e, em
seguida, tentar se recuperar.
try {
// código que pode lançar uma exceção específica
} catch (MyExceptionType e) {
//código a ser executado se MyExceptionType for lançada
}
Declaração finally
A declaração finally define um bloco de código que você sempre deve executar,
independentemente do fato de uma exceção ter sido ou não detectada. O exemplo de código e a
descrição a seguir foram extraídos do “white paper” (esboço de especificação) de Frank Yellin, Low
Level Security in Java (Segurança em nível inferior na linguagem Java):
try {
abrirTorneira();
correrAgua();
} finally {
fecharTorneira();
}
A linguagem Java garante que a “torneira seja fechada mesmo que ocorra uma exceção ao abrir
essa torneira ou molhar a grama”. O código delimitado entre chaves após try é chamado código
protegido.
Exemplo recriado
A linguagem Java fornece várias exceções predefinidas. Estas exceções são as mais comuns que
você pode encontrar:
int i = 12 / 0;
Categorias de exceção
Existem três categorias principais de exceções na linguagem Java. Na verdade, a Java
define a classe java.lang.Throwable, que funciona como a classe pai de todos os objetos que
possam ser lançados e detectados através dos mecanismos de tratamento de exceções. Há três
subclasses essenciais dessa classe, que são mostradas no diagrama abaixo.
A classe Throwable não deve ser utilizada. Em vez disso, uma das outras três descreverá
qualquer exceção especifica. A finalidade de cada uma a descrita abaixo.
• Error indica um problema sério do qual a recuperação a difícil ou ate impossível Um
exemplo e a falta de memória E improvável que um programa consiga tratar tais
condições embora seja possível com cautela.
Ha duas ações que o programador pode executar para satisfazer a essa exigência Um bloco try {
} catch () {} , em que a detecção atribui nomes a qualquer superclasse da exceção lançada a
considerado para a solução do problema, mesmo que o bloco de detecção esteja vazio.
Como alternativa, você pode indicar que a exceção não seja tratada nesse método a que ela, por
conseguinte, seja lançada no método de chamada. Para fazer isso, basta marcar a declaração do
método com uma clausula throws, da seguinte maneira:
Depois da palavra-chave throws se encontra uma lista de todas as exceções que podem ocorrer
em um método, porem sem um tratamento no local. Embora apenas uma exceção seja mostrada
nesse exemplo, uma lista separada por vírgulas poderá ser utilizada se varias exceções não forem
tratadas no método.
Para lançar uma exceção criada por você, utilize esta sintaxe:
Exemplo
Objetivos
Princípios do fluxo
Um fluxo é uma fonte de bytes ou o destino dos bytes. A ordem é significativa. Assim, por exemplo,
um programa que queira ler a partir do teclado poderá utilizar um fluxo para fazer isso.
As duas categorias básicas de fluxo são: fluxos de entrada e fluxos de saída. É possível ler a partir
de um fluxo de entrada, mas é impossível gravá-lo. Da mesma forma, é possível gravar num fluxo de
saída, mas é impossível lê-lo. É claro que para ler bytes a partir de um fluxo de entrada, é
necessário ter uma origem de caracteres associada ao fluxo.
No pacote java.io alguns fluxos são fluxos de "nó", ou seja, lêem a partir de um lugar específico
por exemplo, um arquivo de disco ou uma área de memória ou gravam neles. Outros fluxos são
denominados flltros. Um fluxo de entrada de filtro é criado com uma conexão com um fluxo de
entrada existente, de forma que quando tentar ler a partir do fluxo de entrada de filtro ele lhe fornece
caracteres que originariamente vieram do outro fluxo de entrada.
Nó InputStream FileInputStream
(a partir de arquivo em disco)
int read()
int read(byte[])
int read(byte[], int, int)
Estes três métodos fornecem acesso aos dados provenientes da conexão. O método de leitura
simples retorna um int que contém um byte lido a partir do fluxo ou -1 que indica a condição de fim
de arquivo. Os outros dois são lidos numa matriz de byte e retornam o número de bytes lidos. Os
dois argumentos int no terceiro método indicam um sub-intervalo a ser preenchido na matriz alvo.
Observação - Para maior eficiência, sempre leia dados nos blocos práticos maiores
void close()
Quando tiver terminado um fluxo, feche-o. Se tiver uma "pilha" de fluxos, utilizando fluxos de filtro,
feche o fluxo no topo da pilha. Esta operação fecha os fluxos mais baixos.
int available()
skip(long)
markSupported()
mark(int)
reset()
Estes métodos são utilizados para executar operações de "push back" num fluxo se o fluxo as
suportar. 0 método markSupported retornará true se os métodos mark() e reset() estiverem
operacionais para o fluxo em particular. O método mark (int) é utilizado para indicar que o ponto
atual no fluxo deve ser observado e deve ser alocado um buffer suficientemente grande para
guardar pelo menos o número especificado de caracteres do argumento. Depois das operações
subseqüentes read(), a chamada do método reset() retorna o fluxo de entrada para o ponto
anterior.
write(int)
write(byte[j)
write(byte[], int, int)
Estes métodos gravam no fluxo de saída. Assim como na entrada, você deve sempre tentar gravar
os dados no maior bloco prático.
close()
Fluxos de saída devem ser fechados quando tiverem sido terminados. Novamente, se tiver uma
pilha e fechá-la no topo, isto fechará o restante dos fluxos.
flush()
Algumas vezes um fluxo de saída pode acumular gravações antes de confirmá-las. O método
flush() lhe permite forçar essa confirmação.
InputStream
SequenceInputStream FileInputStream
PipedInputStream ByteArrayInputStream
FilterInputStream StringBufferInputStream
DataInputStream LineNumberInputStream
PushbackInputStream BufferedInputStream
FileInputStream e FileOutputStream
Observação - Mais no final deste módulo você aprenderá outras duas classes que acrescentam
outras capacidades de E/S de arquivo em java.
BufferedInputStream e BufferedOutputStream
Estes são fluxos de filtro que podem ser utilizados para aumentar a eficiência de operações de E/S.
DataInputStrearn e DataOutputStream
Estes fluxos de filtro lhe permitem leitura e gravação de tipos primitivos de Java pelos fluxos. Uma
série de métodos especiais é oferecida às primitivas diferentes. Por exemplo:
Métodos DataInputStream
byte readByte()
long readLong()
double readDouble()
Métodos DataOutputStream
void writeByte(byte)
void writeLong(long)
void writeDouble(double)
Estes fluxos têm métodos para leitura e gravação de seqüências, mas esses métodos não deverão
ser utilizados. Eles foram substituídos por leitores e gravadores que serão discutidos mais tarde.
PipedInputStream e PipedOutputStream
Fluxos de conexão podem ser utilizados para comunicação entre threads. Um objeto de
PipedInputStream num thread recebe suas entradas a partir de um objeto de
PipedOutputStream complementar em outro thread. Os fluxos de conexão devem ser de
entrada e de saída para serem úteis.
java.net.URL imagesource;
try {
imagesource = new URL("http://mysite.com/~info");
} catch (MalformedURLException e) {}
images[0] = getImage(imageSource, "Duke/Tl.gif");
Também é possível abrir um fluxo de entrada fora de uma URL apropriado, armazenando um
arquivo de dados abaixo do diretório de base do documento.
InputStream is;
String datafile = new String("Data/data.1-96");
byte buffer[] = new byte[24];
try {
//nova URL lança uma MalformedURLException
//URL.openStream() lança uma IOException
is = (new URL(getDocumentBase(), datafile)).openStream();
} catch (Exception e) {}
Agora é possível utilizar is para ler informações, assim como com um objeto de FilelnputStream
try {
is.read(buffer, 0, buffer.length);
} catch (IOException el) {}
Cuidado - Lembre-se de que a maioria dos usuários têm sua segurança de navegador configurada
para impedir que applets acessem arquivos.
Leitores e gravadores
Unicode
Java utiliza Unicode para representar seqüências e caracteres, e o código fornece fluxos para
permitir que caracteres sejam tratados da mesma forma. Essas fluxos são denominadas leitores e
gravadores e, como os outros fluxos, muitos deles estão disponíveis no pacote java.io.
Como padrão, se você simplesmente construir um leitor ou gravador conectado a um fluxo, então as
regras de conversão irão mudar entre bytes que utilizam a codificação padrão de caractere da
plataforma e Unicode. Assim, nos países de língua inglesa a codificação de byte utilizada será ISO
8859-1.
É possível especificar urna codificação de byte alternativa utilizando uma das formas de codificação
suportadas apresentadas na lista. Você encontra uma lista das formas de codificação suportadas na
documentação da ferramenta native2ascii.
Visto que a conversão de formatos é como qualquer outra operação de E/S, sendo mais eficiente se
executada em grandes blocos, normalmente é interessante encadeá-la BufferedReader ou
BufferedWriter (conforme apropriado) no fim de um InputStreamReader ou
InputStreamWriter. Lembre-se de usar o método flush() num BufferedWriter.
Leitores e gravadores
Este exemplo mostra a técnica simples a ser utilizada para ler informações de string a partir do canal
de entrada padrão.
import java.io.*;
public class CharInput {
public static void main (String args[]) throws java.io.IOException {
String s;
InputStreamReader ir;
BufferedReader in;
ir = new InputStreamReader (System. in);
in = new BufferedReader(ir);
while ((s = in.readline()) != null) {
System.out.println("Lido: + s);
}
}
}
Se tivesse de ler entrada a partir de uma codificação de caractere que não fosse a sua local, quem
sabe ler a partir de uma conexão de rede com um tipo diferente de máquina, seria possível construir
o InputStreamReader com uma codificação de caractere explícita como a seguinte:
Observação - Se estiver lendo caracteres a partir de uma conexão de rede, devera utilizar
essa forma. Se não o fizer, o programa sempre tentará converter os caracteres lidos como
se estivessem na representação local, o que provavelmente estaria incorreto. ISO 8859_1 é
o esquema de codificação Latin-1 que mapeia em ASCII.
Arquivos
Antes de executar operações de E/S num arquivo, é necessário obter informações básicas sobre o
arquivo. A classe File oferece vários utilitários para tratamento de arquivos e obtenção de
informações básicas sobre esses arquivos.
File myfile;
O construtor utilizado muitas vezes vai depender dos outros objetos de arquivo que você acessar.
Por exemplo, se utilizar apenas um arquivo no seu aplicativo, utilize o primeiro construtor.
Entretanto, se utilizar diversos arquivos a partir de um diretório comum, talvez seja mais fácil usar o
segundo ou o terceiro construtor.
Observação - É possível utilizar um objeto File como argumento do construtor para objetos
FileInputStream e FileOutputStream em vez de uma String. Isto permite maior
independência nas convenções do sistema de arquivo local e é normalmente recomendado.
Nomes de arquivo
• String getName ()
• String getPath()
• String getAbsolutePath()
• String getParent ()
• boolean renameTo(File newname)
Testes de arquivo
• boolean exists()
• boolean canWrite()
• boolean canRead()
• boolean isFile()
• boolean isDirectory()
• boolean isAbsolute()
• Long lastModified()
• long length()
• boolean delete()
Utilitários de diretório
• boolean mkdir()
• String[] list()
O argumento mode determina se tem acesso a esse arquivo somente leitura (r) ou leitura/ gravação
(rw).
Acesso as informações
RandomAccessFile objetos esperam ler e gravar informações da mesma maneira que objetos de
entrada e saída de dados. Você acessa a todas as operações de read() e write() encontradas
nas classes DataInputStream e DataOutputStream
A linguagem Java fornece vários métodos para ajudá-lo a se movimentar dentro do arquivo:
• long getFilePointer();
Configura o ponteiro do arquivo para a posição absoluta especificada. A posição é dada como um
deslocamento em bytes a partir do início do arquivo. Posição o marca o início do arquivo.
• long length();
Anexação de informações
É possível utilizar arquivos de acesso aleatório para realizar um modo de anexação para saída de
arquivo.
Serializável
A partir do JDK 1.1 houve a inclusão da interface java.io.Serializable e mudanças no Java
Virtual Machine para suportar a habilidade de salvar um objeto Java num fluxo.
Grafos de objeto
Quando um objeto for serializado, apenas os dados do objeto são preservados; métodos de classe e
construtores não fazem parte do fluxo serializado. Quando uma variável de dados for um objeto, os
membros dos dados do objeto também são serializados. A árvore ou estrutura de dados do objeto,
inclusive os sub-objetos, constituem o grafo do objeto.
Algumas classes de objeto não são serializáveis pois a natureza dos dados que representam está
em constante mutação. Por exemplo, fluxos como java.io.FileInputStream e
java.io.FileOutputStream e java.lang.Thread. Se um objeto serializável tiver uma
referência a um elemento não~serializável, toda a operação de serialização falhará e uma
NotSerializableException será lançada.
Se o gráfico de objeto tiver uma referência a objeto não-serializável, o objeto ainda poderá ser
serializado se a referência estiver marcada com a palavra-chave transient.
Observe também que o modificador de acesso a campo (public, protected e private) não
afeta o campo de dados sendo serializado, e os dados são gravados no fluxo em formato de byte
com seqüências representadas como caracteres UTF. Utilizando a palavra-chave transient evita
que os dados sejam serializados.
Gravação
Gravação e leitura de um objeto num fluxo de arquivo é um processo simples. Considere o seguinte
fragmento de código que envia uma instância de um objeto java.util.Date para um arquivo:
Leitura
A leitura de objeto é tão simples quanto a gravação, com apenas uma observação-o método
readObject() retorna o fluxo como um tipo Object, e ele deve ser convertido (cast) para o nome
da classe apropriada antes que métodos dessa classe possam ser executados.
1 Date d = null;
2 FileInputStream f = new FileInputStream ("date.ser");
3 ObjectInputStream s = new ObjectInputStream (f);
4 try {
5 d = (Date)s.readObject
6 } catch (IOException e) {
7 e.printStackTrace();
8 }
9 System.out.println ("Data serializada em: "+ d);
Objetivos
Pela introdução de uma camada de abstração, JDBC permite que a escolha do database engine
seja feita pelo integrador ou de forma mais simples. JDBC habilita o desenvolvedor a utilizar uma
mesma API padronizada que toma conta de toda a comunicação entre seu front end e o banco de
dados back end.
Se você decidir mais tarde pela alteração do back end, o servidor de banco de dados pode ser
facilmente substituído, pela alteração dos drivers JDBC carregados.
Sistemas de banco de dados suportam uma vasta gama de sintaxe e semântica SQL . Enquanto
diferentes sistemas podem variar em funcionalidades mais avançadas tais como outer joins, eles
compartilham um suporte comum a ANSI SQL-2. Devido ao fato de que aplicações Java podem ser
escritas para utilizar somente comandos com patíveis com este padrão ANSI, essas aplicações serão
altamente portáveis entre diversos bancos de dados.
Entretanto, JDBC não suporta somente ANSI SQL-2. JDBC permite que qualquer string de query
possa ser passada para o banco de dados, de forma que uma aplicação pode utilizar funções
específicas de determinados sistemas de bancos de dados, penalizando a portabilidade do código
entre diferentes estruturas de bancos de dados.
Existem dois componentes maiores em JDBC: uma interface de implementação para fabricantes de
bancos de dados, e uma interface para desenvolvedores de aplicações. As seções seguintes
discutem a estrutura do JDBC da perspectiva de um fabricante de bancos de dados e então cobrem
em maiores detalhes os passos envolvidos em escrever uma aplicação JAVA usando os drivers
ODBC:JDBC, fornecido pela própria Sun, e drivers de outros fabricantes.
Cada driver de banco de dados deve fornecer uma classe que implementa a interface
java.sql.Driver usada pela classe java.sql.DriverManager genérica quando for
necessário localizar um driver para um banco de dados em particular utilizando um uniform resource
locator (URL). JDBC também fornece uma implementação s obre ODBC simples e eficiente.
A figura abaixo mostra como uma única aplicação (ou applet) Java pode acessar múltiplos sistemas
de bancos de dados através de um ou mais drivers.
Aplicação Java
(API JDBC)
Drivers ODBC
e de database
O JDK fornece um driver de compatibilização JDBC-ODBC que permite que fontes de dados ODBC
possam ser acessadas a partir de aplicativos Java utilizando a API JDBC. Apesar da eficiência do
driver diminuir pela maior quantidade de componentes, é bastante popular pela facilidade de oferta e
configuração de drivers ODBC, principalmente em ambientes de teste.
Diversos fabricantes de bancos de dados oferecem suas próprias implementações da API JDBC,
juntamente com seus produtos. Diversos fornecedores independentes também fornecem
implementações de drivers no mercado.
Esta seção cobre algumas das tarefas comuns que deverão ser realizadas na programação JDBC.
Você aprenderá como:
• Criar uma instância de um driver JDBC
• Carregar drivers JDBC através de jdbc.driver
• Registrar um driver
• Especificar um banco de dados
• Abrir uma conexão de banco de dados
• Submeter uma query
• Receber resultados
Será assumido que uma fonte de dados ODBC será utilizada via ponte JDBC-ODBC.
Package java.sql
Cada uma dessas interfaces habilita uma aplicação em particular a abrir conexões com bancos de
dados, executar comandos SQL e processar os resultados.
Este é um exemplo simples que utiliza o driver de conexão ODBC:JDBC. Este exemplo utiliza os
elementos de uma aplicação JDBC: criar uma instância da classe Driver, obter um objeto
Connection, criar um objeto Statement, executar uma query e processar o objeto ResultSet
resultante.
1. import java.sql.*;
Os fabricantes de bancos de dados disponibilizam drivers JDBC para acesso às suas plataformas.
Os drivers são escritos em Java e devem ser invocados de forma apropriada nos programas que os
utilizarem. A documentação do próprio driver fornecerá informações de como isto deverá ser feito. O
exemplo abaixo utiliza o driver Jdbc para o servidor de banco de dados Interbase.
1. import java.sql.*;
2. public class ListaIB
3. {
4. public static void main(String[] args) throws SQLException {
5. // CARREGAMENTO DO DRIVER JDBC
6. try {
7. Class.forName("interbase.interclient.Driver");
8. } catch (Exception e) { }
9. // CONFIGURACAO E INICIALIZACAO DA CONEXAO
10. String url = "jdbc:interbase://127.0.0.1/e:/databases/employee.gdb";
11. String user = "sysdba";
12. String password = "masterkey";
13. Connection con = DriverManager.getConnection(url,user,password);
14. // CRIACAO DO COMANDO
15. Statement stmt = con.createStatement();
16. // CONFIGURACAO DO COMANDO
17. ResultSet rs = stmt.executeQuery("SELECT * FROM customer");
18. // EXTRACAO DOS DADOS
19. while (rs.next()) {
20. System.out.println("Codigo : "+rs.getString(1));
21. System.out.println("Cliente: "+rs.getString(2));
22. System.out.println("Contato: "+rs.getString(3));
23. System.out.println(" ");
24. }
25. stmt.close();
26. con.close();
27. }
28. }
Os métodos getXXX acessam os campos por ordem numérica ou pelo nome do campo.
Threads
Objetivos
Nesse estágio do debate não há necessidade de se preocupar como o efeito é alcançado, mas
apenas considerar as implicações sob o prisma da programação. Se tivermos mais de um trabalho
sendo executado, isto é semelhante a ter mais de um computador. Para este módulo,
consideraremos um thread, ou contexto de execução, como sendo o encapsulamento de uma CPU
virtual com seus próprios dados e códigos de programa. A classe java.lang.Thread nas
bibliotecas básicas de Java permitem aos usuários criarem e controlarem seus próprios threads.
Um thread compreende três partes principais. Primeiro, há a CPU virtual. Em segundo, há o código
que a CPU está executando. Em terceiro, há os dados sobre os quais o código trabalha.
Em Java, a CPU virtual está incorporada na classe do Thread. Quando um thread é construído, os
argumentos do construtor são passados para o código que ele irá executar e os dados sobre os
quais ele trabalhará.
É importante observar que esses três aspectos são efetivamente independentes. Um thread pode
executar um código que seja igual ao de outro thread ou pode executar um código diferente. Pode
também acessar a dados iguais ou diferentes.
Criação do thread
Vamos analisar a forma de criação de um thread e discutir como os argumentos do construtor são
utilizados para fornecer o código e os dados para quando o thread for executado.
Um construtor de Thread recebe um argumento que seja uma instância de Runnable. C)u seja, é
necessário declarar uma classe para implementar a interface Runnable e construir uma instância
dessa classe. A referência resultante é um argumento conveniente para nosso construtor.
Por exemplo,
A partir disso, temos um novo thread incorporado na referência para o Thread t. Este está
configurado para executar o código iniciando no método run () da classe xyz. (A interface Runnable
exige que um método public void run() esteja disponível.) Os dados que este thread utiliza são
fornecidos pela instância da classe xyz referida como r.
Resumindo, um thread é referenciado por uma instância de um objeto Thread. O código que o
thread irá executar é retirado da classe do argumento passado para o construtor do Thread. Esta
classe deve implementar a interface Runnable. Os dados sobre os quais o thread trabalha são
retirados da instância específica em Runnable, passados para o construtor do Thread.
Inicio do thread
Embora o thread tenha sido criado, ele não é executado imediatamente. Para iniciá-lo, utilizamos o
método start (). Este método encontra-se na classe Thread, assim em vista do esboço anterior
dizemos simplesmente:
t. start() ;
Neste ponto, a CPU virtual incorporada no thread se torna executável. Pense nisto como se
estivesse ligando a CPU virtual.
Escalonamento do thread
Embora o thread se torne executável, isto não quer dizer que ele inicia imediatamente. Em uma
máquina que tenha realmente apenas uma CPU, naturalmente ela só poderá fazer uma coisa de
cada vez. Consideraremos agora a forma de alocação da CPU quando mais de um thread puder
estar trabalhando de forma útil.
Em Java, threads normalmente são preemptivos, mas não necessariamente por fração de tempo. (É
um erro comum acreditar que "preemptivo" seja uma palavra sofisticada para "por fração de tempo".)
Um thread pode deixar de estar pronto por uma série de motivos. Ele pode executar uma chamada
Thread. sleep (), pedindo deliberadamente para ficar suspenso durante um período. Ele pode ter que
esperar por um dispositivo externo lento, talvez um disco ou o usuário.
Todos os threads executáveis, mas que não estejam sendo executados, são mantidos em filas de
acordo com as prioridades. O primeiro thread na fila não-vazia de prioridade mais alta será
executado. Quando um thread interromper a execução por causa da preempção, ele é retirado do
estado de execução e colocado na parte final da fila de execução. Da mesma forma, um thread que
se torna executável depois de ter sido bloqueado (suspenso ou talvez aguardando por E/S) sempre
ira para a parte final da fila.
Programação de thread
Observe a utilização de try e catch. Observe também que a chamada sleep() e um método
static da classe Thread e portanto, é referenciada como Thread.sleep(x). O argumento
especifica o número mínimo de milissegundos que deverá permanecer inativo. A execução do
thread não prosseguirá até esse tempo passe. Este período inclui o tempo para o bloqueio de outros
threads.
Um outro método na classe Thread, yield(), pode ser emitido para dar a outros threads a
oportunidade de serem executados sem interromper de verdade o thread atual, a menos que seja
necessário. Se outros threads com a mesma prioridade forem executáveis, yield colocará o thread
chamador no fim de sua lista de executáveis e permitirá o início de outro thread. Se não existirem
outros threads executáveis com a mesma prioridade, yield () não fará nada.
Observe que uma chamada sleep() pode permitir que threads de prioridade mais baixa tenham a
oportunidade de serem executados. O método yield() apenas dá uma oportunidade a threads
com a mesma prioridade ou mais alta.
Término de um thread
Quando um thread retornar do término do método run (), ele morre. Depois disto, não poderá ser
executado novamente.
Um thread pode ser interrompido forçosamente pela emissão do método stop (). Este deve ser
utilizado numa instância específica da classe Thread; por exemplo:
Dentro de uma parte de código em particular, é possível obter uma referência para o thread atual
utilizando o método estático Thread.currentThread(); por exemplo:
Observe que neste caso a execução de stop() destruirá o contexto da execução atual e portanto,
mais nenhum loop run() será executado neste contexto. A utilização de
Thread.currentThread() não está restrita ao exemplo específico de stop().
Teste de um thread
Algumas vezes é possível que um thread esteja num estado desconhecido (por exemplo, poderá
ocorrer se o código não estiver diretamente no controle de um determinado thread). É possível
perguntar se um thread ainda é viável utilizando o método isAlive(). Alive não implica em que o
thread esteja sendo executado significa apenas que foi iniciado e que o método stop() não foi
executado ou nem esgotado o término de seu método run().
sleep()
O método sleep() foi introduzido na primeira seção e é utilizado para interromper um thread
durante algum tempo. Lembre-se de que normalmente o thread não prosseguirá no momento em
que o tempo de suspensão for esgotado. Isto é porque provavelmente outros threads esteja sendo
executados naquele instante e talvez não possam ser desescalonados a não ser que: (a) o thread
saindo da suspensão seja de prioridade mais alta, (b) o thread em execução seja bloqueado por
algum outro motivo ou, (c) fração de tempo seja ativada. Na maioria dos casos, nenhuma das duas
últimas condições prevalecerá imediatamente.
suspend() e resume()
Dois métodos de Thread estão disponíveis para esse fim. Denominam-se suspend() e resume().
Por exemplo:
Observe que, em geral, um Thread pode ser suspenso por qualquer parte de código que nele tenha
um handle; ou seja, uma variável que se refira a ele. Entretanto, é claro que o Thread só poderá ser
reiniciado por um outro Thread que não seja ele próprio, uma vez que o Thread suspenso não está
executando nenhum código!
join()
O método join() faz com que o thread atual aguarde até que termine o thread sobre o qual o
método join é chamado. Por exemplo:
join também pode ser chamado com um valor de tempo limite em milissegundos.
onde o método join suspenderá o thread atual por timeout milissegundos ou até que o thread
chamado termine.
Aqui existe apenas uma classe, myThread. Quando há a criação do objeto de Thread não são
passados argumentos. Esta forma de construtor cria um Thread que utiliza seu próprio método
run() incorporado.
Qual utilizar?
Dadas as opções de abordagens, como decidir por uma delas? Há pontos favoráveis em todas as
abordagens.
Visto que Java apenas permite herança simples, é impossível estender qualquer outra classe, como
Applet, se você já tiver estendido Thread. Em algumas situações, isto o obrigará a adotar a
abordagem de implementação Runnable.
Onde houver um método de execução incorporado numa classe em que ela própria estenda a
classe Thread, a referência this se refere de fato à instância do Thread real que está no controle
da execução. Por causa disto, o código não precisa mais utilizar controles de longo alcance:
Thread.currentThread().suspend();
suspend();
Como o código resultante é ligeiramente mais simples, muitos programadores de Java utilizam o
mecanismo de estender o Thread. No entanto, se você adotar esta abordagem o modelo de herança
simples poderá trazer problemas mais tarde ao ciclo de vida do código.
Introdução
Este modelo discute utilização da palavra-chave synchronized. Isto oferece à linguagem Java um
mecanismo que permite ao programador o controle sobre threads que estejam compartilhando
dados.
O problema
Imagine uma classe que represente uma pilha. Uma primeira visão da classe apareceria da seguinte
forma:
class stack
{
int idx = 0;
char [] data = new char[6];
Observe que a classe não faz esforço para tratar de estouro ou estouro negativo na pilha, e que a
capacidade da pilha é bem limitada. Estes aspectos são irrelevantes à discussão.
O comportamento deste modelo exige que o valor do índice tenha o subscrito da matriz da próxima
célula vazia da pilha. A abordagem "pré-decremento, pós-incremento" é utilizada para gerar essas
informações.
Imagine agora que dois threads tenham como referência uma única instância dessa classe. Um
thread está empurrando dados para a pilha e o outro, mais ou menos independentemente, está
Suponha que o thread a esteja acrescentando e o thread h removendo caracteres. Thread a acaba
de depositar um caractere, mas ainda não incrementou o contador de índice. Por algum motivo,
esse thread perde agora a prioridade de execução. Neste ponto, o modelo de dados representado
em seu objeto é inconsistente.
buffer |p|q|r| | | |
idx = 2 ^
Especificamente, a consistência exigiria que idx = 3 ou que o caractere ainda não tivesse sido
acrescentado.
Se o thread a prosseguisse na execução talvez não houvesse dano, mas suponha que o thread b
também estivesse aguardando para remover um caractere. Enquanto o thread a estivesse
aguardando uma outra oportunidade de execução, thread h teria sua oportunidade para remover um
caractere.
Na entrada para o método pop(), temos uma situação de dados inconsistente. O método pop
prossegue para diminuir o valor de índice
buffer |p|q|r| | | |
idx = 1 ^
que na verdade serve para ignorar o caractere "r". Depois disto, ele então retorna ao caractere "q".
Até o momento, o comportamento é como se a letra "r" não tivesse sido empurrada, então é difícil de
dizer se existe um problema. Porém, agora, vejamos o que acontece quando o thread original, a,
continuar a ser executado.
Thread a seleciona o que foi deixado pelo método push(). Ele prossegue para incrementar o valor
de índice. Agora temos:
10 buffer |p|q|r| | | |
11 idx = 2 ^
Observe que essa configuração implica em que "q" seja válido e que a célula com "r" seja a próxima
célula vazia. Ou seja, vamos ler "q" como se tivesse sido colocado duas vezes na pilha, mas nunca
observaremos a letra "r".
Este é um exemplo simples de um problema geral que pode surgir quando diversos threads
estiverem acessando dados compartilhados. Precisamos de um mecanismo que assegure a
proteção dos dados contra esse tipo de acesso descontrolado.
Uma abordagem seria evitar que o thread a fosse desligado até que tivesse completado a seção
crítica do código. Esta abordagem é comum em programação de maquina de baixo nível, mas em
geral é inadequada para sistemas de múltiplos usuários.
Uma outra abordagem, em que Java trabalha, é fornecer um mecanismo para tratar dos dados como
frágeis.
Em Java, toda instância de qualquer objeto possui um sinalizador associado a ela. Este sinalizador
pode ser considerado como um "sinalizador de bloqueio". Uma palavra-chave synchronized é
fornecida para permitir interação com o sinalizador. Observe o fragmento do código modificado:
É importante perceber que isso, por si mesmo, não protegeu os dados no objeto this. Se o método
pop(), sem modificação, for invocado por qualquer outro thread ele ainda correrá o risco de
danificar a consistência do objeto this.
O que devemos fazer e modificar o método pop da mesma forma. Na verdade acrescentamos uma
chamada para synchronized(this) em torno de partes frágeis da operação pop exatamente da
mesma forma que fizemos com o método push(). Se um outro thread então tentar executar o
método enquanto o thread original detiver o sinalizador de bloqueio, acontecerá o seguinte:
Visto que um thread que aguarda pelo sinalizador de bloqueio de um objeto não continuará a ser
executado até que o sinalizador retorne ao thread retido, é absolutamente importante retornar o
sinalizador quando não for mais necessário.
Estas regras tornam a utilização de blocos sincronizados muito mais simples de gerenciar do que
facilidades equivalentes encontradas em outros sistemas, como os famosos semáforos binários de
Dijkstra.
Montagem
Considere a acessabilidade dos itens de dados que formam as partes frágeis do objeto. Se estes
não estiverem marcados como private, então poderão ser acessados por código fora da própria
definição de classe. Neste caso, é preciso confiar que outros programadores não omitirão as
proteções necessárias. Nitidamente esta não é uma estratégia segura. Por este motivo, os dados
deverão estar sempre marcados como private.
Uma vez que mostramos que os dados devem ser efetivamente marcados como private, o
argumento para a declaração synchronized () normalmente deverá ser this. Por causa dessa
generalização, a linguagem Java permite uma abreviação. Então, em vez de escrever:
Por que utilizar um em vez do outro? Se utilizarmos synchronized como um modificador de método,
todo o método se tornará um bloco sincronizado, que poderá resultar na retenção do sinalizador de
bloqueio por um tempo maior do que o necessário. Isto poderia ser ineficiente. Por outro lado, a
marcação do método dessa forma permite aos seus usuários saber que sincronização está
ocorrendo, o que pode ser importante quando se projeta defesas contra impasse (que será
abordado na próxima seção). Observe que o gerador de documentação javadoc propagará o
modificador synchronized nos arquivos de documentação, mas a utilização de synchronized
(this) não será documentada.
Impasse
Em programas onde múltiplos threads estão competindo pelo acesso a vários recursos, pode existir
a possibilidade de uma condição conhecida como impasse. Isto ocorre quando um thread estiver
aguardando por um bloqueio retido por outro thread, mas o outro thread está aguardando por um
bloqueio que já foi retido pelo primeiro thread. Nesta condição, nenhum deles pode continuar até o
outro tenha ultrapassado o término do bloco synchronized. Uma vez que ambos são incapazes de
continuar, nenhum deles pode ultrapassar o término do bloco.
Java não detecta nem tenta evitar essa condição. Portanto o programador tem a responsabilidade
de garantir que a condição não ocorra.
Eis uma maneira prática de proceder que lhe permitirá evitar o impasse: Se tiver diversos objetos
que desejam ter acesso synchronized, tome uma decisão global sobre a ordem em que cada um
obterá esses bloqueios e atenha-se a ela ao longo de todo o programa.
Uma discussão mais detalhada sobre o problema ultrapassa o escopo deste material.
Introdução
Com freqüência threads são criados especificamente para executar tarefas não relacionadas. No
entanto, por vezes os trabalhos executados estão na verdade de alguma forma relacionados.
Quando isto ocorrer talvez seja necessário programar alguma interação entre os threads. Há várias
maneiras padronizadas de fazer isso e com qualquer mecanismo, os outros podem ser
implementados com base nesse. Este módulo examina o mecanismo que Java oferece e descreve
sua utilização.
O problema
Por que dois threads precisam interagir? Um exemplo simples: imagine duas pessoas, uma lavando
pratos e a outra secando. Essas pessoas representam nossos threads. Entre elas existe um objeto
compartilhado, o escorredor. As pessoas são preguiçosas e prefeririam estar sentadas se não
tivessem nada de útil para fazer. Certamente, o secador não pode iniciar a secagem de alguma
coisa se não tiver pelo menos um item no escorredor. Assim como o escorredor ficará cheio se o
lavador de pratos for muito rápido, da mesma forma o lavador só poderá prosseguir quando o
escorredor tiver algum espaço livre.
Vamos apresentar o mecanismo que Java oferece para tratar disso de forma eficiente.
A solução
Seria possível programar uma solução para essa situação utilizando os métodos suspend() e
resume(). Tal solução exigiria que ambos os threads fossem criados em cooperação, uma vez que
cada um necessita de um handle do outro. Em vista desta inconveniência, Java fornece um
mecanismo de comunicação com base em instâncias de objetos.
Cada instância de objeto em Java tem duas filas de thread associadas a ela. A primeira é utilizada
por threads que aguardam a obtenção do sinalizador de bloqueio e foi abordada na seção
"Utilização de synchronized em Java." A segunda fila é utilizada para implementar os
mecanismos de comunicação wait() e notify().
Três métodos úteis são definidos na classe de base java.lang.Object. São eles wait(),
notify() e notifyAll(). Em primeiro lugar estamos preocupados com wait() e notify().
Vamos analisar o exemplo da lavagem de pratos.
Thread a está lavando e thread b está secando. Ambos têm acesso ao objeto escorredor. Suponha
que thread b - o thread de secagem, queira secar um item, mas que o escorredor esteja vazio. Então
poderia ser codificado:
if (drainingBoard.isEmpty())
drainingboard.wait();
Agora quando o thread b emitir a chamada wait(), ele interrompe a execução e se junta à lista de
espera do objeto escorredor. Ele não será executado de novo até que algo o remova dessa lista.
Então, como o thread secador poderá ser reiniciado? O thread lavador é responsável pela
informação de que há algo útil para o secador fazer. Isto é atingido pela emissão da chamada
notify() para o escorredor, assim:
drainingBoard.addItem(plate);
drainingboard.notify();
Observe que a chamada notify() é emitida nesse caso sem considerar se os threads estão
aguardando ou não. Esta abordagem é sem dúvida desperdiçadora; pois é possível emitir a
chamada só para o caso de o prato a ser acrescentado transformar o escorredor vazio em cheio.
Porém, isto e um detalhe e foge ao âmbito dessa discussão. O que é mais importante perceber é
que se o método notify() for chamado quando nenhum thread estiver bloqueado na fila de
espera, a chamada é sem efeito. Chamadas para notify() não são armazenadas.
Além disso, notify() libera no máximo um único thread da lista de espera. Se houver mais de um
aguardando, os outros permanecerão na fila. O método notifyAll() pode ser utilizado para
liberar todos os threads em espera se a estrutura do programa exigir esse comportamento.
A verdade
synchronized(drainingboard) {
if (drainingBoard.isEmpty())
drainingboard.wait();
}
e da mesma forma:
synchronized(drainingboard)
drainingBoard.addItem(plate);
drainingBoard.notify();
}
Se desejar poderá assumir isso como uma simples regra de linguagem, mas ela desperta uma
observação importante. Uma vez que sabemos que a instrução synchronized requer o thread
para obter o sinalizador de bloqueio do objeto antes de prosseguir, poderia parecer que o thread
lavador nunca poderia atingir a declaração notify() se o thread secador fosse bloqueado no
estado wait().
Na pratica, a implementação é tal que isso não ocorre. Especificamente, a emissão de uma
chamada wait() primeiro retorna o sinalizador de bloqueio ao objeto. No entanto, para evitar erros
quando um thread notify() for emitido, ele não será executável. Ao contrário, será simplesmente
removido da fila de espera para a fila do sinalizador de bloqueio. Desta maneira, não poderá
realmente prosseguir até que ganhe de novo o sinalizador de bloqueio.
Montagem
Vamos tentar agora montar um exemplo real. Vamos trabalhar com o princípio básico de lavagem e
secagem, mas em vez de pratos num escorredor, passaremos para caracteres num objeto em pilha.
Iniciaremos analisando o esboço do objeto em pilha e os detalhes dos threads que serão produzidos
e consumidos. Finalmente, analisaremos o detalhe da pilha e os mecanismos utilizados para
protegê-la e implementar a comunicação de thread.
A classe da pilha, denominada SyncStack para fazer a distinção entre a classe principal
java.util.Stack, oferecerá a seguinte API pública:
Produtor
Isto irá gerar 20 caracteres de letras maiúsculas aleatórios e os empurrará para a pilha com um
atraso aleatório entre cada push. O atraso estará na faixa de 0 a 100 milissegundos. Cada caractere
empurrado será relatado no console.
Consumidor
Isto coletará 20 caracteres da pilha, com um atraso entre cada tentativa. Aqui o atraso é de 0 a 2
segundos. Isto quer dizer que a pilha será esvaziada mais devagar do que será cheia e portanto,
deverá ficar totalmente cheia muito rápido.
Agora, vamos considerar a construção da classe da pilha. Precisamos de um índice e de uma matriz
de buffer. O buffer não deverá ser muito grande, uma vez que a finalidade do exercício é demonstrar
a correta operação e sincronização no preenchimento. Neste caso a matriz terá seis caracteres.
Classe SyncStack
Uma SyncStack recentemente construída deverá estar vazia. Isto pode ser arranjado facilmente
utilizando a inicialização padrão dos valores, mas será feito aqui para maior clareza por uma
questão de bom estilo. Desta forma, podemos iniciar a construção da classe:
class SyncStack
{
private int index = 0;
private char [] buffer = new char[6];
Observe a ausência de qualquer construtor. Considera-se bom estilo incluir um construtor, mas não
foi incluído para ser mais rápido.
Observe que a chamada wait() foi tomada explícita como this.wait (). A utilização de this é
redundante, mas foi incluída para enfatizar que o encontro foi feito neste objeto da pilha. A chamada
wait () é colocada numa construção try/catch. Por causa da possibilidade de wait () sair
num interrupt (), devemos fazer um loop no teste para o caso de o Thread ser levantado
prematuramente em wait ().
O ponto final a ser considerado é o de verificação de erro. Você pode observar que não há código
explícito para evitar a condição de sobrecarga. Isto é desnecessário, pois a única maneira de
acrescentar à pilha é por meio desse método e este entrará no estado wait() se for chamado para
provocar sobrecarga. Portanto, a verificação de erro é desnecessária. Podemos estar confiantes
sobre isso num sistema em execução por um outro motivo. Se a lógica resultar em falha, faremos
acessos de matriz fora de alcance que imediatamente fará com que uma Exception seja lançada,
para que não haja a possibilidade de o erro passar desapercebido. É possível utilizar exceções de
tempo de execução para criar nossas próprias verificações em outras circunstâncias.
O método pop() é semelhante e os comentários que acabamos de fazer também se referem a ele.
Essas peças precisam ser montadas em classes completas e ter um apetrecho anexado para
manter tudo em execução. Eis o código final.
SyncTest.java
1 package mod13;
2 public class SyncTest
3 {
4 public static void main(String args[]) {
5 SyncStack stack = new SyncStack();
6 Runnable source = new Producer(stack);
7 Runnable sink = new Consumer(stack);
8 Thread t1 = new Thread(source);
9 Thread t2 = new Thread(sink);
10 t1.start();
11 t2.start();
12 } 1
13}
Producer.java
1 package mod13;
2 public class Producer implements Runnable
3 {
4 SyncStack theStack;
5
Consumer.java
1 package mod13;
2 public class Consumer implements Runnable
3 {
4 SyncStack thestack;
5
6 public Consumer(SyncStack s) {
7 thestack - s;
8 }
9
10 public void run() {
11 char c;
12 for (int i = 0; i < 20; i++) {
13 c = theStack.pop();
14 System.out.println("Consumido: " + c);
15 try {
16 Thread.sleep((int)(Math.random() * 1000));
17 } catch (InterruptedException e) {
18 //ignore-o
19 }
20 }
21 }
22 }
SyncStack.java
1 package mod13;
2 public class SyncStack
3 {
4 private int index - 0;
5 private char [] buffer = new char[6];
6
7 public synchronized char pop() {
8 while (index == 0) {
9 try {
10 this.wait();
11 } catch (InterruptedException e) {
Objetivos
Quando processos se comunicam via rede, Java usa o seu modelo de fluxo. Um soquete e mantém
dois fluxos: um fluxo de entrada em um fluxo de saída para enviar dados a um outro processo via
rede, basta que um processo escreva para o fluxo de saída associado ao soquete. Para ler os dados
escritos por outro processo, o processo deve ler a partir do fluxo de entrada associado ao soquete
.
Quando a conexão em rede estiver operacional, o uso de fluxos associados a essa conexão será
semelhante ao uso de qualquer outro fluxo.
Estabelecendo a conexão
Para estabelecer a conexão, uma máquina precisa estar executando um programa que aguarde
uma conexão e a outra extremidade deve tentar alcançar a primeira. É como um sistema telefônico:
uma pessoa faz a chamada enquanto a outra precisa estar aguardando o seu recebimento quando
do momento da chamada.
Fazendo a conexão
Ao fazer uma chamada telefônica, você precisa saber o número a ser discado. Ao fazer a conexão
em rede, você precisa saber um endereço ou nome da máquina remota. Além disso, em uma
conexão de rede, o número de porta que equivale a um número de ramal. Após conectar-se ao
computador apropriado, identifique o objetivo da conexão. Então, assim como um número de
extensão específico é usado para permitir a comunicação com o departamento de contabilidade, o
número de porta específico deve ser usado para permitir a comunicação com o programa de
contabilidade.
Número de portas
Deve haver um acordo prévio entre os dois programas: o cliente deve iniciar a comunicação e o
servidor deve "aguardar a chamada". Se os nomes das portas usadas pelas duas partes do sistema
não coincidirem, não haverá comunicação.
Na linguagem Java, as conexões ao soquete TCP/IP são implementadas com classes do pacote
java.net. O diagrama a seguir mostra o que ocorre no lado servidor e no lado cliente.
• O servidor atribui o número de porta. Quando o cliente é solicita uma conexão, o servidor
abre a conexão de soquete através do método accept ( ).
• O cliente que estabelece uma conexão com o host na porta port #.
• O cliente que o servidor se comunicam através de um InputStream e um OutputStream.
Observe uma amostra do código ser implementado para esse modelo simples nas próximas duas
páginas :
1 import java.net.*;
2 import java.io.*;
3
4 public class SimpleServer {
5 public static void main(String args[]) {
6 ServerSocket s = null;
7 Socket s1;
8 String sendString = "Olá mundo da rede!";
9 int slength = sendString.length();
10 OutputStream s1out;
11 DataOutputStream dos;
12
13 //Registre seu serviço na porta 5432
14 try {
15 s = new ServerSocket (5432);
1 import java.net.*;
2 import java.io.*;
3
4 public class SimpleClient {
5 public static void main(String args[]) throws IOException {
6 int c;
7 Socket s1;
8 InputStream s1In;
9 DataInputStream dis;
10
11 //Abra sua conexão para sunbert na porta 5432
12 s1 = new Socket ("127.0.0.1",5432);
13 // Obtenha um handle do arquivo de entrada a partir do soquete e
leia a entrada
13 s1In = s1.getInputStream();
14 dis = new DataInputStream (s1In);
15
16 String st = new String (dis.readUTF());
17 System.out.printIn(st);
18
19 // Quando terminar, feche a conexão e saia
20 dis.close();
21 s1In.close();
22 s1.close();
23 }
24 }
DatagramPacket
DatagramPacket tem dois construtores: um para receber dados e outro para enviar dados:
Soquetes UDP
DatagramSocket
O DatagramSocket é usado na leitura e gravação de pacotes UDP. Essa classe tem três
construtores que permitem especificar a porta e o endereço de internet aos quais será feito o
vínculo:
Objetivos
Servlets são o paralelo tecnológido de Java aos CGIs (Commom Gateway Interface). São
programas que executam em um servidor Web, agindo como camada intermediária entre uma
requisição vinda de um navegador Web ou outro cliente HTTP e bancos de dados ou aplicações no
servidor HTTP ou outros servidores.
Sua função é:
Java Servlets são mais eficientes, simples para uso, mais poderosos, mais portáveis, seguros e
baratos que CGIs tradicionais e muitas alternativas a CGIs.
Eficiente
Não inicia novo processo a cada cliente.
Conveniente
Já decodifica informações HTTP sem onerar o desenvolvimento com isso.
Poderoso
Pode acessar funções nativas do servidor Web e é baseado em uma linguagem de baixo nível.
Portável
Por serem escritos em Java, os Servlets podem migrar de servidor e sistema operacional sem
recompilação.
Seguros
Muitos problemas de segurança baseados em caracteres codificados em requisições são eliminados
em servlets.
Baratos
Pode-se implementar servlets em servidores gratuitos, com sistemas operacionais gratuitos.
Em servlets, o servidor Web trata a maior parte do processo, sendo responsável pela inicialização
do código Java e pela chamada adequada e fornecimento das APIs. Temos diversos servidores
Web compatíveis com servlets e a implementação de referência, o Jakarta Tomcat, que é parte do
projeto Apache, e portanto, gratuito.
Enquanto em servlets, a codificação pode se tornar grande, em JSP, o código Java é mesclado com
a codificação Html, diminuindo o ciclo de desenvolvimento. A sintaxe é idêntica ao Servlet,
respeitadas as tags Html para início e fim do código JSP.
A listagem abaixo mostra um servlet básico que manuseia requisições GET. A requisição GET é o
tipo usual de requisições de um navegador Web. Servlets podem também manusear requisições
POST.
Existem ainda as exceções que devem ser tratadas e as classes de herança, apontadas na
listagem.
import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
A listagem abaixo mostra um servlet simples enviando uma página estática como resposta. O servlet
não está tratando ainda nenhum parâmetro.
import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
response.setContentType(“text/html”);
PrintWriter out = response.getWriter();
out.println(“<HTML>\n”);
out.println(“<HEAD><TITLE>Ola Mundo</TITLE></HEAD>\n”);
out.println(“<BODY>\n”);
out.println(“<H1>Ola Mundo</H1>\n”);
out.println(“</BODY></HTML>\n”);
}
}
O servlet será iniciado segundo as regras do servidor em que está baseado, mas geralmente será
iniciado pelo próprio nome da classe na URL, como em
Para que uma página tenha um maior grau de dinamismo, é desejável que o usuário possa enviar
parâmetros através de controles de um formulário HTML em uma página web e receba um conteúdo
gerado através desses parâmetros.
Para recebermos parâmetros, devemos possuir uma página Web que possa enviar parâmetros por
um método POST ou GET. Na listagem abaixo, uma página contendo um formulário simples envia
três parâmetros por método GET. Se for desejado enviar os parametros por POST, deve ser
acrescentado method=”POST” na declaração do FORM. Os pontos mais importantes, como a
página que irá tratar a informação e o nome dos parâmetros estão em negrito.
<HTML>
<HEAD>
<TITLE>Enviando tres parametros</TITLE>
</HEAD>
<BODY>
<H1> Coletando tres parametros</H1>
<FORM ACTION="/servlet/TresParametros">
Primeiro parametro: <INPUT TYPE="TEXT" NAME="param1"><BR>
Segundo parametro: <INPUT TYPE="TEXT" NAME="param2"><BR>
Terceiro parametro: <INPUT TYPE="TEXT" NAME="param3"><BR>
<INPUT TYPE="SUBMIT" VALUE=”OK”>
</FORM>
</BODY>
</HTML>
Para se tratar parâmetros enviados por GET ou POST no código Servlet, é necessário que se
capture os parâmetros de forma semelhante ao tratamento de parâmetros de linha de comando. Da
mesma forma que na linha de comando, os parâmetros serão enviados como Strings, ficando a
cargo do servlet converter para outros tipos primitivos ou classes. O método getParameter, aplicado
em request irá retornar os parâmetros.
import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
response.setContentType(“text/html”);
PrintWriter out = response.getWriter();
out.println(“<HTML>\n”);
out.println(“<HEAD><TITLE>Parametros</TITLE></HEAD>\n”);
out.println(“<BODY>\n”);
out.println(“<H1>Primeiro parâmetro:</H1>\n”);
out.println(request.getParameter(“param1”)+”<BR>”);
out.println(“<H1>Segundo parâmetro:</H1>\n”);
out.println(request.getParameter(“param2”)+”<BR>”);
out.println(“<H1>Terceiro parâmetro:</H1>\n”);
}
}
Dessa forma, podemos enviar os parâmetros necessários ao código servlet, e acessar quaisquer
recursos ou informações necessárias para montar a página de resposta.
Apresentações de Introdução