You are on page 1of 130

Introdução a Linguagem Java

Apostila desenvolvida por Alexandre de


Souza Pinto (Engenheiro de Computação –
Unicamp Mestre em Engenharia Eletrica –
Unicamp, Consultor da Software Design
Informática-Campinas, SP) e formatada por
Marco Aurélio Lopes Barbosa

Pág 1
1. INTRODUÇÃO.............................................................................................................5

1.1 Um Pouco de História...................................................................................................5

1.2 Por que Java é tão Interessante?.................................................................................6

1.3 Como Java Pode Mudar Minha Vida?......................................................................8

1.4 Mas Afinal, o Que Java Pode Fazer?.........................................................................9

1.5 Arquitetura Java.........................................................................................................10

1.6 Desenvolvendo os Primeiros Programas em Java..................................................12

1.7 Exercícios:....................................................................................................................16

2. CONCEITOS DE ORIENTAÇÃO A OBJETOS.....................................................17

2.1 Conceitos Básicos........................................................................................................17

2.2 Características da Tecnologia de Objetos ..............................................................20

2.3 Exercícios......................................................................................................................23

3. DIFERENÇAS ENTRE JAVA E C/C++..................................................................24

4. CONSTRUÇÕES BÁSICAS DA LINGUAGEM JAVA.........................................28

4.1 Variáveis e Tipos de Dados........................................................................................28

4.2 Operadores ..................................................................................................................31

4.3 Expressões....................................................................................................................35

4.4 Controle de Fluxo........................................................................................................37

4.5 Arrays e Strings...........................................................................................................42

4.6 Exercícios......................................................................................................................45

5. CLASSES E OBJETOS EM JAVA.........................................................................46

5.1 Classe.............................................................................................................................46

5.2 Ciclo de Vida de um Objeto.......................................................................................48

5.3 Liberando Objetos não mais Utilizados...................................................................52

Pág 2
5.4 Criando Classes...........................................................................................................54

5.5 Exercícios......................................................................................................................76

6. OUTRAS CARACTERÍSTICAS DA LINGUAGEM JAVA...................................80

6.1 Herança.........................................................................................................................80

6.2 Ocultando variáveis....................................................................................................81

6.3 Redefinindo (overriding) métodos............................................................................82

6.4 Métodos e Classes Abstratas......................................................................................83

6.5 Classes e Métodos Finais............................................................................................85

6.6 Interfaces......................................................................................................................86

6.7 Utilizando Interfaces como Tipos de Dados............................................................91

6.8 Pacotes (Packages)......................................................................................................92

6.9 Gerenciando Arquivos Fonte e Arquivos de Classe...............................................95

6.10 Exercícios....................................................................................................................97

7. INTERFACE GRÁFICA..........................................................................................100

7.1Widgets........................................................................................................................100

7.2 Inserindo Widgets em Applets................................................................................106

7.3 Modelo de Eventos do Java (1.1).............................................................................107

7.4 Gerenciadores de Layout (Layout Managers)......................................................112

7.5 Cores e Fontes............................................................................................................113

7.6 Exercícios....................................................................................................................114

8. APPLETS.................................................................................................................115

8.1 Introdução..................................................................................................................115

8.2 Ciclo de Vida de um Applet.....................................................................................116

8.3 Métodos Utilizados para Desenho e Tratamento de Eventos.............................117

8.4 Mostrando Imagens Dentro de um Applet............................................................118

Pág 3
8.5 Adicionando um Applet a uma Página HTML.....................................................118

8.6 Exercícios....................................................................................................................119

9. THREADS................................................................................................................120

9.1 O que é uma Thread.................................................................................................120

9.2 Utilizando o Método run de uma Thread..............................................................121

9.3 Ciclo de Vida de uma Thread..................................................................................123

9.4 Sincronização de Threads........................................................................................125

9.5 Exercícios....................................................................................................................126

10. ENTRADA E SAÍDA.............................................................................................127

10.1 Introdução................................................................................................................127

10.2 Leitura e Escrita de Arquivos...............................................................................127

10.3 Entrada e Saída de Dados (Padrão).....................................................................129

10.4 Exercícios .................................................................................................................130

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.”

Antoine de Saint Exupery

1. Introdução

Nunca anteriormente nenhuma nova linguagem de programação recebeu tanta atenção e


tornou-se tão popular em tão pouco tempo. Desde o seu surgimento, Java tornou-se uma
escolha atraente para o desenvolvimento de aplicações Internet/intranet. O fenômeno Java
cativou a imaginação de programadores em todo mundo e está proporcionando o
desenvolvimento de uma nova geração de aplicações distribuídas.

1.1 Um Pouco de História

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.

No segundo trimestre de 1995, a Sun anunciou oficialmente o Java. A “nova” linguagem


foi rapidamente considerada como uma poderosa ferramenta para desenvolver aplicações
Internet. A Netscape Communications, que desenvolve o browser Netscape, adicionou
suporte a Java na versão 2.0 do seu navegador. Outros fornecedores de browsers, como a
Microsoft (Internet Explorer 3.0), também seguiram o exemplo.

1.2 Por que Java é tão Interessante?

A linguagem Java foi projetada para atender às necessidades do desenvolvimento de


aplicações em um ambiente distribuído (rede) e heterogêneo. Um dos desafios
fundamentais está relacionado com a instalação segura de aplicações que consumam o
mínimo de recursos do sistema, possam executar em qualquer plataforma de hardware e
software e possam ser estendidas dinamicamente.

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.

1.2.1 Simples, Orientada a Objetos e Familiar

Uma das características principais de Java é a simplicidade da linguagem, não sendo


necessário um extensivo treinamento por parte dos programadores. Os conceitos
fundamentais de Java são absorvidos rapidamente e, portanto, os programadores
conseguem ser produtivos em pouco tempo. Por ser parecida, o máximo possível, com
C++, Java acaba se tornando uma linguagem familiar pois possui o “look and feel” do
C++.

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.

A linguagem é acompanhada de um grande número de bibliotecas de classes já testadas e


que proporcionam várias funcionalidades (E/S, networking, interface gráfica, multimídia,
etc.). Estas bibliotecas podem ser facilmente estendidas, de acordo com as necessidades da
aplicação em particular, via herança.

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.

1.2.3 Arquitetura Neutra e Portável

Para suportar a distribuição em ambientes heterogêneos, as aplicações devem ser capazes


de executar em uma variedade de sistemas operacionais e de arquiteturas de hardware. Para
acomodar esta diversidade de plataformas, o compilador Java gera bytecodes, um formato
intermediário neutro projetado para o transporte eficiente em múltiplos ambientes de
software/hardware. Assim, a natureza interpretada de Java resolve o problema de
distribuição de código binário.

A neutralidade da arquitetura é apenas uma parte de um sistema verdadeiramente portável.


Java também define os tamanhos de seus tipos básicos e o comportamento de seus
operadores aritméticos. Desta forma, não existem incompatibilidades de tipos de dados
entre arquiteturas de software/hardware diferentes.

Esta arquitetura neutra e portável é chamada de Máquina Virtual Java (Java Virtual
Machine, JVM, seção ).

1.2.4 Alto Desempenho

Desempenho é um fator que sempre deve ser levado em consideração. O interpretador da


linguagem possui algumas otimizações que permitem a execução mais eficiente de código
Java. Além disto, aplicações que necessitam de grande poder computacional podem ter
suas seções mais críticas reescritas em código nativo (compilado a partir da linguagem C,
por exemplo).

Obviamente, pela característica interpretada da linguagem, seu desempenho, em geral, é


inferior ao de linguagens compiladas para linguagem de máquina (C++, por exemplo).

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.

1.2.5 Interpretada, Multithreaded e Dinâmica

O interpretador da linguagem Java pode executar os bytecodes em qualquer máquina onde


exista o ambiente de execução (run-time environment) instalado. Pela característica
interpretada da linguagem, a fase de ligação (linking) de um programa é simples,
incremental e leve. Assim, o ciclo de desenvolvimento, prototipação e testes tende a ser
mais rápido do que o método tradicional (compilar, ligar e testar).

Aplicações de rede, como browsers, tipicamente precisam realizar várias tarefas


“simultaneamente”. Por exemplo, um usuário utilizando um browser pode rodar várias
animações enquanto busca uma imagem e faz um “scroll” da página Web. A capacidade da
linguagem Java de executar várias threads dentro de um programa (processo) permite que
uma aplicação seja construída de tal forma que para cada atividade seja reservada uma
thread exclusiva de execução. Assim, obtem-se um alto grau de interatividade com o
usuário da aplicação.

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.

1.3 Como Java Pode Mudar Minha Vida?

Consideradas individualmente, as características discutidas nos itens anteriores podem ser


encontradas em uma variedade de ferramentas de desenvolvimento de software. A grande
novidade é a maneira como Java (e seu ambiente de execução) combinaram estas
qualidades e produziram uma linguagem flexível e, ao mesmo tempo, poderosa.

Desenvolver aplicações em Java resulta em um software que é portável em múltiplas


plataformas de hardware e software (sistemas operacionais, interfaces gráficas), seguro e
de alto desempenho. Provavelmente, Java deve tornar seus programas melhores e exigir
menos esforço de desenvolvimento do que outras linguagens. Resumindo, Java pode ajudá-
lo a:

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.

1.4 Mas Afinal, o Que Java Pode Fazer?

Provavelmente, os programas em Java mais conhecidos são os applets. Applet é um


programa que possui determinadas características que o permitem executar em um browser
(que possui suporte a Java).

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:

• Fundamentais: Objetos, strings, threads, números, entrada/saída, estrutura de dados,


data, tempo, etc.

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.

1.5 Arquitetura Java

Na realidade, Java tem dois significados: é uma linguagem de programação e uma


plataforma.

1.5.1 A Linguagem de Programação Java

Java é uma linguagem de programação de alto nível com as seguintes características


(apresentadas nos itens anteriores):

• Simples
• Arquitetura neutra, portável
• Interpretada
• Orientada a objetos
• Distribuída
• Alto desempenho
• Multithreaded
• Robusta
• Segura
• Dinâmica

Java possui uma característica um tanto rara: um programa Java é compilado e


interpretado. Com o compilador, é feita a tradução de um programa Java para um código
intermediário chamado de bytecode. Os bytecodes são independentes de arquitetura de
software/hardware. Com o interpretador cada instrução bytecode é analisada (parse) e
executada no computador. Observe que a compilação ocorre apenas uma vez; já a
interpretação acontece cada vez que o programa é executado. A figura abaixo ilustra esta
situação:

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).

1.5.2 A Plataforma Java

Plataforma é o ambiente de hardware ou software onde um programa é executado. A


plataforma Java difere da maioria das outras pelo fato de ser uma plataforma (de software,
apenas) que executa sobre outras plataformas baseadas em hardware e software (sistema
operacional).

A plataforma Java possui dois componentes:

• A Máquina Virtual Java (JVM)


• A Interface de Programação de Aplicação (Application Programming Interface, API)

A Máquina Virtual Java, apresentada no item anterior, é a fundação (base) da plataforma


Java e já existem implementações da JVM para diversas plataformas de hardware/software.

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

Máquina Virtual Java

Plataforma Hardware

Por ser um ambiente independente de plataforma, Java tende a possuir um desempenho


pior do que aplicações em código nativo. Entretanto, compiladores e interpretadores
otimizados, compiladores just-in-time podem aproximar o desempenho de Java ao do
código nativo sem ameaçar a portabilidade da linguagem.

1.6 Desenvolvendo os Primeiros Programas em Java

Antes de iniciar o desenvolvimento de programas em Java, é necessário possuir um


ambiente de desenvolvimento Java. A ferramenta mais comumente utilizada chama-se Java
Development Kit (JDK), desenvolvida pela própria Sun. As principais ferramentas que
acompanham o JDK são:

