Apostila Java
Apostila Java
Apostila Java
Pág 1
1. INTRODUÇÃO.............................................................................................................5
1.7 Exercícios:....................................................................................................................16
2.3 Exercícios......................................................................................................................23
4.3 Expressões....................................................................................................................35
4.6 Exercícios......................................................................................................................45
5.1 Classe.............................................................................................................................46
Pág 2
5.4 Criando Classes...........................................................................................................54
5.5 Exercícios......................................................................................................................76
6.1 Herança.........................................................................................................................80
6.6 Interfaces......................................................................................................................86
6.10 Exercícios....................................................................................................................97
7. INTERFACE GRÁFICA..........................................................................................100
7.1Widgets........................................................................................................................100
7.6 Exercícios....................................................................................................................114
8. APPLETS.................................................................................................................115
8.1 Introdução..................................................................................................................115
Pág 3
8.5 Adicionando um Applet a uma Página HTML.....................................................118
8.6 Exercícios....................................................................................................................119
9. THREADS................................................................................................................120
9.5 Exercícios....................................................................................................................126
10.1 Introdução................................................................................................................127
Pág 4
“You know you've achieved perfection in design,
Not when you have nothing more to add,
But when you have nothing more to take away.”
1. Introdução
Em 1990, um senhor chamado James Gosling recebeu a tarefa de criar aplicações para
eletrodomésticos. Gosling e sua equipe, da Sun Microsystems, começaram a desenvolver
seu software utilizando C++, uma linguagem considerada atual devido às suas
características de orientação a objetos. Entretanto, Gosling e sua equipe perceberam
rapidamente que C++ não era a linguagem mais adequada para aquele projeto. Aspectos do
C++, como herança múltipla de classes e leaks de memória, dificultavam bastante o
desenvolvimento das aplicações.
Então, Gosling chegou a uma conclusão simples: era necessário criar sua própria
linguagem de programação. Esta linguagem deveria ser simples o bastante para evitar
todos aqueles problemas que estavam relacionados com o C++.
Para desenvolver esta nova linguagem, Gosling usou o próprio C++ como modelo,
aproveitando a sintaxe básica da linguagem e a sua natureza orientada a objetos e retirando
aquelas características que tornavam a linguagem mais complexa. Ao terminar de projetar
sua nova linguagem, Gosling a chamou de Oak (pinheiro, em inglês. Este nome surgiu
pois, da janela do escritório de Gosling, pode-se observar um belo pinheiro).
Oak foi usado pela primeira vez em um projeto chamado Green, onde tentou-se projetar
um sistema de controle remoto para uso doméstico. Este sistema permitiria ao usuário
controlar vários dispositivos (TV, video-cassete, luzes, telefones, etc.) a partir de um
computador portátil chamado *7 (Star Seven). Oak também foi utilizado em um projeto de
vídeo sob demanda (Video on Demand, VOD) como base para um software de controle de
uma TV interativa. Apesar dos projetos Green e VOD não tenham gerado produtos
comerciais, eles proporcionaram a linguagem Oak uma chance de desenvolver-se a
amadurecer.
Depois de algum tempo, a Sun descobriu que o nome Oak já estava registrado por outra
companhia e decidiu trocar o nome da linguagem para Java (segundo a lenda, em
Pág 5
homenagem ao gosto da equipe por café. Java é uma ilha da Indonésia famosa por produzir
tal bebida).
Em 1993, depois da World Wide Web ter transformado a Internet em um ambiente rico em
gráficos, a equipe Java percebeu que a linguagem que desenvolveram era perfeita para
programação Web. Surgiu então o conceito de applets, pequenos programas que podiam
ser incluídos em páginas Web. Além disto, desenvolveram um browser Web completo
(chamado HotJava) para demonstrar o poder da linguagem Java.
A seguir, são apresentadas algumas características da linguagem Java que a tornam tão
interessante e adequada para o desenvolvimento de uma nova geração de aplicações.
Java, ao contrário de C++, foi projetada desde o início para ser orientada a objetos. O
paradigma de orientação a objetos, criado a cerca de 30 anos, provou-se adequado para o
desenvolvimento dos sistemas atuais, cada vez mais complexos.
Pág 6
1.2.2 Robusta e Segura
Java foi projetada para criar software altamente confiável. Além de prover verificações
durante a compilação, Java possui um segundo nível de verificações (em tempo de
execução). As próprias características do Java ajudam o programador a seguir bons hábitos
de programação.
O modelo de gerência de memória é extremamente simples: objetos são criados por meio
de um operador new. Não existem tipos como apontadores, nem aritmética de
apontadores. A liberação da memória dos objetos que não são mais referenciados é feita
por meio de um mecanismo chamado garbage collection (coleta de lixo). Desta forma,
elimina-se um importante tipo de erro que atormentava os programadores C++.
Como Java foi desenvolvida para operar em ambientes distribuídos, aspectos de segurança
são de fundamental importância. A linguagem possui características de segurança e a
própria plataforma realiza verificações em tempo de execução. Assim, aplicações escritas
em Java estão “seguras” contra códigos não autorizados que tentam criar vírus ou invadir
sistemas de arquivos.
Esta arquitetura neutra e portável é chamada de Máquina Virtual Java (Java Virtual
Machine, JVM, seção ).
Pág 7
Existem soluções como compiladores Just-in-Time (JIT), que compilam os byte-codes Java
para a linguagem nativa da máquina em particular, que podem melhorar consideravelmente
o desempenho das aplicações. Atualmente, pode-se dizer que, para aplicações
fundamentalmente interativas, os usuários não notam diferenças significativas entre
aplicações desenvolvidas em Java e outras linguagens compiladas.
Java suporta multithreading no nível da linguagem (suporte nativo e não por meio de
bibiotecas externas). Além disto, a linguagem oferece uma série de primitivas sofisticadas
de sincronização (semáforos, por exemplo). Um fato importante é que todas as bibliotecas
de sistema da linguagem Java foram desenvolvidas para serem “thread safe”, ou seja, não
existe a possibilidade de ocorrência de conflitos caso threads concorrentes executem
funções destas bibliotecas.
A linguagem Java é dinâmica pois as classes (código) somente são ligadas (linked) a
aplicação quando necessário. Novos módulos podem ser ligados sob demanda a partir de
diversas fontes (inclusive, através da própria rede). Esta característica proporciona a
possibilidade de se atualizar transparentemente as aplicações.
Pág 8
• Iniciar rapidamente o desenvolvimento: embora Java seja uma linguagem orientada
a objetos, é fácil de aprender, especialmente para aqueles programadores familiarizados
com C ou C++.
• Escrever menos código: comparações de métricas de programas (número de classes,
métodos, etc.) sugerem que um programa escrito em Java pode ser até quatro vezes
menor do que um mesmo programa desenvolvido em C++.
• Escrever um código melhor: a linguagem encoraja boas práticas de programação, e
seu esquema de gerência de memória (garbage collection) ajuda a evitar os temidos
leaks de memória. A característica de orientação a objetos e a extensa API (Application
Program Interface) permite que um programador reutilize facilmente código
desenvolvido (e testado) por outras pessoas.
• Desenvolver programas mais rapidamente: o tempo necessário para desenvolver
uma aplicação em Java pode ser até duas vezes menor do que construir o mesmo
programa em C++. Por que? Escreve-se menos linhas de código em Java e a linguagem
é mais simples que C++.
• Evitar dependências de plataforma: utilizando alguns conselhos simples de
programação, consegue-se criar aplicações 100% Java (100% Pure Java) que executam
corretamente em qualquer plataforma: Write once, run anywhere.
• Distribuir software mais facilmente: pode-se facilmente atualizar aplicações (applets,
por exemplo) a partir de um servidor central.
Entretanto, Java não serve apenas para escrever applets bonitinhos e divertidos para
WWW. Java é uma linguagem de propósito geral e uma poderosa plataforma de software.
Utilizando a extensa biblioteca (API) Java, pode-se desenvolver programas Java
standalone (isto é, que não executam em browsers) para uma ampla variedade de
aplicações.
Outro tipo de programa especial do Java é o servlet. Servlets são similares a applets pois
também são extensões a um ambiente de execução. Em vez de executar em um browser, os
servlets executam em servidores Java (mail, WWW, por exemplo).
Como a API do Java suporta todos estes tipos de programas? A API do Java é dividida em
pacotes (packages) de componentes de software que oferecem uma ampla gama de
funcionalidades. Existe uma API central (core API) que está incluída em qualquer
implementação da plataforma Java. Esta API central proporciona as seguintes
funcionalidades:
Pág 9
• Applets: funções utilizadas pelos applets.
• Networking: URLs, TCP and UDP sockets, and endereços IP.
• Internacionalização: auxilia o desenvolvimento de programas que possam ser
localizados para usuários de todo o mundo. Os programas podem se adaptar
automaticamente a locais específicos e apresentar a linguagem apropriada.
• Segurança: suporte, em alto e baixo nível, para assinaturas eletrônicas, gerência de
chaves públicas e privadas, controle de acesso e certificados.
• Serialização: permite um esquema de persistência leve e uma comunicação via Remote
Method Invocation (RMI).
• Java Database Connectivity (JDBC): oferece um acesso uniforme a uma grande
variedade de bancos de dados relacionais.
Além da API central, Java possui extensões padronizadas. Estas extensões definem API´s
para manipulação de imagens 3D, servidores, groupware, telefonia, processamento de fala,
animação, entre outras.
• Simples
• Arquitetura neutra, portável
• Interpretada
• Orientada a objetos
• Distribuída
• Alto desempenho
• Multithreaded
• Robusta
• Segura
• Dinâmica
Pág 10
Pode-se imaginar os bytecodes como a “linguagem assembly” da Máquina Virtual Java
(Java Virtual Machine, JVM). Todo interpretador Java, presente em uma ferramenta de
desenvolvimento ou em um browser, é uma implementação da JVM. A JVM também pode
ser implementada diretamente em hardware.
O conceito de bytecodes auxilia a tornar o lema “write once, run anywhere” possível.
Pode-se compilar os programas Java em qualquer plataforma que possuir um compilador
Java. O produto da compilação (bytecodes) pode então ser executado em qualquer
implementação da Máquina Virtual Java. Assim, o mesmo programa Java pode executar
em Windows NT, Solaris e Macintosh sem a necessidade de recompilação (figura a
seguir).
Pág 11
A API do Java é uma grande coleção de componentes de software, prontos para uso, que
oferecem muitas funcionalidades úteis como elementos de interface gráfica (GUI). A API
Java é agrupada em bibliotecas (packages) de componentes relacionados.
A figura a seguir ilustra um programa Java, standalone ou applet, que executa em uma
plataforma Java. Note que, a API e a JVM da plataforma Java “isolam” o programa Java
das diferenças das plataformas de hardware.
Programa Java
API Java
Plataforma Java
Plataforma Hardware
A versão mais recente do JDK é a 1.2.2 (a Sun passou a denominar a versão 1.2 do JDK de
Java 2). Entretanto, a versão da API que é suportada pelos browsers atuais (IE 5.0 e
Communicator 4.6) é a 1.1.8.
Pág 12
1.6.1 Aplicação “Hello World”
Nesta seção, são descritos os passos para a criação de uma aplicação Java “standalone”.
Usando um editor de texto qualquer (notepad, Word, Textpad, etc.), crie um arquivo
(ASCII) chamado HelloWorldApp.java com o seguinte código Java:
/**
* A classe HelloWorldApp implementa uma aplicação que
* simplesmente mostra "Hello World!" na saída padrão (monitor).
*/
class HelloWorldApp {
public static void main(String[] args) {
System.out.println("Hello World!"); //Mostra a string
}
}
javac HelloWorldApp.java
Executar a Aplicação
java HelloWorldApp
Pág 13
1.6.2 Anatomia da Aplicação
Uma vez executada a primeira aplicação Java, vamos analisar esta aplicação standalone.
Comentários em Java
A aplicação “Hello World” tem dois blocos de comentários. O primeiro bloco, no início do
programa, usa os delimitadores /** e */. Depois, uma linha de código é explicada por meio
de um comentário marcado com os caracteres //. A linguagem Java suporta um terceiro
tipo de comentário, estilo C, delimitado por /* e */.
Na linguagem Java, cada método (função) e variável deve existir dentro de uma classe ou
objeto (uma instância de uma classe). A linguagem Java não suporta funções ou variáveis
globais. Portanto, o esqueleto de qualquer programa Java é uma definição de classe.
class name {
...
}
O Método main
O ponto de entrada de qualquer aplicação Java é o método main. Quando se executa uma
aplicação, via interpretador Java, na verdade, especifica-se o nome da classe a ser
executada. O interpretador, então, invoca o método main definido dentro daquela classe. O
método main controla o fluxo do programa, aloca recursos e executa quaisquer outros
métodos que fazem parte da funcionalidade da aplicação.
A aplicação “Hello World”, por exemplo, utiliza uma outra classe, System, que é parte da
API que acompanha o ambiente Java. A classe System oferece acesso a funções do sistema
operacional.
Pág 14
1.6.3 Applet “Hello World”
Nesta seção, são descritos os passos para a criação de um applet Java, que pode ser
executado em um browser Web.
Usando um editor de texto qualquer (notepad, Word, Textpad, etc.), crie um arquivo
(ASCII) chamado HelloWorld.java com o seguinte código Java:
import java.applet.Applet;
import java.awt.Graphics;
javac HelloWorld.java
<HTML>
<HEAD>
<TITLE> Um Programa Simples </TITLE>
</HEAD>
<BODY>
Pág 15
</APPLET>
</BODY>
</HTML>
Executar o Applet
Para executar o applet, deve-se carregar o arquivo HTML em um ambiente que possa
executar applets Java. Este ambiente pode ser um browser compatível com Java ou um
programa para visualização de applets, como o Applet Viewer que acompanha o JDK.
No browser, para carregar o applet, pode-se digitar, no campo Location, uma URL do tipo:
file:/home/asouza/HTML/Hello.html
appletviewer c:\src\Hello.html
1.7 Exercícios:
Pág 16
2. Conceitos de Orientação a Objetos
2.1.1 Objetos
Estes objetos do mundo real compartilham duas características: possuem um estado e tem
um comportamento. Por exemplo, um cachorro tem um estado (nome, cor, raça, com
fome) e um comportamento (latindo, comendo, lambendo). Analogamente, objetos de
software são modelados de acordo com os objetos do mundo real, ou seja, possuem
também estado e comportamento. Um objeto de software armazena seu estado em
variáveis e implementa seu comportamento com métodos.
Portanto, pode-se representar objetos do mundo real utilizando objetos de software. Além
disto, objetos de software podem ser usados para modelar conceitos abstratos (por
exemplo, um evento de interface gráfica que representa a ação do usuário pressionando
uma tecla).
Pág 17
A ilustração a seguir é uma representação comum do conceito de objeto:
API (pública)
2.1.2 Mensagens
Geralmente, um objeto sozinho não é muito útil e aparece como um componente de uma
aplicação maior que contém vários outros objetos. A interação entre os objetos é que
permite se obter todas as funcionalidades de uma aplicação. Por exemplo, uma bicicleta só
é útil quando um outro objeto (ciclista) interage com ela.
Pág 18
Mensagem
Objeto A Objeto B
Algumas vezes, o objeto que recebe a mensagem precisa de mais informações para saber
exatamente o que fazer. Por exemplo, quando você quer trocar as marchas em uma
bicicleta, deve-se indicar qual a marcha desejada. Esta informação acompanha a mensagem
como um parâmetro.
Observação: objetos não precisam estar no mesmo processo ou na mesma máquina para
enviar e receber mensagens de uns para os outros.
2.1.3 Classes
No mundo real, muitas vezes existem vários objetos do mesmo tipo. Por exemplo,
utilizando a terminologia de orientação a objetos, pode-se dizer que um objeto bicicleta é
uma instância de uma classe de objetos conhecida como bicicletas. Bicicletas possuem
estado e comportamento comuns. Entretanto, o estado de cada bicicleta é independente e
pode ser diferente de outras bicicletas.
Em software orientado a objetos, é possível ter vários objetos do mesmo tipo que
compartilham características. Desta forma, pode-se tirar vantagem de que os objetos do
mesmo tipo são similares e criar uma “fôrma” para estes objetos. Tais fôrmas de software
são chamadas de classes .
Resumindo, uma classe é uma fôrma (protótipo) que define as variáveis e métodos comuns
a todos os objetos de um certo tipo.
Valores de variáveis de instância existem para cada instância (objeto) da classe. Assim,
depois de criar a classe bicicleta, deve-se instanciá-la a fim de utilizar seus objetos.
Quando se cria uma instância de uma classe, cria-se um objeto daquele tipo e o sistema
aloca memória para as variáveis de instância definidas para a classe. Depois de criado,
pode-se invocar os métodos de instância do objeto.
Pág 19
Além das variáveis e métodos de instâncias, classes podem também definir variáveis de
classe (class variables) e métodos de classe (class methods). Pode-se acessar variáveis e
métodos de classe sem ter a necessidade de se instanciar um objeto da classe. Métodos de
classe só podem manipular variáveis de classe, ou seja, não podem acessar métodos ou
variáveis de instância.
O sistema cria uma única cópia de uma variável de classe, ou seja, todos os objetos daquela
classe compartilham as mesmas variáveis de classe.
• Encapsulação
• Herança
• Polimorfismo
2.2.1 Encapsulação
Pág 20
2.2.2 Herança
De maneira geral, objetos são definidos em termos de classes. Consegue-se obter muitas
informações sobre um objeto conhecendo a sua classe. Por exemplo, você pode não saber o
que é uma penny-farthing, mas se lhe disserem que é uma bicicleta, você consegue
imaginar o objeto (duas rodas, pedais, assento, etc.).
Sistemas orientados a objetos permitem também que classes sejam definidas em termos de
outras classes. Por exemplo, mountain bikes e bicicletas de corrida são diferentes tipos de
bicicletas. Na terminologia de orientação a objetos, mountain bikes e bicicletas de corrida
são subclasses da classe bicicleta. Analogamente, a classe bicicleta é uma superclasse de
mountain bikes e bicicletas de corrida.
Bicicletas
Cada classe herda o estado (na forma das declarações de variáveis) da superclasse.
Mountain bikes e bicicletas de corrida compartilham alguns estados (velocidade, por
exemplo). Além disto, cada subclasse herda os métodos da superclasse. Mountain bikes e
bicicletas de corrida compartilham certos comportamentos (frear, por exemplo)
O conceito de herança pode ser aplicado para mais de um nível. A árvore de herança, ou
hierarquia de classe pode ser tão “profunda” quanto necessário. Os métodos e variáveis
são herdados através dos níveis. Em geral, quanto mais baixa na hierarquia for a posição de
uma classe, mais especializado é o seu comportamento.
Pág 21
• Subclasses oferecem comportamentos especializados a partir de elementos básicos
comuns, oferecidos pela superclasse. Por meio da utilização de herança, programadores
podem reusar o código da superclasse várias vezes.
• Programadores podem definir classes abstratas que determinam comportamentos
“genéricos”. A superclasse abstrata define e pode implementar parcialmente o
comportamento de uma classe mas a maioria das informações da classe fica indefinida
ou não é implementada. Outros programadores completam estes detalhes por meio de
subclasses especializadas.
2.2.3 Polimorfismo
Existem vários tipos de polimorfismo. O primeiro tipo, descrito no item anterior, é quando
uma classe redefine a implementação de um método herdado. Este polimorfismo é
classificado como polimorfismo de inclusão.
Conta conta_normal_do_joao;
ContaEspecial conta_especial_do_joao;
Conta contas_do_cliente[20]
...
contas_do_cliente[0] = conta_normal_do_joao;
contas_do_cliente[1] = conta_especial_do_joao;
...
contas_do_cliente[0].retirada(); // O interpretador invoca o método de Conta
contas_do_cliente[1].retirada(); // O interpretador invoca o método de
ContaEspecial
Pág 22
2.3 Exercícios
Pág 23
3. Diferenças entre Java e C/C++
Java é bastante parecido com C, o que torna relativamente simples que programadores C
aprendam a linguagem. Entretanto, existe uma série de diferenças importantes entre as
linguagens como o mecanismo de tratamento de exceções.
Apesar de Java utilizar muitas das terminologias do C++, as analogias entre Java e C++
não são tão fortes quanto aquelas entre Java e C. Assim, programadores C++ devem tomar
cuidado para não ter uma falsa sensação de familiaridade com Java só porque as
linguagens compartilham uma série de palavras-chaves.
Como visto no exemplo da seção , toda a aplicação Java standalone deve possuir uma
classe que defina o método main(). Este método tem a seguinte assinatura:
Observe, na seção , que a função main() deve ser declarada com parâmetro de retorno do
tipo void. Portanto, não é possível retornar um valor de seu programa Java com return na
função main(). Se for necessário retornar algum valor, deve-se usar o método
System.exit() com o valor desejado.
3.1.3 Comentários
Pág 24
3.1.4 Caracteres Unicode
O formato Unicode é compatível com o ASCII pois os 256 primeiros caracteres Unicode
são idênticos aos caracteres do padrão ISO8859-1 (Latin-1). Além disto, a API de
manipulação de Strings torna a representação de caracteres totalmente transparente para o
programador. Assim, se você estiver utilizando apenas caracteres Latin-1, não há como
distinguir um caracter Java de 16 bits de caracteres de 8 bits convencionais.
Variáveis globais simplesmente não fazem parte da linguagem. Também não existem
funções ou procedimentos “globais”.
Em Java, toda variável e método deve ser declarada dentro de uma classe. Assim, toda
variável (método) em Java deve ser referenciado pelo seu nome qualificado, que consiste
do nome do pacote que contém a classe, o nome da classe (ou objeto) e o nome do membro
(variável ou método) da classe, separados por ponto. Exemplos:
MinhaClasse MeuObjeto;
Java não inclui nenhum tipo de pré-processador. Assim, não existem diretivas como
#define, #include, #ifdef e nem typedef.
Pág 25
Em Java, obtém-se os efeitos de #define utilizando constantes. Para substituir o efeito do
typedef basta declarar classes, afinal de contas uma classe efetivamente define um novo
tipo.
Removendo todos estes recursos, a linguagem Java torna-se bastante livre de contexto. Ou
seja, programadores podem ler, entender e modificar código de maneira mais fácil e rápida.
Java não possui estruturas ou uniões como tipos de dados. Não há a necessidade de
estruturas ou uniões quando se possui classes; obtém-se o mesmo efeito simplesmente
declarando uma classe com as variáveis de instância apropriadas.
Java não possui tipos enum. Pode-se obter algo similar declarando uma classe que possua
apenas constantes. Exemplo:
class Direcao {
public static final int Norte = 1;
public static final int Sul = 2;
public static final int Leste = 3;
public static final int Oeste = 4;
}
Utilizar classes que contêm constantes proporciona uma vantagem em relação aos tipos
enumerados de C/C++. Em C/C++, nomes definidos via enums devem ser únicos: se você
possui um tipo enumerado chamado HotColors contendo os nomes Vermelho e Amarelo,
você não pode utilizar estes nomes em nenhum outro enum. Com Java, pode-se utilizar o
mesmo nomes em diferentes classes pois os nomes são qualificados pela classe que os
contêm.
Pág 26
3.1.9 Sem herança múltipla
Herança múltipla, e todos os problemas que pode causar, foi descartada de Java. As
vantagens da herança múltipla são proporcionadas por interfaces (que serão discutidas
mais profundamente em uma aula próxima).
Uma interface não é uma definição de uma classe. Na verdade, é uma definição de um
conjunto de métodos que uma ou mais classes irão implementar. Interfaces só podem
declarar métodos e constantes. Variáveis não podem ser definidas em interfaces.
Java não possui um comando goto. A experiência mostra que o uso de goto, em geral, leva
a programas mais difíceis de entender e modificar.
Em Java não é possível sobrecarregar os operadores aritméticos padrões (+, -, ++, <<).
Esta decisão de projeto da linguagem,auxilia a simplificação do código-fonte.
Java não possui apontadores e não permite manipular endereços de memória de nenhuma
forma:
• Apontadores são famosos por ser uma fonte quase inesgotável de bugs. Eliminando-os,
simplifica-se a linguagem e elimina-se vários potenciais bugs.
• Apontadores e aritmética de ponteiros podem ser utilizadas para “contornar” os
mecanismos de segurança do Java. Removendo os apontadores, temos um ambiente
mais seguro e robusto.
Para um programador C, a falta de apontadores pode ser uma restrição infeliz do Java.
Mas, uma vez, acostumado com o modelo de programação Java, esta restrição não parecerá
tão importante. Na verdade, o uso de apontadores é essencial apenas para programação de
baixo nível (device drivers, por exemplo).
Pág 27
4. Construções Básicas da Linguagem Java
import java.io.*;
public class Count {
public static void countChars(Reader in) throws IOException
{
int count = 0;
Todas as variáveis em Java possuem um tipo, nome e escopo. Por exemplo, o método
countChars define duas variáveis:
int count = 0;
Reader in
A declaração de uma variável sempre contém dois componentes: o tipo da variável e seu
nome. A localização de uma declaração de variável, ou seja, onde a declaração aparece em
relação a outros elementos do código, determina seu escopo.
O tipo de dados de uma variável determina quais valores a variável pode conter e as
operações que podem ser realizadas sobre ela. Por exemplo, a declaração int count define
que a variável count é um inteiro (int). Inteiros só podem possuir valores integrais
(positivos ou negativos) e pode-se utilizar os operadores aritméticos (+,-,*,/) sobre este tipo
de variável.
Pág 28
Existem duas grandes categorias de tipos de dados na linguagem Java: primitivo e
referência. A tabela a seguir apresenta todos os tipos primitivos suportados por Java.
Vetores (arrays), classes e interfaces são tipos de referência. O valor de uma variável deste
tipo, em contraste com os tipos primitivos, é uma referência para o valor ou conjunto de
valores representados pela variável. A referência é como o endereço de um amigo: o
endereço não é o seu amigo, mas é uma forma de se alcançá-lo.
O método countChars usa uma variável do tipo de referência, in¸ que é um objeto
Reader. Assim, pode-se utilizar o nome do objeto para acessar suas variáveis ou chamar
algum de seus métodos.
Observação Importante: Em Java, tipos primitivos sempre são passados por valor; já
vetores e objetos são passados por referência. Exemplo:
int a,b;
a = 1;
b = a;
a = 2; // a = 2 e b = 1 pois int é um tipo primitivo (passagem por valor)
Button p, q;
p = new Button();
q = p; // q refere-se ao mesmo objeto que p (passagen por referência)
p.setLabel(“Ok”); // Uma mudança no objeto via p
String s = q.getLabel(); // s contém “Ok”
Convenção: Nomes de variáveis começam com uma letra minúscula e nomes de classes
com uma letra maiúscula. Se o nome da variável consiste de mais de uma palavra, as
palavras são agrupadas e cada palavra depois da primeira começa com uma letra maiúscula
(exemplo: numeroClientes).
Pág 29
dentro do programa estabelece o seu escopo. De acordo com o seu escopo, uma variável
pode ser das seguintes categorias:
• Variável membro
• Variável local
• Parâmetro de método
• Parâmetro de tratamento de exceção
Uma variável membro está associada a um objeto ou a uma classe. Pode ser declarada em
qualquer lugar dentro de uma classe, desde que fora de um método. Uma variável membro
está acessível para todo código dentro da classe. A classe Count não declara nenhuma
variável membro.
Parâmetros de tratamento de exceção são similares aos parâmetros de método mas são
argumentos de um tratador (handler) de exceções em vez de um método ou construtor. O
método countChars não possui tratadores de exceção e, portanto, não possui parâmetros
desta categoria.
Variáveis locais e variáveis membros podem ser inicializadas na sua própria declaração.
Por exemplo, o método countChars fornece um valor inicial de zero para a variável count:
int count = 0;
Pág 30
Parâmetros de método de tratadores de exceção não podem ser inicializados desta forma. O
valor do parâmetro é determinado pelo objeto que invoca o método.
Pode-se declarar qualquer variável como final, incluindo parâmetros. O valor de uma
variável final não pode ser alterado depois de inicializado. Para declarar uma variável
deste categoria, basta usar a palavra chave final antes do tipo:
4.2 Operadores
Operadores realizam alguma função em um, dois ou três operandos. Por exemplo, ++ é um
operador unário, = é um operador binário. Java possui um único operador ternário ? :
(equivalente a um if-else).
Além de realizar uma função, um operador também retorna um valor. O valor e seu tipo
dependem do operador e dos tipos de operandos. Por exemplo, operadores aritméticos
retornam números. O tipo de dado retornado vai depender do tipo de operandos: se dois
inteiros são somados, o resultado é um inteiro.
Java suporta vários operadores aritméticos para número inteiros e de ponto flutuante. A
tabela a seguir lista os operadores aritméticos binários da linguagem:
Pág 31
System.out.println("Counted " + count + " chars.");
Existem também dois outros operadores aritméticos unários, ++ que incrementa seu
operando de 1 e – que decrementa seu operando de 1. O método countChars usa ++ para
incrementar a variável count cada vez que é lido um caracter da entrada:
count++;
Observe que, no exemplo, o operador ++ aparece depois de seu operando (versão pós-
fixa). ++ também possui uma versão pré-fixa onde o operador aparece antes do operando.
Ambas as versões incrementam o operando de 1. Entretanto, op++ retorna o valor do
operando antes incrementá-lo e ++op retorna o valor do operando depois de incrementá-lo.
Por exemplo, o laço abaixo irá executar uma vez a menos se mudarmos count++ para
++count.
count = 0;
do {
...
} while (count++ < 6);
Analogamente, -- têm versões pré-fixa e pós-fixa. A tabela abaixo resume a função destes
operadores:
Um operador relacional compara dois valores e determina o relacionamento entre eles. Por
exemplo, o método countChars() utiliza o operador != para determinar se o valor
retornado por in.read não é igual a 1. A tabela abaixo apresenta os operadores relacionais
de Java:
Pág 32
Operador Uso Retorna True se
> op1 > op2 op1 é maior que op2
>= op1 >= op2 op1 é maior ou igual a op2
< op1 < op2 op1 é menor que op2
<= op1 <= op2 op1 é menor ou igual a op2
== op1 == op2 op1 e op2 são iguais
!= op1 != op2 op1 e op2 não são iguais
Em geral, operadores relacionais são utilizados com operadores condicionais para construir
expressões mais complexas. Um destes operadores é &&, que realiza a operação E
(booleana). Por exemplo:
O operador & é similar ao && se os seus operandos são do tipo boolean. Entretanto, &
sempre calcula ambos os seus operandos. Analogamente, | é similar a | | se ambos os
operandos são booleanos.
Pág 33
Operador Uso Operação
>> op1 >> op2 shift dos bits de op1 p/ direita por uma distância dada por op2
<< op1 << op2 shift dos bits de op1 p/ esquerda por uma distância dada por
op2
>>> op1 >>> op2 shift dos bits de op1 p/ direita por uma distância dada por op2 (unsigned)
& op1 & op2 bitwise and
| op1 | op2 bitwise or
^ op1 ^ op2 bitwise xor
~ ~op2 bitwise complemento
Por exemplo, a expressão a seguir desloca (shift) os bits do inteiro 13 para a direita (uma
posição):
13 >> 1;
O operador and bitwise executa a função and, de forma paralela, em cada par de bits da
cada operando. Suponha a seguinte operação:
12 & 13
1101
& 1100
------
1100
int count = 0;
Java proporciona vários operadores “short cuts” que permitem realizar operações
aritméticas, lógicas e bitwise juntamente com a atribuição. Por exemplo:
Pág 34
i += 2;
é equivalente a:
i = i + 2;
4.3 Expressões
Expressões é que realizam o trabalho de um programa Java. Entre outras coisas, expressões
são utilizadas para calcular e atribuir valores para variáveis e ajudar a controlar o fluxo de
execução de um programa. Assim, o trabalho de uma expressão é duplo: realizar o cálculo
indicado pelos elementos da expressão e retornar algum valor que é o resultado desta
computação. Então, podemos definir um expressão da seguinte forma: série de variáveis,
operadores e chamadas de métodos que retorna um único valor.
count++
é uma expressão que retorna o valor de count antes que a operação de incremento ocorra.
O tipo de dado retornado por uma expressão depende dos elementos utilizados nesta
expressão. A expressão count++ retorna um inteiro pois ++ retorna o mesmo valor que seu
operando (count é um inteiro).
in.read() != -1
in.read()
Uma chamada de método é uma expressão que retorna o valor do método. Portanto, o tipo
de dado desta expressão é o mesmo tipo de dado do parâmetro de retorno do método. O
método in.read() tem como parâmetro de retorno um inteiro, assim a expressão in.read
retorna um inteiro.
Pág 35
Pode-se observar, a partir destes dois exemplos, que Java permite a construção de
expressões compostas de várias partes (expressões) menores desde que os tipos de dados
requeridos por uma parte da expressão sejam compatíveis com os tipos de dados das outras
partes.
x*y*z
Neste exemplo em particular, a ordem com que a expressão é avaliada não é importante
pois os resultados das multiplicações são independentes de ordem. Entretanto, isto não vale
para todas as expressões. Por exemplo, a expressão a seguir fornece resultados diferentes
dependendo de que operação é realizada primeiro:
x + y / 100
Você pode determinar, explicitamente, para o compilador Java como deve ser avaliada
uma expressão utilizando parênteses. Por exemplo, para tornar a expressão anterior não-
ambígua, poderia se escrever (x + y) / 100.
Se não for determinado explicitamente a ordem desejada para o cálculo das operações, o
compilador toma esta decisão baseado na precedência atribuída aos operadores e outros
elementos utilizados em uma expressão. Operadores com precedência maior são calculados
primeiro. Por exemplo, o operador de divisão tem uma precedência maior do que o de
adição. Portanto, na expressão composta x + y / 100, o compilador vai calcular primeiro a
expressão y / 100. Assim,
x + y / 100
é equivalente a
x + (y / 100)
Para tornar o código fácil de ler e manter, sempre que possível, deve-se ser explícito e
indicar com parênteses quais operadores devem ser avaliados primeiro.
A tabela a seguir mostra a precedência dos operadores Java. Os operadores nesta tabela
estão listados em ordem de precedência: quanto mais alta a posição na tabela, maior a sua
precedência. Operadores com precedência maior são avaliados antes de operadores com
uma precedência relativa menor. Operadores que estão em uma mesma linha da tabela
possuem igual precedência.
Pág 36
additive + -
shift << >> >>>
relational < > <= >= instanceof
equality == !=
bitwise AND &
bitwise exclusive OR ^
bitwise inclusive OR |
logical AND &&
logical OR ||
conditional ? :
assignment = += -= *= /= %= &= ^= |= <<= >>= >>>=
O método countChars utiliza um while para realizar um laço que lê e conta todos os
caracteres da entrada :
De maneira geral, o while realiza alguma ação enquanto uma certa condição continua
verdadeira. A sintaxe geral do while é:
while (expression)
statement
Convenção: a chave { deve ficar no final da mesma linha que o while e a chave } deve
começar uma nova linha, alinhada com o while.
Pág 37
Comando Palavra-chave
tomada de decisão if-else, switch-case
laço for, while, do-while
exceção try-catch-finally, throw
miscelâneos break, continue, label: , return
4.1.1 If-else
if (DEBUG)
System.out.println("DEBUG: x = " + x);
Esta é a versão mais simples do if: o código é executado caso a condição dada seja
verdadeira. Generalizando, a forma mais simples do if pode ser escrita como:
if (expression)
statement
else é utilizado quando um diferente conjunto de operações deve ser executado quando a
expressão é falsa:
if (response == OK) {
...
// código para realizar a ação OK
...
} else {
...
// código para realizar a operação Cancel ...
}
A outra forma do else é o else if que executa um bloco de código baseado em outra
expressão. Exemplo:
int testscore;
char grade;
Pág 38
Um if pode ter um número qualquer de else if, mas apenas um else. Note que, apesar de
alguns valores de testscore satisfazerem mais de uma expressão, apenas um bloco de
código é executado (o primeiro onde a condição é verdadeira) e o fluxo de controle sai do
if sem avaliar as demais condições.
4.1.2 Switch
O switch é utilizado para realizar uma execução condicional baseada em uma expressão.
Exemplo:
int month;
switch (month) {
case 1: System.out.println("January"); break;
case 2: System.out.println("February"); break;
case 3: System.out.println("March"); break;
case 4: System.out.println("April"); break;
case 5: System.out.println("May"); break;
case 6: System.out.println("June"); break;
case 7: System.out.println("July"); break;
case 8: System.out.println("August"); break;
case 9: System.out.println("September"); break;
case 10: System.out.println("October"); break;
case 11: System.out.println("November"); break;
case 12: System.out.println("December"); break;
}
O switch avalia sua expressão, neste caso, o valor do mês e executa o bloco de código
apropriado. Obviamente, poderíamos implementar tal funcionalidade com if:
int month;
if (month == 1) {
System.out.println("January");
} else if (month == 2) {
System.out.println("February");
...
Um ponto interessante do switch é o break colocado depois de cada case. O break faz
com que o fluxo de controle saia do switch e continue na primeira expressão após do
switch. O break é necessário pois os case estão em “cascata”. Ou seja, sem um break
Pág 39
explícito, o fluxo de controle irá passar sequencialmente pelos próximos case. Entretanto,
existem algumas situações onde deseja-se proceder sequencialmente pelos case. Exemplo:
int month;
int numDays;
switch (month) {
case 1:
case 3:
case 5:
case 7:
case 8:
case 10:
case 12:
numDays = 31;
break;
case 4:
case 6:
case 9:
case 11:
numDays = 30;
break;
case 2:
if ( ((year % 4 == 0) && !(year % 100 == 0)) || (year % 400 == 0) )
numDays = 29;
else
numDays = 28;
break;
}
Finalmente, pode-se usar default no final do switch para tratar todos os valores que não
foram explicitamente tratados por um dos case. Exemplo:
int month;
switch (month) {
case 1: System.out.println("January"); break;
case 2: System.out.println("February"); break;
case 3: System.out.println("March"); break;
case 4: System.out.println("April"); break;
case 5: System.out.println("May"); break;
case 6: System.out.println("June"); break;
case 7: System.out.println("July"); break;
case 8: System.out.println("August"); break;
case 9: System.out.println("September"); break;
case 10: System.out.println("October"); break;
case 11: System.out.println("November"); break;
Pág 40
case 12: System.out.println("December"); break;
default: System.out.println("Hey, that's not a valid month!");
break;
}
4.1.1 Laços
Além do while, Java possui duas outras comandos de laço: for e do-while.
inicialização é uma expressão que inicia o laço – é executada uma vez no começo do laço.
terminação é uma expressão que determina quando encerrar o laço. Esta expressão é
avaliada no início de cada nova iteração do laço. Quando a expressão é avaliada como
false, o laço termina. Finalmente, incremento é uma expressão que é chamada a cada
iteração do laço. Qualquer (ou todos) componente do for pode ser vazio.
O outro laço suportado pelo Java, o do-while, é similar ao while exceto que a expressão é
avaliada apenas no final da iteração do laço:
do {
statements
} while (booleanExpression);
Em geral, o do-while é o tipo de laço menos utilizado, mas pode ser útil em determinadas
situações. Por exemplo, quando os comando do laço devem ser executados, sempre, pelo
menos uma vez.
Pág 41
Quando um erro ocorre em um método Java, a função pode levantar (throw) uma exceção
para indicar a ocorrência de um erro (e seu tipo) para quem invocou a função. O método
que chamou a função que levantou a exceção pode usar try, catch e finally para capturar e
tratar estas exceções. O conceito de exceção será abordado mais profundamente nas
próximas aulas do curso.
break faz com que o fluxo de controle “salte” para o bloco de código (statement)
imediatamente subsequente ao bloco de código atual.
O continue faz com que o fluxo de controle vá para a condição de terminação do laço.
Assim, a condição de terminação é reavaliada neste ponto e o laço continua ou não
dependendo dos resultados do teste. Em laços do tipo for e while, isto significa que o
controle retorna ao “topo” do laço. Em laços do-while, o controle retorna para o “fundo”
do laço.
O return é utilizado para sair do método atual e retornar para o ponto imediatamente
subsequente a chamada do método. Existem duas formas de return: uma retorna um valor
e a outra não. Para retornar um valor, simplesmente coloque um valor (ou expressão que
calcula o valor) depois do return:
return ++count;
O valor retornado deve ser compatível com o tipo do parâmetro de retorno declarado pelo
método. Quando um método é declarado como void, deve se utilizar a versão de return
sem valor.
Return;
4.5.1 Array
Pág 42
O método countChars não utiliza um vetor, mas o método main declara um vetor como
parâmetro de entrada. Esta seção mostra como criar e utilizar vetores em Java.
A declaração de um vetor, como qualquer outra variável, tem dois componentes: o tipo e o
nome. O tipo do vetor inclui os tipos dos dados que compõem o vetor. Por exemplo, para
declarar um vetor de inteiros, deve-se utilizar
int[] arrayOfInts;
A parte int[] da declaração indica que arrayOfInts é um vetor de inteiros. Esta declaração
não aloca nenhuma memória para armazenar os elementos do vetor. Se o seu programa
tentar acessar qualquer elemento deste vetor antes de se alocar memória para ele, o
compilador irá exibir um erro e se recusará a compilar o seu programa.
Para alocar memória aos elementos de um vetor, deve-se instanciar o objeto. Para isto,
utiliza-se o operador new (o operador new será apresentado com maior profundidade nas
próximas aulas). A expressão a seguir aloca memória suficiente para que arrayOfInts
armazene 10 elementos do tipo inteiro:
Em geral, ao criar um vetor, usa-se o operador new mais o tipo de dado dos elementos do
vetor mais o número de elementos (delimitado por [ ] ):
Após alocar memória ao vetor, pode-se atribuir e recuperar os valores dos elementos:
Observe no exemplo acima que o laço for possui a expressão arrayOfInts.length que
retorna o tamanho atual do vetor. length é uma propriedade oferecida por todos os vetores
em Java.
Pág 43
O ambiente de execução Java aloca o espaço para o vetor args e, portanto, main não
precisa se preocupar em alocar memória para args. O método main verifica a existência de
pelo menos um elemento em args e, em caso afirmativo, utiliza o primeiro elemento do
vetor (nome) para abrir um arquivo.
Vetores podem armazenar qualquer tipo de dado de Java, incluindo tipos referência como
objetos e outros vetores. Por exemplo, o código a seguir declara um vetor que pode conter
objetos String:
Os elementos neste vetor são de tipos de referência, ou seja, cada elemento contém uma
referência para um objeto String. Neste ponto, memória suficiente foi alocada para conter
as referências, mas ainda não foi alocada memória para as strings propriamente ditas.
Assim, deve-se alocar os objetos String separadamente:
4.5.2 String
String[] args
Este código declara explicitamente um vetor que contém objetos String. Os colchetes
vazios indicam que o tamanho do vetor é desconhecido em tempo de compilação pois o
vetor é passado, com os argumentos da linha de comando, em tempo de execução.
"Counted "
...
" chars."
Neste caso, o programa implicitamente aloca dois objetos String, um para cada literal.
Observação Importante: objetos String são imutáveis, ou seja, uma vez criados, eles não
podem ser modificados. Existe uma classe diferente, no pacote java.lang, chamada
Pág 44
StringBuffer que pode ser usada para criar e manipular caracteres sob demanda (“on the
fly”).
Java permite que se concatene strings utilizando o operador +. O método countChars usa
esta funcionalidade para imprimir sua saída. O código a seguir concatena 3 strings para
produuzir sua saída:
Duas destas strings são literais: Counted e chars. A terceira string, a do meio, na verdade
é um inteiro que é convertido para string e então concatenado às demais,
4.6 Exercícios
5. Escreva um laço while que execute 20 vezes. Converta este laço para um do-while.
6. Escreva um laço (while, for, do-while) que resulte em um laço infinito.
7. Quantas vezes o laço abaixo irá executar? Qual o valor de x quando o laço terminar?
Pág 45
5. Classes e Objetos em Java
Esta seção aborda como a linguagem Java implementa os conceitos de orientação a objetos
(seção 2). Além disto, apresenta exemplos práticos do uso destes conceitos em Java.
5.1 Classe
O código a seguir define uma classe, chamada SimplePoint, que representa um ponto em
um espaço bidimensional.
Este segmento de código declara uma classe (na verdade, um novo tipo de dado). A classe
SimplePoint possui duas variáveis membro do tipo inteiro, x e y. A palavra-chave public
que antecede a declaração de x e y indica que qualquer outra classe pode acessar
livremente estas duas variáveis.
Para criar um objeto da classe SimplePoint, deve-se instanciar a classe. Quando um novo
objeto é criado, memória é alocada para o objeto e seus membros x e y. Além disto, as
variáveis recebem o valor 0 (expressões de atribuição para os dois membros da classe).
O código a seguir define uma outra classe, SimpleRectangle, que representa um retângulo:
Além de duas variáveis membros do tipo inteiro, width e height, SimpleRectangle possui
uma terceira variável, origin, cujo tipo é SimplePoint. O nome de uma classe pode ser
utilizado da mesma forma que o nome de um tipo primitivo para declarar variáveis.
Pág 46
Como no exemplo anterior, a criação de um objeto do tipo SimpleRectangle aloca
memória para as suas variáveis. Neste caso, a variável origin cria um objeto SimplePoint
utilizando: new SimplePoint(), como ilustrado a seguir:
Esta figura mostra a diferença entre tipos primitivos e tipos de referência (seção 4.1). Tanto
width quanto height são inteiros e estão inteiramente contidos dentro de
SimpleRectangle. Por outro lado, origin simplesmente referencia um objeto SimplePoint
que está em outro lugar.
Pág 47
A seguir, tem-se uma nova versão de SimpleRectangle, chamada Rectangle, que possui
quatro construtores, um método para mover o retângulo, um método para calcular a área do
retângulo e um método finalize que “limpa” a variável de referência do objeto:
Tipicamente, um programa Java cria muitos objetos de várias classes. Estes objetos
interagem entre si via envio/recebimento de mensagens. Estas interações é que irão
proporcionar as funcionalidades de uma aplicação. Uma vez que um objeto terminou seu
trabalho, ele é destruído e seus recursos são “reciclados” para que outros objetos possam
utilizá-los.
Em Java, cria-se um objeto por meio de uma instanciação de classe. Por exemplo, o código
a seguir cria um objeto do tipo Rectangle a partir da classe Rectangle:
Pág 48
• Declaração: Rectangle rect é uma declaração de variável que informa ao compilador
que o nome rect será utilizado para referir-se a um objeto da classe Rectangle.
• Instanciação: new é o operador Java que cria um novo objeto, ou seja, aloca espaço
para o objeto.
• Inicialização: Rectangle() é uma chamada para o construtor de Rectangle, que
incializa o objeto.
Declarando um Objeto
A declaração de um objeto não é uma parte necessária para a criação de um objeto, embora
muitas vezes apareça na mesma linha. Como outras declarações de variáveis, podem
aparecer da seguinte forma:
Rectangle rect;
Em Java, classes e interfaces (a serem discutidas em uma próxima aula) podem ser usadas
como tipos de dados. Lembre-se que classes e interfaces são tipos de referência (seção
4.1), ou seja, o valor da variável é uma referência para o valor representado pela variável.
Declarações apenas notificam o compilador que vai se utilizar o nome rect para referenciar
uma variável cujo tipo é Rectangle. Assim, a declaração Rectangle rect não cria um
novo objeto, apenas uma variável que pode referenciar um objeto Rectangle. Para criar
um novo objeto, deve-se utilizar o operador new.
Instanciando um Objeto
O operador new cria um objeto, alocando a memória necessária. O operador new exige um
único argumento: uma chamada a um construtor. Cada classe em Java oferece um conjunto
de construtores para inicializar seus objetos. Assim, o operador new cria o objeto e o
construtor o inicializa. Por exemplo:
Aqui, Rectangle(100, 200) é o argumento de new. O operador new retorna uma referência
para uma variável:
Depois desta expressão, rect refere-se para um objeto Rectangle cuja origem está em (0,
0), possui largura igual a 100 e altura igual a 200.
Inicializando um Objeto
Pág 49
public Rectangle(Point p)
public Rectangle(int w, int h)
public Rectangle(Point p, int w, int h)
public Rectangle()
Cada um destes construtores permite se definir valores iniciais para diferentes aspectos do
retângulo: a origem; a largura e a altura; origem, largura e altura; ou nenhum valor. Se uma
classe possui vários construtores, estes devem possuir um número diferente de argumentos
e/ou argumentos de tipos distintos. Desta forma, o compilador é capaz de determinar qual
construtor deve ser invocado.
Um construtor que não possui argumentos é chamado de construtor default. Se uma classe
(como SimplePoint e SimpleRectangle, vistas no começo da seção) não definir
explicitamente um construtor, a linguagem Java automaticamente oferece um construtor
sem argumentos, que não faz nada. Portanto, é correto afirmar que todas as classes
possuem pelo menos um construtor.
As classes Point e Rectangle permitem livre acesso a suas variáveis. Note que não se
consegue colocar um objeto Point e um Retângulo em um estado inconsistente
modificando as suas variáveis (e se fosse um triângulo?).
Pág 50
necessário atualizar o ponto de origem do objeto. A classe retângulo oferece duas maneiras
equivalentes de realizar isto:
Suponha que você tenha criado um retângulo chamado rect, como descrito na seção . Para
mover rect para uma nova localização, deve-se escrever:
Esta expressão move o retângulo, atribuindo para a origem um novo ponto. rect.origin é a
variável que contém o ponto de origem de rect. Pode-se utilizar este tipo de variável da
mesma maneira que os tipos de variáveis vistos anteriormente. Por isto, é possível utilizar
o operador = para atribuir um valor a rect.origin.
Para calcular a área de rect, poderíamos usar as outras duas variáveis do objeto, height e
width (ou simplesmente executar o método area()):
objectReference.variable
Esta expressão cria um novo objeto retângulo e imediatamente acessa a sua variável height
(que, no caso, possui o valor default dado pelo construtor).
Para mover rect para uma nova localização, utilizando o método move(), devemos
escrever:
rect.move(15, 37);
Pág 51
Esta expressão invoca o método move() de rect() com dois parâmetros (inteiros), 15 e 37.
O método move o objeto rect pois atribui novos valores às variáveis origin.x e origin.y.
Esta expressão é equivalente àquela descrita no item anterior:
A notação utilizada para invocar o método de um objeto é similar a aquela usada para
referenciar as suas variáveis: acrescenta-se o nome do método à referência do objeto, com
um ponto (.) no meio. Além disto, define-se os argumentos para o método dentro de
parênteses. Se o método não possuir argumentos, utiliza-se parênteses vazios:
objectReference.methodName(argumentList);
ou
objectReference.methodName();
Como descrito no item anterior, objectReference deve ser uma referência para um objeto
(variável ou uma expressão que retorna uma referência de um objeto). Assim, podemos
usar:
A expressão new Rectangle(100, 50) retorna um objeto retângulo. Como mostrado, pode-se
invocar o método area() para calcular a área deste objeto recém-criado.
Para aqueles métodos que retornam algum valor, como area(), pode-se utilizar a invocação
do método em expressões. Por exemplo, pode-se atribuir o valor de retorno a uma variável,
usá-lo para tomar decisões (if-else) ou controlar um laço:
Java permite que se crie quantos objetos forem necessários (obviamente, o número de
objetos é limitado pelos recursos disponíveis no sistema), e o programador não precisa se
Pág 52
preocupar em destruí-los. O ambiente de execução Java (JVM) remove os objetos quando
percebe que eles não são mais utilizados. Este processo é denominado de coleta de lixo
(garbage collection).
Um objeto está sujeito a coleta de lixo quando não existem mais referências para ele.
Referências são armazenadas em variáveis e são descartadas quando estas variáveis saem
de escopo (pode-se também remover uma referência de um objeto atribuindo à variável o
valor null).
A plataforma Java possui um coletor de lixo que periodicamente libera a memória utilizada
pelos objetos que não são mais necessários. O coletor de Java é do tipo mark-sweep: ele
inspeciona as áreas de memória dinâmica em busca de objetos e marca aqueles que estão
sendo referenciados. Depois que todos os caminhos para os objetos são investigados,
objetos não marcados (ou seja, não mais referenciados) são considerados como lixo e então
são “coletados” (liberando a memória alocada para aqueles objetos).
O coletor de lixo é uma thread que executa com baixa prioridade de forma síncrona ou
assíncrona, dependendo da situação e do sistema onde Java está rodando. Por exemplo, o
coletor roda sincronamente quando o sistema fica sem memória disponível ou quando um
programa Java requisita, explicitamente, a sua execução.
5.3.2 Finalização
Durante a finalização, um objeto pode desejar liberar outros recursos do sistema como
arquivos e sockets ou eliminar referências para outros objetos de tal forma que estes
tornem-se também candidatos à coleta de lixo. Por exemplo, o método finalize da classe
Rectangle libera o objeto Point, atribuindo null a variável que o referenciava (origin):
Pág 53
método finalize de Rectangle executa o método super.finalize para dar a oportunidade a
classe pai de realizar uma “limpeza” final (a utilização do método finalize será discutida
com maior detalhe em uma próxima aula).
Esta seção mostra como escrever classes a partir das quais objetos são criados. Para isto,
será apresentado um pequeno exemplo que implementa uma pilha (last-in-first-out, LIFO).
A figura a seguir lista a classe e identifica a estrutura do código.
Esta implementação de uma pilha utiliza um outro objeto, Vector, para armazenar os seus
elementos. A classe Vector é um vetor de objetos que tem uma característica muito
interessante: é capaz de alocar dinamicamente espaço para novos objetos à medida que seja
necessário. A classe Stack usa então um objeto da classe Vector para armazenar seus
elementos. Entretanto, Stack impõe as restrições de uma estrutura LIFO ao objeto Vector,
ou seja, pode-se apenas remover (adicionar) elementos do (ao) topo da pilha.
Pág 54
5.4.1 Declaração da Classe
O lado esquerdo da figura a seguir mostra os componentes que podem fazer parte da
declaração de uma classe, na ordem que podem (ou devem) aparecer na declaração. O lado
direito do diagrama descreve o propósito do componente. Os componentes indispensáveis
em uma declaração de classe são: a palavra-chave class e o nome da classe. Todos os
demais elementos são opcionais. O texto em itálico identifica o nome de uma classe (ou
interface).
A lista a seguir apresenta algumas explicações básicas sobre cada um destes componentes
da declaração de uma classe. A discussão mais detalhada de tais elementos será feita ao
longo do curso.
•public: por default, uma classe pode ser utilizada apenas por classes que fazem parte
do mesmo package (package é um conceito equivalente a uma bibliotecas de objetos,
em Java, pode-se agrupar classes em pacotes, ou packages). Utilizando a palavra-chave
public, o programador indica que tal classe pode ser utilizada por qualquer outra,
independente do pacote a que ela pertença.
•abstract: serve para declarar que a classe não pode ser instanciada (criada). Em uma
próxima aula, serão apresentadas situações onde é vantajoso se utilizar uma classe
deste tipo.
•final: serve para declarar que a classe não pode ser “derivada” (ou seja, ter
subclasses). Também, posteriormente, serão apresentadas razões para utilizar classes
do tipo “finais”.
•class NameofClass: a palavra-chave class indica ao compilador que o código a seguir
é uma declaração de uma classe cujo nome é NameofClass.
•extends Super: o elemento extends identifica que a classe Super é a superclasse da
classe declarada. O conceito de herança em Java será abordado com mais detalhes em
uma próxima aula.
•implements Interfaces: para declarar que uma classe implementa uma ou mais
interfaces, utiliza-se a palavra-chave implements seguida de uma lista, delimitada por
vírgulas, dos nomes das interfaces. Novamente, o conceito de interfaces em Java será
apresentado com maior profundidade em uma próxima aula.
Pág 55
Observação: se o programador não declarar explicitamente os componentes opcionais, o
compilador Java assume algumas “declarações” default: uma subclasse de Object não-
final, não-pública, não-abstrata que não implementa nenhuma interface.
No exemplo da classe pilha, vemos que a declaração da classe é bastante simples e indica
que tal classe é pública e que seu nome é Stack.
A classe Stack define, em seu corpo, uma variável membro que armazena os itens da pilha
(items, da classe Vector). Também são definidos um construtor, o default, e quatro
métodos: push, pop, isEmpty, e finalize. Lembre-se que finalize é um método especial
que proporciona a “limpeza” dos objetos da classe Stack.
Todas as classes Java possuem construtores que são utilizados para inicializar um novo
objeto daquele tipo. Construtores sempre possuem o mesmo nome da classe. No exemplo,
a classe Stack define um único construtor:
public Stack() {
items = new Vector(10);
}
Como já visto, Java suporta a sobrecarga de construtores de tal forma que uma classe pode
possuir vários construtores (obviamente, de mesmo nome). Stack poderia definir, por
exemplo, um construtor que atribui um tamanho inicial para a pilha:
Note que os construtores de uma mesma classe devem possuir uma lista de argumentos
diferentes. Baseando-se no número e no tipo de argumentos passados para o construtor é
que o compilador consegue determinar qual construtor utilizar. Por exemplo, no código
Pág 56
abaixo, o compilador sabe que deve usar o construtor que tem como argumento um único
valor (do tipo inteiro):
new Stack(10);
new Stack();
Note que se nenhum construtor for definido explicitamente para a classe, o ambiente de
execução do Java (JVM) automaticamente irá proporcionar um construtor default. Este
construtor “automático” oferecido pelo sistema não faz nada (ou seja, é uma função
“vazia”).
O construtor da classe a seguir, derivada da classe Thread, atribui alguns valores default,
como a velocidade de exibição das imagens (framesPerSecond), o número de imagens
(numImages) e depois carrega as imagens.
super("AnimationThread");
this.framesPerSecond = fps;
this.numImages = num;
super("AnimationThread");
Pág 57
Esta expressão invoca o construtor da superclasse de AnimationThread, ou seja, Thread.
Este construtor da classe Thread tem como parâmetro uma String que define o nome da
thread. Na maioria das vezes, é interessante (e até necessário, em certos casos) invocar o
construtor da superclasse para aproveitar o código de inicialização e/ou para que a classe
funcione corretamente.
Os componentes listados abaixo, que devem ser associados aos construtores, especificam
quais objetos podem criar instâncias da classe:
•private: nenhuma outra classe pode instanciar a classe. A classe em questão pode
possuir métodos de classe (públicos), chamados de fábricas, e estes métodos podem
construir um objeto da classe e retornar a sua referência.
•protected: apenas subclasses da classe em questão podem criar instâncias dela.
•public: qualquer classe pode instanciar a classe declarada.
•package: apenas classes que fazem parte do mesmo pacote podem construir um
objeto da classe em questão.
5.4.4 Variáveis
A classe Stack utiliza a seguinte linha de código para definir sua única variável membro:
Esta declaração refere-se a uma variável membro e não a um outro tipo de variável (local,
por exemplo) pois aparece no corpo da classe mas fora de qualquer método ou construtor.
A variável declarada tem nome items e é do tipo Vector. Além disso, a palavra-chave
private identifica a variável itens como uma variável privada. Ou seja, apenas o código
dentro da classe Stack pode acessá-la.
•accessLevel: permite controlar quais outras classes têm acesso à variável membro.
Pode-se definir quatro níveis de acesso: public, protected, package, e private. Estes
níveis são detalhados na seção 5.4.6.
•static: define que a variável é uma variável de classe em vez de uma variável de
instância. static também pode ser utilizado na declaração de métodos (seção 5.4.7).
Pág 58
•final: indica que o valor da variável não pode ser alterado. A seguinte declaração
define uma constante chamada AVOGADRO, cujo valor é 6.023x1023.
Ocorrerá um erro em tempo de compilação caso seu programa tente alterar o valor de
alguma variável final. Por convenção, o nome de valores constantes é composto de letras
maiúsculas.
5.4.5 Métodos
A figura abaixo mostra o código do método push da classe Stack. Este método insere um
objeto, passado como argumento da função, no topo da pilha e retorna o próprio objeto.
Pág 59
Como uma classe, um método possui duas partes principais: declaração e corpo. A
declaração de um método define todos os seus atributos como: nível de acesso, tipo de
retorno, nome e argumentos.
O corpo do método é onde a “a ação ocorre”. Ele contém todas as instruções Java que
implementam a função.
Declaração do Método
Uma declaração de método possui vários componentes, a maioria deles pode ser declarada
implicitamente. Os únicos componentes indispensáveis em qualquer declaração são: o
nome do método, seu tipo de retorno, e um par de parênteses ( ). A figura abaixo mostra
todos os elementos que podem estar presentes na declaração:
Pág 60
•returnType: em Java, é necessário que um método declare o tipo de dado do valor a
ser retornado. Se o método não retorna nenhum valor, deve-se usar a palavra-chave
void como tipo de retorno.
•methodName: o nome do método pode ser qualquer identificador Java válido.
•( paramlist ): é por meio dos parâmetros de um método que passamos as informações
para a função.
•[ throws exceptions ]: caso o método possa “levantar” alguma exceção (seção ), a
declaração deve indicar o tipo desta exceção.
Dentro do corpo do método, para retornar um valor, utiliza-se o operador return. Todo
método que não foi declarado como void deve conter pelo menos um return. Por exemplo,
a classe Stack declara o método isEmpty, que retorna um boolean:
Um erro de compilação ocorrerá caso tente-se escrever um método onde o valor de retorno
não é compatível com o tipo definido na declaração da função.
Métodos também podem retornar tipos de referência. Por exemplo, Stack declara o método
pop que retorna uma referência para o tipo Object:
Quando um método retorna um objeto, a classe do objeto retornado deve ser uma subclasse
(ou a própria classe) do tipo de retorno declarado. Suponha que, exista uma hierarquia de
classes onde ImaginaryNumber é uma subclasse de java.lang.Number que, por sua vez,
é uma subclasse de Object:
Pág 61
Agora, suponha que um método tenha como tipo de retorno um Number:
Este método pode retornar um ImaginaryNumber mas não um Object. Isto porque
ImaginaryNumber, por ser uma subclasse Number, “é” um número; entretanto, um
Object não é necessariamente um número (pode ser uma String, por exemplo).
Nome do Método
Java suporta sobrecarga de métodos de tal forma que vários métodos podem compartilhar o
mesmo nome. Por exemplo, suponha que você esteja escrevendo uma classe que imprime
vários tipos de dados (strings, números, etc.) em uma área de desenho. Assim, é necessário
escrever um método que saiba imprimir cada tipo de dado. Em outras linguagens isto
poderia ser feito definindo nomes diferentes para cada método drawString(),
drawInteger(), ... Em Java, podemos utilizar o mesmo nome para todos estes métodos
desde que possuam parâmetros de tipos diferentes.
class DataRenderer {
void draw(String s) {
...
}
void draw(int i) {
...
}
void draw(float f) {
...
}
}
Na verdade, métodos sobrecarregados são diferenciados pelo número e tipo dos seus
argumentos.
Quando se declara um método, são declarados também o número e o tipo dos argumentos
necessários. Por exemplo, o método a seguir calcula os pagamentos mensais de um
empréstimo, baseado no valor do empréstimo:
I = rate / 100.0;
Pág 62
partial1 = Math.pow((1 + I), (0.0 - numPeriods));
denominator = (1 - partial1) / I;
answer = ((-1 * loanAmt) / denominator)
- ((futureValue * partial1) / denominator);
return answer;
}
Este método possui quatro argumentos: o valor do empréstimo, a taxa de juros, o valor
futuro (se a dívida for inteiramente paga -> valor futuro igual a 0) e o número de períodos.
O conjunto de argumentos de qualquer método é uma lista de declarações de variáveis,
separadas por vírgulas, onde cada declaração é um par nome/tipo.
Em Java, pode-se passar como argumento para um método qualquer tipo de dado válido.
Isto inclui tipos primitivos (inteiros, números reais, etc.) e tipos de referências (objetos e
vetores). A seguir, temos um exemplo de um construtor que aceita um vetor de objetos (da
classe Point) como argumento:
Observação: Diferente de outras linguagens (C, por exemplo), em Java, não se pode
passar métodos como argumentos de outros métodos. O que se pode fazer é passar um
objeto para um método e, no corpo do método, invocar alguma das funções deste objeto.
Importante também salientar que o argumento de um método pode ter o mesmo nome que
uma das variáveis-membro da classe do objeto. Neste caso, dizemos que o argumento
ocultou (hide) a variável-membro. Argumentos que ocultam variáveis-membros são
muitas vezes utilizados em construtores para inicializar uma classe. Por exemplo:
class Circle {
int x, y, radius;
public Circle(int x, int y, int radius) {
...
}
}
class Circle {
int x, y, radius;
public Circle(int x, int y, int radius) {
this.x = x;
Pág 63
this.y = y;
this.radius = radius;
}
}
Observação: Os nomes dos argumentos de um método não podem ser iguais ao nome de
alguma variável local dentro do corpo do método.
Considere o seguinte exemplo, que tenta recuperar a cor atual de um objeto (pen):
...
int r = -1, g = -1, b = -1;
pen.getRGBColor(r, g, b);
System.out.println("red = " + r + ", green = " + g + ", blue = " +
b);
...
class Pen {
int redValue, greenValue, blueValue;
void getRGBColor(int red, int green, int blue) {
// red, green, and blue have been created
// and their values are -1
...
}
}
Ou seja, o método possui a sua própria cópia dos valores de r, g e b para utilizar dentro do
método. Qualquer alteração realizada nestas cópias locais não se reflete nas variáveis
originais (r, g e b). Assim, o método abaixo não funciona como esperado:
class Pen {
Pág 64
int redValue, greenValue, blueValue;
...
// this method does not work as intended
void getRGBColor(int red, int green, int blue) {
red = redValue;
green = greenValue;
blue = blueValue;
}
}
...
int r = -1, g = -1, b = -1;
pen.getRGBColor(r, g, b);
System.out.println("red = " + r + ", green = " + g + ", blue = " +
b);
...
A passagem por valor garante ao programador uma certa segurança: os métodos não
podem modificar variáveis que estão fora de seu escopo. Entretanto, muitas vezes, é
desejável que um método seja capaz de modificar um ou mais argumentos (por exemplo, a
própria função getRGBColor citada acima). Então, como um método pode retornar mais
de um valor ou modificar variáveis fora de seu escopo? (observação: se for necessário
retornar somente um valor podemos utilizar return).
A saída para esta situação é utilizar, como argumentos, os tipos de referência. Observe que,
objetos e vetores também são passados por valor, porém o seu valor é, na verdade, uma
referência para um objeto. Ou seja, o argumento do método vai estar referenciando o
mesmo objeto que a variável original.
Aproveitando este fato, podemos rescrever getRGBColor para que faça aquilo que é
esperado. Primeiramente, vamos criar um novo tipo de objeto RGBColor que guarda os
valores de vermelho, verde e azul:
class RGBColor {
public int red, green, blue;
}
Agora, vanos rescrever getRGBColor para que o método aceite um objeto RGBColor
como argumento. Note que o método agora atribui os valores das cores para as variáveis
membros do objeto RGBColor.
class Pen {
Pág 65
int redValue, greenValue, blueValue;
void getRGBColor(RGBColor aColor) {
aColor.red = redValue;
aColor.green = greenValue;
aColor.blue = blueValue;
}
}
...
RGBColor penColor = new RGBColor();
pen.getRGBColor(penColor);
System.out.println("red = " + penColor.red + ", green = " +
penColor.green + ", blue = " +
penColor.blue);
...
Exemplo 1:
class HSBColor {
int hue, saturation, brightness;
HSBColor (int hue, int saturation, int brightness) {
this.hue = hue;
this.saturation = saturation;
this.brightness = brightness;
}
Pode-se também utilizar this para chamar um dos métodos do objeto atual. Novamente,
isto só é necessário caso exista alguma ambigüidade relacionada com o nome do método;
em geral, é utilizada com a intenção de tornar o código mais claro.
Exemplo 2:
class ASillyClass {
boolean aVariable;
void aMethod() {
aVariable = true;
Pág 66
}
}
super.aMethod();
Isto faz com que a versão oculta de aVariable (aquela que foi declarada na superclasse
ASillyClass) receba o valor true. Então o método aMethod imprime as duas versões de
aVariable, que possuem diferentes valores:
false
true
Em Java, pode-se utilizar especificadores de acesso (access especifiers) para proteger tanto
variáveis quanto os métodos de uma classe. Este controle é definido na própria declaração
da variável (ou método). A linguagem Java suporta quatro níveis de acesso distintos:
privado (private), protegido (protected), público (public) e o nível default, pacote
(package).
Pág 67
A primeira coluna indica se a própria classe possui acesso ao membro (variável ou
método). Como pode se observar, a classe sempre possui acesso aos seus próprios
membros. A segunda coluna indica se as subclasses (independente do pacote a qual
pertencem) possuem acesso a variável/método. A terceira coluna indica se classes, que
estão no mesmo pacote da classe em questão, possuem acesso ao membros. A quarta
coluna indica se todas as classes possuem acesso as variáveis/métodos.
*: Este nível de acesso possui uma pequena particularidade, a ser descrita ainda nesta
seção.
Privado (Private)
Este é o nível de acesso mais restritivo. Um membro privado é acessível apenas pela
própria classe que o definiu. Ou seja, deve-se declarar como privadas aquelas
variáveis/métodos que só devem ser utilizados pela classe
class Alpha {
private int iamprivate;
private void privateMethod() {
System.out.println("privateMethod");
}
}
class Beta {
void accessMethod() {
Alpha a = new Alpha();
a.iamprivate = 10; // ilegal
a.privateMethod(); // ilegal
}
}
Pág 68
Beta.java:12: No method matching privateMethod() found in class
Alpha.
a.privateMethod(); // ilegal
2 errors
class Alpha {
private int iamprivate;
boolean isEqualTo(Alpha anotherAlpha) {
if (this.iamprivate == anotherAlpha.iamprivate)
return true;
else
return false;
}
}
Protegido (protected)
Este nível de acesso permite que a classe, suas subclasses e todas as classes no mesmo
pacote (package) acessem seus membros. Para declarar um membro protegido, utiliza-se a
palavra-chave protected.
Primeiramente, vamos analisar como o protected afeta classes que estão no mesmo pacote.
Considere esta versão de Alpha que é declarada dentro de um pacote chamado Greek e
possui uma variável e um método protegidos:
package Greek;
class Alpha {
protected int iamprotected;
protected void protectedMethod() {
System.out.println("protectedMethod");
}
}
Suponha que a classe Gamma também foi declarada como membro do pacote Greek (e
não é uma subclasse de Alpha). Assim, a classe Gamma pode acessar os membros
protegidos de Alpha:
package Greek;
Pág 69
class Gamma {
void accessMethod() {
Alpha a = new Alpha();
a.iamprotected = 10; // válido
a.protectedMethod(); // válido
}
}
Agora, vamos investigar como protected afeta as subclasses de Alpha. Considere a classe
Delta que é uma subclasse de Alpha mas pertence a um outro pacote, Latin. A classe
Delta pode acessar iamprotected e protectedMethod, mas apenas em objetos do tipo
Delta ou suas subclasses. A classe Delta não pode acessar os membros protegidos de
objetos do tipo Alpha. Observe o seguinte exemplo:
import Greek.*;
package Latin;
Agora, se a subclasse pertencer ao mesmo pacote da classe com o membro protegido, então
a subclasse possui acesso ao membro.
Público (public)
O nível de acesso mais simples é o público. Qualquer classe, em qualquer pacote, possui
acesso aos membros públicos de uma classe. Para declarar um membro público, utiliza-se a
palavra-chave public:
package Greek;
Pág 70
Rescrevendo a classe Beta, de tal forma que esta não possua nenhum relacionamento
(superclasse/subclasse) com Alpha:
import Greek.*;
package Roman;
class Beta {
void accessMethod() {
Alpha a = new Alpha();
a.iampublic = 10; // válido
a.publicMethod(); // válido
}
}
Observe que a classe Beta continua com acesso aos membros públicos de Alpha.
Pacote (package)
Este nível de acesso é o default, ou seja, quando não se atribui explicitamente um nível de
acesso para uma variável/método de uma classe. Este nível permite que classes do mesmo
pacote acessem os membros da classe. Por exemplo:
package Greek;
class Alpha {
int iampackage;
void packageMethod() {
System.out.println("packageMethod");
}
}
package Greek;
class Beta {
void accessMethod() {
Alpha a = new Alpha();
a.iampackage = 10; // válido
a.packageMethod(); // válido
}
}
Pág 71
5.4.7 Membros de Instância X Membros de Classe
class MyClass {
float aFloat;
}
tem-se uma variável de instância. Toda vez que se cria uma instância de uma classe, o
ambiente de execução cria uma cópia de cada variável de instância da classe para o novo
objeto.
Existem também variáveis de classe (que são declaradas utilizando a palavra-chave static).
O ambiente de execução aloca memória para variáveis de classe apenas uma única vez,
independentemente do número de instâncias criadas para aquela classe. O sistema aloca a
memória na primeira vez que encontra uma referência no código a aquela classe. Todas as
instâncias (objetos) compartilham a mesma cópia das variáveis de classe. Pode-se acessar
uma variável de classe por meio de um objeto ou da própria classe. Exemplo:
myClass myObject;
...
myObject.mystaticvariable = 1; // válido
myClass.mystaticvariable = 1; // válido
O mesmo raciocínio é válido para os métodos: classes podem possuir métodos de instância
e de classe.
Por default, um membro declarado em uma classe é uma variável/método de instância. Por
exemplo, a classe abaixo declara uma variável de instância (um inteiro chamado x) e dois
métodos de instância, x e setX:
class AnIntegerNamedX {
int x;
public int x() {
return x;
}
public void setX(int newX) {
x = newX;
}
}
Pág 72
Observação: os métodos de instância atuam sobre as variáveis de instância do objeto atual.
Considere o seguinte exemplo:
Note que o código acima está manipulando duas cópias diferentes de x: uma que pertence
ao objeto myX e outra que pertence ao objeto anotherX. Assim, a saída deste trecho de
código é:
myX.x = 1
anotherX.x = 2
Para especificar que uma variável membro é uma variável de classe, utiliza-se a palavra-
chave static. Por exemplo, a variável x declarada abaixo é uma variável de classe:
class AnIntegerNamedX {
static int x;
public int x() {
return x;
}
public void setX(int newX) {
x = newX;
}
}
Neste caso, o mesmo trecho de código, mostrado anteriormente, que cria dois objetos da
classe AnIntegerNamedX, atribui valores a x e mostra o resultado, terá a seguinte saída:
myX.x = 2
anotherX.x = 2
A saída é diferente pois agora x é uma variável de classe e só existe uma cópia da variável,
que é compartilhada por todos os objetos da classe AnIntegerNamedX.
class AnIntegerNamedX {
private int x;
static public int x() {
return x;
}
Pág 73
static public void setX(int newX) {
x = newX;
}
}
Isto ocorre pois métodos de classe não podem acessar variáveis de instância (no caso, x).
class AnIntegerNamedX {
static private int x;
static public int x() {
return x;
}
static public void setX(int newX) {
x = newX;
}
}
Como já foi dito, podemos acessar membros de classe por meio do nome da própria classe:
AnIntegerNamedX.setX(1);
System.out.println("AnIntegerNamedX.x = " + AnIntegerNamedX.x());
Note que não é mais necessário criar os objetos myX e anotherX. Pode-se acessar a
variável x a partir da classe AnIntegerNamedX (isto não pode ser feito com variáveis de
instância).
Exceção é um tipo especial de objeto que é criado quando algo sai errado em um
programa. Após criar uma exceção, o ambiente de execução envia este objeto ao seu
programa, numa ação denominada “levantar uma exceção” (throwing an exception). É
responsabilidade do seu programa capturar (catch) esta exceção. Para isto, deve-se
escrever um código de tratamento de exceção:
Pág 74
try {
a.metodoQuePodeGerarUmaExcecao();
a.outroMetodo();
}
catch (Excecao e)
{
System.out.println(“Exceção !!!”);
}
Para capturar uma exceção, utiliza-se as cláusulas try e catch. Todo o código que pode
levantar uma exceção é colocado em um bloco try. Caso ocorra uma exceção em algum
ponto do bloco, o restante do código é ignorado e o fluxo de controle do programa “salta”
para o bloco catch. Por outro lado, se nenhuma exceção for gerada, o código do bloco
catch é ignorado.
A linguagem Java define várias exceções (objetos) que podem ser geradas pelos métodos
das classes de sua API. Estas exceções devem ser sempre tratadas pelo código do seu
programa, caso contrário ocorrerá um erro de compilação.
A documentação on-line da API da linguagem Java indica quais métodos podem levantar
exceções e quais são os tipos destas exceções.
Observação: não é necessário realizar o tratamento de exceção no mesmo método onde ela
foi levantada. Pode-se “passar para a frente” a exceção, utilizando-se a palavra-chave
throws:
No exemplo acima, dentro do método GetURL pode ser levantada uma exceção
(MalformedURLException). Tal exceção não será tratada por GetURL e sim enviada
para o método que invocou GetURL. Este método, por sua vez, pode realizar o tratamento
de exceção (via try e catch) ou “repassá-la” utilizando throws.
Exceção Descrição
ArithmeticException
Caused by math errors such as division by
zero
ArrayIndexOutOfBounds Exception Caused by bad array indexes
ArrayStoreException
Caused when a program tries to store the
wrong type of data in an array
Pág 75
FileNotFoundException
Caused by an attempt to access a nonexistent
file
IOException
Caused by general I/O failures, such as
inability to read from a file
NullPointerException Caused by referencing a null object
NumberFormatException
Caused when a conversion between strings
and numbers fails
OutOfMemoryException
Caused when there's not enough memory to
allocate a new object
Caused when an applet tries to perform an
SecurityException action not allowed by the browser's security
setting
StackOverflowException
Caused when the system runs out of stack
space
StringIndexOutOfBounds Exception
Caused when a program attempts to access a
nonexistent characterposition in a string
Observação: pode-se tratar todos os tipos de exceção utilizando um único bloco catch
utilizando
catch (Exception e) pois Exception é a superclasse de todos os objetos de exceção.
5.5 Exercícios
class UmaClasse {
private int x;
public void UmaClasse(int a) {
x = a;
}
public void UmaClasse(int x) {
this.x = x;
}
}
O código acima é válido? Justique.
2. Agora, considere o código abaixo:
class OutraClasse
{
public float x;
}
O código acima é válido? É possível criar classes sem construtores? Como Java trata esta
questão?
Pág 76
3. Suponha que você deva criar uma classe que represente um triângulo. Seria
interessante permitir que outros objetos acessem diretamente suas variáveis (vértices do
triângulo)? Por que? Como você implementaria, em Java, tal classe?
4. Como visto na seção 5.3, o ambiente de execução Java (JVM) remove os objetos
quando percebe que eles não são mais utilizados. Este processo é denominado de coleta
de lixo (garbage collection). Suponha que no seu programa exista um objeto X que
possui um vetor (variável membro) com milhares de referências a objetos que você
sabe que não serão mais utilizados. Porém, este objeto X é utilizado para outros fins
dentro do seu programa (ou seja, o compilador não tem como saber que os objetos
referenciados pelo vetor de X não são mais utilizados). Como você faria para liberar a
memória alocada para os objetos referenciados pelo vetor de X? Dica: pensar em uma
maneira de “forçar” a coleta de lixo.
5. Apesar do ambiente Java realizar a liberação automática da memória alocada aos
objetos, em certas situações é importante a existência do método finalize. Justifique
esta afirmativa com exemplos.
6. Considere o código abaixo:
class A {
protected void finalize throws Throwable{
System.out.println(“Fim de A”);
}
}
class B extends A {
protected void finalize throws Throwable {
System.out.println(“Fim de B”);
super.finalize();
}
}
Quando um objeto da classe B for liberado, qual será a saída do programa? E se tirarmos a
linha super.finalize()? Justifique.
7. Faça um programa que instancie e utilize um objeto da classe Stack (seção 5.4). Insira
e retire alguns objetos desta pilha. Note que você pode inserir qualquer tipo de objeto
nesta pilha (inclusive colocar objetos de tipos diferentes na mesma pilha). Por que?
8. Qual a função da instrução super(), colocada no início do construtor de uma classe?
9. Como se declara constantes em Java? Dê um exemplo.
10. O código abaixo é válido?
Pág 77
11. Suponha que a classe B seja uma subclasse de A. O código abaixo é válido? Justifique.
...
A objetoA = new A();
B objetoB = new B();
...
public A metodo1()
{
return objetoB;
}
public B metodo2()
{
return objetoA;
}
class Ponto {
int x, y;
public Ponto() { x=0; y=0;}
public void atribui(int x, int y) {
System.out.println(“x = “ + this.x + “ x = “ + x);
.this.x = x; this.y = y;
System.out.println(“x = “ + this.x + “ x = “ + x);
}
}
...
Ponto p = new Ponto();
p.atribui(1,2);
class MaisUmaClasse {
int x;
static int y;
objeto1.modificaX(1);
Pág 78
objeto2.modificaX(2);
objeto1.modificaY(1);
objeto2.modificaY(2);
class MaisUmaClasse {
private int a;
public static MaisUmaClasse A () { return a;}
}
16. Quando você utiliza no seu programa um método que pode levantar uma exceção, é
obrigatório tratar, no seu código, esta exceção?
17. Quantas exceções podem ser associadas a um único bloco try?
18. Como se “repassa” uma exceção. Dê um exemplo.
19. Escreva um programa que levante, propositadamente, algum tipo de exceção (divisão
por zero, por exemplo). Escreva o código que realize o tratamento desta exceção e
imprima a sua descrição (dica: utilize o método getMessage() da superclasse
Exception).
20. Crie uma classe em Java (cachorro, por exemplo) com atributos públicos (raça, cor,
nome, idade) e privados (filhos; sugestão: utilizar um vetor). Implemente também
métodos que manipulem as variáveis privadas (novoFilho(), imprimirFilhos(), etc.).
Pág 79
6. Outras Características da Linguagem Java
6.1 Herança
Como foi apresentado na seção anterior, a palavra-chave extends declara que uma classe é
subclasse de uma outra. Em Java, pode-se especificar apenas uma superclasse para cada
classe (ou seja, não há suporte para herança múltipla). Mesmo que na declaração da classe
a palavra-chave extends seja omitida, a classe possui uma superclasse (classe Object,
definida no pacote java.lang). Desta forma, podemos dizer que, em Java, toda classe
possui uma superclasse. A figura abaixo mostra o topo da hierarquia de classes da
linguagem:
A classe Object define e implementa um comportamento que toda a classe de Java precisa.
É a mais geral das classes. Suas subclasses mais imediatas, perto do topo da hierarquia,
implementam um comportamento genérico, subclasses nos níveis mais baixos da
hierarquia possuem um comportamento mais especializados.
Definição de subclasse: classe que estende outra classe. Uma subclasse herda estado e
comportamento das suas classes ancestrais. O termo superclasse refere-se ao ancestral
direto da classe e também a todas as outras que são superclasses das classes ancestrais.
Uma subclasse pode utilizar os membros herdados da maneira como foram definidos pelas
superclasses, ocultar algumas variáveis ou redefinir os métodos herdados.
Pág 80
Regra: Uma subclasse herda todos os membros de sua superclasse que são acessíveis à
subclasse, a menos que a subclasse explicitamente oculte a variável ou redefina o método.
Note que construtores não são herdados pelas subclasses.
A lista a seguir cita os membros que são herdados por uma subclasse:
Como foi dito, variáveis membro definidas na subclasse ocultam variáveis de mesmo nome
declaradas na superclasse. Esta característica da linguagem Java traz grande flexibilidade,
porém pode ser uma fonte de muitos erros. Portanto, deve-se ser cuidadoso no momento de
escolher os nomes das variáveis da subclasse de tal forma a ocultar apenas aquelas
variáveis desejadas.
Uma outra característica interessante da linguagem é que uma classe pode acessar um
membro oculto, definido na superclasse. Considere o seguinte exemplo:
class Super {
Number aNumber;
}
class Subbie extends Super {
Float aNumber;
}
super.aNumber
Pág 81
6.3 Redefinindo (overriding) métodos
A possibilidade de uma subclasse redefinir um método da sua superclasse permite que uma
classe herde de uma outra cujo comportamento é “próximo” ao desejado e então
complemente ou modifique o comportamento da superclasse.
Por exemplo, todas as classes descendem da classe Object. Object possui um método
chamado toString, que retorna um objeto String contendo o nome da classe do objeto e o
seu código hash. A maioria, senão todas as classes, desejarão redefinir este método e
imprimir algo mais relevante.
Observe que o tipo de retorno, o nome do método e os tipos dos parâmetros devem ser
iguais aos do método que está sendo redefinido. O especificador de acesso do método
redefinido pode permitir acesso menos restritivo que o método da superclasse, mas não
pode definir um acesso mais restritivo. Por exemplo, um método protected definido na
superclasse pode ser redefinido como público na subclasse mas não pode ser redefinido
como privado.
Pág 82
6.3.1 Executando o método que foi redefinido
Uma subclasse não pode redefinir métodos que estão declarados como final na superclasse.
Ao tentar redefinir um método final, o compilador irá exibir uma mensagem de erro
similiar a esta:
Uma subclasse também não pode redefinir métodos declarados como static na superclasse.
Em outras palavras, uma subclasse não pode redefinir um método de classe.
Uma subclasse deve redefinir métodos que são declarados como abstract na superclasse,
ou a subclasse também deve ser abstrata. Na próxima seção, serão discutidos os conceitos
de métodos e classes abstratas.
Algumas vezes, uma classe representa um conceito abstrato e, como tal não deve ser
instanciada. Considere, por exemplo, comida no mundo real. Alguém já viu uma instância
(objeto) do tipo comida? Não. O que existe são instâncias de cenouras, maçãs, bananas,
chocolates, etc. Comida representa o conceito abstrato e não é capaz de criar uma
instância própria.
Pág 83
número. Esta classe Number é a superclasse de classes como Integer e Float. Classes,
como Number, que representam um conceito abstrato e não podem ser instanciadas, são
chamadas de classes abstratas. Uma classe abstrata é uma classe que só pode ser utilizada
como superclasse.
Para declarar uma classe como abstrata, utiliza-se a palavra-chave abstract antes da
palavra-chave class:
Se você tentar instanciar uma classe abstrata, o compilador irá mostrar um erro parecido
com este:
Uma classe abstrata pode conter métodos abstratos, ou seja, métodos que não possuem
implementação definida. Por exemplo, suponha que você deseja criar uma classe com um
método abstrato dentro dela. Em uma aplicação de desenho, as classes (círculo, retângulos,
linhas, curvas Bezier, etc.) compartilham alguns estados (posição, cor) e comportamento
(mover, redimensionar, desenhar). Pode-se tirar vantagem destas similaridades e declarar
estas classes como subclasses de uma classe única: GraphicObject, por exemplo:
Entretanto, os objetos gráficos são bastante diferentes: desenhar um círculo é bem diferente
de desenhar um retângulo. Ou seja, todos os objetos (retângulos, círculos, etc.) devem
saber como desenhar a si mesmos – eles compartilham o comportamento (desenhar) mas
diferem em como este comportamento é implementado. Esta é uma situação perfeita para a
criação de uma superclasse abstrata.
Primeiramente, deve-se declarar uma classe abstrata, GraphicObject, que possui variáveis
membros e métodos que são compartilhados (inteiramente) por todas as subclasses, tais
como a variável posição e o método mover (moveTo). GraphicObject também declara
métodos abstratos como desenhar (draw), que precisam ser implementados por todas as
subclasses. Cada subclasse vai implementar o método draw de maneira diferente. Por isto,
não faz sentido definir uma implementação “default” para a superclasse. Então, a classe
GraphicObject possuiria uma implementação como:
Pág 84
abstract class GraphicObject {
int x, y;
...
void moveTo(int newX, int newY) {
...
}
abstract void draw();
}
Cada subclasse não abstrata de GraphicObject, como Circle e Rectangle, deve definir
uma implementação para o método draw:
Pode-se declarar uma classe como final, ou seja, a classe não pode ser derivada (possuir
subclasses). Existem pelo menos duas razões para desejar que uma classe possua tal
característica:
• Segurança: Um mecanismo que hackers utilizam para atacar sistemas é criar uma
subclasse de uma classe e substituir a classe original pela subclasse. A subclasse parece
e age como a classe original mas faz coisas bastante diferentes (e devastadoras),
podendo causar danos ou acessando informação privada. Para prevenir este tipo de
ataque, você pode declarar sua classe como final. Por exemplo, a classe String de Java
é uma classe final por esta razão. Esta classe é tão vital para a operação do compilador
e do interpretador que o ambiente Java deve garantir que quaisquer métodos ou objetos
que usem uma String acessem a classe definida pelo ambiente e não um outro tipo de
String. Isto garante que as Strings näo possuem nenhum comportamento estranho,
inconsistente, indesejável ou imprevisível. Ao tentar compilar uma subclasse de uma
classe final, o compilador exibirá uma mensagem de erro. Além disto, o verificador
Pág 85
bytecode do interpretador também garante que nenhum tipo de ataque deste tipo está
sendo feito, verificando se a classe que está sendo executada não é uma subclasse de
uma classe final.
• Boas práticas de projeto: por razões do projeto orientado a objetos, pode-se desejar
criar uma classe final. Pode-se pensar que uma dada classe é “perfeita” ou,
conceitualmente, que não deve possuir subclasses.
Para especificar que uma classe é final, utiliza-se a palavra-chave final antes da palavra-
chave class na declaração da classe. Por exemplo, suponha que você deseja declarar sua
classe (perfeita) ChessAlgorithm como final, a declaração será:
As vezes, o que se deseja é, na verdade, proteger apenas alguns dos métodos da classe de
serem redefinidos por uma subclasse. Para isto, utiliza-se a palavra-chave final nas
declarações de método para indicar para o compilador que tais métodos não podem ser
redefinidos por subclasses.
Pode-se querer que um método seja final porque sua implementação não pode ser alterada
ou porque tal método é crítico para manter o estado do objeto consistente . Por exemplo,
em vez de tornar a sua classe ChessAlgorithm final, você poderia querer tornar apenas o
método nextMove final:
class ChessAlgorithm {
...
final void nextMove(ChessPiece pieceMoved,
BoardLocation newLocation) {
...
}
...
}
6.6 Interfaces
Pág 86
Em inglês (ou português), uma interface é um dispositivo ou sistema que entidades
utilizam para interagir entre si. De acordo com esta definição, um controle remoto é uma
interface entre você e a televisão, o português é uma interface entre duas pessoas.
Analogamente, uma interface Java é um mecanismo que objetos utilizam para interagir.
Uma interface Java define um conjunto de métodos mas não define uma implementação
para eles. Uma classe que implementa a interface deve implementar todos os métodos
definidos na interface.
Considere o seguinte exemplo que apresenta duas classes que utilizam uma interface para
interagir. O exemplo é bem simples mas mostra como criar e utilizar uma interface e
também dá algumas “dicas“ sobre quando utilizar interfaces ou classes abstratas.
A classe AlarmClock (despertador) é uma “provedora de serviços” – ela avisa objetos que
um determinado período de tempo passou. Para utilizar os “serviços” do AlarmClock, um
objeto deve fazer duas coisas:
Se o AlarmClock tiver espaço, então ele registra o objeto “dorminhoco” (sleeper), inicia
uma nova thread AlarmThread e retorna true. Depois do período de tempo determinado,
o AlarmClock vai executar o método wakeUp do objeto dorminhoco.
Assim, um objeto que queira usar o despertador deve implementar o método wakeUp de
tal forma que o AlarmClock possa avisar o objeto “dorminhoco” que o tempo já passou.
Mas como isto é feito? Isto é feito por meio do tipo de dado do objeto dorminhoco que está
sendo registrado.
Pág 87
Note que o primeiro argumento de letMeSleepFor é o objeto que quer ser acordado. O tipo
de dado deste argumento é Sleeper, que é o nome da interface:
A interface Sleeper define o método wakeUp mas não o implementa. A interface também
define duas constantes úteis (ONE_SECOND e ONE_MINUTE). Classes que
implementam esta interface “herdam” as constantes e devem implementar o método
wakeUp.
Qualquer objeto que seja um Sleeper (e que, portanto, possa ser passado como parâmetro
do método letMeSleepFor) implementa esta interface. Isto significa que o objeto
implementa todos os métodos definidos pela interface. Por exemplo, considere a seguinte
classe, GUIClock, que é um applet que mostra o tempo atual e utiliza um objeto
AlarmClock para acordá-lo a cada minuto para que ele possa atualizar o display.
Depois que vimos a utilização de uma interface, surgem algumas questões importantes :
Observe que uma interface é simplesmente uma lista de métodos não implementados, ou
seja, abstratos. A seguinte classe Sleeper não faria a mesma coisa que a interface Sleeper?
Não. As duas soluções não são equivalentes. Se Sleeper é uma classe abstrata, então todos
os objetos que desejarem utilizar AlarmClock devem ser instâncias de uma subclasse de
Sleeper. Entretanto, muitos objetos que desejam utilizar AlarmClock já possuem uma
superclasse. Por exemplo, a classe GUIClock é uma subclasse de Applet – caso contrário
não seria possível executá-lo em um browser. Mas Java não possui herança múltipla, ou
seja, GUIClock não pode ser subcclasse de Sleeper e Applet.
Pág 88
Então as interfaces proporcionam herança múltipla?
Muitas vezes interfaces são apresentadas como uma alternativa para herança múltipla de
classes. Apesar de interfaces resolverem muitos problemas parecidos, interfaces e herança
múltipla são soluções bem distintas:
Importante: Java permite herança de multiplas interfaces. Ou seja, uma interface pode
possuir múltiplas “superinterfaces”.
Você pode usar uma interface para definir um comportamento que pode ser implementado
por qualquer classe, independente da sua hierarquia de classes. Interfaces são úteis nos
seguintes casos:
• Captura de similaridades entre classes não relacionadas, sem ser obrigado a criar
artificialmente uma relação de herança entre elas.
• Declaração de métodos que uma ou mais classes esperam implementar.
A figura a seguir mostra que uma definição de interface possui dois componentes: a
declaração e o corpo da interface:
Declaração da Interface
Pág 89
public interface Sleeper {
...
}
Uma interface pode ter outro componente: uma lista de “superinterfaces”. Uma declaração
completa de interface é mostrada na figura abaixo:
Caso a interface seja declarada como public, ela pode ser utilizada por qualquer classe. Se
nâo for especificado que a interface é pública, apenas as classes que estão definidas no
mesmo pacote podem acessá-la.
Uma interface pode ser derivada de outras interfaces, como ocorre com as classes.
Entretanto, uma interface pode ser derivada de mais de uma interface. A lista de
“superinterfaces” é uma lista delimitada por vírgulas.
Corpo da Interface
Sleeper.ONE_SECOND
Classes que implementam uma interface podem acessar estas constantes como se fossem
variáveis herdadas:
Pág 90
...
public void wakeUp() {
repaint();
clock.letMeSleepFor(this, ONE_MINUTE);
}
}
Lembre-se que quando uma classe implementa uma interface, ela deve implementar todos
os métodos declarados na interface (e em suas superinterfaces, se for o caso). Ou, a classe
deve ser declarada como abstrata. A assinatura do método (o nome e o número e o tipo dos
parâmetros) do método da classe deve ser idêntico ao da assinatura do método definido na
interface.
Quando se define uma nova interface, essencialmente, define-se um novo tipo de dado
(referência). Você pode utilizar nomes de interface como um tipo comum de variável. Por
exemplo:
Observação: Interfaces não podem crescer. Note que toda vez que você realizar uma
modificação em uma interface, todas as classes que implementam a interface antiga terão
problemas pois elas não implementam mais a interface. Programadores que utilizam a
interface certamente vâo reclamar bastante. Portanto, deve-se antecipar ao máximo todos
os usos da sua interface e especificá-la completamente desde o início.
Pág 91
6.8 Pacotes (Packages)
Para tornar as classes mais fáceis de se encontrar e utilizar, para evitar conflitos de nomes e
para controlar o acesso, pode-se agrupar classes relacionadas em pacotes (packages).
As classes e interfaces que fazem parte do kit de desenvolvimento Java (JDK) são
membros de vários pacotes: classes dos applets estão no pacote java.applet, de
entrada/saída no pacote java.io e as classes de elementos de interface gráfica estão no
pacote java.awt. Pacotes também podem conter interfaces.
• Fica mais fácil para outros programadores determinar quais classes e interfaces estão
relacionadas.
• Os nomes das classes de um pacote não irão conflitar com nomes de classes de outros
pacotes.
• Pode-se permitir que classes dentro de um mesmo pacote tenham acesso irrestrito entre
si, e restringir o acesso por parte de classes definidas fora do pacote.
Para criar um pacote, simplesmente coloca-se a classe ou interface dentro dele. Para isto,
coloca-se a palavra-chave package no início do arquivo onde a classe ou interface é
definida. Por exemplo, o código a seguir coloca a classe Circle (que está no arquivo
Circle.java) no pacote graphics:
package graphics;
Deve-se incluir a palavra-chave package no início de todo arquivo que define uma classe
ou interface que pertence ao pacote. Assim, seguindo o exemplo acima, deve-se incluir
package no arquivo Rectangle.java:
package graphics;
Lembrete: Apenas membros (classes, variáveis e métodos) públicos são acessíveis fora do
pacote onde foram definidos.
Pág 92
Observação Importante: se você não utilizar a palavra-chave package, sua classe ou
interface será automaticamente incluída no pacote default, que é um pacote que não possui
nome. Este pacote default deve ser utilizado apenas para pequenas aplicações ou no início
do desenvolvimento de aplicações mais complexas. Ou seja, aconselha-se que todas as
classes e interfaces pertençam a pacotes “com nome”.
Apenas membros (classes, métodos e variáveis) são acessíveis fora do pacote onde foram
definidos. Para utilizar membros públicos fora do seu pacote, deve-se:
graphics.Rectangle
Pág 93
Para importar um membro específico do pacote, deve-se colocar a palavra-chave import
no início do arquivo (antes das definições de classes e interfaces, mas depois da declaração
do package). Por exemplo, para importar a classe Circle do pacote graphics:
import graphics.Circle;
Agora, pode-se referenciar qualquer classe ou interface do pacote graphics pelo seu nome
curto:
O asterisco só pode ser utilizado para importar todas as classes de um pacote. Ele não pode
ser usado para importar um subconjunto de classes do pacote. Por exemplo, o código a
seguir não importa todas as classes do pacote graphics que começam om a letra “A”:
Nomes Ambíguos
Caso uma classe em um pacote possua o mesmo nome que uma classe definida em outro
pacote, e ambos os pacotes são importados, deve-se referenciar tais classes pelo seu nome
completo. No exemplo anterior, foi definida uma classe Rectangle no pacote graphics. O
pacote java.awt também possui uma classe chamada Rectangle. Se ambos pacotes forem
importados, o código abaixo torna-se ambíguo:
Rectangle rect;
Neste caso, deve-se obrigatoriamente ser mais específico e indicar exatamente qual classe
Rectangle deseja-se referenciar:
Pág 94
graphics.Rectangle rect;
O código fonte de uma classe ou interface deve ser colocado em um arquivo texto cujo
nome é igual ao nome da classe e cuja extensão é .java. Este arquivo fonte deve ficar em
um diretório cujo nome é igual ao do pacote que contém a classe ou interface. Por
exemplo, o código fonte da classe Rectangle deve estar em um arquivo chamado
Rectangle.java e o arquivo deve estar em um diretório chamado graphics. O diretório
graphics, por sua vez, pode estar em qualquer diretório do sistema de arquivos. O
diagrama a seguir mostra como isto funciona.
Além disto, por convenção, cada empresa utiliza o seu domínio de Internet invertido para
denominar os seus pacotes. Por exemplo, a UEMG/Passos possui o domínio
passos.uemg.br, assim todos os pacotes desenvolvidos na Universidade deveriam ter seus
nomes precedidos por br.uemg.passos. Cada componente do nome do pacote corresponde
a um subdiretório. Assim, supondo que a UEMG/Passos tenha um pacote chamado
graphics que contém um arquivo chamado Rectangle.java, este arquivo ficaria em:
package br.uemg.passos.graphics
br/uemg/passos/graphics/Retangle.java (UNIX)
br\uemg\passos\graphics\Retangle.java (Windows)
Ao compilar o arquivo fonte, o compilador cria um arquivo de saída para cada classe e
interface definida. O nome do arquivo é igual ao nome da classe ou interface e sua
extensão é .class:
Pág 95
Como o arquivo .java, um arquivo .class deve estar em uma série de diretórios que reflete o
nome do pacote. Entretanto, ele não precisa estar no mesmo diretório que o arquivo fonte.
Ou seja, pode-se definir diretórios separados para os arquivos fonte e os arquivos de
classes, por exemplo:
fontes\br\uemg\passos\graphics\Retangle.java (Windows)
classes\br\uemg\passos\graphics\Retangle.class (Windows)
Desta forma, você pode fornecer o seu diretório de classes a outros programadores sem ter
que revelar o código-fonte. Mas, afinal de contas, para que se preocupar em seguir esta
convenção de nomes diretórios/arquivos? Isto é necessário pois somente desta forma o
compilador e o interpretador Java são capazes de encontrar todas as classes e interfaces
utilizadas pela sua aplicação. Quando o compilador encontra uma nova classe enquanto
compila a sua aplicação, ele deve ser capaz de encontrar o arquivo correspondente.
Analogamente, quando o interpretador encontra uma nova classe enquanto está executando
a sua aplicação, ele deve ser capaz de executar os métodos desta classe. Tanto o
compilador quanto o interpretador procuram por classes em cada diretório (ou arquivo .zip)
definido no class path (caminho de classes).
Cada diretório definido no class path é um local onde os diretórios dos pacotes podem
aparecer. A partir dos diretórios listados no classpath, o compilador e o interpretador
podem construir o resto do caminho do arquivo baseado no nome do pacote e da classe Por
exemplo, o class path para a estrutura de diretórios
classes\br\uemg\passos\graphics\Retangle.class deve incluir o diretório classes
mas não os subdiretórios br ou passos, por exemplo.
Por default, o compilador e o interpretador procuram o diretório atual (.) e o arquivo .zip
que contém todas as classes do JDK. Em outras palavras, o diretório atual e os arquivo .zip
das classes do JDK já fazem parte automaticamente do seu class path.
Pág 96
Não é recomendado alterar a variável de ambiente CLASSPATH pois tal mudança pode
ser duradoura (se estiver no autoexec.bat, por exemplo). Assim, é fácil esquecer de que
uma mudança foi feita e corre-se o risco de, um dia, seu programa não funcionar pois o
compilador/interpretador carregou uma classe “antiga” que estava em um diretório
“esquecido” definido no class path.
A segunda opção é preferível pois define o class path apenas para a execução atual do
compilador/interpretador. O exemplo abaixo mostra como utilizar a opção -classpath
para definir o seu classpath:
6.10 Exercícios
class Carnivoro {
int numeroDeDentes;
}
class Pessoa {
String nome;
}
Pág 97
private int x;
protected int y;
int w;
public int z;
public int getX() { return x;}
public MinhaClasse() { x=y=z=w=0;}
}
4. Considere as classes:
class A {
public int x;
}
class B extends A {
public int x;
public static voi main (String args[]) {
x = 1;
super.x = 2;
System.out.println(“ x = “ + this.x);
System.out.println(“ x = “ + super.x);
}
10. Quais são as diferenças entre uma classe abstrata e uma interface?
11. A declaração da classe abaixo está correta? Justifique.
12. Defina uma interface chamada BomAluno. Defina alguns métodos e constantes para tal
interface.
13. O trecho de código abaixo está correto? Justifique.
Pág 98
interface UmaInterface {
...
}
class UmaClasse implements UmaInterface
{
...
};
class A
{
int x;
}
Pág 99
7. Interface Gráfica
Esta seção mostra como se pode criar interfaces gráficas (GUI, Graphical User Interface)
para aplicações stand-alone e applets Java, utilizando o pacote AWT (Abstract Windowing
Toolkit). Uma interface gráfica (GUI) consiste de componentes (widgets), como botões e
text-fields, posicionados de alguma forma em um applet ou em uma janela de uma
aplicação stand-alone. Estes componentes respondem a eventos iniciados pelo usuário,
como cliques no mouse, teclas pressionadas e mudam seu estado ou alteram algum dado
interno da aplicação.
7.1 Widgets
7.1.1 Component
Um componente é um widget genérico que não pode ser instanciado (Component é uma
classe abstrata). Todos os widgets gráficos são subclasses de Component.
Pág 100
• public void setForeground(Color c): define a cor do primeiro plano do
componente
Ex. TextField tf = new TextField(“0”);
tf.setForeground(new Color(0,255,0);
• public void setBackground(Color c): define a cor de “fundo” do
componente
Ex. TextField tf = new TextField(“0”);
tf.setBackground(new Color(255,0,0);
7.1.2 Button
A classe Button representa um (botão) que possui um label que pode ser “pressionado” por
meio de um clique do mouse. Exemplo:
import java.applet.*;
import java.awt.*;
public class ButtonWidget extends Applet {
public void init() {
Button b = new Button("OK");
add(b);
}
}
7.1.3 Canvas
A classe Canvas é um componente gráfico genérico que representa uma região onde se
pode desenhar coisas como retângulos, círculos e strings.
7.1.4 Checkbox
Pág 101
Alguns métodos importantes:
7.1.5 CheckBoxGroup
import java.applet.*;
import java.awt.*;
public class CheckboxGroupWidget extends Applet {
public void init() {
// create button controller
CheckboxGroup cbg = new CheckboxGroup();
Checkbox cb1;
cb1 = new Checkbox("Show lowercase only",cbg,true);
Checkbox cb2;
cb2 = new Checkbox("Show uppercase only",cbg,false);
add(cb1);
add(cb2);
}
}
7.1.6 Choice
A classe Choice representa uma lista de seleção. Os itens desta lista são adicionados ao
objeto Choice por meio do método addItem. Por exemplo:
import java.applet.*;
import java.awt.*;
public class ChoiceWidget extends Applet {
public void init()
{
Choice rgb = new Choice();
rgb.addItem("Red");
rgb.addItem("Green");
rgb.addItem("Blue");
add(rgb);
}
}
Pág 102
Alguns métodos importantes:
7.1.7 Label
A classe Label representa uma string de texto mostrada na tela. Por exemplo:
import java.applet.*;
import java.awt.*;
public class LabelWidget extends Applet {
public void init()
{
add(new Label("a label"));
// right justify
add(new Label("right justified label",Label.RIGHT));
}
}
7.1.8 List
A classe List é uma lista de seleção que permite a escolha de um ou mais itens. Múltiplos
argumentos podem ser selecionados se passarmos true como segundo argumento do
construtor de List. Por exemplo:
import java.applet.*;
import java.awt.*;
public class ListWidget extends Applet {
public void init()
{
List l = new List(2, true);
l.addItem(“Maçã”);
l.addItem(“Pera”);
l.addItem(“Banana”);
add(l);
}
}
Pág 103
• public void addItem(String item) : Adiciona um item a lista
• public int getSelectedIndex(): retorna o item selecionado (indíce numérico)
• public int[] getSelectedIndexes() : retorna os itens selecionados
(vetor de índices numéricos)
• public void select(int index): seleciona um item (índice numérico)
7.1.9 Scrollbar
Esta classe representa um “barra rolante” que contém um valor inteiro. Existem scroll bars
verticais e horizontais. Exemplo:
import java.applet.*;
import java.awt.*;
public class ScrollbarWidget extends Applet {
public void init() {
// using a GridLayout ensures Scrollbar fills applet
setLayout(new GridLayout(1,1));
Scrollbar sb;
sb = new Scrollbar(Scrollbar.HORIZONTAL);
add(sb);
}
}
7.1.10 TextComponent
A classe TextComponent não pode ser instanciada, ela é a superclasse dos widgets de
edição de texto (TextField e TextArea).
7.1.11 TextArea
Representa um campo de texto com várias linhas. A largura e altura do campo são
definidas pelo seu construtor. Exemplo:
Pág 104
import java.applet.*;
import java.awt.*;
public class TextAreaWidget extends Applet {
public void init() {
TextArea disp = new TextArea("A TextArea", 3, 30);
add(disp);
}
}
• public TextArea(String text, int rows, int cols): Cria uma área de
texto com o número especificado de linhas e colunas, além do texto inicial.
7.1.12 TextField
Um campo texto representa um widget que mostra uma linha de texto. A largura do campo
é especificada pelo construtor da classe e um texto inicial também pode ser especificado.
Exemplo:
import java.applet.*;
import java.awt.*;
public class TextFieldWidget extends Applet {
public void init()
{
TextField f1 = new TextField("type something");
add(f1);
}
}
7.1.13 Frame
Esta classe representa objetos que são janelas. Em geral, são utilizados por aplicações Java
standalone. Exemplo:
import java.applet.*;
import java.awt.*;
Pág 105
public static void main(String args[])
{
MinhaApp app = new MinhaApp();
}
public MinhaApp()
{
TextArea disp;
Frame f;
Panel p;
setLayout(new BorderLayout());
setTitle("Minha Janela");
reshape(250,250,250,250);
p = new Panel();
add("North", p);
disp = new TextArea("A TextArea", 3, 30);
p.add(disp);
show();
}
}
Todos os widgets que podem ser adicionados a um applet são subclasses da classe
Component. A fórmula básica para se inserir um widget em um applet é a seguinte:
Como os widgets adicionados aos applets vão aparecer na tela? Para responder esta
questão, é importante entender o conceito de um Container. Containers contêm objetos
Pág 106
que irão aparecer na tela. Estes containers possuem uma região associada. O simples fato
de se adicionar um Component a um Container faz com que este componente (widget)
seja desenhado na tela. Por exemplo, o applet é um tipo de container associado com uma
região retangular dentro de uma janela de um browser (Applet é uma subclasse de Panel
que, por sua vez, é uma subclasse de Container). Assim, podemos adicionar widgets ao
applet, no método init:
import java.applet.*;
import java.awt.*;
Em geral, um layout manager não será suficiente para posicionar seus applets - a região do
applet deve então ser subdividida. Cada uma destas sub-regiões é um container e possui o
seu layout manager associado. Os diferentes tipos de layout managers serão apresentados
em uma próxima seção.
Pág 107
mais recentes (Communicator 4.x e IE 4.x e 5.x), enquanto o outro modelo (versão 1.0) é
suportado pelos browsers mais antigos (Communicator 3.x e IE 3.x).
Uma interface de ouvinte pode definir mais de um método. Por exemplo, uma classe de
eventos como MouseEvent representa vários tipos de eventos do mouse (botão
pressionado, botão liberado, etc.); cada um destes tipos de evento corresponde a execução
de um método diferente nos objetos ouvintes.
Pág 108
WindowEvent WindowListener windowActivated()
windowClosed()
windowClosing()
windowDeactivated()
windowDeiconified()
windowIconified()
windowOpened()
A tabela a seguir lista alguns widgets e os eventos que eles podem gerar:
Dica: Para descobrir quais eventos um widget pode gerar, basta olhar a documentação da
API da classe. Por exemplo, pesquisando na documentação, podemos observar que a classe
Button possui os métodos addActionListener() e removeActionListener(), portanto a
classe Button gera eventos da classe ActionEvent.
O applet a seguir, que é uma extensão do exemplo da seção , mostra como o modelo de
eventos do Java funciona. Neste exemplo, o applet se registra como um ouvinet dos
eventos gerados pelo botão. Assim, quando o usuário clicar o botão, um evento da classe
ActionEvent é gerado e o método do applet ouvinte (actionPerformed()) será executado.
Este método, por sua vez, incrementa o conteúdo do text field do applet.
Pág 109
import java.applet.*;
import java.awt.*;
import java.awt.event.*;
v = v+1;
import java.applet.*;
import java.awt.*;
import java.awt.event.*;
Pág 110
FocusListener
{
Button b;
TextField tf;
}
public void actionPerformed(ActionEvent e)
{
// Converte a string dentro do text field p/ um
valor
// inteiro
int v = Integer.parseInt(tf.getText());
v = v+1;
v = v-1;
Pág 111
7.4 Gerenciadores de Layout (Layout Managers)
7.4.1 FlowLayout
Exemplo:
import java.awt.*;
import java.applet.Applet;
O mesmo applet terá um layout diferente dependendo da largura e altura do seu container:
<applet code=FlowLayoutTest.class CODEBASE="/applets/magelang/AWT-
Training/Examples/" width=150 height=35>
</applet>
7.4.2 BorderLayout
A classe BorderLayout possui as posições North, East, South, West, Center onde os
widgets podem ser colocados. Os componentes das posições North, East, South, West são
colocados na “borda” e os componentes na posição Center ocupam o espaço no centro.
Exemplo:
import java.awt.*;
import java.applet.Applet;
public class BorderLayoutTest extends Applet {
public void init() {
setLayout(new BorderLayout());
Pág 112
add("North", new Label("A Header Maybe"));
add("South",new TextField("Type some text here"));
add("West",new Button("QUIT"));
add("East", new Button("HELP"));
add("Center",new TextArea("Some random text\nin a TextArea"));
}
}
7.4.3 GridLayout
import java.awt.*;
import java.applet.Applet;public class GridLayoutTest extends Applet {
public void init() {
setLayout(new GridLayout(3,2));
add(new Button("1")); // uppper left button
add(new Button("2"));
add(new Button("3"));
add(new Button("4"));
add(new Button("5"));
add(new Button("6")); // lower right button
}
}
Color é uma classe que encapsula informações sobre cores. Pode-se criar objetos Color
com parâmetros RGB (Red, Green e Blue) ou HSV ou utilizar cores pré-definidas como
Color.red e Color.blue.
Pág 113
As fontes de um widget podem ser definidas pelo método setFont. Font é uma classe que
armazena informações de uma fonte. Para obter uma lista de fontes disponíveis, basta usar:
Exemplo:
import java.awt.*;
7.6 Exercícios
Pág 114
8. Applets
8.1 Introdução
Cada applet é implementado por uma subclasse da classe Applet, que acompanha o kit de
desenvolvimento Java. A figura a seguir mostra a hierarquia de herança da classe Applet.
Esta hierarquia é muito importante pois determina a maioria das tarefas que um applet
pode fazer, como será mostrado nas próximas seções.
O código a seguir refere-se a um applet chamado Simple. Este applet mostra uma string
sempre que passa por um fato importante da sua “vida” como, por exemplo, a primeira vez
que um usuário visita a página Web onde está o applet. Este exemplo será utilizado nas
próximas seções para ilustrar conceitos comuns à maioria dos applets Java.
import java.applet.Applet;
import java.awt.Graphics;
Pág 115
void addItem(String newWord) {
System.out.println(newWord);
buffer.append(newWord);
repaint();
}
public void paint(Graphics g) {
//Draw a Rectangle around the applet's display area.
g.drawRect(0, 0, getSize().width - 1, getSize().height - 1);
//Draw the current string inside the rectangle.
g.drawString(buffer.toString(), 5, 15);
}
}
Observação: Os exemplos de applet desta apostila utilizam a API da versão 1.1 do JDK.
Browsers mais antigos (Netscape 3.x e IE 3.x) não suportam tal versão.
A classe Applet oferece uma série de métodos relacionados com a execução de applets,
definindo métodos que o sistema chama de “marcos” (milestones), eventos importantes na
“vida” do applet. Estes métodos são executados à medida que estes marcos vão
acontecendo. A maioria dos applets redefine estes métodos de acordo com suas
necessidades.
O applet Simple, como qualquer outro applet, é uma subclasse de Applet. A classe Simple
redefine quatro métodos da classe Applet de tal forma que possa responder a estes eventos
importantes:
Nem todo applet precisa redefinir cada um destes métodos. Alguns applets muito simples
não redefinem nenhum destes métodos. Por exemplo, o applet HelloWorld, apresentado na
Pág 116
seção 1, não redefine estes métodos, ele apenas mostra uma string, utilizando o método
paint.
O método init é útil para realizar inicializações que não demorem muito. Em geral, o
método init deve conter o código que normalmente seria colocado em um construtor. O
motivo pelo qual applets não devem possuir construtores é que um applet não tem
garantias que o ambiente de execução (browser) está pronto até que o método init seja
executado. Por exemplo, os métodos para carregar imagens em um applet simplesmente
não funcionam dentro do construtor do applet. Por outro lado, o método init é o lugar ideal
para executar os métodos que carregam imagens.
O método start em geral é utilizado para iniciar uma ou mais threads que realizam alguma
tarefa do applet. A maioria dos applets que redefinem o método start também devem
redefinir o método stop. O método stop deve suspender a execução das threads do applet a
fim de liberar recursos quando o applet não estiver sendo visto pelo usuário. Por exemplo,
um applet que apresenta uma animação deve pará-la quando o usuário tiver “minimizado”
a página.
Muitos applets não redefinem o método destroy pois o método stop (que é chamado antes
do destroy) faz todo o trabalho necessário para finalizar a execução do applet. De qualquer
forma, destroy pode ser utilizado por applets que querem liberar outros recursos.
O método paint é um dos dois métodos de apresentação que um applet pode redefinir:
Pág 117
Applets herdam os métodos paint e update da classe Applet que, por sua vez, herda os
métodos da classe Component do AWT. O modelo de eventos do Java (1.1) foi
apresentado na seção 7.
Para exibir imagens dentro de um applet, utilizamos a classe abstrata Image, do pacote
AWT. Podemos imaginar esta classe como um “apontador” de imagens. A classe Applet
possui um método chamado getImage que serve para carregar imagens (nos formatos
JPEG ou GIF). Os parâmetros desta função são a URL onde está localizada a imagem e o
nome do arquivo da imagem. Para desenhar a imagem, utilizamos o método drawImage
da classe Graphics, passando como parâmetros um objeto da classe Image, além do
tamanho e da largura da imagem. Por exemplo:
import java.applet.Applet;
import java.awt.*;
Image im;
Para incluir um applet em uma página HTML basta utilizar a tag HTML <applet>.
Exemplo:
A tag <applet> possui alguns atributos: code é o nome do arquivo que contém os
bytecodes do applet (.class), os parâmetros width e height definem a largura e altura do
applet, respectivamente. Estes três parâmetros são obrigatórios. Também é obrigatória a
Pág 118
tag </applet>. A tag <applet> possui outros atributos, opcionais, que não serão abordados
nesta seção.
Este arquivo HTML pode ser visualizado por meio de um browser compatível com Java ou
utilizando a ferramenta do JDK chamada appletviewer.
8.6 Exercícios
1. O que é o ciclo de vida de um applet? Quais métodos do applet são invocados durante
seu ciclo de vida?
2. É possível executar um applet sem possuir um browser compatível com Java?
3. Quais são os três atributos obrigatórios da tag <applet>. Qual a sua finalidade?
4. Desenvolva um applet que possua uma lista de seleção (classe Choice) e que,
dependendo do item escolhido, mostre uma imagem diferente. Dica: o applet deve
implementar a interface java.awt.event.ItemListener e utilizar o método repaint().
Pág 119
9. Threads
Threads são uma podersosa ferramenta que permite realizar mais de uma tarefa
“simultaneamente” em um mesmo processo (programa em execução). Esta seção apresenta
o suporte da linguagem Java para programação envolvendo várias threads.
Uma thread é similar aos programas sequenciais descritos acima. Uma única thread tem
um começo, uma sequência de execução e um fim. Em qualquer instante, dentro de uma
thread, também apenas uma única instrução está sendo executada. Entretando, uma thread
não é um programa (processo), ou seja, não pode ser executada isoladamente. Na verdade,
uma thread deve existir dentro de um programa (processo). A figura a seguir mostra este
relacionamento:
Observe que não há nada de novo no conceito de uma única thread. A grande novidade do
conceito de threads é a possibilidade de utilizar várias threads dentro de um mesmo
programa (processo), executando ao mesmo tempo e realizando tarefas distintas. A figura a
seguir ilustra esta idéia:
Pág 120
9.2 Utilizando o Método run de uma Thread
O método run de uma thread permite que esta realize alguma tarefa. O código deste
método implementa o comportamento da thread. A thread pode fazer tudo que pode ser
implementado na linguagem Java: calcular um expressão matemática, ordenar alguns
dados, realizar alguma animação, etc.
A classe Thread implementa uma thread genérica que, por default, não faz nada. Ou seja,
a implementação do método run é vazia. Existem duas maneiras de implementar o método
run de uma thread, descritas nas seções a seguir.
A classe a seguir, TwoThreadTest, define um método main que cria dois objetos
SimpleThread: um com nome “Chocolate” e outro com o nome de “Sorvete”.
Pág 121
new SimpleThread("Chocolate”).start();
new SimpleThread("Sorvete").start();
}
}
O método main também inicia cada thread, imediatamente após a sua criação, executando
o método start. Ao executar esta classe, teremos uma saída parecida com esta:
0 Chocolate
0 Sorvete
1 Chocolate
1 Sorvete
2 Chocolate
2 Sorvete
3 Chocolate
3 Sorvete
4 Chocolate
4 Sorvete
5 Chocolate
5 Sorvete
6 Chocolate
6 Sorvete
7 Chocolate
7 Sorvete
8 Chocolate
8 Sorvete
9 Chocolate
9 Sorvete
ACABOU! Chocolate
ACABOU! Sorvete
Observe que as saídas das threads estão intercaladas, isto porque cada objeto
SimpleThread está sendo executado simultaneamente.
Pág 122
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(i + nome);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {}
}
System.out.println("ACABOU! " + nome);
}
}
Observe que agora SimpleThread não é uma subclasse de Thread e, portanto, não pode
mais executar o construtor e o método getName desta classe. Para executar o método
sleep, que também pertence a classe Thread, devemos colocar agora o nome da classe pois
tal método é estático (ou seja, é um método de classe e pode ser executado sem termos que
instanciar um objeto da classe Thread).
A classe TwoThreadsTest também deve sofrer uma modificação: para executar as threads
devemos chamar o construtor da classe Thread, passando como parâmetro um objeto da
classe SimpleThread (que implementa a interface Runnable).
new Thread(t1).start();
new Thread(t2).start();
}
}
Regra básica: Em geral, se a thread (classe) que você está desenvolvendo deve ser
subclasse de uma outra (Applet, por exemplo) utiliza-se a técnica descrita nesta seção.
Caso contrário, podemos utilizar a maneira descrita na seção anterior.
O diagrama a seguir mostra os estados em que uma thread pode estar durante o seu ciclo de
vida. A figura também mostra quais métodos causam uma transição para outros estados.
Pág 123
9.3.1 Criando uma Thread
Para criar uma thread, como para qualquer outro objeto, utiliza-se o construtor da classe.
Após executar o construtor, a thread fica no estado “New Thread” (ver figura acima).
Neste estado, temos uma thread “vazia” - nenhum recurso do sistema (memória,
processamento) foi reservado para a thread.
Para iniciar uma thread que está no estado “New Thread”, deve-se executar o método
start. O método start reserva os recursos do sistema necessários para que a thread possa
ser executada e executa o método run da thread.
Uma thread vai para o estado “not runnable” quando um destes eventos ocorre:
Enquanto a thread está neste estado, ela não é executada, mesmo que o processador esteja
disponível. Obviamente, existem eventos, relacionados com as 3 situações descritas acima,
que retiram a thread do estado “Not runnable”.
Pág 124
9.3.4 Terminando com uma Thread
A thread termina a sua execução quando o método run termina. Após o fim deste método,
a thread “morre” e os recursos do sistema reservados para ela são liberados.
Até agora, temos visto exemplos de threads independentes. Ou seja, cada thread contém
todos os dados e métodos necessários para a sua execução e não precisa de recursos ou
métodos externos. Entretanto, existem situações onde as threads podem compartilhar
dados.
Por exemplo, imagine uma aplicação Java onde uma thread (produtora) escreve dados em
um arquivo enquanto uma outra thread (consumidor) lê os dados do mesmo arquivo.
Sempre que duas ou mais threads compartilham algum recurso (arquivo, variável, etc.)
deve-se sincronizá-las de alguma forma.
Existem várias formas de sincronizar threads. A maneira mais simples, apresentada nesta
seção, é a utilização de métodos synchronized. Este mecanismo garante que enquanto uma
thread estiver executando um método synchronized, nenhuma outra thread pode executar
este mesmo método. As threads que desejarem executar este método, ficarão bloqueadas
até que este retorne. Adaptando o exemplo da seção , temos uma thread que imprime os
valores 0 a 49 (método imprimeValor).
0
1
2
0
1
3
.
Pág 125
.
.
0
1
2
3
...
49
0
1
2
3
...
49
Isto ocorre pois, agora, o método imprimeValor só pode ser executado por uma thread.
Assim uma das threads fica bloqueada, aguardando que a outra thread termine de executar
o método imprimeValor. Esta técnica de sincronização pode ser utilizada para organizar o
compartilhamento de recursos entre as threads (por exemplo, em aplicações produtor x
consumidor).
9.5 Exercícios
1. Crie uma terceira thread, no programa mostrado na seção , chamada biscoito. Qual é a
nova saída do programa?
2. Ainda com relação ao exemplo da seção , o que acontece se tiramos a instrução
sleep(1000) do método run das threads? Qual será a saída do programa? Explique este
novo comportamento do programa.
3. Explique o que significam métodos synchronized e qual a sua finalidade.
4. Crie um programa que possua duas threads. Uma deve incrementar uma variável e outra
decrementar. Entretanto, a thread que faz o decremento deve verificar se o valor da
variável é positivo; se não for, a variável não deve ser decrementada. Mostre o valor da
variável na tela.
Pág 126
10. Entrada e Saída
Muitas vezes, as aplicações necessitam buscar informações de uma fonte externa ou enviar
dados para um destino externo. A informação pode estar em vários lugares: em um
arquivo, em algum lugar da rede ou em outro programa, por exemplo. Esta seção mostra
alguns exemplos de como realizar esta troca de informação.
10.1 Introdução
Em Java, para buscar informações, um programa deve abrir um stream (canal) para a fonte
de informação (um arquivo, por exemplo) e ler a informação de maneira sequencial:
Importante: não importa de onde a informação vem nem para onde está indo; também não
importa qual o tipo de dado que está sendo lido ou escrito, os algoritmos de leitura e
escrita, descritos acima, são sempre os mesmos.
Leitura Escrita
abrir um stream abrir um stream
enquanto houver mais enquanto houver mais
informação informação
ler informação escrever informação
fechar o stream fechar o stream
O pacote java.io contém uma grande coleção de classes que representam vários tipos de
canais (streams). Nas seções a seguir, serão apresentadas algumas destas classes.
Pág 127
um objeto da classe File. A classe File representa um arquivo - o construtor desta classe
tem como parâmetro o nome do arquivo.
import java.io.*;
in.close();
out.close();
}
}
Inicialmente, o programa cria dois objetos da classe File que representam os arquivos de
entrada e saída do programa. Depois, são criados os dois streams, um da classe
FileInputStream (para a leitura de dados) e FileOutputStream (para a escrita de dados).
Então, o programa realiza um laço onde faz uma leitura do FileInputStream (método read
- quando este método retorna -1 significa que se chegou ao fim do arquivo). Cada dado
lido é escrito no FileOutputStream (método write).
import java.io.*;
Pág 128
PrintWriter out = new PrintWriter(outputFile);
String s;
out.flush();
in.close();
out.close();
}
}
A diferença principal com relação ao exemplo anterior é que neste programa fazemos a
leitura e a escrita de linhas de texto (métodos readLine e println). Note que é necessário
executar o método flush do objeto PrintWriter para que os dados sejam efetivamente
escritos no arquivo de saída.
A classe System do pacote java.lang possui três variáveis stream que representam:
import java.io.*;
Pág 129
System.out.print("Digite outro valor: ");
x = ent.readLine();
ent.close();
}
}
10.4 Exercícios
1. Faça um programa que leia dez strings digitadas pelo usuário e que escreva estas strings
em um arquivo.
2. Faça um programa que leia dez strings que estão em um arquivo texto e que as exiba na
tela.
Pág 130