• javac: compilador que pode compilar qualquer tipo de programa Java


• java: interpretador utilizado para executar aplicações Java (standalone)
• appletviewer: ambiente para execução de applets Java

Além destas ferramentas, o JDK oferece outros utilitários (depurador, gerador de


documentação, “compactador” de classes, etc.) que serão apresentados ao longo do curso.
O JDK contém toda a API central (core API) e sua documentação, além de exemplos de
applets.

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.

O JDK é gratutito para download e pode ser obtido em:


http://java.sun.com/j2se/1.4/index.html

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”.

Criar um Arquivo Fonte em Java

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
}
}

Compilar o Código Fonte

No prompt do DOS, execute o compilador Java:

javac HelloWorldApp.java

Se a compilação tiver êxito, o compilador vai criar um arquivo chamado


HelloWorldApp.class no mesmo diretório do arquivo fonte (HelloWorldApp.java). O
arquivo gerado contém os bytecodes Java, independentes de plataforma, que são
interpretados pelo ambiente de execução do Java.

Se a compilação falhar, verifique se o programa foi digitado corretamente (cuidado com as


letras maiúsculas/minúsculas).

Executar a Aplicação

No prompt do DOS, execute o interpretador Java:

java HelloWorldApp

Observação Importante: o argumento do interpretador Java é o nome da classe a ser


executada e não o nome do arquivo. O nome da classe deve ser digitado da mesma
maneira que foi definido no código fonte (maiúsculas/minúsculas).

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 */.

Definindo uma Classe

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.

Em Java, a forma mais simples de se definir uma classe é:

class name {
...
}

A palavra-chave class começa a definição de uma classe denominada name. As variáveis e


os métodos da classe são delimitados por um par de chaves. A aplicação “Hello World”
não possui variáveis e tem um único método chamado main.

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.

Utilizando Classes e Objetos

Os outros componentes de uma aplicação Java são os objetos, classes, métodos e


comandos (statements) da linguagem Java escritos para implementar as funcionalidades
desejadas.

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.

Criar um Arquivo Fonte em Java

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;

public class HelloWorld extends Applet {


public void paint(Graphics g) {
g.drawString("Hello world!", 50, 25);
}
}

Compilar o Código Fonte

No prompt do DOS, execute o compilador Java:

javac HelloWorld.java

Se a compilação tiver êxito, o compilador vai criar um arquivo chamado HelloWorld.class


no mesmo diretório do arquivo fonte (HelloWorld.java). O arquivo gerado contém os
bytecodes Java, independentes de plataforma, que são interpretados pelo ambiente de
execução.

Se a compilação falhar, verifique se o programa foi digitado corretamente (cuidado com as


letras maiúsculas/minúsculas).

Criar um Arquivo HTML que Inclua o Applet

Utilizando um editor de texto qualquer, crie um arquivo chamado Hello.html no mesmo


diretório que contém a classe HelloWorld.class. Este arquivo HTML deve conter o
seguinte código:

<HTML>
<HEAD>
<TITLE> Um Programa Simples </TITLE>
</HEAD>
<BODY>

Aqui está o meu applet:


<APPLET CODE="HelloWorld.class" WIDTH=150 HEIGHT=25>

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

Para utilizar o Applet Viewer, digite no prompt do DOS:

appletviewer c:\src\Hello.html

1.7 Exercícios:

2. Qual a diferença entre um applet Java e uma aplicação standalone?


3. Por que os applets Java são considerados como uma aplicação neutra (em termos de
plataforma)?
4. Explorar, na Internet, algumas das várias fontes para programadores Java, executar e
observar o código-fonte de alguns exemplos disponíveis.
5. Como você acha que a citação apresentada no início da apostila se relaciona com Java?

Pág 16
2. Conceitos de Orientação a Objetos

Linguagens de programação, como os próprios idiomas humanos, evoluem ao longo do


tempo. Há um constante refinamento e aperfeiçoamento para atender às exigências cada
vez maiores dos usuários. Como outras linguagens de programação modernas, como C++,
Java é um amálgama de várias técnicas desenvolvidas ao longo dos anos.

Assim, seguindo os paradigmas de desenvolvimento mais utilizados, a linguagem Java é


orientada a objetos. O paradigma de orientação a objetos já demonstrou que é muito mais
do que uma simples “moda”. Benefícios da tecnologia de objetos são observados à medida
que um número cada vez maior de empresas “migram” seus produtos para este tipo de
modelo.

Infelizmente, o conceito de “orientação a objetos” continua um pouco confuso e é


propagado como a “bala de prata” que vai resolver todos os problemas do mundo do
software. Por exemplo, uma visão superficial de programação orientada a objetos é afirmar
que este paradigma é simplesmente uma nova maneira de organizar o código fonte. Na
verdade, pode-se alcançar, com técnicas de programação orientada a objetos, resultados
que não seriam possíveis de se obter com técnicas procedurais.

Como uma linguagem orientada a objetos, Java aproveita os melhores conceitos e


funcionalidades de linguagens mais antigas (principalmente Eiffel, SmallTalk, Objective C
e C++). Java vai além do C++ pois estende o modelo de objetos e remove as maiores
complexidades da linguagem. Por exemplo, com exceção dos tipos de dados primitivos,
tudo em Java é um objeto (até mesmo os tipos primitivos podem ser encapsulados dentro
de objetos, se for necessário).

2.1 Conceitos Básicos

2.1.1 Objetos

Como o próprio nome “orientado a objetos” indica, o conceito de objetos é fundamental


para o entendimento desta tecnologia. Pode-se olhar a sua volta e encontrar vários
exemplos de objetos: cachorro, cadeira, televisão, bicicleta, etc.

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:

Detalhes de implementação (privados)

API (pública)

Tudo que o objeto de software conhece (estado) e pode fazer (comportamento) é


expressado pela variáveis e métodos do objeto. Por exemplo, um objeto que modela uma
bicicleta poderia ter variáveis que indicam seu o estado atual: velocidade é 20 km/h,
marcha atual é a quinta. Exemplos de métodos deste objeto seriam: frear, aumentar a
velocidade da pedalagem e trocar de marcha. Estas variáveis e métodos são formalmente
chamados de variáveis de instância e métodos de instância a fim de distinguí-los de
variáveis e métodos de classe (descritos no item Classes).

As variáveis de um objeto fazem parte do seu núcleo (centro). Os métodos envolvem e


escondem o núcleo do objeto de outros componentes (objetos) da aplicação. Empacotar as
variáveis de um objeto sobre a proteção de seus métodos é chamado de encapsulação
(seção ). Basicamente, a encapsulação é usada para esconder detalhes de implementação
pouco importantes. Por exemplo, quando se deseja trocar uma marcha na sua bicicleta, não
é preciso saber como o mecanismo de troca de marchas funciona, apenas qual alavanca
deve ser movida. Analogamente, em programas, muitas vezes não é necessário saber como
um objeto foi implementado, apenas saber qual método deve ser invocado. Assim, os
detalhes de implementação podem ser mudados sem afetar outras partes do programa.

Esta representação conceitual de um objeto, um núcleo de variáveis dentro de uma


membrana protetora de métodos, é um modelo ideal que deve servir de como meta para
projetistas de sistemas orientados a objetos. Entretanto, esta representação não é totalmente
realista. Muitas vezes, por razões de implementação ou eficiência, um objeto pode desejar
expor algumas de suas variáveis ou esconder alguns de seus métodos. Esta capacidade de
um objeto controlar quais componentes podem acessar seus métodos e variáveis é chamada
de Controle de Acesso (a ser descrita mais detalhadamente nas próximas aulas do curso).

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.

Objetos comunicam-se entre si por meio do envio de mensagens. Quando um objeto A


deseja que o objeto B realize um dos seus métodos (de B), o objeto A envia uma
mensagem para o objeto B.

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.

Assim, três componentes fazem parte de uma mensagem:

• o objeto para onde a mensagem é endereçada (bicicleta)


• o nome do método a realizar (mudar a marcha)
• parâmetro(s) necessário(s) para realizar o método (segunda marcha)

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.

2.2 Características da Tecnologia de Objetos

Para ser considerada verdadeiramente orientada a objetos, uma linguagem de programação


deve oferecer, no mínimo, estas três características:

• Encapsulação
• Herança
• Polimorfismo

2.2.1 Encapsulação

Uma das principais diferenças entre o paradigma de programação estruturada e o modelo


de orientação a objetos é a característica de encapsulação. Encapsulação permite esconder,
dentro de um objeto, tanto sua variáveis quanto os métodos que manipulam estas variáveis.
Assim, é possível controlar acessos de outros componentes aos dados do objeto.

De que maneira este ocultamento de informação difere da abordagem estruturada? Por


exemplo, na programação estruturada também pode-se esconder os dados dentro de uma
função simplesmente criando variáveis locais. O problema surge quando se deseja tornar
uma variável disponível a outras funções. Em um programa estruturado, a solução é criar
variáveis globais. Infelizmente, desta forma qualquer função dentro do programa pode
acessar os dados.

Na programação orientada a objetos, consegue-se criar variáveis de instância, que podem


ser acessadas por qualquer método do objeto mas não são visíveis por outros objetos.

Resumindo, a idéia de encapsulação, apesar de simples, é uma ferramenta poderosa que


oferece dois benefícios principais aos programadores:

• Modularidade: o código fonte de um objeto pode ser escrito e mantido


independentemente do código fonte de outros objetos.
• Ocultamento da informação: um objeto possui uma interface pública que outros
objetos podem utilizar para comunicar-se com ele. Além disto, o objeto pode manter
informações e métodos privados que podem ser mudados a qualquer momento sem
afetar os outros objetos.

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

Mountain Bikes Bicicletas de Corrida

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)

Entretanto, subclasses não estão limitadas ao comportamento herdado de sua superclasse.


Subclasses podem adicionar variáveis e métodos a aqueles herdados.

Subclasses também podem redefinir (override) métodos herdados e oferecer


implementações especializadas para estes métodos. Por exemplo, se uma montain bike
possui um conjunto extra de marchas, você poderia sobrecarregar o método “mudar
marcha” de tal forma que seja possível escolher este novo conjunto de marchas.

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.

Como benefícios do conceito de herança, podemos citar:

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

Segundo a terminologia de orientação a objetos, polimorfismo significa que uma mesma


mensagem enviada a diferentes objetos resulta em um comportamento que é dependente da
natureza do objeto que está recebendo a mensagem.

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.

Se a subclasse oferece um método de assinatura (nome, parâmetro de retorno e


argumentos) parecida com a do método herdado então não se trata de uma redefinição e
sim de uma sobrecarga pois criou-se, na verdade, um novo método.

Outro tipo bastante interessante de polimorfismo é conhecido como acoplamento dinâmico


(dynamic binding). Por acoplamento, entenda-se a escolha correta de um método a ser
executado para uma variável declarada como de uma classe, mas que pode conter um
objeto de uma subclasse desta. Por dinâmico, entenda-se tempo de execução.

Devido às características de herança, pode-se atribuir um objeto de uma subclasse a uma


variável da classe pai (mas não o contrário!). Com o acoplamento dinâmico, é possível
fazer com que a execução de um método dependa do tipo do objeto atribuído a variável.
Por exemplo:

Sejam duas classes Conta e ContaEspecial, onde ContaEspecial é uma subclasse de


Conta. Suponha que exista um método do Conta (retirada()), que foi redefinido por
ContaEspecial. Assim:

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

1 Quais são os dois principais componentes de um objeto?


2 Qual a relação entre classe e objeto?
3 Quais os benefícios do uso do conceito de mensagens para descrever a interação entre
objetos?
4 Quais são as três principais características presentes no paradigma de orientação a
objetos? Defina cada um destes conceitos.
5 Considere um objeto do mundo real, como um aparelho de som ou um forno. Liste as
variáveis e métodos deste objeto. Tente definir também subclasses

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.

A seguir são abordadas algumas das principais diferenças entre as linguagens.

3.1.1 Argumentos de Linha de Comando

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:

public static void main(String args[])

O argumento da função é um vetor de strings, convencionalmente chamado de args ou


argv.Os elementos deste vetor são os argumentos, se existirem, da linha de comando. O
tamanho do vetor argv é dado por argv.length (assim como para qualquer vetor em Java).
Ao contrário de C, o primeiro elemento do array não é o nome da classe e sim o primeiro
argumento da linha de comando.

3.1.2 Valor de Saída do Programa

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

Java suporta os mesmo delimitadores de comentário do C/C++ (/* */ e //) além de um


terceiro tipo (/** */). Este último tipo serve para produzir documentação on-line, por meio
da ferramenta javadoc (a ser apresentada no final do curso).

Pág 24
3.1.4 Caracteres Unicode

Caracteres, Strings e identificadores (nomes de variáveis, métodos e classes) são


compostos de caracteres de 16 bits, chamados de Unicode (C/C++ utilizam caracteres de 8
bits, no formato ASCII). Isto facilita a migração de programas Java para outros idiomas
diferentes do inglês. Por exemplo, um programador Tailândes pode usar o alfabeto Thai
para nomear variáveis e métodos no seu código-fonte.

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.

3.1.5 Sem Variáveis Globais

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;

MeuObjeto.contador = 1; // Variável de instância


MeuObjeto.Incrementar(); // Método de instância
MinhaClasse.incremento = 5; // Variável de classe
MinhaClasse.MostraIncremento(); // Método de classe

3.1.6 Sem Pré-processador

Java não inclui nenhum tipo de pré-processador. Assim, não existem diretivas como
#define, #include, #ifdef e nem typedef.

Assim, Java elimina a necessidade de arquivos de cabeçalhos (headers files). Em vez de


cabeçalhos, os arquivos-fonte em Java possuem simplesmente as declarações das suas
classes e métodos. Java define um mapeamento entre os nomes dos pacotes/classes e os
diretórios onde estão de tal forma que quando o compilador Java precisa ler um
determinado arquivo de classe, ele sabe exatamente onde encontrá-lo.

Um dos problemas principais de C/C++ é a quantidade de contexto necessária para se


entender o código de outros programadores: deve-se ler todos os cabeçalhos relacionados,
todos os #defines e typedefs.

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.

Java não possui nenhum mecanismo de compilação condicional, na forma de diretivas


#ifdef ou #if. Teoricamente, compilação condicional não é necessária em Java pois a
linguagem é independente de plataforma e, portanto, não existem partes do código
específicas para uma determinada plataforma. Na prática, a compilação condicional
também é utilizada para fins de depuração. Nestes casos, a saída é utilizar constantes em
Java para obter os mesmos resultados da compilação condicional (bons compiladores
conseguem realizar compilação condicional implicitamente, ou seja, a compilação não será
realizada em um trecho de código se o compilador conseguir provar que este trecho nunca
será executado).

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.

3.1.7 Sem tipo struct ou union

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.

3.1.8 Sem tipos enumerados

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.

3.1.10 Sem goto

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.

3.1.11 Sem sobrecarga de operadores

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.

3.1.12 Sem apontadores

Java não possui apontadores e não permite manipular endereços de memória de nenhuma
forma:

• Não é possível converter referência de objetos ou vetores em inteiros ou vice-versa.


• Não é possível realizar aritmética de ponteiros.
• Não é possível calcular o tamanho em bytes de qualquer tipo primitivo ou objeto.

Existem duas razões para estas restrições:

• 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

A classe Count apresentada a seguir possui um método chamado countChars que lê e


conta os caracteres de um Reader (um objeto que implementa um fluxo, stream, de entrada
de caracteres) e mostra o número de caracteres lidos. Mesmo um método pequeno como
este utiliza muitas das construções básicas de Java e classes que pertencem a API central
da linguagem.

import java.io.*;
public class Count {
public static void countChars(Reader in) throws IOException
{
int count = 0;

while (in.read() != -1)


count++;
System.out.println("Counted " + count + " chars.");
}
public static void main(String[] args) throws Exception
{
if (args.length >= 1)
countChars(new FileReader(args[0]));
else
System.err.println("Usage: Count filename");
}
}

4.1 Variáveis e Tipos de Dados

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.

Tipo Tamanho/Formato Descrição


byte 8-bit complemento de 2 Byte-length integer
short 16-bit complemento de 2 Short integer
int 32-bit complemento de 2 Integer
long 64-bit complemento de 2 Long integer
float 32-bit IEEE 754 Single-precision floating point
double 64-bit IEEE 754 Double-precision floating point
char 16-bit Unicode A single character
boolean true ou false A boolean value (true or false)

Uma variável de um tipo primitivo contém um único valor de tamanho/formato


apropriados. O valor da variável count em countChars pode assumir desde 0 (valor
inicial) até o número que representa o número de caracteres lidos.

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).

O escopo de uma variável corresponde ao bloco de código onde a variável é acessível e


determina quando a variável é criada e destruída. A localização da declaração da variável

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.

Variáveis locais são declaradas em qualquer ponto dentro de um método ou de um bloco


de código dentro de um método. No método countChars, count é uma variável local. O
escopo de count vai da declaração da variável até o fim do método countChars.

Parâmetros de métodos são argumentos formais de métodos e construtores que são


utilizados para passar valores para os métodos. Em countChars, in é um parâmetro do
método. O escopo de um parâmetro é todo o método que o declara.

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:

final int aFinalVar = 0;

Pode-se adiar, se necessário, a inicialização da variável. Porém depois de inicializada a


primeira vez, novas modificações não são permitidas. Exemplo:

final int blankfinal;


...
blankfinal = 0;

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.

Os operadores de Java podem ser divididos nestas categorias: aritméticos, relacionais e


condicionais, bitwise e lógicos e de atribuição. Tais categorias são descritas a seguir.

4.2.1 Operadores Aritméticos

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:

Operador Uso Descrição


+ op1 + op2 Soma op1 e op2
- op1 - op2 Subtrai op2 de op1
* op1 * op2 Multiplica op1 por op2
/ op1 / op2 Divide op1 por op2
% op1 % op2 Calcula o resto da divisão de op1 por op2

Observação: A linguagem Java estende a definição do operador + para incluir


concatenação de strings. Por exemplo, o método countChars utiliza o + para concatentar
“Counted”, o valor de count e “ chars”:

Pág 31
System.out.println("Counted " + count + " chars.");

Esta operação automaticamente converte o valor de count para o tipo String.

Os operadores + e - possuem versões unárias que realizam as seguintes operações:

Operador Uso Descrição


+ +op Promove op to int se é um byte, short, or char
- -op Negates aritmeticamente op

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:

Operador Uso Descrição


++ op++ Incrementa op de 1; retorna o valor antes do incremento
++ ++op Incrementa op de 1; retorna o valor depois do incremento
-- op-- Decrementa op de 1; retorna o valor antes do decremento
-- --op Decrementa op de 1; retorna o valor depois do decremento

4.2.2 Operadores Relacionais e Condicionais

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:

0 < index && index < NUM_ENTRIES

Em algumas situações, o segundo operando de um operador condicional nem chega a ser


calculado. Considere a expressão:

((count > NUM_ENTRIES) && (in.read() != -1))

Se count é menor do que NUM_ENTRIES, o operando a esquerda de && retorna false.


Neste caso, o valor de retorno de && pode ser determinado sem calcular o valor do
operando a direita. Assim, o interpretador não irá executar o método in.read da expressão.

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.

Java suporta cinco operadores condicionais binários:

Operador Uso Retorna true se


&& op1 && op2 op1 e op2 são ambos true, calcula condicionalmente op2
|| op1 || op2 op1 ou op2 é true, calcula condicionalmente op2
! ! op op é false
& op1 & op2 op1 e op2 são ambos true, sempre calcula op1 e op2
| op1 | op2 op1 ou op2 é true, sempre calcula op1 e op2

Além disto, Java possui um outro operador condicional, o operador : ?. É um operador


ternário que, basicamente, funciona como uma “abreviatura” da expressão if-else:

expression ? op1 : op2

O operador ?: avalia expression e retorna op1 se expression é verdadeira e op2 se é


falsa.

4.2.3 Operadores Bitwise

Um operador bitwise permite realizar manipulação de bits. A tabela abaixo apresenta os


operadores deste tipo:

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;

A representação binária do número 13 é 1101. Assim, o resultado da operação de shift é


110 ou 6 em decimal. Um shift a direita de 1 é equivalente, mas bem mais eficiente do que
dividir o operando da esquerda por 2. Analogamente, um shift a esquerda equivale a uma
multiplicação por 2.

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

O resultado é igual a 12. A representação binária de 12 é 1100 e a de 13 é 1101. Assim:

1101
& 1100
------
1100

O operador | realiza a função ou inclusivo e ^ realiza a função ou exclusivo.

O operador complemento ~ inverte o valor de cada bit do operando: se o bit do operando é


1 o resultado é 0 e se o bit do operando é 0, o resultado é 1.

4.2.4 Operadores de Atribuição

O operador = é utilizado para se atribuir um valor a outro. O método countChars utiliza =


para inicializar count:

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.

Como discutido no item anterior, operadores retornam um valor. Portanto, o uso de um


operador é uma expressão. Por exemplo:

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).

O método counntChars() contém, além da expressão count++, algumas outras expressões,


como esta:

in.read() != -1

Tal expressão é interessante pois consiste, na verdade, de duas expressões. A primeira é


uma chamada de método:

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.

A segunda expressão utiliza o operador !=. Na expressão em questão, os operandos de !=


são in.read() e -1. Note que in.read() é um operando válido para esta expressão pois
retorna um inteiro. Assim, in.read() != -1 compara dois inteiros, o valor retornado por
in.read() e -1. O valor retornado por != é true ou false dependendo do resultado da
comparação.

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.

Note, também, que a ordem de como a expressão é avaliada também é importante.


Considere esta expressão composta:

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.

postfix operators [] . (params) expr++ expr--


unary operators ++expr --expr +expr -expr ~ !
creation or cast new (type)expr
multiplicative * / %

Pág 36
additive + -
shift << >> >>>
relational < > <= >= instanceof
equality == !=
bitwise AND &
bitwise exclusive OR ^
bitwise inclusive OR |
logical AND &&
logical OR ||
conditional ? :
assignment = += -= *= /= %= &= ^= |= <<= >>= >>>=

Quando operadores de igual precedência aparecem em uma mesma expressão , algumas


regras devem determinar qual deve ser avaliado primeiro. Em Java, todos os operadores
binários (exceto o operador de atribuição) são avaliados da esquerda para a direita. Os
operadores de atribuição são avaliados da direita para a esquerda.

4.4 Controle de Fluxo

O método countChars utiliza um while para realizar um laço que lê e conta todos os
caracteres da entrada :

while (in.read() != -1)


count++;

De maneira geral, o while realiza alguma ação enquanto uma certa condição continua
verdadeira. A sintaxe geral do while é:

while (expression)
statement

Ou seja, enquanto a expressão for verdadeira, execute statement. Um statement, na


verdade, pode ser um bloco de código Java delimitado por chaves { }. Por exemplo:

while (in.read() != -1) {


count++;
System.out.println("Read a character. Count = " + count);
}

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.

while é chamado de um comando de controle de fluxo, ou seja, determina a ordem de


execução do código. A linguagem Java suporta vários tipos de controle de fluxo, incluindo:

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;

if (testscore >= 90) {


grade = 'A';
} else if (testscore >= 80) {
grade = 'B';
} else if (testscore >= 70) {
grade = 'C';
} else if (testscore >= 60) {
grade = 'D';
} else {
grade = 'F';
}

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");
...

Decidir quando utilizar if ou switch depende do “feeling” do programador. Deve-se fazer


esta escolha baseado, principalmente, na facilidade de leitura do código.

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.

O for é utilizado quando se sabe, a priori, as restrições do laço (instrução de inicialização,


critério de terminação e instrução de incremento). Exemplo:

// a é um vetor de algum tipo


...
int i;
int length = a.length;
for (i = 0; i < length; i++) {
...
// faz alguma coisa com o elemento de a
...
}

A forma geral do for é dada por:

for (inicialização; terminação; incremento)


statements

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.

4.1.1 Tratadores de Exceção

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.

4.1.1 break, continue e return (comandos de branching)

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.

Observação 1: continue e break possuem versões “labeled”, ou seja que permitem a


mudança do fluxo de controle para labels pré-definidos. Tais versões funcionam como uma
alternativa ao famigerado goto e, portanto, devem ser evitadas.

Observação 2: continue somente pode ser chamado dentro de um 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 Arrays e Strings

Como outras linguagens de programação, Java permite agrupar e gerenciar múltiplos


valores por meio de um objeto array (vetor). Pode-se também tratar dados compostos de
múltiplos caracteres via objeto String.

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:

int[] arrayOfInts = new int[10];

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 [ ] ):

elementType[] arrayName = new elementType[arraySize];

Após alocar memória ao vetor, pode-se atribuir e recuperar os valores dos elementos:

for (int j = 0; j < arrayOfInts.length; j ++) {


arrayOfInts[j] = j;
System.out.println("[j] = " + arrayOfInts[j]);
}

Observação Importante: Em Java, os índices de um vetor começam em 0 e vão até o


tamanho do vetor menos 1.

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.

Vamos analisar novamente o método main, em particular o uso do vetor args:

public static void main(String[] args) throws Exception {


if (args.length >= 1)
countChars(new FileReader(args[0]));
else
System.err.println("Usage: Count filename");
}

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:

String[] arrayOfStrings = new String[10];

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:

for (int i = 0; i < arrayOfStrings.length; i ++) {


arrayOfStrings[i] = new String("Hello " + i);
}

Também é possível realizar a inicialização de um vetor durante sua própria declaração.

String arrayOfStrings[] = {“Joao”, “Jose”, “ Maria”};

4.5.2 String

Uma sequência de caracteres é chamada de string e é implementada em Java pela classe


String. O método main utiliza String na declaração do vetor args:

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.

O método countChars também utiliza dois objetos na forma de strings literais:

"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:

"Counted " + count + " chars."

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

1 Faça um programa que realize um “eco” (apresente na tela) de seus argumentos da


linha de comando.
2 Qual a principal diferença entre os tipos primitivos e os tipos de referência em Java?
3 O que é escopo de uma variável? Como é possível determiná-lo?
4 Quantas vezes o código abaixo será executado? Qual será o valor do contador quando o
laço terminar?

int count = 10;


do {
...
} while (count++ <= 15);

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?

for (int x=3; x<12; x+=2)


++count;

8. Como você criaria uma matriz bidimensional de 20 x 10 elementos utilizando Java?


9. Dada uma matriz bidimensional, como descobrir as suas dimensões? (dica: uma matriz
em Java pode ser vista como um vetor de vetores)
10. Escreva um laço for que inicialize um vetor de 50 inteiros com os valores 50 a 99.
11. Escreva um laço for que inicialize uma matriz 10x15 com valores de 0 a 149.
12. Escreva um código que mostre, de forma tabular, os valores da matriz do exercício
anterior.

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.

public class SimplePoint {


public int x = 0;
public int y = 0;
}

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:

public class SimpleRectangle {


public int width = 0;
public int height = 0;
public SimplePoint origin = new SimplePoint();
}

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.

As classes SimplePoint e SimpleRectangle são implementações simplificadas. Poderia-se


criar um mecanismo para inicializar suas variáveis com valores diferentes de 0. Assim, a
seguir, tem-se uma nova versão de SimplePoint, chamada Point que contém um construtor
que pode ser utilizado para inicializar um novo objeto da classe Point para um valor
diferente de (0,0):

public class Point {


public int x = 0;
public int y = 0;
// a constructor!
public Point(int x, int y) {
this.x = x;
this.y = y;
}
}

Agora, ao criar um ponto, pode-se definir valores iniciais:

new Point(44, 78)

Os valores 44 e 78 são passados para o construtor da classe e atribuídos então as variáveis


x e y do objeto:

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:

public class Rectangle {


public int width = 0;
public int height = 0;
public Point origin;
// four constructors
public Rectangle() { origin = new Point(0, 0); }
public Rectangle(Point p) { origin = p; }
public Rectangle(int w, int h) { this(new Point(0, 0), w, h); }
public Rectangle(Point p, int w, int h) {
origin = p;
width = w;
height = h;
}
// a method for moving the rectangle
public void move(int x, int y) { origin.x = x; origin.y = y; }
// a method for computing the area of the rectangle
public int area() { return width * height; }
protected void finalize() throws Throwable {
origin = null;
super.finalize();
}
}
As seções a seguir descrevem com detalhes o código acima, apresentando o ciclo de vida
de um objeto (criação, uso e destruição).

5.2 Ciclo de Vida de um 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.

5.2.1 Criando Objetos

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:

Rectangle rect = new Rectangle();

Esta expressão realiza, na verdade, três ações:

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:

new Rectangle(100, 200);

Aqui, Rectangle(100, 200) é o argumento de new. O operador new retorna uma referência
para uma variável:

Rectangle rect = new Rectangle(100, 200);

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

Como visto no código da classe Rectangle, classes podem oferecer um ou mais


construtores para inicializar um objeto. Os construtores de uma classe sempre possuem o
mesmo nome da classe e não possuem parâmetro de retorno. Observe, as declarações dos
construtores de Rectangle:

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.

5.2.2 Utilizando Objetos

Pode-se utilizar um objeto de duas maneiras:

1. Manipulando ou inspecionando as suas variáveis


2. Executando (invocando) seus métodos

Como já foi discutido, a programação orientada a objetos desencoraja a manipulação direta


das variáveis de um objeto; corre-se o risco de colocar o objeto em um estado
inconsistente. Um objeto “ideal” oferece métodos pelos quais se consegue inspecionar ou
modificar o seu estado. Estes métodos garantem que um objeto nunca entrará em um
estado inconsistente. Entretanto, em situações práticas, algumas vezes faz sentido utilizar
diretamente as variáveis de um objeto.

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?).

Java proporciona um mecanismo de controle onde as classes podem restringir ou permitir o


acesso a suas variáveis e métodos (tal mecanismo será descrito com mais detalhe em uma
aula posterior). Assim, uma classe pode proteger suas variáveis contra manipulação direta
por outros objetos se estas manipulações podem ameaçar a consistência do objeto. Tais
modificações devem ser realizadas por meio de métodos. Se um objeto oferece acesso a
suas variáveis, é razoável, então, assumir que não há problemas em se
inspecionar/modificar tais variáveis.

Por exemplo, suponha que o objeto Rectangle representa um objeto retangular em um


programa de desenho e que o usuário moveu este objeto para um outro local. Assim, é

Pág 50
necessário atualizar o ponto de origem do objeto. A classe retângulo oferece duas maneiras
equivalentes de realizar isto:

1. Manipulando diretamente a variável origin do objeto


2. Executando o método move

Referenciando as Variáveis de um Objeto

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:

rect.origin = new Point(15, 37);

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()):

area = rect.height * rect.width;

De maneira geral, para se referir a uma variável de um objeto, acrescenta-se o nome da


variável à referência do objeto, com um ponto (.) no meio:

objectReference.variable

A primeira parte do nome da variável, objectReference, deve refernciar um objeto.


Pode-se utilizar também qualquer expressão que retorne uma referência para um objeto.
Por exemplo:

height = new Rectangle().height;

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).

Observação: depois de executada a expressão acima, o programa não possui nenhuma


referência (variável) para o objeto recém-criado. Assim, este objeto está sujeito à “coleta
de lixo” (descrita em um dos itens a seguir).

Executando (invocando) os Métodos de um Objeto

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:

rect.origin = new Point(15, 37);

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:

new Rectangle(100, 50).area()

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:

int areaOfRectangle = new Rectangle(100, 50).area();

Observação: Lembre-se que invocar o método de um objeto equivale a enviar uma


mensagem para aquele objeto. Neste caso, o objeto é um retângulo chamado rect.
Provavelmente, você obterá uma resposta distinta se enviar a mesma mensagem para um
outro objeto (bob):

int areaOfRectangle = bob.area();

5.3 Liberando Objetos não mais Utilizados

A maioria das linguagens orientadas a objetos exige que o programador mantenha um


controle sobre todos os objetos criados e que, explicitamente, destrua-os quando não
precisar mais deles. Esta técnica de gerência de memória é entediante e bastante suscetível
a erros (memory leaks).

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).

5.3.1 Coletor de Lixo (Garbage Coletor)

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.

O coletor de lixo executa assincronamente, em sistema como o Windows 95/NT, quando o


sistema está ocioso. Assim que uma outra thread torna-se ativa, o coletor de lixo termina a
sua execução.

5.3.2 Finalização

Antes que um objeto seja “coletado”, o coletor dá ao objeto uma oportunidade de se


“limpar” por meio da chamado do método finalize do objeto. Este processo é chamado de
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):

protected void finalize() throws Throwable {


origin = null;
super.finalize();
}

O método finalize é um membro da classe Object. A classe Object fica no topo da


hierarquia de classes de Java e é “pai” de todo mundo. Uma classe deve redefinir o método
finalize para realizar as finalizações necessárias para os objetos daquele tipo. Note que o

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).

5.4 Criando Classes

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.

A seguir, vamos analisar cada uma das estruturas da figura acima.

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.

5.4.2 Corpo da Classe

O corpo da classe segue a declaração e é delimitado por chaves: { e }. O corpo de uma


classe contém todo o código que proporciona o ciclo de vida dos objetos instanciados a
partir da classe: construtores para incializar novos objetos, declarações das variáveis que
guardam o estado da classe e de seus objetos, métodos que implementam o comportamento
da classe e de seus objetos e, quando necessário, um método finalize que realiza a
“limpeza” dos objetos.

Observação: variáveis e métodos são chamados de membros da classe. Rigorosamente


falando, os construtores não são classificados nem como métodos ou variáveis.

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.

5.4.3 Construtores da Classe

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:

public Stack(int initialSize) {


items = new Vector(initialSize);
}

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);

Analogamente, no código abaixo, o compilador escolhe o construtor default:

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.

class AnimationThread extends Thread {


int framesPerSecond;
int numImages;
Image[] images;

AnimationThread(int fps, int num) {

super("AnimationThread");
this.framesPerSecond = fps;
this.numImages = num;

this.images = new Image[numImages];


for (int i = 0; i <= numImages; i++) {
...
// Load all the images.
...
}
}
...
}

Observe que o corpo de um construtor é como o corpo de um método; ou seja, contém


declarações de variáveis locais, laços e outras expressões. Entretanto, uma linha no
construtor de AnimationThread apresenta um código que não será visto em métodos
comuns:

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.

Observação: Se presente, o construtor da superclasse deve ser a primeira expressão dentro


do construtor da subclasse.

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:

private Vector items;

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.

A declaração de uma variável pode possuir os seguintes componentes:

•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.

final double AVOGADRO = 6.023e23;

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.

•transient: esta palavra chave é utilizada no caso de serialização de objetos


para identificar aquelas variáveis que não precisam ser serializadas (serialização
vai ser abordada no fim do curso).
•volatile: é utilizada para evitar que o compilador realize algumas otimizações
na variável.
•type: pode-se utilizar, para definir uma variável membro, tipos primitivos (int,
float, ou boolean, por exemplo) ou tipos de referência (vetores, objetos ou
interfaces).
•name: o nome da variável pode ser qualquer identificador Java válido (por
convenção, começando com uma letra minúscula). Não se pode declarar mais
de uma variável membro com o mesmo nome dentro de uma classe, mas uma
subclasse pode utilizar o mesmo nome de uma variável declarada pela
superclasse. Além disto, uma variável membro e um método podem possuir o
mesmo nome. Por exemplo, o código abaixo está correto:

public class Stack {


private Vector items;
// um método que utiliza o mesmo nome de uma variável membro
public Vector items() {
...
}
}

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:

•accesLevel: como as variáveis, pode-se controlar quais classes podem acessar um


método, utilizando um dos quatro níveis de acesso: public, protected, package, e
private. A seção 5.4.6 descreve cada um destes níveis de acesso.
•static: define que o método é um método de classe em vez de um método de instância
(seção 5.4.7).
•abstract: um método abstrato não possui implementação e deve ser membro de uma
classe abstrata. Na próxima aula, abordaremos a necessidade de se escrever métodos
abstratos e como estes métodos afetam as subclasses.
•final: um método final não pode ser redefinido por subclasses. Na próxima aula,
abordaremos a necessidade de se escrever métodos finais e como estes métodos afetam
as subclasses
•native: métodos implementados em linguagens diferentes de Java são chamados
métodos nativos e são declarados utilizando a palavra-chave native. Um dos tópicos
das próximas aulas está relacionado com a utilização de métodos nativos.
•synchronized: threads concorrentes muitas vezes invocam métodos que acessam os
mesmos dados. Estes métodos podem ser declarados como syncronized para garantir
que as threads acessem as informações compartilhadas de forma segura, evitando
inconsistências.

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.

Retornando um Valor de um Método

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:

public boolean isEmpty() {


if (items.size() == 0)
return true;
else
return false;
}

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:

public synchronized Object pop() {


int len = items.size();
Object obj = null;
if (len == 0)
throw new EmptyStackException();
obj = items.elementAt(len - 1);
items.removeElementAt(len - 1);
return obj;
}

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:

public Number returnANumber() {


...
}

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.

Passando Informação para um Método

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:

double computePayment(double loanAmt, double rate,


double futureValue, int numPeriods) {
double I, partial1, denominator, answer;

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:

Polygon polygonFrom(Point[] listOfPoints) {


...
}

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) {
...
}
}

A classe Circle tem 3 variáveis-membro: x, y e radius. Observe que o construtor da classe


aceita 3 argumentos que têm os mesmos nomes das variáveis-membros. Como foi dito, os
nomes dos argumentos ocultam as variáveis-membro. Portanto, a utilização de x, y ou
radius dentro do corpo do construtor refere-se aos seus argumentos e não às variáveis-
membro do objeto. Para acessar uma variável-membro do construtor, deve-se referenciá-
la utilizando a palavra-chave this (este objeto):

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.

Observação Importante: Passagem por Valor


Em Java, os argumentos são passados por valor. Ou seja, o método recebe o valor (uma
cópia) da variável passada como argumento. Quando o argumento é do tipo primitivo,
passagem por valor significa que o método não pode alterar o valor da variável passada
como argumento. Quando o argumento é do tipo de referência, significa que o método não
pode alterar a referência armazenada pela variável.

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);
...

No momento que o método getRGBColor é executado. as variáveis r, g, e b possuem


valor –1. A intenção do código, ao executar getRGBColor, era receber os valores de
volta (vermelho, verde e azul) nas variáveis r, g e b. Entretanto, os valores das variáveis (-
1) é que são passados para o método e não as suas referências. Então podemos visualizar a
chamada a getRGBColor como: getRGBColor(-1, -1, -1).

Quando o fluxo de controle entra no método getRGBColor, os argumentos entram no


escopo (ou seja são alocados) e são inicializados com os valores passados para o método:

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;
}
}

Quando o fluxo de controle voltar para a expressão println, no código a seguir os


argumentos de getRGBColor (red, green e blue) não existem mais. Portanto, as
atribuições realizadas a estes argumentos dentro do método não tem nenhum efeito; r, g e
b ainda continuam iguais a –1.

...
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;
}
}

Desta forma, poderíamos escrever:

...
RGBColor penColor = new RGBColor();
pen.getRGBColor(penColor);
System.out.println("red = " + penColor.red + ", green = " +
penColor.green + ", blue = " +
penColor.blue);
...

As modificações feitas no objeto RGBColor dentro do método getRGBColor afetam o


objeto original pois a variável original (penColor) e o argumento do método (aColor)
referem-se ao mesmo objeto.

Observação: no corpo de um método, pode-se utilizar a palavra-chave this para referir-se


explicitamente a membros do objeto atual. O objeto atual é aquele que teve o seu método
invocado. Também pode-se utilizar a palavra-chave super para referir-se a membros da
superclasse que o objeto atual redefiniu ou sobrecarregou.

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
}
}

class ASillierClass extends ASillyClass {


boolean aVariable;
void aMethod() {
aVariable = false;
super.aMethod();
System.out.println(aVariable);
System.out.println(super.aVariable);
}
}

Primeiramente, aMethod() atribui a variável aVariable (que oculta a variável declarada na


superclasse) o valor false. Depois, aMethod() invoca o método da superclasse:

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

5.4.6 Controle de Acesso a Membros da Classe

Como visto na seção 2, um dos benefícios do paradigma de orientação a objetos é a


possibilidade de se “proteger” variáveis e métodos do acesso por parte de outros objetos.
Tal característica é denominada de encapsulação.

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).

A tabela a seguir apresenta os níveis de acesso permitidos:

nível de acesso classe subclasse pacote (package) mundo


private X
protected X X* X
public X X X X
package X X

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.

Vamos abordar com maior detalhe cada um destes níveis:

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

Para declarar um membro privado, utiliza-se a palavra-chave private em sua declaração. A


classe a seguir contém uma variável e um método privados.

class Alpha {
private int iamprivate;
private void privateMethod() {
System.out.println("privateMethod");
}
}

Apenas objetos da classe Alpha podem inspecionar ou modificar a variável iamprivate e


podem executar o método privateMethod. Por exemplo:

class Beta {
void accessMethod() {
Alpha a = new Alpha();
a.iamprivate = 10; // ilegal
a.privateMethod(); // ilegal
}
}

A classe Beta não pode acessar a variável iamprivate ou executar o método


privateMethod de um objeto da classe Alpha e o compilador Java geraria uma mensagem
de erro do tipo:

Beta.java:9: Variable iamprivate in class Alpha not


accessible from class Beta.
a.iamprivate = 10; // ilegal
^

Pág 68
Beta.java:12: No method matching privateMethod() found in class
Alpha.
a.privateMethod(); // ilegal
2 errors

Observação importante: programadores novatos geralmente perguntam se um objeto


Alpha pode acessar membros privados de outro objeto Alpha. Isto é ilustrado no exemplo
a seguir:

class Alpha {
private int iamprivate;
boolean isEqualTo(Alpha anotherAlpha) {
if (this.iamprivate == anotherAlpha.iamprivate)
return true;
else
return false;
}
}

Isto é perfeitamente válido. Um objeto possui acesso a membros privados de outros


objetos do mesmo tipo. Isto ocorre pois as restrições de acesso se aplicam às classes (todas
as instância de uma classe) e não aos objetos (uma instância da classe).

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;

class Delta extends Alpha {


void accessMethod(Alpha a, Delta d) {
a.iamprotected = 10; // ilegal
d.iamprotected = 10; // válido
a.protectedMethod(); // ilegal
d.protectedMethod(); // válido
}
}

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;

public class Alpha {


public int iampublic;
public void publicMethod() {
System.out.println("publicMethod");
}
}

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");
}
}

Todas as classes declaradas no mesmo pacote de Alpha possuem acesso a iampackage e


packageMethod. Assim, o código abaixo é válido:

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

Quando se declara uma variável como aFloat em myClass:

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.

Observação Importante: métodos de instância podem acessar variáveis de instância e de


classe. Em contrapartida, métodos de classe somente podem acessar as variáveis de classe
declaradas. Além disto, para executar um método de classe não é necessário criar um
objeto da 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:

AnIntegerNamedX myX = new AnIntegerNamedX();


AnIntegerNamedX anotherX = new AnIntegerNamedX();
myX.setX(1);
anotherX.x = 2;
System.out.println("myX.x = " + myX.x());
System.out.println("anotherX.x = " + anotherX.x());

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.

Analogamente, para especificar que um método é um método de classe, utiliza-se também


a palavra-chave static:

class AnIntegerNamedX {
private int x;
static public int x() {
return x;
}

Pág 73
static public void setX(int newX) {
x = newX;
}
}

Quando se tenta compilar o código acima, ocorrerão erros de compilação:

AnIntegerNamedX.java:4: Can't make a static reference to nonstatic


variable x in class AnIntegerNamedX.
return x;
^
AnIntegerNamedX.java:7: Can't make a static reference to nonstatic
variable x in class AnIntegerNamedX.
x = newX;
^
2 errors

Isto ocorre pois métodos de classe não podem acessar variáveis de instância (no caso, x).

Para solucionar este problema, devemos tornar x uma variável de classe:

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).

5.4.8 Tratamento de Exceções

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.

O parâmetro de catch é o tipo de exceção a ser capturada (tal como o argumento de um


método). Pode-se acessar os métodos, se existirem, da exceção por meio deste argumento.
Para um mesmo bloco try podem existir mais de um bloco catch (cada um capturando um
determinado tipo de exceção).

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:

protected void GetURL() throws MalformedURLException

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.

A tabela abaixo lista alguma das exceções mais comuns em Java:

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

1. Considere o seguinte trecho de código:

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?

public class OutraClasse {


private int x;
// um método que utiliza o mesmo nome de uma variável membro
public int x() {
...
}
}

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;
}

12. Qual será a saída do trecho de código abaixo? Justifique

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);

13. Qual a finalidade das palavras-chave this e super? Dê exemplos.


14. Considere a seguinte classe:

class MaisUmaClasse {

int x;
static int y;

public void modificaX(int x) { this.x = x; }


public static void modificaY (int y) { this.y = y;}
}

Qual será a saída do seguinte trecho de código? Justifique.

MaisUmaClasse objeto1 = new MaisUmaClasse();


MaisUmaClasse objeto2 = new MaisUmaClasse();

objeto1.modificaX(1);

Pág 78
objeto2.modificaX(2);
objeto1.modificaY(1);
objeto2.modificaY(2);

System.out.println (“Objeto1: x =” + objeto1.x + “ y = “ + objeto1.y);


System.out.println (“Objeto2: x =” + objeto2.x + “ y = “ + objeto2.y);

15. Por que o código abaixo é inválido?

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

A seção anterior, Classes e Objetos em Java, descreveu as características da linguagem que


todo programador Java necessita saber para utilizar objetos e escrever classes. Esta seção
apresenta alguns conceitos mais avançados que ajudarão a organizar e projetar melhor o
seu código.

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.

Quais membros são herdados pela subclasse?

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:

• Subclasses herdam os membros da superclasse declarados como públicos (public) ou


protegidos (protected).
• Subclasses herdam aqueles membros da superclasse declarados sem especificadores de
acesso (público, privado ou protegido), desde que estejam no mesmo pacote que a
superclasse.
• As subclasses não herdam os membros da superclasse caso a subclasse declare um
membro com o mesmo nome. No caso de variáveis, dizemos que a subclasse oculta a
variável da superclasse. No caso de métodos, dizemos que a subclasse redefine o
método herdado.
• Subclasses não herdam os membros privados (private) da superclasse.

6.2 Ocultando variáveis

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;
}

A variável aNumber na classe Subbie oculta a variável aNumber definida na classe


Super. Mas é possível acessar a variável aNumber, a partir da classe Subbie, desta forma:

super.aNumber

A palavra-chave super permite que um método faça referência a variáveis ocultas ou a


métodos redefinidos da superclasse.

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.

Vamos reconsiderar o exemplo da classe Stack e redefinir o métod toString. A saída do


método toString seria a representação textual do objeto. Para a classe Stack, uma listagem
dos itens da pilha seria algo interessante a se fazer:

public class Stack


{
private Vector items;

// code for Stack's methods and constructor not shown

// overrides Object's toString method


public String toString() {
StringBuffer result = new StringBuffer();
result.append("[");
for (int i = 0; i < n; i++) {
result.append(items.elementAt[i].toString());
if (i == n-1) result.append("]");
else result.append(",");
}
return result.toString();
}
}

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

Algumas vezes, não se deseja redefinir completamente um método. Simplesmente,


necessita-se apenas adicionar maior funcionalidade ao método herdado. Para fazer isto, é
preciso executar o método da superclasse utilizando a palavra-chave super. Por exemplo, a
implementação do método finalize da classe Stack executa o método finalize definido na
superclasse (Object):

protected void finalize() throws Throwable {


items = null;
super.finalize();
}

6.3.2 Métodos que uma subclasse não pode redefinir

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:

FinalTest.java:7: Final methods can't be overridden. Method void


iamfinal() is final in class ClassWithFinalMethod.
void iamfinal() {
^
1 error

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.

6.3.3 Métodos que uma subclasse deve redefinir

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.

6.4 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.

Analogamente, na programação orientada a objetos, pode-se modelar um conceito abstrato.


Por exemplo, a classe Number do pacote java.lang representa o conceito abstrato de um

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:

abstract class Number {


...
}

Se você tentar instanciar uma classe abstrata, o compilador irá mostrar um erro parecido
com este:

AbstractTest.java:6: class AbstractTest is an abstract class. It can't


be instantiated.
new AbstractTest();
^
1 error

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:

class Circle extends GraphicObject {


void draw() {
...
}
}
class Rectangle extends GraphicObject {
void draw() {
...
}
}

Observação: Uma classe abstrata não precisa, necessariamente, possuir um método


abstrato. Porém, qualquer classe que possuir um método abstrato, ou não definir uma
implementação para um método abstrato que foi herdado, deve ser declarada como uma
classe abstrata.

6.5 Classes e Métodos Finais

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á:

final class ChessAlgorithm {


...
}

Quaisquer tentativas de criar uma subclasse de ChessAlgorithm irão resultar em um erro


de compilação, como este:

Chess.java:6: Can't subclass final classes: class ChessAlgorithm


class BetterChessAlgorithm extends ChessAlgorithm {
^
1 error

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.

Definição: Uma interface é uma coleção de definições de métodos (sem implementação).


Uma interface também pode incluir declarações de constantes.

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:

1. Pedir para que o despertador o “acorde”.


2. Implementar o método WakeUp (acordar).

Para satisfazer o primeiro requisito, um objeto executa o método letMeSleepFor do


AlarmClock, que é implementado da seguinte forma:

public synchronized boolean letMeSleepFor(Sleeper theSleeper,


long time) {
int index = findNextSlot();
if (index == NOROOM) {
return false;
} else {
sleepers[index] = theSleeper;
sleepFor[index] = time;
new AlarmThread(index).start();
return true;
}
}

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:

public interface Sleeper {


public void wakeUp();

public long ONE_SECOND = 1000; // in milliseconds


public long ONE_MINUTE = 60000; // in milliseconds
}

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.

class GUIClock extends Applet implements Sleeper {


...
public void wakeUp() {
repaint();
clock.letMeSleepFor(this, ONE_MINUTE);
}
}

Depois que vimos a utilização de uma interface, surgem algumas questões importantes :

Por que não utilizar uma classe abstrata?

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?

abstract class Sleeper {


public abstract void wakeUp();
}

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:

• Uma classe só herda constantes de uma interface.


• Uma classe não poder herdar implementações de métodos de uma interface.
• A hierarquia de interfaces é independente da hierarquia de classes. Classes que
implementam a mesma interface podem ou não estar relacionadas em uma hierarquia
de classes. Isto não ocorre no caso de herança múltipla.

Importante: Java permite herança de multiplas interfaces. Ou seja, uma interface pode
possuir múltiplas “superinterfaces”.

Afinal, qual a utilidade de interfaces?

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.

6.6.1 Definindo uma Interface

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

A declaração da interface Sleeper utiliza os dois elementos obrigatórios da linguagem: a


palavra-chave interface e o nome da interface, além do modificador de acesso (public):

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

O corpo da interface contém a declaração de todos os métodos da interface. Por exemplo:

public interface Sleeper {


public void wakeUp();

public long ONE_SECOND = 1000;


public long ONE_MINUTE = 60000;
}

Todos os métodos declarados na interface são, implicitamente, públicos e abstratos. O


corpo da interface também pode possuir declarações de constantes. Todas as constantes
declaradas são, implicitamente, públicas, estáticas (static) e finais (final).

Para utilizar as constantes definidas na interface, deve-se referenciar o nome da interface.


Por exemplo:

Sleeper.ONE_SECOND

Classes que implementam uma interface podem acessar estas constantes como se fossem
variáveis herdadas:

public class GUIClock extends Applet implements Sleeper {

Pág 90
...
public void wakeUp() {
repaint();
clock.letMeSleepFor(this, ONE_MINUTE);
}
}

6.6.2 Implementando uma Interface

O exemplo da classe GUIClock já demonstrou como implementar uma interface


(Sleeper):

public class GUIClock extends Applet implements Sleeper {


...
public void wakeUp() {
// update the display
}
}

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.

6.7 Utilizando Interfaces como Tipos de Dados

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:

private Sleeper[] sleepers = new Sleeper[MAX_CAPACITY];

private boolean letMeSleepFor(Sleeper theSleeper, int time)


{
...
}

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.

Podemos citar as seguintes vantagens da utilização de pacotes:

• 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.

6.8.1 Criando um 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;

public class Circle extends Graphic implements Draggable {


...
}

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;

public class Rectangle extends Graphic implements Draggable {


...
}

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”.

Definindo um Nome para o Pacote

Com programadores em todo mundo escrevendo classes e interfaces em Java,


provavelmente dois programadores utilizem o mesmo nome para duas classes diferentes. O
conceito de pacote permite que classes em pacotes diferentes possuam o mesmo nome sem
causar erros de compilação. Mas se os pacotes forem definidos com o mesmo nome,
ocorrerão problemas. Como evitar esta situação? Utilizando uma convenção.

Convenção: Empresas utilizam os seus nomes de domínio da Internet, de maneira inversa,


para definir o nome dos seus pacotes. Por exemplo: com.minhaempresa.meupacote,
br.uemg.passos.meupacote

6.8.2 Utilizando Membros de um Pacote

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:

• refrir-se ao membro utilizando o seu nome completo,


• importar o membro do pacote, ou
• importar todo o pacote.

Referenciar um Membro do Pacote pelo Nome

Quando se deseja utilizar um membro de um pacote diferente, e o pacote não foi


importado, deve-se utilizar o nome completo (long name) que inclui o nome do pacote.
Por exemplo, este é o nome completo da classe Rectangle declarada no pacote graphics:

graphics.Rectangle

Para criar uma instância desta classe:

graphics.Rectangle myRect = new graphics.Rectangle();

Importar um Membro do Pacote

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 a classe círculo pelo seu nome curto:

Circle myCircle = new Circle();

Importar Todo o Pacote

Para importar todas as classes e interface pertencentes a um pacote, utiliza-se o import


junto com o caracter * :
import graphics.*;

Agora, pode-se referenciar qualquer classe ou interface do pacote graphics pelo seu nome
curto:

Circle myCircle = new Circle();


Rectangle myRectangle = new Rectangle();

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”:

import graphics.A*; // não funciona ! Irá gerar um erro


de compilação

Por conveniência, o ambiente de execução Java automaticamente importa três pacotes:

• O pacote default (que não possui nome)


• O pacote java.lang
• O pacote atual

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;

6.9 Gerenciando Arquivos Fonte e Arquivos de Classe

O ambiente de desenvolvimento JDK baseia-se em um sistema de arquivos hierárquico


para gerenciar os arquivos fonte (.java) e de classes (.class), embora a especificação da
linguagem Java não determine isto. A estratégia é a seguinte.

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.

O nome (longo) do membro do pacote e o caminho (pathname) do arquivo são “paralelos”


(considerando o separador de arquivos do Unix “/” e não do Windows “\”):

graphics.Rectangle Nome da classe


graphics/Rectangle.java caminho do arquivo

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).

Definição: O class path é uma lista de diretórios ou arquivos compactados onde o


compilador/interpretador realiza a busca por classes ou interfaces.

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.

Definindo o Class Path

Há duas maneiras para se alterar o class path:

• Definir a variável de ambiente CLASSPATH (não recomendado)


• Utilizar a opção -classpath quando invocar o compilador ou o interpretador.

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:

javac -classpath .:~/classes:/JDK/lib/classes.zip (UNIX)


javac -classpath .;C:\classes;C:\JDK\lib\classes.zip (Windows)

Observação Importante: ao definir o class path desta forma, você redefine


completamente o class path atual. Portanto, deve-se sempre incluir o arquivo classes.zip
do JDK em seu class path. Também é aconselhável incluir o diretório atual.

Observação: A ordem dos diretórios no class path é importante. Quando o interpretador


Java está procurando por uma classe ele procura as entradas no seu class path, na ordem,
até que a primeira classe com o nome correto seja encontrada. O interpretador, então,
não realiza a busca nos demais diretórios.

6.10 Exercícios

1. Considere o seguinte código


class Mamifero {
int idade;
}

class Carnivoro {
int numeroDeDentes;
}

Por que o código abaixo irá gerar um erro de compilação?

class Aluno extends Mamifero, Carnivoro {


int matricula;
}

2. Qual é a superclasse de:

class Pessoa {
String nome;
}

3. Considere a seguinte classe:


class MinhaClasse {

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;}
}

Quais os membros (variáveis/métodos) de MinhaClasse que são herdados por uma


subclasse definida no mesmo pacote?

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);
}

Qual a saída do programa definido na classe B (java B)? Justifique.


5. Suponha que você criou um método em uma classe, deseja que ele seja herdado por
subclasses, porém não que ele possa ser redefinido pelas subclasses. Como você
declararia este método? Dê um exemplo em Java.
5. Dê um exemplo de uma entidade do mundo real que possa ser modelada como uma
classe abstrata. Escreva a definição desta classe em Java.
6. É possível que uma classe abstrata defina a implementação de alguns métodos? É
possível que uma classe não-abstrata não defina a implementação de um de seus
métodos?
7. Em Java, como se declara uma classe que não pode ser derivada (isto é, não pode ter
subclasses)? Indique duas razões para criar uma classe com esta característica.
8. O que é uma interface? O que podemos definir em uma interface Java?
9. O que acontece quando uma classe é declarada desta maneira?

class MinhaClasse implements MinhaInterface

10. Quais são as diferenças entre uma classe abstrata e uma interface?
11. A declaração da classe abaixo está correta? Justifique.

class MinhaClasse implements UmaInterface, OutraInterface

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
{
...
};

UmaInterface a = new UmaClasse();

14. O que é um pacote? Qual a sua finalidade?


15. A classe abaixo pertence a qual pacote?

class A
{
int x;
}

16. Como se adiciona uma classe ou interface a um pacote. Dê exemplos.


17. Quais são as 3 maneiras de se utilizar uma classe ou interface que pertence a um outro
pacote? Dê exemplos.
18. Quais os 3 pacotes que o ambiente Java importa automaticamente?
19. O que é o class path? Como ele pode ser definido?
20. Como o nome de um pacote em Java se relaciona com a estrutura de diretórios do
sistema de arquivo. Dê exemplos.
21. Crie um pacote chamado br.uemg.passos.seunome.maquinas que possua uma classe
carro, por exemplo. Defina uma aplicação, que não pertença a este pacote, que utilize a
classe carro.

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

A figura abaixo ilustra a hierarquia de classes dos componentes gráficos (widgets) do


pacote AWT:

Cada um destes componentes será apresentado nas subseções a seguir.

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.

Alguns métodos importantes:

• public void disable(): desabilita o componente (em relação às ações do


usuário)
• public void enable(): habilita o componente (em relação às ações do
usuário)
• public Graphics getGraphics(): retorna o contexto gráfico do componte
• public void paint(Graphics g): faz com que o componente seja
(re)desenhado na tela (no contexto gráfico fornecido)
• public void repaint(): redesenha o componente; o componente é apagado
e então desenhado

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);
}
}

Alguns métodos importantes:

• public Button(String label): Cria um botão com o label dado

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.

• public void paint(Graphics g): normalmente, deriva-se a classe Canvas


e o método paint é então redefinido de acordo com a aplicação
(desenhar uma figura qualquer, por exemplo)..

7.1.4 Checkbox

A classe Checkbox representa um label com um pequeno botão. O estado de um checkbox


é verdadeiro (botão pressionado) ou falso (botão liberado); o estado inicial (default) é
falso. Por exemplo:
import java.applet.*;
import java.awt.*;
public class CheckboxWidget extends Applet {
public void init()
{
Checkbox m = new Checkbox("Allow Mixed Case");
add(m);
}
}

Pág 101
Alguns métodos importantes:

• public Checkbox(String label): Cria um checkbox com o label


especificado
• public Checkbox(String label, CheckboxGroup g, boolean state) :
Construtor utilizado quando o checkbox é membro de um CheckBoxGroup
• public boolean getState() : obtém o estado do checkbox (true ou false)
• public void setState(boolean state) : define o estado do checkbox

7.1.5 CheckBoxGroup

A classe CheckBoxGroup é utilizada para controlar o comportamento de um grupo de


objetos checkbox. Em um checkboxgroup, em um dado instante, apenas um dos objetos
checkbox pode ser verdadeiro (ou seja estar “marcado”). Objetos checkbox controlados
com um ckeckboxgroup podem ser chamados de radio buttons. Por exemplo:

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:

• public int getSelectedIndex(): retorna o índice (numérico) do item


selecionado
• public String getSelectedItem() : retorna o label (string) do item
selecionado
• public void select(int pos) : define o item selecionado (por meio do
índice)
• public void select(String str) : define o item selecionado (por meio do
label)

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);
}
}

Alguns métodos importantes:

• public List(int rows, boolean multipleSelections):cria uma lista que


mostra o número de linhas especificado. Se multipleSelections for true, pode-se
selecionar mais de um item da lista

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);
}
}

Alguns métodos importantes:

• public Scrollbar(int orientation):Constrói um scroll bar com a orientação


(Scrollbar.HORIZONTAL ou Scrollbar.VERTICAL) especificada.

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).

Alguns métodos importantes:

• public String getSelectedText():Obtém o texto selecionado.


• public String getText():Obtém todo o texto do componente.
• public void setEditable(boolean t): Se nâo-editável, o usuário não pode
modificar o conteúdo do componente.
• public void setText(String t): Define o conteúdo (texto) do componente.

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);
}
}

Alguns métodos importantes:

• 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);
}
}

Alguns métodos importantes:

• public TextField(String text): Cria um campo texto com o conteúdo inicial


especificado.
• public TextField(String text, int cols): Cria um campo texto com o
conteúdo inicial e o tamanho (número de colunas) do campo.

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.*;

public class MinhaApp extends Frame


{

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();
}
}

Alguns métodos importantes:

• public Frame(): cria uma nova janela


• public void SetTitle(String title): define o título da janela.
• public void show(): exibe a janela
• public void hide(): oculta a janela
• public void reshape(int x, int y, int widht, int height): redimensiona a janela (largura
e altura) e a posiciona nas coordenadas (x,y).

7.2 Inserindo Widgets em Applets

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:

1. Criar o componente (widget) com o operador new, por exemplo:

Button b = new Button("Hit me");


TextField tf = new TextField("0");

2. Adicionar o componente ao applet, por exemplo:


add(b);
add(tf);

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.*;

public class MeuApplet extends Applet


{
Button b;
TextField tf;
public void init()
{
b = new Button("Hit me");
tf = new TextField("0");
add(b);
add(tf);
}
}

Para organizar a apresentação (layout) dos widgets dentro de um container, utiliza-se um


layout manager. Um layout manager é um objeto que posiciona e redimensiona os
componentes de um container de acordo com um algoritmo. Por exemplo, o layout
manager, da classe FlowLayout, posiciona os componentes da esquerda para a direita até
que não haja mais espaço e, então, continua a posicionar os objetos na “linha” abaixo.
FlowLayout é o layout manager default para applets.

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.

7.3 Modelo de Eventos do Java (1.1)

Como os componentes da GUI respondem a estímulos do usuário (cliques no mouse, teclas


pressionadas, etc.)? Na linguagem Java, estes estímulos são chamados de eventos. Os
eventos são representados por diversas classes em Java. Entretanto, todos os eventos são
subclasses de java.awt.AWTEvent. Por conveniência, todos os tipos de evento do AWT
são colocados no pacote java.awt.event.

Observação Importante: O modelo de eventos apresentado nesta seção refere-se a versão


1.1 da linguagem Java. Este modelo de eventos é incompatível com o modelo de eventos
anterior (versão 1.0). O novo modelo de eventos (versão 1.1) é suportado pelos browsers

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).

O modelo de eventos é baseado no conceito de event listener (ouvinte de eventos). Um


ouvinte de eventos é um objeto interessado em receber eventos. Um objeto que gera
eventos (botão, por exemplo) mantém uma lista de ouvintes que estão interessados em ser
notificados quando os eventos ocorrerem e oferece métodos que permitem que objetos se
“cadastrem” nesta lista de ouvintes. Quando um evento ocorre, o objeto que o gerou avisa
todos os objetos ouvintes, executando um método destes ouvintes e passando para eles um
objeto da classe Event. Para que este modelo funcione, todos os ouvintes de um tipo de
evento devem implementar uma interface correspondente. Por exemplo, objetos ouvintes
dos eventos da classe ActionEvent deve implementar a interface ActionListener. O
pacote java.awt.event define uma interface específica para cada classe de evento.

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.

A tabela a seguir lista algumas classes de eventos definidas no pacote java.awt.event, a


interface do objeto ouvinte correspondente, e os métodos definidos na interface

Classe de Evento Interface do Ouvinte Métodos do Ouvinte


ActionEvent ActionListener actionPerformed()
ComponentEvent ComponentListener componentHidden()
componentMoved()
componentResized()
componentShown()
FocusEvent FocusListener focusGained()
focusLost()
ItemEvent ItemListener itemStateChanged()
KeyEvent KeyListener keyPressed()
keyReleased()
keyTyped()
MouseEvent MouseListener mouseClicked()
mouseEntered()
mouseExited()
mousePressed()
mouseReleased()
MouseEvent MouseMotionListener mouseDragged()
mouseMoved()
TextEvent TextListener textValueChanged()

Pág 108
WindowEvent WindowListener windowActivated()
windowClosed()
windowClosing()
windowDeactivated()
windowDeiconified()
windowIconified()
windowOpened()

Depois de implementar os métodos da interface de eventos desejada, o objeto ouvinte deve


se registrar no objeto “fonte” dos eventos. No caso do AWT, este objeto fonte é algum tipo
de widget (botão, text field, etc.). O registro do objeto ouvinte segue esta convenção: se o
objeto fonte gera eventos do tipo X, ele possui um método chamado addXListener() para
adicionar um ouvinte e um método chamado removeXListener() para remover um
ouvinte.

A tabela a seguir lista alguns widgets e os eventos que eles podem gerar:

Widget Eventos que pode gerar Significado do evento


Button ActionEvent Usuário clicou no botão
CheckBox ItemEvent Usuário selecionou ou de-selecionou um item
Choice ItemEvent Usuário selecionou ou de-selecionou um item
Component ComponentEvent O componente foi movido, redimensionado,
escondido ou mostrado
FocusEvent O componente ganhou ou perdeu o foco
KeyEvent Usuário pressionou ou soltou uma tecla
MouseEvent Usuário pressionou ou soltou o botão do
mouse, o mouse entrou ou saiu do
componente ou o usuário moveu o mouse
List ActionEvent Usuário deu um clique duplo em um item da
ItemEvent lista
Usuário selecionou ou de-selecionou um item
da lista
TextComponent TextEvent Usuário alterou o texto
TextField ActionEvent Usuário terminou de editar o texto
Window WindowEvent Janela foi aberta, fechada, iconificada,
deiconificada

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.*;

public class MeuApplet extends Applet implements


ActionListener
{
Button b;
TextField tf;

public void init()


{
b = new Button("Hit me");
tf = new TextField("0");
add(b);
add(tf);

// O applet é o ouvinte dos eventos do


// tipo Action Event gerados pelo botão
b.addActionListener(this);
}

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;

// Muda o valor do text field


tf.setText(String.valueOf(v));
}
}

Observação: como todos os widgets são subclasses de Component, todos os objetos do


pacote AWT herdam os eventos de Component. Assim, se quisermos, por exemplo,
decrementar o valor do text field caso o foco saia do botão temos que registrar o applet
como um FocusListener dos eventos gerados pelo botão. Este tipo de evento
(FocusEvent), na verdade, é um evento associado ao Component. Entretanto, como o
botão é uma subclasse de Component, podemos garantir que o botão também gera tais
eventos. O código a seguir mostra como isto pode ser feito:

import java.applet.*;
import java.awt.*;
import java.awt.event.*;

public class MeuApplet extends Applet implements


ActionListener,

Pág 110
FocusListener
{
Button b;
TextField tf;

public void init()


{
b = new Button("Hit me");
tf = new TextField("0");
add(b);
add(tf);

// O applet é o ouvinte dos eventos do


// tipo Action Event e Focus Event gerados pelo
botão
b.addActionListener(this);
b.addFocusListener(this);

}
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;

// Muda o valor do text field


tf.setText(String.valueOf(v));
}
public void focusLost(FocusEvent e)
{
// Converte a string dentro do text field p/ um
valor
// inteiro
int v = Integer.parseInt(tf.getText());

v = v-1;

// Muda o valor do text field


tf.setText(String.valueOf(v));
}
public void focusGained(FocusEvent e)
{
// Não faz nada quando o foco do mouse volta para o
botão
}
}

Pág 111
7.4 Gerenciadores de Layout (Layout Managers)

Um gerenciador de layout é um objeto que posiciona e redimensiona os componentes em


um container AWT, de acordo com algum algoritmo. Esta seção aborda alguns dos
gerenciadores mais utilizados na prática.

7.4.1 FlowLayout

Um gerenciador da classe FlowLayout posiciona os widgets da esquerda para a direita até


que não haja mais espaço na linha. A partir daí, os objetos são colocados em uma linha
abaixo. Este gerenciador é o default para a classe Panel e, consequentemente, para a classe
Applet.

Exemplo:

import java.awt.*;
import java.applet.Applet;

public class FlowLayoutTest extends Applet {


public void init() {
setLayout(new FlowLayout()); // default
add(new Button("B1"));
add(new Button("B2"));
add(new Button("B3"));
}
}

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>

<applet code=FlowLayoutTest.class CODEBASE="/applets/magelang/AWT-


Training/Examples/" width=50 height=90>
</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

A classe GridLayout posiciona os componentes em linhas e colunas. Deve-se preencher,


com widgets, o “grid” linha por linha. Por exemplo, o primeiro elemento adicionado em
um grid de 3 linhas x 2 colunas será o primeiro elemento da primeira linha. O terceiro
elemento a ser adicionado será o primeiro elemento da segunda linha e assim por diante.
Exemplo:

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
}
}

7.5 Cores e Fontes

Widgets possuem cores de primeiro e segundo plano (foreground e backgorund). Para


definir estas cores utiliza-se o método setForeground e setBackground. Por exemplo, o
applet abaixo mostra um label vermelho:
import java.awt.*;
public class ColorTest extends java.applet.Applet {
public void init() {
Label lab = new Label("A Red Label");
lab.setForeground(Color.red);
add(lab);
}
}

Pode-se também definir a cor de um container. Neste caso, todos os componentes


adicionados ao container possuirão aquela cor.

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:

String[] fontList = Toolkit.getDefaultToolkit().getFontList();

Para criar um objeto Font:

Font myFont = new Font("Helvetica", Font.BOLD, 12);

Exemplo:

import java.awt.*;

public class FontTest extends java.applet.Applet {


public void paint(Graphics g) {
Font f = new Font("Helvetica", Font.BOLD, 16);
g.setFont(f);
g.drawString("Helvetica 16 point Bold", 10, 30);
}
}

7.6 Exercícios

1. Qual é o único argumento do construtor da classe Button?


2. Como se pode alterar o nome mostrado em um botão (pesquisar métodos da classe
Button)?
3. Em um applet que possui vários botões, como é possível determinar qual botão foi
pressionado (dica: pesquisar a classe java.util.EventObject que é a superclasse de todos
os eventos do pacote java.awt.event)?
4. Escreva um applet que mostra o texto Java é legal!
5. Escreva um applet que possua um botão chamado Enviar Mensagem.
6. Escreva um applet que contenha dois labels e dois botões organizados em duas linhas.
Cada linha deve possuir um label seguido de um botão.
7. Escreva um applet que contenha um botão e um label. Quando o usuário clicar o botão,
o label deve mostrar o nome do botão.
8. Escreva um applet que mostra um checkbox com o texto Aperte Aqui.
9. Escreva um applet que possua um campo texto, cujo texto inicial é Escreva Aqui.
10. Escreva um applet que possua um checkbox e um campo texto. Mude o label do
checkbox toda a vez que o usuário escrever no campo texto.
11. Escreva um applet que possua um checkboxgroup.
12. Escreva um applet que possua uma lista contendo o nome de cinco filmes.
13. Escreva um applet que mostre uma área de texto. A área de texto deve possuir cinco
linhas.
14. Escreva um applet que possua uma lista e uma área de texto. A lista deve possuir 10
opções. Quando o usuário selecionar uma opção, esta opção deve ser impressa na área
de texto

Pág 114
8. Applets

Esta seção apresenta os aspectos fundamentais relacionados ao desenvolvimento de applets


Java.

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.

Exemplo de um Applet Simples

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;

public class Simple extends Applet {


StringBuffer buffer;

public void init() {


buffer = new StringBuffer();
addItem("initializing... ");
}
public void start() {
addItem("starting... ");
}
public void stop() {
addItem("stopping... ");
}
public void destroy() {
addItem("preparing for unloading...");
}

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.

8.2 Ciclo de Vida de um Applet

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.

public class Simple extends Applet {


...
public void init() { . . . }
public void start() { . . . }
public void stop() { . . . }
public void destroy() { . . . }
...
}

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:

• init: executado cada vez que o applet é (re)carregado.


• start: executado ao iniciar a execução do applet, por exemplo quando a página Web é
vistada ou quando o usuário “restaura” a janela do browser.
• stop: executado ao terminar a execução do applet, por exemplo quando o usuário
deixa a página ou minimiza o browser.
• destroy: executado para realizar a “limpeza” final do applet antes de ser
descarregado.

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.

8.3 Métodos Utilizados para Desenho e Tratamento de Eventos

Applets herdam os métodos de desenho e de tratamento de eventos da classe Component


do pacote AWT (Abstract Windowing Toolkit). O pacote AWT, apresentado na seção 7, é
utilizado pelos applets (e aplicações stand-alone) para implementar interfaces gráficas.
Métodos de desenho referem-se a funções relacionadas a apresentação do applet na tela:
desenhando imagens ou mostrando uma interface gráfica (botões, check-box, etc.).
Tratamento de eventos refere-se a detecção e processamento de eventos de entrada (por
exemplo, usuário clica no mouse, usuário pressiona uma tecla).

O applet Simple define sua aparência na tela redefinindo o método paint:

class Simple extends Applet {


...
public void paint(Graphics g) { . . . }
...
}

O método paint é um dos dois métodos de apresentação que um applet pode redefinir:

• paint: o método de apresentação básico. Muitos applets implementam o método paint


para desenhar a representação do applet na página Web.
• update: um método que pode ser utilizado junto com o paint para melhorar o
desempenho do applet.

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.

8.4 Mostrando Imagens Dentro de um Applet

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.*;

public class MostraImagem extends Applet {

Image im;

public void init()


{
im = getImage(getCodeBase(), "foto.jpg");
}

public void paint(Graphics g)


{
g.drawImage(im, 10, 15, this);
}
}

8.5 Adicionando um Applet a uma Página HTML

Para incluir um applet em uma página HTML basta utilizar a tag HTML <applet>.
Exemplo:

<applet code=”MeuApplet.class” width=120 height=120>


</applet>

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.

9.1 O que é uma Thread

Uma thread, também chamada de contexto de execução ou processo leve, é um fluxo de


controle dentro de um programa. Todos os programadores, independente da linguagem,
estão acostumados em escrever programas seqüenciais. Estes programas seqüenciais
possuem um início, uma seqüência de execução e um fim. Em qualquer instante, durante a
execução do programa, uma única instrução está sendo executada.

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:

Um browser é um exemplo de uma aplicação com várias threads (multithreaded). Dentro


de um browser é possível fazer um “scroll” da página, enquanto o browser carrega uma
imagem, toca uma música ou imprime a página.

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.

9.2.1 Criando uma Subclasse de Thread e Redefinindo o Método run

A primeira maneira possível de desenvolver uma thread é criando uma subclasse de


Thread e redefinindo o seu método (vazio) run. O exemplo a seguir faz justamente isto:

public class SimpleThread extends Thread {


public SimpleThread(String str) {
super(str);
}
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(i + " " + getName());
try {
sleep(1000));
} catch (InterruptedException e) {}
}
System.out.println("ACABOU! " + getName());
}
}

O primeiro método da classe SimpleThread é o construtor. O parâmetro do construtor é


uma String que corresponde ao nome da thread. Este parâmetro é repassado para o
construtor da classe Thread.

O próximo método da classe é o run. O método run é o “coração” de qualquer thread, é


neste método que se determina o que a thread vai fazer. No nosso exemplo, a thread irá
executar um laço (10 vezes). A cada iteração do laço, o método vai mostrar o número da
iteração e o nome da thread e vai “dormir” (método sleep) por 1 segundo. Depois que o
laço terminar, o método run irá imprimir ACABOU! junto com o nome da thread.

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”.

public class TwoThreadsTest {


public static void main (String[] args) {

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.

9.2.2 Implementando a Interface Runnable

Outra forma de desenvolver threads é implementar a interface Runnable que define o


método run. Adaptando o exemplo da seção anterior, temos:

public class SimpleThread implements Runnable {


String nome;

public SimpleThread(String str)


{
nome = str;
}

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).

public class TwoThreadsTest {


public static void main (String[] args) {
SimpleThread t1 = new SimpleThread("Chocolate");
SimpleThread t2 = new SimpleThread("Sorvete");

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.

9.3 Ciclo de Vida de uma Thread

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.

9.3.2 Iniciando uma 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.

Depois da execução do método start, a thread está executando (running). Na verdade, o


conceito é um pouco mais complexo: a thread está em um estado “executável” (runnable).
Muitos computadores possuem um único processador, ou seja, é impossível executar mais
de uma thread ao mesmo tempo. O que ocorre é um escalonamento entre as threads para
permitir o compartilhamento do processador da máquina. Assim, uma thread no estado
runnable pode estar, na verdade, esperando a sua vez de utilizar a CPU.

9.3.3 Tirando uma Thread do Estado Runnable

Uma thread vai para o estado “not runnable” quando um destes eventos ocorre:

• O método sleep é executado


• A thread executa o método wait, esperando que uma determinada condição seja
satisfeita
• A thread está bloqueada esperando E/S de dados.

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.

9.4 Sincronização de Threads

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).

public class SimpleThread extends Thread {


public SimpleThread(String str) {
super(str);
}
public static void imprimeValor() {
for (int i = 0; i < 50; i++)
System.out.println(i + " " + getName());
}
public void run() {
imprimeValor();
}
}

Se executarmos a classe TwoThreadsTest, teremos uma saída parecida com esta:

0
1
2
0
1
3
.

Pág 125
.
.

Isto ocorre pois as threads estão executando “simultaneamente” o método imprimeValor.


Se quisermos que apenas uma thread execute este método em um dado instante, devemos
defini-lo com synchronized:

public synchronized static void imprimeValor() { ....

Desta forma, teremos a seguinte saída:

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).

Observação: na verdade, o mecanismo synchronized é válido por objeto. Ou seja, se uma


classe define dois ou mais métodos synchronized, apenas um destes métodos do objeto
pode ser executado por uma thread em um determinado instante.

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:

Analogamente, um programa pode enviar informações para um destino externo abrindo um


stream para este destino e escrevendo a informação sequencialmente:

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.

10.2 Leitura e Escrita de Arquivos

Para realizar a leitura e escrita de arquivos utilizamos as classes FileInputStream e


FileOutputStream, respectivamente. Os construtores destas classes têm como parâmetro

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.

O exemplo abaixo realiza a cópia dos dados de um arquivo para outro:

import java.io.*;

public class CopyBytes {


public static void main(String[] args) throws IOException {
File inputFile = new File("oi.txt");
File outputFile = new File("ola.txt");

FileInputStream in = new FileInputStream(inputFile);


FileOutputStream out = new FileOutputStream(outputFile);
int c;

while ((c = in.read()) != -1)


out.write(c);

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).

Finalmente, as duas streams são fechadas (método close).

FileInputStream e FileOutputStream são adequadas para escrever dados binários


(bytes). Para manipular mais facilmente texto (strings) podemos utilizar as classes
FileReader, BufferedReader, FileWriter e PrintWriter. O exemplo a seguir também
realiza a cópia de um arquivo para outro, porém utiliza outras classes:

import java.io.*;

public class CopyBytes {


public static void main(String[] args) throws IOException {
FileReader inputFile = new FileReader("oi.txt");
FileWriter outputFile = new FileWriter("ola.txt");

BufferedReader in = new BufferedReader(inputFile);

Pág 128
PrintWriter out = new PrintWriter(outputFile);
String s;

while ((s = in.readLine()) != null)


out.println(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.

10.3 Entrada e Saída de Dados (Padrão)

A classe System do pacote java.lang possui três variáveis stream que representam:

• A entrada padrão do sistema (normalmente o teclado): in


• A saída padrão do sistema (normalmente o monitor): out
• A saída de erro padrão do sistema (normalmente o monitor): err

A variável in é da classe InputStream e as variáveis out e err são da classe PrintStream.


Para imprimir strings na saída padrão utilizamos os métodos print ou println. Já para ler
algum valor do teclado, devemos criar um objeto da classe BufferedReader que possui o
método readLine.

O programa a seguir lê duas strings, armazena-as em duas variáveis e as exibe na tela:

import java.io.*;

public class Eco {


public static void main(String[] args) throws IOException {
String a,x;

BufferedReader ent = new BufferedReader(new


InputStreamReader(System.in));

System.out.print("Digite um valor: ");


a = ent.readLine();

Pág 129
System.out.print("Digite outro valor: ");
x = ent.readLine();

System.out.println("Você digitou: " + a + " " + x);

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

You might also like