Você está na página 1de 257

Java Fundamentals

Java Fundamentals

Sumário

1. A tecnologia Java e configuração do ambiente


ambiente..............
nte.............. 1-1
Objetivos .......................................................................................................................1-2
O que é Java? .............................................................................................................1-3
Simples e orientada a objetos ...................................................................................1-5
Uma linguagem robusta.............................................................................................1-7
Multiplataforma e interpretada ................................................................................1-9
A Java Virtual Machine - JVM .................................................................................1-11
Uma Linguagem Segura e Dinâmica.....................................................................1-14
Aspectos de segurança ...........................................................................................1-16
O Just in Time Compiler - JIT .....................................................................................1-18
O Java Standard Development Kit – J2SDK..........................................................1-19
Configurando o ambiente.......................................................................................1-22
API’s da linguagem ...................................................................................................1-23

2. Tipos e Operadores
Operadores ....................................................
.................................................... 2-1
Objetivos .......................................................................................................................2-2
Variáveis ........................................................................................................................2-3
Tipos primitivos e tipos compostos ............................................................................2-4
Tipos Primitivos ..............................................................................................................2-6
Declarando um tipo primitivo..................................................................................2-10
Tipo caracter – char ..................................................................................................2-12
Tipo booleano – boolean .........................................................................................2-14
Tipos inteiros - byte, short, int e long .......................................................................2-15
Tipos de ponto flutuante - float e double..............................................................2-17
Tipo composto – String ..............................................................................................2-19
Conversões de tipos – casting.................................................................................2-20
Tipos de referência ....................................................................................................2-22
Expressões e operadores..........................................................................................2-23
Sumário de operadores............................................................................................2-25
Precedência...............................................................................................................2-27
Associatividade..........................................................................................................2-28
Tipos de operadores: unários e binários ................................................................2-29

3. Criando classes e objetos ...........................................


................................ ........... 3-1
Objetivos .......................................................................................................................3-2
Classes e Objetos.........................................................................................................3-3
Criando uma classe ....................................................................................................3-4
Padrões..........................................................................................................................3-6

I
Java Fundamentals

Criando e importando pacotes................................................................................3-9


Static import................................................................................................................3-11
Criando objetos .........................................................................................................3-12
O que é a referência null? .......................................................................................3-13
Atribuindo referências...............................................................................................3-15
Visibilidade aplicada a classes ...............................................................................3-16
Definindo operações ................................................................................................3-18
Comando return ........................................................................................................3-21
Visibilidade para operações ...................................................................................3-22
Definindo atributos ....................................................................................................3-25
Visibilidade aplicada a atributos ............................................................................3-26
Acessando atributos .................................................................................................3-32
Comentários no código fonte.................................................................................3-33
Escopo das variáveis.................................................................................................3-36
Passando Tipos Primitivos para Métodos ...............................................................3-39
Passando Referências para Métodos ....................................................................3-40
Exercícios.....................................................................................................................3-42

4. Comandos da Linguagem ...........................................


................................ ........... 4-1
Objetivos .......................................................................................................................4-2
Comandos ....................................................................................................................4-3
Comando if / else / else if ..........................................................................................4-4
Seqüência de Cláusulas else if ..................................................................................4-7
Operador ternário .......................................................................................................4-9
Comando switch .......................................................................................................4-10
Comando while .........................................................................................................4-13
Comando do .............................................................................................................4-14
Comando for..............................................................................................................4-15
Comando “for-each” ...............................................................................................4-18
Comando break ........................................................................................................4-19
Comando continue ..................................................................................................4-21
Exercícios.....................................................................................................................4-24

5. Aprofundando o estudo sobre Classes.........................


Classes ......................... 5-1
Objetivos .......................................................................................................................5-2
Visão Geral ...................................................................................................................5-3
Overloading – sobrecarga de operação ...............................................................5-4
Métodos construtores .................................................................................................5-6
Referência this..............................................................................................................5-9
Compartilhando código entre Construtores ........................................................5-11
Método destrutor – finalize() ....................................................................................5-12
Variáveis de instância...............................................................................................5-13
Métodos de instância ...............................................................................................5-15
Variáveis de classe ....................................................................................................5-16
Inicializando Variáveis de Classe ............................................................................5-19
Métodos de classe ....................................................................................................5-20

II
Java Fundamentals

Exemplos de variáveis e métodos estáticos .........................................................5-22


O mecanismo de herança entre classes ..............................................................5-23
Herdando estrutura e comportamento.................................................................5-24
Especificando herança em Java ...........................................................................5-25
Objetos de subclasses...............................................................................................5-26
Chamando construtores da superclasse...............................................................5-27
Overloading e Overriding de métodos..................................................................5-29
Redefinindo métodos – overriding..........................................................................5-30
Referência super........................................................................................................5-33
Invocando métodos da superclasse......................................................................5-35
Visibilidade protected ..............................................................................................5-36
Varargs ........................................................................................................................5-37
Polimorfismo................................................................................................................5-39
Modificador final........................................................................................................5-41
Enums...........................................................................................................................5-43
Exercícios.....................................................................................................................5-46

6. Coleções, Arrays, Strings, e Wrapper Classes ............... 6-1


Objetivos .......................................................................................................................6-2
Strings .............................................................................................................................6-3
Criando Strings .............................................................................................................6-4
Concatenando Strings................................................................................................6-5
Executando operações em objetos String..............................................................6-6
Comparando duas Strings .........................................................................................6-8
Obtendo strings a partir de objetos..........................................................................6-9
Convertendo tipos primitivos em strings ................................................................6-10
Wrapper Classes ........................................................................................................6-11
Conversões com Wrapper Classes .........................................................................6-12
StringBuffer e StringBuilder ........................................................................................6-13
Arrays ...........................................................................................................................6-14
Arrays de tipos primitivos ..........................................................................................6-15
Declarando arrays de tipos primitivos....................................................................6-16
Criando arrays............................................................................................................6-17
Inicializando arrays ....................................................................................................6-18
Arrays de objetos .......................................................................................................6-20
Declarando arrays de objetos ................................................................................6-21
Inicializando arrays de objetos................................................................................6-22
Utilizando arrays de objetos .....................................................................................6-23
Arrays e Exceções......................................................................................................6-24
Arrays multidimensionais...........................................................................................6-25
O método main(String[] args) ........................................................................6-26
API Colletion ...............................................................................................................6-27
A interface Iterator ....................................................................................................6-28
A interface Enumeration ..........................................................................................6-29
Interfaces do framework ..........................................................................................6-30
A classe ArrayList........................................................................................................6-31
A classe Vector ..........................................................................................................6-32

III
Java Fundamentals

A classe Hashtable ....................................................................................................6-34


A classe LinkedList......................................................................................................6-35
Generics ......................................................................................................................6-36
Autoboxing .................................................................................................................6-39
Exercícios.....................................................................................................................6-43

7. Tratamento de Exceções .............................................


............................................. 7-1
Objetivos .......................................................................................................................7-2
Introdução ....................................................................................................................7-3
1a Vantagem: Separação de Código .....................................................................7-4
2a Vantagem: Propagação de Erros........................................................................7-6
3a Vantagem: Agrupar Tipos de Erros ......................................................................7-8
4a Vantagem: Exceções não são Ignoradas........................................................7-10
Manipulando Exceções............................................................................................7-11
Tratando Exceções....................................................................................................7-13
Manipulando Exceções............................................................................................7-14
Manipulando Exceções: Exemplo Prático.............................................................7-16
Propagando Exceções.............................................................................................7-17
Lançando Exceções .................................................................................................7-18
Criando Exceções .....................................................................................................7-19
Capturando Exceções e Levantando Exceções Diferentes..............................7-20
Exercícios.....................................................................................................................7-22

8. Classes abstratas e Interfaces .....................................


................................ ..... 8-1
Objetivos .......................................................................................................................8-2
Abstração .....................................................................................................................8-3
Definindo classes abstratas........................................................................................8-4
Métodos Abstratos.......................................................................................................8-6
Definindo métodos abstratos ....................................................................................8-7
Interfaces ......................................................................................................................8-8
Exemplos de interfaces...............................................................................................8-9
Definindo Interfaces ..................................................................................................8-10
Implementando Interfaces ......................................................................................8-11
Exercícios.....................................................................................................................8-13

IV
Java Fundamentals

1. A tecnologia Java e
configuração do ambiente

1-1
A tecnologia java e configuração do ambiente

Objetivos
• Compreender os fundamentos da tecnologia Java
• Discutir vantagens da tecnologia
• Entender o funcionamento da JVM (Java Virtual Machine)
• Configurar o ambiente de desenvolvimento para o programador

1-2
A tecnologia java e configuração do ambiente

O que é Java?

• Linguagem orientada a objetos, simples, portável, interpretada,


distribuída, robusta, segura, dinâmica, de alto desempenho, multi-thread e
independente de plataforma
• Projetada pela Sun Microsystems inicialmente para dispositivos eletrônicos
• Utilizada posteriormente em navegadores web, para permitir que uma
aplicação pudesse ser desenvolvida e “executada na web”. Aqui é o
nascimento da tecnologia applet
• Uma linguagem muito utilizada atualmente para desenvolvimento de
sistemas que precisam rodar na web bem como sistemas desktop

Breve Histórico

Java começou em 1991, quando um grupo de analistas da Sun, liderados


por Patrick Naughton e James Gosling, procurou desenvolver uma linguagem de
computador que fosse usada em equipamentos domésticos, tais como
comutadores de canais para TV a cabo, videocassetes e outros.

Como estes equipamentos não dispõem de muita memória ou velocidade,


a linguagem tinha de ser reduzida e gerar código eficiente. Além disto, como
diferentes fabricantes poderiam escolher diferentes CPUs, tal linguagem não
poderia se restringir a uma única arquitetura.

O projeto ficou conhecido como Green Project.

DUKE, um dos símbolos do Java

Visando satisfazer todas estas exigências, a equipe de analistas optou por


uma linguagem que gerasse código intermediário (os famosos bytecodes Java),
e que a interpretação deste código não fosse feita diretamente pelo hardware e
sim por uma máquina virtual disposta sobre ele (conhecida hoje como JVM –
Java Virtual Machine).

Esta linguagem foi batizada inicialmente com o nome Oak (que, em


português significa carvalho). Possivelmente o nome escolhido por Gosling se
deve a um carvalho que existia em frente a sua janela na Sun MicroSystems.
1-3
A tecnologia java e configuração do ambiente

Posteriormente se descobriu que Oak já era o nome de uma outra


linguagem, e o nome foi então trocado para Java.

Java não obteve muito sucesso como linguagem de controle de


eletrodomésticos (e isto nós podemos muito bem atestar :-). Em vão, a equipe do
Green Project tentou vender a idéia para fabricantes de tais dispositivos.
Dissolvida a equipe, por absoluta falta de êxito econômico, alguns dos antigos
componentes perceberam que uma das possíveis utilidades para tal linguagem
seria embuti-la em navegadores, como os encontrados no mercado.

Tais aplicativos exigiam justamente uma linguagem independente de


plataforma, confiável, segura e em tempo real: todas as características
“estranhas” que Java possuía.

1-4
A tecnologia java e configuração do ambiente

Simples e orientada a objetos

• Uma linguagem simples e orientada a objetos


• Baseada em Smalltalk e C++
• A tecnologia de objetos para esta linguagem foi baseada em Smalltalk
• Reaproveitou grande parte da sintaxe utilizada pelo C e C++
• Eliminou construções de C++ consideradas complexas ou desnecessárias
• Reduzido conjunto de palavras reservadas e um grande poder de
expressão

Uma Linguagem Orientada a Objetos

Problemas surgidos na área de engenharia de software, tais como aumento


explosivo da complexidade dos sistemas, dificuldades na manutenção e
evolução dos softwares existentes, fraca robustez apresentada pelos aplicativos e
outros, fizeram com que os paradigmas de análise e desenvolvimento de
software fossem alterados.

Java surge junto com o “boom” da orientação por objetos, à ampla


aceitação deste paradigma pelos técnicos de informática como o mais
adequado a enfrentar os problemas encontrados.

A cultura dos técnicos da equipe de desenvolvimento da Sun Microsystems


fez com que, entre as linguagens orientadas por objetos conhecidas,
escolhessem C++ como modelo para o desenvolvimento de Java. C++ era então
uma das mais difundidas linguagens de programação orientada por objetos em
voga no mundo UNIX.

Mas C++ era complexa demais. Várias construções em C++ eram de


questionável utilidade e de difícil implementação por parte dos construtores de
compiladores.

Herança múltipla e sobrecarga de operadores, por exemplo, eram


construções que demandavam grande esforço por parte dos implementadores e
que não fazem parte do núcleo mínimo exigido pelo paradigma. Desta forma
alguns recursos ficaram de fora e decidiu-se manter inicialmente uma gama
menor de recursos para viabilizar o projeto e manter o mesmo mais somples.
Atualmente alguns recuros mais sofisticados como “Generics” foram incluídos. Os
recursos básicos que foram atendidos na época são:

• Abstração

1-5
A tecnologia java e configuração do ambiente

• Encapsulamento

• Herança

• Polimorfismo

Decidiram então simplificar:

• A linguagem não seria compilada para uma plataforma nativa e sim


para uma máquina virtual. Esta técnica que dá ao java a possibilidade
de rodar em múltiplas plataformas foi baseada no Smalltalk

• Não haveria programação genérica (na época, mas a partir da versão


1.5 este recurso já é suportado)

• Não haveria sobrecarga de operadores

• Herança múltipla seria substituída pelo mecanismo de interfaces, mais


simples e com poder de expressão equivalente

• A velha sintaxe, tomada de empréstimo da linguagem C, seria


enxugada, de modo a conter somente as palavras reservadas
necessárias

Destas idéias iniciais surge então a tecnologia Java. Claro que há muito mais
coisas presentes no java do que as listadas acima. Atualmente o java é uma
tecnologia tão completa que permite você desenvolver aplicações para rodar
na Web, em máquinas cliente, Celulares, Palm Tops e muito mais.

1-6
A tecnologia java e configuração do ambiente

Uma linguagem robusta

• Java é uma linguagem fortemente tipada


• Ampla verificação de erros e checagem de tipos em tempo de
compilação
• Não existem apontadores na linguagem
• Linguagem multiplataforma
• Gerência automática da memória (garbage collection)

Erro de Ponteiro?

Um dos principais objetivos dos projetistas da linguagem era conseguir


conciliar a flexibilidade e o poder da orientação por objetos com a segurança e
a robustez da checagem de tipos e erros em tempo de compilação.

Java é uma linguagem fortemente tipada.

E o que queremos dizer com isto?

Que, em tempo de compilação, são checados todos os problemas


referentes à passagem de parâmetros e atribuições de variáveis no que diz
respeito à compatibilidade de tipos entre a variável que recebe o valor e a
variável atribuída.

Outra importante característica da linguagem Java é fato de não possuir


apontadores, com o sentido que tal termo possui em C ou em C++.

Em C ou C++ é perfeitamente possível navegarmos despreocupadamente


pela memória do processo: basta, para tal, declararmos um apontador para
uma região de memória e sairmos a incrementá-lo ou decrementá-lo.

Toda a aritmética com apontadores não só é permitida pela linguagem,


como também incentivada.

Em Java não há apontadores explícitos, nem tampouco aritmética de


apontadores, mas sim referências feitas a objetos criados.

Objetos são criados através do operador new e o espaço de memória que


ocupam é automaticamente gerenciado por um sistema de coleta de lixo
(garbage collection) , que o libera tão logo não haja mais referências a um
objeto.

1-7
A tecnologia java e configuração do ambiente

Com isto, não existem em Java os erros de ponteiro (dangling pointers,


memory leak e outros) tão conhecidos de linguagens como C e C++.

1-8
A tecnologia java e configuração do ambiente

Multiplataforma e interpretada

Ao contrário das outras tecnologias onde os programas são compilados


para um sistema operacional específico, os fontes Java são compilados para
uma máquina virtual. Isto significa que após a compilação os mesmos, são
executados por uma máquina virtual (JVM) e não diretamente pelo sistema
operacional. Abaixo veja a forma tradicional de compilação dos programas e
mais abaixo a forma como funciona o Java.

Figura 1-1: Compilação em C++.

Em linguagens tradicionais como C, C++ ou pascal, o código fonte é


convertido para um conjunto de instruções de máquina da plataforma de
hardware onde a compilação teve lugar. Este programa é executado
diretamente pela CPU da máquina e está vinculado à plataforma em que foi
compilado: só poderá ser executado por CPUs que compartilhem o mesmo
conjunto de instruções, o que geralmente significa ficar limitado a um
determinado fabricante de hardware ou a uma família de produtos.

Figura 1-2: Compilação em Java.

1-9
A tecnologia java e configuração do ambiente

Java adota uma filosofia diferente. Os fontes Java não são convertidos
diretamente para instruções de máquina. Ao contrário, a partir da compilação
dos fontes são gerados arquivos contendo código intermediário que independe
de plataforma.

O código intermediário Java, conhecido como “bytecode” Java, foi


projetado para uma máquina hipotética. Contém instruções similares a de uma
máquina real (operações aritméticas, controle de fluxo, etc.), mas não tem em
vista, e nem se limita, a uma determinada arquitetura de computador.

Em vez de ser executado diretamente pelo hardware, este código é


interpretado por uma JVM (Máquina Virtual Java  Java Virtual Machine). Desta
forma, em qualquer arquitetura onde exista uma JVM será possível executar um
programa Java sem a necessidade de se re-compilar os fontes.

Os binários Java (código intermediário ou Java bytecode) são


independentes de plataforma ou, dito de outra maneira, neutros quanto à
arquitetura da máquina.

Os fontes Java são armazenados em arquivos com a extensão .java. A


compilação dos arquivos .java trará como resultado arquivos com a extensão
.class.

Arquivos com a extensão .class contém somente Java bytecodes e são


também conhecidos como binários Java. Cabe novamente ressaltar que os
binários Java (arquivos .class) não são executados diretamente pelo hardware
e sim interpretados pela JVM.

Sem a JVM não há como executar nenhum programa Java.

1-10
A tecnologia java e configuração do ambiente

A Java Virtual Machine - JVM

• Camada intermediária e isolante entre o sistema operacional e as


aplicações Java.
• Elimina a dependência do código quanto à arquitetura e quanto às
facilidades do SO.
• Responsável pela interpretação dos bytecodes Java.
• Traduz os bytecodes em instruções de máquina e em chamadas de
sistema (API do SO).
• Pode ser implementada através de um programa avulso ou estar
embutida em um navegador (Browser).

A máquina virtual Java (ou, como é comumente chamada, JVM, Java


Virtual Machine) integra o ambiente de programação e execução Java. Ela é
responsável por interpretar os bytecodes Java e traduzi-los em instruções reais de
máquina e em chamadas de sistema (syscalls).

Figura 1-3: Esquema de independência da JVM.

Desta forma, as requisições feitas por um programa Java a recursos do


sistema são mapeadas pela JVM em requisições feita ao SO sobre o qual a JVM
executa. O SO responde a tais requisições e estas respostas são encaminhadas
ao código Java em execução.

Uma vez compilado um programa Java, os binários (bytecodes Java)


podem ser executados em toda a plataforma para a qual já a JVM já tenha sido
portada. Atualmente, a grande maioria dos sistemas operacionais conhecidos
possui uma implementação da JVM (Solaris, Windows98, WindowsNT, Linux,
MacOS, HP-UX, AIX, etc.).

1-11
A tecnologia java e configuração do ambiente

A JVM pode ser implementada através de um programa avulso, pode estar


embutida em um navegador, fazer parte do núcleo de um banco de dados, ou
mesmo integrar o kernel de um SO.

A única exigência que se faz à execução de um aplicativo Java é a


existência da JVM para aquele ambiente.

Como executam os programas java

A linguagem Java é orientada a objetos, com linhas de execução (threads)


dinâmicas e muitos outros recursos. Mas o que faz a diferença é o modo como os
programas Java são executados. Eles rodam dentro de máquinas virtuais (virtual
machines), que ficam dentro do computador. Por isso, um programa Java não
tem contato com o computador real, ele conhece apenas a máquina virtual.
Logo, os programas Java são independentes de plataforma. Se você já precisou
desenvolver programas para vários sistemas operacionais, sabe que isso é uma
grande vantagem.

Quando você escreve um programa Java e o compila, ele está pronto para
ser executado em qualquer PC que contenha a máquina virtual Java. De certa
forma, você está escrevendo para apenas uma plataforma: a máquina virtual.

A virtual machine determina o que os programas Java podem ou não fazer.


Os programas escritos em linguagens compiladas, como C ou C++, são
executados diretamente pelo sistema operacional. Assim sendo, eles têm acesso
direto a todos os recursos do sistema, incluindo memória e sistema de arquivos.
Como os programas Java são executados de dentro da máquina virtual, as
pessoas (programadores e desenvolvedores) que criam a máquina virtual podem
decidir o que um programa pode ou não fazer no computador. O ambiente
criado para os programas Java chama-se ambiente de runtime. A máquina
virtual age como um firewall (barreira) entre o computador e o programa Java.
Um programa nunca acessa os dispositivos de entrada e saída, o sistema de
arquivos ou mesmo a memória do seu computador. Em vez disso, ele pede que a
máquina virtual faça isso.

Quando rodamos as applets, elas são descarregadas para uma máquina


virtual que proíbe o acesso ao sistema de arquivos. Assim, ela só permite acesso
indireto aos recursos do sistema.

Por ser uma linguagem interpretada, isso explica a independência de


plataforma Java.

1-12
A tecnologia java e configuração do ambiente

Por que interpretada?


Porque o compilador Java gera o bytecode (código especial Java), que
será executado por uma máquina virtual implementada em software, chamada
de JVM – Java Virtual Machine (Máquina Virtual Java).

A diferença entre o Java que é uma linguagem interpretada, comparada


com uma linguagem compilada, como o caso do C ou C++ é que enquanto
nessas linguagens tradicionais, para cada plataforma precisamos que o
compilador gere um código especifico, como por exemplo, para um PC. Se
quisermos que o mesmo programa rode em um Macintosh, precisaremos
compilá-lo para rodar em Macintosh e assim por diante. Já com o Java isso não
aconteceria, pois para rodarmos um programa feito em Java, usamos o
interpretador Java (contido na JVM) para executar o bytecode resultante da
compilação. Como o bytecode Java é independente de plataforma, os
programas Java podem rodar em qualquer plataforma para a qual a JVM tenha
sido portada. A JVM inclui o interpretador mais o sistema de runtime.

Em um ambiente interpretado, a fase de linkagem que existem no


desenvolvimento de programas tradicionais compilados praticamente
desaparece. O equivalente em Java à fase de linkagem consiste apenas do
processo de carregar novas classes no ambiente de execução da JVM.

Esse é um processo leve e incremental, que ocorre em tempo de execução.


Isso é diferente do ciclo compilar-linkar-rodar, mais trabalhoso, comum em
linguagens como C e C++. O resultado é uma redução no tempo de
desenvolvimento dos programas.

Agora, depois dessa explicação, fica claro o que eles quiseram dizer com a
frase "Write once, Compile once and Run anywhere" – ("Escreva uma vez, compile
uma vez e rode em qualquer lugar"), frase esta que se tornou uma das marcas
registradas de Java.

1-13
A tecnologia java e configuração do ambiente

Uma Linguagem Segura e Dinâmica

• Os bytecodes de Java são constantemente policiados pela JVM


• Instruções não autorizadas levantam exceções de segurança
• A carga das classes é feita de maneira dinâmica
• Uma classe pode ser carregada através da rede ou do disco local da
máquina

Figura 1-4: Fluxo de compilação e execução de uma classe Java

Durante a execução de um programa Java, a JVM pode importar código


de qualquer lugar. A fim de tornar, a linguagem segura, é necessário ou nos
certificarmos de que o local de onde o código se origina é seguro, ou policiarmos
constantemente o código inseguro contra eventuais violações de segurança.

A primeira opção é implementada através de sistemas de assinatura digital


de código.

A segunda opção, por sua vez, é de responsabilidade do sistema de


runtime. Cabe a este policiar a execução dos bytecodes Java, verificando se
não há, entre eles, nenhuma instrução não autorizada, que viole as regras de
segurança estabelecidas pela linguagem.

1-14
A tecnologia java e configuração do ambiente

O carregador de classes do sistema de runtime assegura que classes com


origem em uma fonte local (classes Built-in) e classes provenientes da rede sejam
armazenadas separadamente.

Durante a execução, o sistema de runtime sempre procura resolver primeiro


uma referência nas classes locais. Isto garante que classes locais não sejam
substituídas por classes carregadas a partir da rede (fonte insegura).

Elimina a possibilidade de sobrescrita de classes seguras (spoofing) por


classes não confiáveis. O acesso ao sistema de arquivos locais e aos recursos de
rede é controlado por classes da linguagem (Built-in classes). Estas classes são
restritivas por default. Se código importado e classificado como inseguro tenta
acessar o sistema de arquivos, os mecanismos de segurança avisam
imediatamente ao usuário.

1-15
A tecnologia java e configuração do ambiente

Aspectos de segurança

Talvez pelo fato de Java estar fortemente associado à Internet, um dos


aspectos mais divulgados da linguagem é sua segurança. Isso é natural: sem a
certeza da segurança, provavelmente ninguém iria querer baixar código de um
site desconhecido na Internet e deixar que ele rodasse em seu computador.E no
entanto, isso já está sendo feito todos os dias com os applets Java.

Java foi projetado com a segurança em mente. Por isso, oferece várias
camadas de controles de segurança que protegem contra código malicioso,
permitindo que os usuários rodem tranqüilamente programas de origem
desconhecida, como os applets.

No nível mais baixo, a segurança é uma conseqüência da robustez de Java.


Como já vimos, os programas Java não podem forjar ponteiros para a memória,
nem estourar arrays, nem ler memória que esteja fora das fronteiras de um array
ou string. Esses recursos são uma das principais defesas de Java contra código
malicioso. Ao impedir totalmente qualquer acesso direto à memória, toda uma
enorme classe de ataques à segurança é evitada.

A segunda barreira contra código malicioso é o processo de verificação do


bytecode que é executado pelo interpretador Java sobre qualquer código de
origem desconhecida que é carregado. Essa verificação assegura que o código
seja bem formado – isto é, que ele não avance na memória que fica acima ou
abaixo dos limites da pilha (stack overflow ou underflow), nem implemente
bytecode ilegal. Se esse passo de verificação do bytecode não existisse, código
corrompido por incompetência ou má fé poderia tirar partido de pontos fracos
na implementação de um interpretador Java.

Outra camada de proteção para segurança é comumente chamada de


modelo de caixa de areia (sandbox): o código de origem desconhecida pode
rodar, mas é mantido isolado dentro de uma caixa de areia, onde pode rodar
em segurança sem causar qualquer dano ao "mundo real", que é o ambiente
Java como um todo.

Quando um applet, ou outro código de origem desconhecida está rodando


dentro da caixa de areia, ele fica submetido a diversas restrições sobre o que
pode fazer. A mais óbvia dessas restrições é que ele não tem acesso de nenhum
tipo ao sistema de arquivos local. Existem ainda várias outras restrições à caixa de
areia. Na verdade, existe uma classe, chamada SecurityManager, especialmente
para cuidar da implementação dessas restrições. Para assegurar o
funcionamento do modelo de segurança, todas as classes do núcleo Java que
executam operações de risco, como acesso ao sistema de arquivos, primeiro
pedem permissão ao SecurityManager atualmente instalado. Se a chamada está
sendo feita, direta ou indiretamente, por código de origem desconhecida, o
gerenciador de segurança lança uma exceção, impedindo a operação.

1-16
A tecnologia java e configuração do ambiente

A versão Java 1.1, implementa um recurso adicional à questão da


segurança: a assinatura digital. Anexando uma assinatura digital ao código Java,
a origem desse código pode ser estabelecida de uma forma criptograficamente
segura e impossível de falsificar.

Desta forma, o usuário pode definir que uma determinada pessoa ou


organização merece sua confiança. A partir daí, o código que traz a assinatura
digital dessa entidade merece confiança mesmo que seja carregado através da
rede, podendo rodar sem as restrições do modelo de caixa de areia.

Porém, quando se trata de segurança, é preciso ser realista. Da mesma


forma que nunca se pode garantir que um programa seja 100% livre de bugs,
nenhuma linguagem ou ambiente pode ter a garantia de ser 100% seguro.
Dentro desses limites, Java com certeza oferece um bom nível de segurança
para a maioria das aplicações práticas. Java antecipa e se defende contra a
maioria das técnicas que têm sido usadas para fazer com que software tenha
comportamento malicioso.

A segurança de Java foi intensamente testada por experts em segurança e


também por hackers. Desta forma, foi possível sanar alguns furos de segurança
encontrados nas primeiras versões de Java. É igualmente razoável esperar que
quaisquer furos que venham a ser descobertos no futuro sejam sanados com a
mesma rapidez.

1-17
A tecnologia java e configuração do ambiente

O Just in Time Compiler - JIT

• Traduzem bytecodes Java para instruções nativas da máquina.


• Evitam a reinterpretação de blocos de código Java.
• Úteis se o mesmo bloco de código é executado mais de uma vez.
• Otimizam a execução de blocos de código repetitivos, como laços
(for/while).

A JVM transforma os bytecodes Java em instruções nativa de máquina. Se


um bloco de código é executado diversas vezes, a JVM tem o trabalho
reinterpretar este mesmo bloco de código toda vez que o fluxo de execução o
atingir.

Mas porque interpretar um bloco de código que não muda e já foi uma vez
interpretado?

Uma nova técnica, conhecida como compilação sob demanda (Just-in-


Time Compilation ou JIT Compilation), foi então desenvolvida para contornar este
tipo de situação. Compiladores JIT, quando encontram um bloco de bytecodes
Java pela primeira vez, o traduzem de maneira definitiva para instruções nativas
de máquina.

Se o mesmo bloco é executado novamente, não há porque novamente


interpretá-lo: as instruções nativas de máquina resultado da compilação Just-in-
Time assumem o controle da execução, dispensando a necessidade de
interpretação. Compiladores JIT aumentam o desempenho de programas Java,
fazendo-os rodar mais rapidamente, uma vez que dispensam a necessidade de
traduções sucessivas e “inúteis” de um mesmo bloco de bytecodes Java.

Os ganhos de desempenho são mais significativos quando da otimização de


laços e funções recursivas, em que um mesmo bloco de código é
exaustivamente repetido.

1-18
A tecnologia java e configuração do ambiente

O Java Standard Development Kit – J2SDK

Abaixo temos uma figura que mostra toda a plataforma java de


desenvolvimento. Observe que existem muitas tecnologias presentes nesta
plataforma. Você não precisa saber todas elas para trabalhar com java.
Colocamos esta figura de forma que você possa ver algumas importantes, as
quais listamos abaixo:

• [ javac ] – Compilador Java, traduz programas Java para bytecodes


• [ java ] – Interpretador de bytecodes, para executar programas java
• [ appletviewer ] – Visualizador de applets, lê uma página HTML, carrega o
applet referenciado pela página e o executa, mostrando em sua própria
tela a entrada e saída do applet
• [ javadoc ] – Gerador de documentação, ferramenta que gera
automaticamente documentação em HTML a partir dos comentários do
código fonte Java
• [ jar ] – Empacotador de aplicações java, para a geração e manutenção
de archives (jar). O formato de arquivos JAR possibilita o agrupamento de
múltiplos arquivos em um único, que recebe o nome de Java Archive (ou
.jar). Desta forma, um único arquivo .jar tipicamente contém vários
binários Java (arquivos .class) e arquivos auxiliares de recursos utilizados
pelo applet ou application Java
• [ javap ] – Disassembler, ferramenta recebe como entrada um binário
Java (arquivo .class) e fornece como saída uma lista de comandos
assembly da máquina virtual Java referentes ao binário analisado, ou uma
lista de protótipos de classes encontradas no binário
• [ Swing ] – Framework para criação de interfaces dráficas
• [ Java Web Start ] – Tecnologia para distribuição de software
• [ JDBC ] – Framework para conexão com banco de dados

1-19
A tecnologia java e configuração do ambiente

Arquitetura da tecnologia java

Na figura abaixo você tem listadas as tecnologias que fazem parte da


versão 5.0 do Java. Estas tecnologias são parte do JRE (Java Runtime
Environment) e do JDK ( Java Development Kit).

Figura 1-5: Estrutura do JDK e JRE

1-20
A tecnologia java e configuração do ambiente

Figura 1-6: Relação dos grupos de tecnologias

1-21
A tecnologia java e configuração do ambiente

Configurando o ambiente

Para trabalhar com a linguagem java na sua máquina é preciso configurar


algumas coisas antes de escrever o promeiro programa. Estas configurações não
são necessárias na máquina do usuário que irá rodar a sua aplicação, sendo
configurações somente para a máquina do desenvolvedor.

Você precisará adicionar as seguintes variáveis de ambiente ao sistema


operacional que estiver utilizando:

1) JAVA_HOME - local onde foi instalado o java na sua máquina. Esta


variável é utilizada por programas para localizar a máquina virtual java
e o compilador

2) CLASSPATH – esta variável contém o caminho onde estão as


classes/bibliotecas java que o programa irá utilizar. Se você baixar
algum pacote jar da internet com classes que desejar utilizar, será
necessário colocar o caminho deste pacote nesta variável para que as
classes do mesmo sejam localizadas no momento da compilação

3) PATH – esta variável é importante estar configurada para poder rodar os


programas que estão localizados dentro do diretório bin da sua
instalação do java

Versão para windows:

JAVA_HOME=C:\jdk1.5
CLASSPATH=%JAVA_HOME%\lib\tools.jar
PATH=%JAVA_HOME%\BIN;%PATH%
Codigo 1-1: Configuração das variáveis de ambiente para windows

Versão para linux:

JAVA_HOME=/jdk1.5
CLASSPATH=$JAVA_HOME/lib/tools.jar
PATH=$JAVA_HOME/bin;$PATH
Codigo 1-2: Configuração das variáveis de ambiente para linux

1-22
A tecnologia java e configuração do ambiente

API’s da linguagem

Java possui um conjuntomuito grande classes para serem utilizadas no


desenvolvimento de palicações. Estas classes estão organizadas em pacotes que
fazem parte do JDK. Abaixo você tem alguns dos principais pacotes que serão
utilizados para o desenvolviemtno de aplicações. Para ter uma visão do todo
basta acessar a documentação do JDK através de um browser. Siga o seguinte
link para isto:

http://java.sun.com/j2se/1.5.0/docs/api/

java.applet Fornece as classes necessárias para a criação de um applet e


para a comunicação que posteriormente se estabelece entre
o applet e o contexto em que executa.
java.awt Contém todas as classes para a criação de interfaces gráficas
do usuário e controle de imagens.
java.beans Contém as classes relacionadas aos desenvolvimento de Java
Beans.
java.io Permite entrada e saída através de data streams, serialization e
sistema de arquivos.
java.lang Classes relacionadas ao próprio projeto da linguagem Java,
referindo-se à manipulação e conversão de tipos, strings,
threads e ambiente de execução.
java.math Classes utilizadas para aritmética de inteiros de precisão
arbitrária (BigInteger) e para a aritmética de decimais de
precisão arbitrária (BigDecimal)
java.net Classes contendo facilidades de comunicação em rede
(manipulação sockets, resolução de nomes, estabelecimento
de canais de comunicação).
java.security Classes e interfaces relacionadas às questões de segurança.
java.sql Classes que permitem interface com o banco de dados (JDBC)
java.text Classes e interfaces para manipulação de texto, datas,
números e mensagens.
java.util Miscelânea de classes auxiliares: tratamento do tempo, gerador
de números aleatórios, vetores de bits, internacionalização do
aplicativo, manipulação de archives Java, etc.
javax.swing Conjunto de componentes visuais “peso leve” que, com o
máximo grau de compatibilidade possível, funcionam da

1-23
A tecnologia java e configuração do ambiente

mesma maneira em todas as plataformas.


Tabela 1-1: Pacotes mais comuns da API J2SDK

1-24
A tecnologia java e configuração do ambiente

Espaço para anotações

1-25
Java Fundamentals

2. Tipos e Operadores

2-1
Tipos e operadores

Objetivos
• Compreender as seguintes estruturas:
o Variáveis
o Tipos Primitivos
o Literais
• Compreender os tipos de dados primitivos:
o char
o boolean
o byte, short, int, long
o float, double
• Compreender os tipos de dados compostos
• Apreender conversões de tipos
• Conceituar expressões e operadores
• Definir precedência e associatividade entre estruturas

2-2
Tipos e operadores

Variáveis

Java é uma linguagem fortemente tipada.

Isto significa que a utilização de qualquer variável deve ser compatível com
a sua prévia definição.

Compete ao compilador da linguagem verificar, antes mesmo da execução


do programa (isto é, de maneira estática), se há a referida compatibilidade entre
definição e uso de uma variável.

Os contextos em que uma variável pode ser validamente empregada são


determinados pelo seu tipo. O tipo determina a utilização: restringe as operações
aplicáveis e concede sentido ao resultado de uma operação válida (semântica
da operação). Seria contra-senso somarmos um tipo booleano com outro tipo
booleano ou, ainda pior, multiplicarmos um booleano por um número fracionário.

Qual a resposta esperada para estas operações?

Não há resposta, porque a operação frente aos tipos carece de coerência.


Ao definirmos um inteiro (ou um número real, ou um booleano, etc.), estamos a
dizer quais operações são válidas quando aplicadas a este inteiro (real,
booleano, etc.) e quais simplesmente carecem de sentido.

Decorrem do fato de Java ser fortemente tipada:

• Todas as variáveis em Java devem ter um tipo associado (a fim de que se


possa avaliar futuramente se os empregos dados a esta variável são ou
não corretos)
• A utilização de uma variável deve ser posterior à sua definição
• As conversões forçadas de tipo (casts) devem ser rigidamente controladas
pela linguagem, tanto sob o aspecto estático (não se permitindo
conversões entre tipos distintos, e autorizando tão somente a conversão
entre tipos derivados), quanto sob o aspecto dinâmico (controle em
tempo de execução das conversões de tipo efetuadas).
• Não se permitem conversões implícitas de tipo. Todas as conversões de
tipo devem ocorrer de maneira expressa.

2-3
Tipos e operadores

Tipos primitivos e tipos compostos

Tipos primitivos são aqueles tipos já embutidos na linguagem java e


estudados no capítulo anterior, enquanto tipos compostos são todas as classes
da linguagem java ou classes que você venha a criar.

Variáveis de tipos primitivos são tratadas de uma maneira bem diferente de


variáveis de referências para objetos ( variáveis de tipos compostos). É bastante
importante saber quais são estas diferenças e porque Java foi projetada desta
forma.

Variáveis de tipos primitivos

Quando você declara uma variável de um tipo primitivo, Java aloca um


pedaço de memória que é grande o suficiente para guardar valores do tipo
primitivo declarado. Se você define uma variável de tipo primitivo como uma
variável de instância, a variável é inicializada para 0 se for inteira, para false se
for booleana e para '\0' se for caractere.

Variáveis de tipos primitivos armazenam o seu valor diretamente. Se você,


por exemplo, declara uma variável do tipo int e atribui um valor 3 para esta
variável, o valor é armazenado diretamente nos quatro bytes reservados para a
variável.

Variáveis de referências para objetos (tipos compostos)

Quando você declara uma variável de referência, você também recebe


um pedaço de memória, mas esta memória é grande o suficiente para
armazenar apenas uma referência para o objeto. Talvez seja útil pensar em uma
referência como um apontador para o objeto.

Como mencionado antes, declarar uma variável de referência não cria um


objeto do tipo especificado. Conseqüentemente, uma variável de instância é
inicializada para null a fim de indicar que ainda não recebeu referência para
nenhum objeto ainda.

2-4
Tipos e operadores

Use o operador de atribuição para fazer com que uma variável de


referência se refira a uma instância de uma classe. A atribuição pode ser para
um objeto existente ou para um objeto recém criado através do operador new.

2-5
Tipos e operadores

Tipos Primitivos

Tipos primitivos são aqueles fornecidos diretamente pela linguagem, ou seja,


tipos já embutidos na linguagem. São tipos atômicos, indivisíveis e sobre os quais
a linguagem, de ante mão, já oferece operações pré-definidas. Exemplos de
tipos primitivos são inteiros e booleanos. Para usá-los, basta associá-los a uma
variável. A maior parte das linguagens permite ao programador criar tipos
compostos. Tipos compostos procuram representar de maneira conjunta e
unificada os vários atributos de uma entidade modelada. Um catálogo telefônico
pode ser representado dentro de um programa como uma lista de Strings
contendo nomes, outra contendo os endereços e, por fim, uma outra contendo
os telefones.

Todas as informações necessárias à pesquisa ou à manipulação do


catálogo estão presentes em tais listas. Mas esta definitivamente não é a melhor
maneira de se representar um catálogo telefônico. Se perguntarmos a alguém o
que é um catálogo, dificilmente ouviremos como resposta uma lista de nomes,
endereços e telefones.

A resposta mais provável é uma lista de assinantes. Um assinante, por sua


vez, é identificado pelo nome que possui ou endereço onde reside. Ao
procurarmos descobrir o telefone de um assinante, pesquisaremos pelas chaves
que o identificam: nome ou endereço. A representação estruturada da
informação é, em si, informação relevante. Ao modelarmos um catálogo como
uma lista de assinantes e identificarmos um assinante pelo seu nome ou
endereço, estamos trazendo para dentro do programa não apenas informação
contida em um catálogo, mas também a própria estrutura de um catálogo:
informação sobre como a informação é organizada.

Os tipos compostos não são apenas armazéns de dados. A sua função


principal não é conter o dado, pois para isto já temos tipos primitivos. O seu
objetivo principal é organizar a informação. Aproximar a estrutura empregada na
confecção do modelo da estrutura existente no mundo real modelado. Tornar a
tarefa de programar mais intuitiva, permitindo uma relação direta entre o modelo
e o objeto modelado. A representação do dado dentro do programa facilita a
compreensão do próprio programa como modelo. Não basta fazer um programa
(modelo) que funcione. É necessária tanto uma compreensão do mundo real
através do programa, quanto uma compreensão do próprio programa como
modelo do mundo real.

Tipos compostos são construídos tendo como blocos construtores tipos


primitivos ou, de maneira recursiva, outros tipos compostos. Em última análise, os
tipos primitivos são os únicos responsáveis pela construção dos tipos compostos.
Um tipo composto nada mais é que um conjunto organizado de tipos primitivos.
Tipos compostos serão vistos em capítulos posteriores.

2-6
Tipos e operadores

Java possui oito tipos primitivos: boolean, char, byte, short, int, long,
float e double. Cada um destes tipos será estudado de maneira mais
detalhada nas seções seguintes. A tabela abaixo apresenta um resumo dos tipos
primitivos encontrados na linguagem Java.

Cumpre observar que o tamanho do tipo em bytes e a sinalização fazem


parte da especificação da linguagem.

Ou seja, um char em Java ocupará sempre 16 bits, um int 32 bits, um long


64 bits, e assim por diante, independente da plataforma em que o programa
esteja sendo executado.

2-7
Tipos e operadores

Tipo Contém Default Tamanho Faixa de valores

boolean true ou false false 1 bit Pode assumir o valor true ou o


valor false

char caracter Unicode \u0000 16 bits Serve para a armazenagem de


dados alfanuméricos. Também
pode ser usado como um dado
inteiro com valores na faixa
entre:
0 e 65535 (\u0000 a \uFFFF)

byte inteiro com sinal 0 8 bits Inteiro de 8 bits em notação de


complemento de dois. Pode
assumir valores entre:
-27 a 27-1

short inteiro com sinal 0 16 bits Inteiro de 16 bits em notação


de complemento de dois. Os
valores possívels cobrem a
faixa entre:
-215 a 215-1

int inteiro com sinal 0 32 bits Inteiro de 32 bits em notação


de complemento de dois. Pode
assumir valores entre:
-231 a 231-1

long inteiro com sinal 0 64 bits Inteiro de 64 bits em notação


de complemento de dois. Pode
assumir valores entre:
-263 a 263-1

float ponto flutuante 0.0 32 bits Representa números em notação


de ponto flutuante. A sua
representação é exponencial,
sendo alguns bits utilizados
para base e outros para o
expoente.

double ponto flutuante 0.0 64 bits Representa números em notação


de ponto flutuante. A sua
representação é exponencial,
sendo alguns bits utilizados
para base e outros para o
expoente.

Tabela 2-1: Tipos primitivos e suas características.

Ao contrário do que acontece com outras linguagens de programação, as


características dos tipos de dados listados acima idependem da plataforma em
que o programa deverá ser executado.

Dessa forma, os tipos de dados primitivos são realmente únicos e garantem a


capacidade de intercâmbio de informações entre diversos tipos de

2-8
Tipos e operadores

computadores, aliviando o programador da preocupação e da árdua tarefa de


converter dados em formatos apropriados para a portagem.

2-9
Tipos e operadores

Declarando um tipo primitivo

São oito os tipos primitivos de Java: boolean, char, byte, short, int, long,
float e double. Quarenta e quatro são os operadores que podem ser utilizados
na manipulação destes tipos primitivos.

Declarar e inicializar uma variável de um tipo primitivo é extremamente fácil.


Basta, a exemplo da seguinte linha de código, associar ao identificador escolhido
pelo usuário para a variável um dos tipos acima, atribuindo, logo em seguida, um
literal para esta variável.

Código 2-1: Literais primitivos.

O que é uma literal?

Uma resposta simples seria: constantes para cada um dos tipos primitivos de
uma linguagem. Na verdade, literais são construções sintáticas fornecidas pela
linguagem para exprimir valores constantes para cada um dos tipos primitivos.
Tais construções determinam como devem ser escritos um caractere, um inteiro,
um valor booleano, etc.

A maior parte das linguagens permite que haja mais de uma forma de se
escrever um valor inteiro, ora transcrevendo o valor inteiro em uma base de
numeração Octal, ora transcrevendo-o em uma base Decimal, ora em uma base
Hexadecimal.

O mesmo ocorre para os números em ponto flutuantes: a maior parte das


linguagens permite que ora sejam escritos em notação convencional (parte
inteira + caractere separador + parte fracionária), ora em notação científica
(número fracionário + expoente em potência decimal).

Conjunto de Caracteres Unicode

2-10
Tipos e operadores

Os programas Java são escritos usando o conjunto de caracteres Unicode.


Ao contrário da codificação de 7 bits ASCII, que é útil somente para a língua
inglesa, e codificações de 8 bits, tais como ISO Latin-1, que é útil somente para a
maior parte das línguas da Europa Ocidental, a codificação Unicode, de 16 bits,
consegue representar praticamente todas as linguagens de uso freqüente no
planeta. Entretanto, poucos editores de texto suportam caracteres Unicode, e na
prática, a maior parte dos programas são escritos em ASCII.

Os caracteres Unicode, de 16 bits, são usualmente escritos em arquivos


usando uma codificação conhecida como UTF-8, que converte os caracteres de
16 bits em uma seqüência de bytes. O formato é projetado de forma que um
texto escrito em ASCII ou em ISO Latin-1 são seqüências de bytes UTF-8 válidas.

Desta forma, você pode simplesmente escrever programas em ASCII e eles


se comportarão, graças às características de projeto da codificação UTF-8, como
textos Unicode válidos.

Se você quiser inserir um caracter Unicode dentro de um programa Java


escrito em ASCII, basta usar a seqüência de escape especial para caracteres
Unicode \uxxxx. Isto é, uma contrabarra mais a letra u em minúscula seguida de
quatro caracteres hexadecimais.

A seqüência de escape \u0020 representa caracter de espaço, e a


seqüência \u3c00 é o caracter π. Você pode usar caracteres Unicode em
qualquer lugar em um programa Java, incluindo comentários e nome de
variáveis.

Podemos encontrar a tabela unicode no seguinte endereço:

http://www.unicode.org

2-11
Tipos e operadores

Tipo caracter – char

O tipo char representa caracteres Unicode. É motivo de surpresa para


experientes programadores descobrir que valores char em Java possuem 16 bits
de extensão.

Na prática, no entanto, isto fica completamente transparente.Um literal


caracter em Java sempre vem entre aspas simples.

char a = ′A′, espaco = ′ ′;

Código 2-2: Declarando um tipo char

Você pode, é claro, usar qualquer caracter Unicode como um literal


caracter, sendo necessário tão somente o emprego de seqüências de escape
\uxxxx Unicode.

Java suporta também várias outras seqüências de escape que facilitam a


representação de caracteres ASCII não visíveis, tais como nova linha ou
backspace, e permitem o escape na construção de literais contendo caracteres
de pontuação com significado especial, tais como aspas duplas ( “ ) e aspas
simples ( ‘ ).

Por exemplo:

char tab = ′\t′, apostrofe = ′\′′, nulo = ′′;


char unicode = ′\u05D0′;

Código 2-3: Exemplos de char especiais

2-12
Tipos e operadores

A tabela abaixo mostra os caracteres de escape que podem ser utilizados


na construção de um literal caracter.

Seqüência de Valor do caracter


escape

\b backspace

\t tabulação horizontal

\n nova linha

\f alimentação de formulário

\r retorno de carro

\” aspas duplas

\’ aspas simples

\\ contrabarra

\uxxxx Caracter Unicode com codificação xxxx, onde xxxx são quatro dígitos
hexadecimais.

Tabela 2-2: Caracteres de escape do tipo char

Estas seqüências de escape podem também ser usadas em literais para


Strings.

Valores do tipo char não podem ser convertidos para e a partir de tipos
inteiros. Ao contrário de byte, short, int e long, o tipo char é um tipo não
sinalizado (unsigned).

A classe Character vários métodos estáticos (static methods) para trabalhar


com caracteres, incluindo isLowerCase() e toUpperCase().

2-13
Tipos e operadores

Tipo booleano – boolean

O tipo boolean procura representar predicados. Há somente dois valores


possíveis para este tipo, representando os dois estados booleanos: ligado ou
desligado, sim ou não, verdadeiro ou falso.

Java reserva as palavras true e false para representar estes dois valores
booleanos.

Programadores C e C++ devem ter notar que Java é bem restritiva quanto à
utilização do tipo booleano: valores booleanos não podem ser diretamente
convertidos para outros tipos de dados, nem estes podem ser convertidos para
booleanos.

Em especial, o valor booleano não é um tipo inteiro (onde false é


representado por 0 e true por qualquer valor diferente de 0), e valores inteiros
não podem ser usados no lugar de um valor booleano.

Os exemplos abaixo mostram a utilização incorreta de valores booleanos. No


primeiro exemplo, um booleano é esperado e um inteiro é fornecido.

Errado Correto
int i = 10; int i = 10;
while(i) { while(i!=0) {
... ...
i--; i--;
} }

Tabela 2-3: Utilização do tipo boolean em C++ e Java

Não há (como ocorre com C e C++) conversão, implícita ou forçada, entre


booleanos e inteiros (ou outro tipo primitivo qualquer).

2-14
Tipos e operadores

Tipos inteiros - byte, short, int e long

Os tipos inteiros em Java são byte, short, int e long. Os tipos inteiros
diferem exclusivamente no número de bits usados para a representação do
número e, portanto, na extensão da faixa de números que conseguem
representar. Todos os tipos inteiros são sinalizados.

Não existe em Java nada equivalente à especificação unsigned de C ou


C++.

Literais inteiros para cada um destes tipos são construídos da maneira


esperada: uma seqüência de números decimais. Qualquer literal inteiro pode ser
precedido por um operador unário menos, a fim de indicar um número negativo.

Alguns literais inteiros:

0
1
-123
-4200

Código 2-4: Literais inteiros int

Literais inteiros podem ser expressos também em notação octal ou


hexadecimal. Um literal que começa com 0x ou 0X é interpretado como um
número hexadecimal, valendo-se das letras A a F (ou a a f) para os dígitos
restantes requeridos em números de base 16.

Literais inteiros que têm um dígito 0 em seu início são interpretados como
números octais (base 8) e, portanto, entre os dígitos subseqüentes não podem
figurar os algarismos 8 e 9. Java não permite que literais inteiros sejam expressos
em notação binária (base 2). Literais inteiros octais e decimais válidos:

0xff // decimal 255 expresso em hexa


0377 // mesmo número expresso em octa

Código 2-5: Literais inteiros int em hexa e octal

Literais inteiros exprimem valores de 32 bits, a menos que terminem com o


caracter L ou l, indicando, neste caso, serem tais literais valores de 64 bits.

1234 // um valor int


1234L // um valor long
0xffL // outro valor long

2-15
Tipos e operadores

Código 2-6: Literais inteiros int e long

A aritmética inteira em Java é modular. Não há transbordo ou estouro na


manipulação de números inteiros.

Ao contrário, ao exceder a faixa de um determinado tipo inteiro, em uma


das extremidades, o resultado da operação avança de maneira circular sobre a
extremidade oposta.

Por exemplo:

byte b1 = 127, b2 = 1;
byte sum = b1 + b2;
// sum = -128, que é o menor byte

Código 2-7: Exemplo de expressão byte com aritmética circular

Nem o compilador Java, nem a JVM avisará quando ocorrer transbordo em


aritmética inteira. Desta maneira, ao manipular inteiros, você deve ter certeza
que o tipo que está usando é grande o suficiente para os propósitos que tem em
mente. Que a faixa de números oferecida pelo tipo é larga o suficiente para as
operações pretendidas.

Divisões inteiras por zero e operações módulo zero não são aceitas e lançam
a exceção ArithmeticException.

Cada tipo inteiro tem uma classe invólucro correspondente (wrapper


classes): Byte, Short, Integer e Long. Cada uma dessas classes define as
constantes MIN_VALUE e MAX_VALUE que descrevem as extremidades da faixa de
números suportada pelo tipo.

Estas classes também definem vários métodos estáticos extremamente úteis,


tais como Byte.parseByte() e Integer.parseInt(), empregados na
conversão de strings para inteiros.

2-16
Tipos e operadores

Tipos de ponto flutuante - float e double

Números reais são representados em Java com os tipos float e double.


Como mostrado anteriormente, o tipo float representa um valor de ponto
flutuante, de 32 bits, de precisão simples, enquanto o tipo double representa um
valor de ponto flutuante de 64 bits, de precisão dupla. Ambos os tipos seguem o
padrão IEEE 754-1985, que especifica tanto o formato dos números quanto o
comportamento da aritmética com estes números.

Literais de ponto flutuante podem ser representados em Java como uma


seqüência opcional de dígitos decimais seguido por um ponto e outra seqüência
de dígitos decimais.

Alguns exemplos:

123.45f // float
0.0 // double
.02 // double

Código 2-8: Literais float e double

Literais de ponto flutuante também podem ser representados através de


notação científica, na qual um número é seguido pela letra e ou E (assinalando o
expoente) e por um outro número. Este segundo número representa a potência
de dez pela qual o primeiro número é multiplicado.

Por exemplo:

1.2345E02 // 1.2345 x 102 ou 123.45


1e-6 // 1 x 10-6 ou 0.000001
6.02e23 // No. de Avogrado 6.02 x 1023

Código 2-9: Literais float em notação científica

Números de ponto flutuante são double por default. Para representar um


literal de ponto flutuante float em um programa, basta acrescentar o caracter
f ou F no final do número.
double d = 6.02e23;
float f = 6.02e23f;

Código 2-10: Literais float e double em notação científica

Literais de ponto flutuante não podem ser representados em notação


hexadecimal ou octal. A maior parte dos números reais, pela sua natureza, não

2-17
Tipos e operadores

pode ser representada exatamente em um número finito de bits. Portanto, é


importante se lembrar que os valores double e float são apenas aproximações
dos números que eles procuram efetivamente representar.

Um float é uma aproximação de 32 bits, o que resulta em pelo menos 6


dígitos decimais significativos, e o double é uma aproximação de 64 bits, o que,
por sua vez, resulta em pelo menos 15 dígitos decimais significativos. Na prática,
estes tipos são suficientes para a maior parte das computações de ponto
flutuante.

2-18
Tipos e operadores

Tipo composto – String

Além dos tipos primitivos caracter, booleano, inteiro e real, Java também
possui um tipo de dado próprio para trabalhar com seqüências de texto (Strings).

O tipo String, no entanto, não é um tipo primitivo da linguagem. Devido


ao uso extremamente freqüente de Strings em um programa, Java permite uma
sintaxe especial para a representação de literais Strings. Um literal String consiste
de uma porção arbitrária de texto delimitada por aspas duplas.

Por exemplo:

"Hello, World"
"Isto é uma string \n"

Código 2-11: Literias Strings

Literais Strings podem conter qualquer uma das seqüências de escapes


permitidas para um valor char. Utilize a seqüência \" para incluir aspas duplas
dentro de um literal String. String e literais Strings serão discutidos com mais
detalhes a seguir.

2-19
Tipos e operadores

Conversões de tipos – casting

Java permite conversão entre valores inteiros e valores reais. Além disto, por
corresponder todo caracter a um número na codificação Unicode, o tipo char
pode ser convertido para e a partir de um tipo inteiro ou de ponto flutuante. De
fato, o tipo boolean é o único tipo que não pode ser convertido para ou a partir
de um outro tipo primitivo em Java. Há dois tipos de conversão.

Uma conversão de ampliação (widening conversion) ocorre quando um


valor de um tipo é convertido para outro tipo mais amplo  um tipo que possui
mais bits de representação e, portanto, uma faixa mais ampla de valores
representados.

Uma conversão de redução (narrowing conversion) ocorre quando um valor


é convertido para outro que possui menos bits de representação. Java
automaticamente se encarrega das conversões de ampliação quando, por
exemplo, uma variável double recebe um valor int, ou um literal char é
atribuído a uma variável int.

Conversões de redução são um problema a parte, e nem sempre são


seguras. É razoável, por exemplo, converter o valor inteiro (int) 13 para um byte,
mas não o é fazer a mesma conversão para o valor 13000, pois o tipo byte
suporta somente valores entre –128 e 127. A informação pode ser perdida em
conversões de redução e o compilador Java sempre reclama quando há uma
tentativa de efetuar uma conversão de redução, ainda que o valor sendo
convertido se situe entre os valores aceitos pela faixa mais estreita do novo tipo.

int i = 13;
byte b = i; // Erro em tempo de compilação.

Código 2-12: Atribuição de valor inteiro int não permitida para byte

A única exceção a esta regra é que você pode atribuir um literal inteiro
(portanto, um valor int) para uma variável byte e short, se o literal pertencer à
faixa de representação desta variável. Se você precisa de conversões de
redução e está confiante que o pode fazer sem perda de informação ou
precisão, você pode forçar Java a executar a conversão usando uma
construção da linguagem conhecida como cast (conversão explícita).

A conversão explícita é feita colocando-se o nome do tipo pretendido entre


parênteses antes da variável a ser convertida.

Por exemplo:

int i = 13;
byte b = (byte) i;
i = (int) 13.456;
2-20
Tipos e operadores

Código 2-13: Conversão forçada de tipos primitivos

Casting de tipos primitivos são mais freqüentes em conversões de números


reais para inteiros. Quando isto ocorre, a parte fracional do número real é
simplesmente truncada (o valor de ponto flutuante é arredondado para zero e
não para o inteiro mais próximo). Os métodos Math.round(), Math.floor() e
Math.ceil() permitem outros tipos de arredondamento.

short s = (short) 0xffff;


char c = '\uffff';

int i1 = s;
int i2 = c;

Código 2-14: Atribuição de valores implícitos e expressos

O tipo char funciona como um tipo inteiro a maior parte das vezes,
podendo, portanto, ser usado em qualquer lugar em que se espere um int ou
long.

Lembre-se, no entanto, que o tipo char é não sinalizado e se comporta de


maneira diferente do tipo short, embora ambos possuam 16 bits.

2-21
Tipos e operadores

Tipos de referência

Além dos oito tipos primitivos, Java define duas outras categorias de tipos:
classes e vetores. Programas em Java são construídos através de definições de
classes; cada classe define um novo tipo de dado que pode ser manipulado por
programas Java.

Um programa pode, por exemplo, definir uma classe chamada Ponto e usá-
la para armazenar coordenadas (x,y) de um sistema cartesiano de coordenadas.
Com isto, Ponto passa a ser um novo tipo de dado em um programa.

Um tipo vetor representa uma lista de valores de um determinado tipo. int é


um tipo de dado, e um vetor de valores do tipo int é outro tipo de dado, escrito
em Java como int[]. Um vetor de objetos do tipo Ponto também é um tipo,
escrito como Ponto[]. E um vetor de vetores de Ponto é também um outro tipo,
escrito em Java como Ponto[][].

Como você pode ver, há um número infinito de tipos de dados de classes e


vetores possíveis. Basta o programador defini-los. Estes tipos são conhecidos
coletivamente como tipos de referência.

A razão para este nome ficará clara logo a seguir. Por enquanto, é
importante compreender apenas que classes e vetores são tipos de dados
diferentes dos tipos primitivos. Tipos de dados de classes e vetores são conhecidos
como tipos compostos. Um tipo de dado primitivo guarda somente um único
valor.

Classes e vetores são tipos agregados que contém vários valores. O tipo
Ponto, por exemplo, armazena dois valores double representando as
coordenadas x e y do ponto. Classes e vetores serão retomados logo adiante.

int[] vet = {1,2,3};


String str = new String(“Java”);

Código 2-15: Tipos de referências: arrays e Strings

2-22
Tipos e operadores

Expressões e operadores
Até o presente momento, aprendemos sobre os tipos primitivos que os
programas Java são capazes de manipular e vimos como construir literais para
cada um destes tipos. Utilizamos também variáveis, nomes simbólicos que
representam e armazenam um valor.

Literais e variáveis são importantes tokens com os quais programas são


construídos em Java.

Expressões são o próximo nível de estrutura de um programa Java. O


interpretador Java (JVM) avalia uma expressão para determinar o seu resultado.
As expressões mais simples são chamadas expressões primárias e consistem de
apenas um literal ou uma variável.

São exemplos de expressões primárias:

1.7
'A'
true
sum

Código 2-16: Expressões primárias

Quando o interpretador Java avalia uma expressão literal, o resultado da


avaliação é o próprio literal. Quando o interpretador avalia uma expressão
contendo uma variável, o resultado da avaliação é o resultado armazenado
nesta variável.

Expressões primárias não são muito interessantes. Expressões mais complexas


são construídas utilizando operadores para combinar expressões primárias. Por
exemplo, a seguinte expressão usa o operador de atribuição ( = ) para combinar
duas expressões primárias – uma variável e um literal de ponto flutuante – em

sum = 1.7

uma expressão de atribuição.

Código 2-17: Expressão de atribuição

Mas operadores não são usados apenas com expressões primárias; eles
também podem ser usados com expressões de qualquer nível de complexidade.
As seguintes construções são, portanto, expressões válidas.
sum = 1 + 2 + 3 * 1.2 + (4 + 8) / 3.0;
sum = (sum / Math.sqrt(3.0 * 1.234) );
sum = (int) (sum + 33);

2-23
Tipos e operadores

Código 2-18: Expressões válidas e combinadas

2-24
Tipos e operadores

Sumário de operadores
Os tipos de expressões que você pode escrever em uma linguagem de
programação dependem completamente do conjunto de operadores que a
linguagem disponibiliza. A tabela abaixo apresenta síntese dos operadores
disponíveis em Java. A colunas P e A desta tabela indicam, respectivamente, a
precedência e a associatividade de cada grupo de operadores correlatos. Neste
curso veremos alguns operadores listados na tabela abaixo, os demais serão
vistos a medida do estudo da linguagem.

Operadores Tipo de Operação executada


operando

. objeto, membro acesso a membro do objeto

[] vetor, inteiro acesso a elemento do vetor

( args ) método, lista de invocação do método


args

++, -- variável pós incremento, pós decremento

++, -- variável pré incremento, pré decremento

+, - número mais unário, menos unário

~ inteiro complemento (NOT bit a bit)

! booleano NOT booleano

new classe, lista de args criação de objetos

(type) tipo, qualquer cast (conversão explícita de tipo)


coisa

*, /, % número, número multiplicação, divisão e resto

+, - número, número adição e subtração

+ string, qqr coisa concatenação de strings

<, <= número, número menor que, menor que ou igual

>, >= número, número maior que, maior que ou igual

instanceof referência, tipo comparação de tipos

== primitivo, primitivo igual (o mesmo valor)

!= primitivo, primitivo não igual (valores diferentes)

2-25
Tipos e operadores

== referência, igual (o mesmo objeto)


referência

!= referência, não igual (objetos diferentes)


referência

& inteiro, inteiro AND bit a bit

& booleano, AND booleano


booleano

^ inteiro, inteiro XOR bit a bit

^ booleano, XOR booleano


booleano

| inteiro, inteiro OR bit a bit

| booleano, OR booleano
booleano

&& booleano, AND booleano com curto circuito


booleano

|| booleano, OR booleano com curto circuito


booleano

? : booleano, qqr operador ternário condicional


coisa, qqr coisa

= variável, qqr coisa atribuição

*=, /=, %=, +=, variável, qqr coisa atribuição geminada com operação
-=, <<=, >>=,
>>>=, &=, ^=,
|=

Tabela 2-4:Sumário de Operadores

2-26
Tipos e operadores

Precedência

A precedência indica a ordem na qual as operações são executadas.

Considere a seguinte expressão:

a + b * c

Código 2-19: Expressão com precedência

O operador de multiplicação tem prioridade mais alta que o operador de


adição, logo a é somado ao resultado da multiplicação de b e c.

A precedência de um operador pode ser pensada como uma medida de


quão firmemente está ligado o operador aos seus operandos.

Quanto maior a precedência de um operador, mais firmemente ele está


ligado aos seus operandos.

A precedência default de um operador pode ser afastada através do uso


de parêntesis, de forma a explicitar a ordem exata das operações. A expressão
anterior pode ser rescrita como se segue, determinando explicitamente que a
adição deve ocorrer aqui antes da multiplicação.

(a + b) * c

Código 2-20: Expressão utilizando parêntesis

A precedência default dos operadores de Java foi escolhida procurando


torná-la compatível com C. Os projetistas de C, ao determinar a precedência
default de cada um dos operadores, tiveram em mente a maneira mais fácil e
direta de se escrever expressões sem a necessidade do uso de parêntesis.

Há apenas algumas expressões em Java em que se faz necessária a


presença de parêntesis.

2-27
Tipos e operadores

Associatividade
Quando uma expressão contém vários operadores que têm a mesma
precedência, a associatividade dos operadores indicarão a ordem na qual as
operações serão executadas. A maior parte dos operadores tem associatividade
da esquerda para direita, o que significa que as operações são feitas da
esquerda para a direita. Operadores unários e de atribuição possuem,
entretanto, associatividade da direita para esquerda.

Os operadores aditivos possuem associatividade da esquerda para direita,


sendo a expressão a+b-c avaliada da esquerda para direita: (a+b)-c.
Operadores unários e de atribuição são avaliados da direita para a esquerda.

Considere a seguinte expressão:

int a = 5;
int b = 4;
int c = a++ + b++

Código 2-21: Associatividade com incremento

O valor das variáveis seria:

a = 6
b = 5
c = 9

Código 2-22: Resultado depois da execução do código acima

A associatividade, assim como a precedência, estabelece a ordem default


de avaliação para uma expressão. Esta ordem default pode ser afetada com o
uso de parênteses. Entretanto, a escolha da precedência e da associatividade
conduz a uma sintaxe mais natural, e raramente você precisará alterá-la.

2-28
Tipos e operadores

Tipos de operadores: unários e binários


A quarta coluna da tabela acima especifica o número e o tipo dos
operandos esperados para cada operador. Alguns operadores atuam somente
sobre um único operando; são chamados operadores unários.

A maior parte dos operadores, no entanto, são operadores binários e atuam


sobre dois operandos.

Exemplo operador unário: i++ é igual a i = i + 1

Exemplo operador binário: res = x + y

2-29
Tipos e operadores

Espaço para anotações

2-30
Tipos e operadores

2-31
Java Fundamentals

3. Criando classes e objetos

3-1
Criando classes e objetos

Objetivos

• Definir classes, atributos e operações


• Trabalhar com pacotes
• Conhecer padrões para codificação
• Aplicar visibilidade a classes, atributos e operações
• Comentar os códigos fonte
• Gerar documentação do código fonte
• Estudar o escopo das variáveis

3-2
Criando classes e objetos

Classes e Objetos

Uma classe é um gabarito utilizado para criar múltiplos objetos. Uma classe
encapsula todos os dados e comportamentos que definem um objeto. Quando
você pede ao interpretador Java para criar ou instanciar um objeto, Java usa a
definição de classes como gabarito para a criação de novos objetos.

Uma classe pode conter atributos que são atribuídos a todos os novos
objetos desta classe. Atributos são informações ou dados que descrevem,
categorizam ou quantificam um objeto. Cada novo objeto de uma dada classe
terá seu próprio conjunto de atributos de classe. Um objeto da classe Cliente, por
exemplo, terá um nome, um endereço e um número telefônico como atributos.

Dados armazenados em um objeto podem ser tanto primitivos, tais como


inteiros ou caracteres, ou referências para outros objetos.

Uma classe pode conter também métodos ou funções que especifiquem o


comportamento ou ações que a classe consegue realizar. A classe Cliente, por
exemplo, pode alugar um carro, efetuar um pagamento ou alterar o seu
endereço.

Java utiliza pacotes para agrupar classes que estão logicamente


relacionadas. Pacotes consistem de classes fisicamente localizadas em um
mesmo diretório. Pacotes também são usados para controlar o acesso de classes
definidas fora do pacote.

3-3
Criando classes e objetos

Criando uma classe

O bloco contendo a definição de uma classe começa pela palavra


reservada class. No código abaixo temos a criação de uma classe para
reprensentar um Produto.

class Produto {

Código 3-1: Declaração da classe Produto

Uma classe – o bloco de construção básico para uma linguagem orientada


a objetos – é uma coleção de dados e de métodos que manipulam estes dados.
A classe é um modelo abstrato, a partir do qual serão criadas instâncias desta
classe, contendo os dados e os métodos nela definidos.

Observe que a definição da classe acima é muito simples e a mesma é feita


através da palavra reservada class. O par de chaves utilizado serve para
delimitar o corpo da classe, ou seja, onde iremos colocar as operações e
atributos da mesma. Veremos mais adiante como colocar os atributos e
operações

Os dados referentes a uma classe são armazenados em variáveis e as


operações efetuadas sobre estes dados são implementadas através de métodos.

Seguindo os passos contidos nesta lição você conseguirá criar e executar


sua primeira Java Application. Para isto vamos adicionar um método especial
nesta classe que nos permitirá executar a máquina virtual e executar algum
código.

Tomaremos como exemplo, para a primeira Java Application, o consagrado


“Hello World”. Este programa, bastante simples, imprime na tela do computador a
mensagem “Hello World”. Usando um editor de texto, crie um arquivo de nome
HelloWorld.java contendo as seguintes linhas.

class HelloWorld {

public static void main( String[] args ) {


System.out.println( "Hello World!" );
}

Código 3-2: Exemplo de HelloWorld Java

3-4
Criando classes e objetos

O arquivo fonte pode ser compilado através de seu IDE ou usando o


compilador javac através de linha de comando como mostrado abaixo

C:\> javac HelloWorld.java

Código 3-3: Compilando uma classe Java

Obtendo êxito na compilação, o arquivo HelloWorld.class, contendo os


bytecodes Java, será criado pelo compilador no mesmo diretório em que se
encontra o arquivo fonte.

Caso o compilador tenha reportado alguma falha, verifique se não digitou


algo incorretamente, prestando atenção para o uso de maiúsculas e minúsculas.
Certifique-se também de ter dado ao arquivo o nome correto. Execute a
aplicação Java usando a máquina virtual fornecida pelo JDK (java):

C:\>java HelloWorld
Hello World!

Código 3-4: Executando uma classe Java sem pacote

Você verá a mensagem “Hello World!” escrita na tela de seu computador.

3-5
Criando classes e objetos

Padrões

A linguagem java segue padrões para escrita de código especificados


pela Sun, empresa criadora da linguagem java. Desenvolvedores devem
procurar seguir estes padrões, uma vez que todo código de APIs que existem
seguem este padrão para facilitar o endendimento dos mesmos bem como a
leitura destes códigos.

O compilador Java (javac), bem como todas as demais ferramentas do JDK,


diferenciam maiúsculas de minúsculas. Este recurso é chamado de case-sensitive.
Portanto, bastante cuidado ao grafar o nome de classes, métodos, variáveis e
pacotes. Erros comuns decorrem muitas das vezes de problemas envolvendo
grafia, em que um nome, por exemplo, é definido em maiúsculas, e depois, logo
adiante, é utilizado em minúsculas. O compilador simplesmente alegará que tal
variável, método, classe ou pacote não foi definido. Abaixo são apresentados
alguns padrões:

Nomes de arquivos

Arquivos contendo os fontes Java terminam com a extensão .java. Todo


arquivo fonte Java deve ter no máximo uma única classe public. Pode haver
mais de uma classe por arquivo, porém somente uma pública. O nome do
arquivo fonte java é case-sensitive, ou seja, deve respeitar letras maiúsculas e
minúsculas, devendo também ter o mesmo nome da classe publica declarada
dentro do mesmo.

Exemplo: Produto.java

Nomes de classes

Escolha nomes significativos, de tal forma que a simples leitura do nome já


crie uma expectativa acerca do que se pode esperar da classe. Use para isto um
ou mais substantivos. O primeiro caractere de cada palavra utilizada para o
nome da classe deve ser maiúsculo. Quando o nome de uma classe tiver mais de
uma palavra as mesmas devem estar concatenadas.

Exemplo: NotaFiscal, Cliente, Item

3-6
Criando classes e objetos

Nomes de operações

Conselho similar pode ser dado à escolha dos nomes dos métodos: use
nomes significativos. Use verbos ou estruturas verbais que captem a ação
principal contida no método. O nome deve representar a real utilidade
desempenhada pelo método. A primeira letra do nome de uma operação deve
ser em minúscula e a primeira letra de todas as demais palavras que compõem o
nome da operação, caso existam, em maiúsculo.

Exemplo: getDescricao(), setPrecoFinal(…), imprimirRelatorio()

Nomes de atributos/variáveis

Use nomes curtos, mas que não sejam críticos. A primeira letra do nome da
variável deve ser em minúscula e a primeira letra de todas as demais palavras
presente no nome deve ser maiúscula. Evite usar nomes de variáveis com uma
única letra. O que você esperaria de uma variável de nome r ou x? O nome
deve ser representativo e a simples leitura do mesmo leva à compreensão do
papel desempenhado pela variável no aplicativo. A única exceção aceitável é
para contadores em laços, ou variáveis temporárias de uso restrito.

Exemplo: nome, descricao, precoFinal, idade, dataNascimento

Nomes de constantes

O nome de constantes é definido de uma forma um tanto diferente. Toda


constante tem o seu nome escrito sempre com todos os caracteres em
maiúsculo, e se o nome for composto por mais de uma palavra, as mesmas
devem ser separadas pelo caractere ‘_’. Veja abaixo um exemplo:

Exemplo: TAMANHO_MAXIMO_VETOR = 100, BORDA = 4

Caracteres especiais

Números, sublinhado e dólar podem ser usados em nomes. A única


restrição é que o nome deve sempre começar por um caractere que seja
diferente de um número. Mas evite o mau uso de tais caracteres especiais.

3-7
Criando classes e objetos

Nome de pacotes

Pacotes devem possuir nomes significativos e não devem ter seus nomes
separados por caracteres como “_”. Seus nomes devem ser escritos sempre com
todas as letras em minúsculo e os nomes de pacotes seprados pelo caractere
ponto (“.”). Veja o exemplo abaixo:

Exemplo: com.targettrust.database

Observe que o nome do pacote foi iniciado com a palvra “com” e em


seguida com a palavra targettrust. Normalmente começamos o nome de um
pacote com o inverso do domínio da empresa. Isto explica o porque utilizar
com.targettrust no início do nome do pacote.

3-8
Criando classes e objetos

Criando e importando pacotes

Java fornece o mecanismo de pacote como uma maneira de agrupar as


classes relacionadas.

A declaração de pacote, caso exista, deve estar no início do arquivo de


origem. Ela pode ser precedida apenas de espaços em branco e comentários.
Somente uma declaração de pacote é permitida e influencia todo o arquivo de
origem.

Os nomes de pacote são hierárquicos e separados por pontos.

Convencionalmente, os elementos do nome de pacote estão


normalmente em letras minúsculas. O nome de classe, entretanto, geralmente
começa com letra maiúscula e pode usar maiúsculas para separar as palavras
do nome.

Veja o exemplo a seguir:


package com.targettrust.java;

class HelloWorld {
public static void main( String[] args ) {
System.out.println( "Hello World!" );
}
}

Código 3-5: Definindo um pacote para uma classe

Compilando e executando:

C:\> javac –d . HelloWorld.java


C:\> java com.targettrust.java.HelloWorld
Hello World!

Código 3-6: Compilando um classe com pacote e realizando sua execução. A opção –d indica o
deretório de destino para o byte-code

package com.targettrut.java;

import java.sql.*;
import java.io.*;
import java.net.*;
import javax.swing.JFrame;

Código 3-7: Sintaxe para importar pacotes

3-9
Criando classes e objetos

3-10
Criando classes e objetos

Static import

Este recurso presente a partir da versão 1.5 do java é extremamente útil


para simplificar e facilitar a escrita de código. Permite utilizarmos os membros
(atributos ou métodos) static sem que tenhamos que prefixar os membros com o
nome da classe. Veja o exemplo abaixo:

System.out.println( Math.random() );
System.out.println( ( raio * raio) * Math.PI );

Código 3-8: Uso de operações static sem a importação dos membros static

Este código utilizando o static import ficaria assim:

import static java.lang.Math.*;


import static java.lang.System.*;
...
out.println( random() );
out.println( ( raio * raio) * PI );

Código 3-9: Importanto os membros static da classe Math

Veja que o código acima fica mais claro e fácil de ser escrito. Esta
facilidade de escrita do código deve ser utilizada mas com cuidado para não ser
abusada, pois pode prejudidar a legibilidade do código das suas aplicações.
Sempre que muitos membros static de uma classe forem ser utilizados é útil este
recurso, mas se somente um ou dois membros forem ser utilizados o uso deste
recurso não é aconselhável.

A importação dos membros static pode ser feito de forma genérica ou


específica como mostra o código abaixo:

1 import static java.lang.Math.PI;


2 import static java.awt.BorderLayout.*;
Código 3-10: Importando a estrutura estatica de uma classe

Com o static import da linha 2 é possível se utilizar todas as constantes da


classe BorderLayout. Isto evita termos que escrever portanto o nome da classe
cada vez que uma constante for utilizada.

3-11
Criando classes e objetos

Criando objetos

Neste momento do seu aprendizado iremos mostrar como pode ser criao
um objeto de uma determinada classe para que você possa aproveitar melhor
os passos seguintes do curso. Não iremos ainda aqui nos aprofundar neste
assunto, visto que a criação de objeto é algo que merece um tempo maior de
estudo. Veremos então como pode ser criado um objeto.

...

Produto prod;
prod = new Produto();

...

Código 3-11: Criando um objeto da classe Produto

Observe no código acima a criação de um objeto que será referenciado


por “prod”. Na primeira linha estamos fazendo a declaração de uma referência
para um futuro objeto da classe produto. Esta referência irá receber um objeto
que está sendo criado na linha segunte. A palavra reservada “new” é a
responsável por alocar memória e construir o objeto da classe Produto. Logo
depois de construído este objeto o endereço do mesmo ;e atribuído para a
referência prod.

Para criar um objeto precisamos da classe e no momento de criação do


mesmo veja que logo depois deste nome de classe colocamos os sinais de
parênteses.

Esta criação também pode ser feita em uma única linha de código para
simplificar o processo, como mostrado abaixo:

...

Produto prod = new Produto();

...

Código 3-12: Criand objeto da classe Produto em uma única linha

Agora em uma única linha você esta declarando uma referência para o
objeto, criando o objeto e atribuindo o endereço de memória do mesmo para
esta referência.

3-12
Criando classes e objetos

O que é a referência null?

Sempre que declararmos uma referência e esta não receber valor teremos
um valor null “dentro” da mesma. Desta forma é importante estudarmos um
pouco este valor null, pois muito erros em tempo de execução na linguagem java
ocorrem em função desta palavra.

Considere o seguinte comando:

Produto prod = null;

Código 3-13: Atribuindo null para a referência de Produto

Ele declara uma variável/referência prod e a inicializa com null. Isto indica
que a variável de referência prod não se refere a nenhum objeto ainda, em
outras palavras, ela somente foi delarada.

null é uma palavra reservada na linguagem Java. Você pode usá-la com o
operador de igualdade para verificar se uma variável de referência já recebeu
uma referência válida a um objeto ou mesmo para saber se duas referências

if( prod == null ) {


...
...
}

...

prod == prodRef

...

apontam para o mesmo objeto

Código 3-14: Teste entre uma referência e a palavra reservada null

Quando você tiver terminado de usar um objeto, você pode atribuir o valor
null para a variável que o referencia. Isto indica que a variável não mais se
refere ao objeto. Quando nenhuma referência houver para um objeto, o objeto
será marcado como inacessível e será considerado pelo coletor de lixo (garbage
collector). Iremos estudar com mais detalhes o coletor de lixo nos próximos
capítulos.

Na prática, somente em casos muito especiais você precisa atribuir null


para uma variável. A JVM automaticamente decrementa o número de
referências ativas para um objeto sempre que a variável que o referencia recebe

3-13
Criando classes e objetos

o valor null, sai de fora de escopo ou então recebe o valor de uma referência
para outro objeto.

3-14
Criando classes e objetos

Atribuindo referências
Como mencionamos antes, quando você declara uma variável de
referência para um objeto, esta variável tem o seu valor inicializado para null.
Antes que você possa usar esta variável (ou seja, acessar o objeto a que ela
referencia), você tem de inicializá-la. Todos os atributos de uma classe que forem
de tipos compostos (Classes) serão inicializados automaticamente pela JVM no
momento da criação de um objeto da classe para o valor null.

Quandor declaramos uma referência para um objeto e na mesma linha já


criamos este objeto, estamos inicializado a referência com um valor diferente de
null, ou seja, o endereço do objeto criado.V eja código abaixo:

Produto prod = new Produto();

Código 3-15: Criação do produto prod

Podemos também fazer atribuição de referência, para isto basta utiliza o


operador de atribuição. Neste caso as duas referências irão apontar para o
mesmo objeto. Veja o código abaixo:
Produto prod1 = prod2;

Código 3-16: Atribuição da referência prod1 para prod2

Esta sintaxe está perfeitamente correta, mas vale frisar que há apenas um
objeto Produto criado. Quando você atribui uma referência para um objeto
para outra referência, você termina com duas referências para o mesmo objeto,
e não uma cópia do objeto.

Você pode acessar o mesmo objeto através de ambas as referências,


entretanto, há apenas um único objeto. Este objeto é conhecido como objeto de
referência.

É importante se lembrar disto, principalmente ao passar uma referência para


uma operação . Isto será visto em maiores detalhes adiante.

Você pode alterar uma referência (mudando o valor da variável de


referência), sem que isto afete a outra referência (o valor da outra variável de
referência).

Caso você precise de um outro objeto, ao invés de ter múltiplas referências


para o mesmo objeto, você terá de criar este novo objeto.

3-15
Criando classes e objetos

Visibilidade aplicada a classes

Encapsulamento é uma das palavras-chave para a orientação a objetos.


Uma classe bem defina deve separar completamente a sua interface pública da
implementação interna. Neste momento do estudo iremos mostrar quais são so
tipos de encapsulamentos que uma classe pode receber. Certos de que a esta
altura dos estudos você já conhece os tipos de encapsulamentos existentes
vamos agora aplicar os mesmos para classe. A palavra visibilidade também pode
ser aplicada para este assunto, uma vez que de acordo com o modificador que
utilizaremos iremos tormar mais ou menos visível uma classe.

pacote/default

Esta visibilidade é a visibiliade padrão quando não se especifica nenhuma


visibilidade. Ao contrátio de ontras linguagen onde a visibilidade padrão é public
java define a visibilidade pacote/default. Se uma classe possuir esta visibilidade
isto significa que a mesma pode ser utilizada somente por outras classes que
estiverem dentro do mesmo pacote. Classes que estiverem em pacotes
diferentes do pacote onde esta classe estiver não “saberão” que a mesma existe.
No exemplo abaixo a classe email poderá ser utilizada somente por classes do
pacote com.targettrust.comunicacao e nenhum outro pacote mais

package com.targettrust.comunicacao;
class Email {
...
...
}

Código 3-17: Visibilidade de package aplicada a classes

3-16
Criando classes e objetos

public

Uma classe normalmente é declara como sendo pública, isto deixa a


classe visível, ou seja, capaz de ser utilizada por qualquer outra classe do sistema,
idependente do pacote onde a mesma se encontra. Recomenda-se que a
classe sempre seja pública a não ser que você queira restringir o acesso de uso a
esta classe. Quando em um arquivo fonte for delcarado mais de uma classe o
nome do arquivo fonte deve levar o mesmo nome da classe pública que foi
declarado neste. Lembre-se que não é possível declarar mais de uma clase
pública em cada arquivo fonte. Veja o exemplo abaixo de uma classe pública:

public class Email {


...
...
}

class Produto {
...
...
}

Código 3-18: Duas classes declaradas no mesmo arquivo fonte

3-17
Criando classes e objetos

Definindo operações

Quando você define uma classe é necessário definir também as operações


que farão parte desta classe. As operações são definidas dentro das chaves das
classes. Cada operação terá uma implementação específica que é conhecida
como método. Um método, portanto, é a implementação de uma operação.

Um método em Java é equivalente a uma função, procedimento ou sub-


rotina em outras linguagens, com a diferença que ele deve ser definido dentro
da declaração da classe. Em outras palavras, não existe em Java métodos
globais (completamente desvinculados de uma classe). Todo método deve ser
definido dentro de uma classe.

Anteriormente quando criamos uma classe, definimos uma operação


chamada “main”, agora iremos estudar a anatomia das operações bem como
definir outras operações em uma classe.

Anatomia de uma operação:

Abaixo temos uma operação definda dentro da classe Produto. Observe os


elementos que fazem parte desta operação:

public class Produto {


public void foo(String arg, int cod) {

}
}
Código 3-19: Declaração da operação

Os elementos integrantes de uma operação são:

• Modificador de acesso: utilizados para restringirem o acesso as operações.


Pode ser public, default, protected ou private.

public void foo(String arg, int cod) {

}
Código 3-20: Visibilidade da operação

3-18
Criando classes e objetos

• Tipo de retorno: pode ser um tipo primitivo (como int ou char – tipos
primitivos serão estudados mais adiante), ou um tipo composto (um objeto
String ou Produto, por exemplo), ou mesmo não haver retorno algum.
Neste caso deverá constar void como tipo de retorno. Em resumo: toda
operação em java deve indicar o tipo de retorno.

public void foo(String arg, int cod) {

public String getDescricao() {

}
Código 3-21: Tipo de retorno das operações

• Nome da operação: O nome da operação deve expressar o que a


operação faz. Normalmente este nome deve ser um verbo ou estar no
imperativo dando a idéia de uma ação.

public String getDescricao() {

}
Código 3-22: Nome da operação

Parâmetros: representam os valores que podemos passar para a operação.


Toda operação pode receber parâmetros, sendo assim a operação pode definir
logo após o seu nome um ou uma lista de parâmetros. Se o método recebe
vários parâmetros, cada um deles deve ser separado dos demais por vírgula.
Uma operação também pode não receber parâmetros. Havendo ou não
parâmetros, os mesmos devem ser representados entre parênteses.

public void foo(String arg, int cod) {

public String getDescricao() {

}
Código 3-23: Parâmetros da operação

3-19
Criando classes e objetos

• Corpo da operação: é o local onde fica a implementação da mesma.


Este código deve ficar entre chaves, que representam o início e fim do
bloco.

public void foo(String arg, int cod) {


System.out.println( arg );
System.out.println( cod );
}
Código 3-24: Corpo da operação. Impressão dos valores passados como parâmetros

3-20
Criando classes e objetos

Comando return
O comando return é utilziado na linguagem java para que as operações
possam retornar valores.

Um comando return aceita um valor simples ou uma expressão que deve


ser compatível com o tipo de retorno do método em que este comando está

public class Produto {

public String getDescricao() {


return "Nome: " + nome + " Preço: R$ " + preco;
}

public void setNome( String n ) {


nome = n;
return;
}

inserido.
Código 3-25: Comando return para retorno de método com ou sem valor de retorno.

Quando um comando de return é encontrado, o fluxo de execução


abandona o método imediatamente, ignorando quaisquer comandos que
existam entre o comando de return e o final do método (o fecha chaves do
corpo do método).

Esta alteração do fluxo de execução pode ser usada mesmo em métodos


que não retornam nenhum valor (métodos cujo tipo de retorno é void), com a
finalidade de abandonar o método imediatamente.

Neste caso especial, o comando return não aceitará nenhum valor de


retorno ou expressão (afinal o método é void).

3-21
Criando classes e objetos

Visibilidade para operações

Operações assim como os atributos e as classes podem ter visibilidades.


Para as operações são aplicados os seguintes tipos de visibilidades: private,
public, protected e pacote/default. O uso destas visibilidades é freqüente para
as operações uma vez que pode ser utilizada paa restringir o acesso a um objeto.

public

Esta visibilidade expõe as operações para serem chamadas de outros


objetos. Acima quando criamos as operações você pode observar que as
mesmas foram definidas como sendo públicas. Uma operação é definida
sempre, por padrão, como sendo pública, veja o código abaixo:

package com.targettrust.java;

public class Produto {

public void foo() {



}

Código 3-26: Visibilidade pública

A operação foo() mostrada acima pode ser chamada a partir de qualquer


outra classe independende do pacote onde se encontrar esta classe. Veja o
código abaixo onde criamos um objeto para poder chamar esta operação:

package com.targettrust.vendas;
import com.targettrust.java.*;

public class CriaObjetos {

public static void main(String[] args) {


Produto prod = new Produto();
prod.foo();
}

Código 3-27: Criando um objeto de uma classe e chamando a iperação pública

3-22
Criando classes e objetos

private

A visibilidade private restringe o acesso a esta operação ao próprio objeto


que a contém. Uma operação privada portanto não pode ser acessada por
outro objeto a não ser aquela que a definiu. Operações privadas não são
comuns e se existirei representam um serviço interno do próprio objeto.

Veja abaixo como pode ser definida uma operação privada:

public class Produto {

private void foo() {



}

Código 3-28: Visibilidade provada

Esta operação foo() é uma operação que pode ser chamada somente
através de outras operações da própria classe, veja abaixo:

public class Produto {

private void foo() {



}

public void salvar() {


foo();
}

Código 3-29: Chamando operação com visibiliade privada

3-23
Criando classes e objetos

O código abaixo não é permitido, pois veja que agora a operação foo() esta
definida com sendo privada.

public class CriaObjetos {

public static void main(String[] args) {

Produto prod = new Produto();

prod.foo();

Código 3-30: Chamada de operação privada fora da classe

protected

O uso desta visibilidade, assim como para os atributos está ligada ao uso de
herança, desta forma iremos deixar este tipo de visibilidade para ser
demonstrado mais adiante quando estudarmos o mecanismo de herança.

Uma operação protegida é uma operação visível para objetos de classes


que estão no mesmo pacote daquela classe que define a operação ou para
uma classe filha desta classe que contém a operação protegida.

package/defalut

Uma operação com visibilidade de pacote/default é uma operação que


tem comportamento de pública dento do pacote que a contém. Normalmente
uma operação não é definida como package, mas sim como public ou private.

Para definir uma operação com esa visibilidade não se deve especificar
nenhum modificador de acesso, isto a tornará default/package.

Veja abaixo na classe Produto como ficaria a operação com esta


visibilidade:

public class Produto {

void foo() {

}

Código 3-31: Visibilidade default

3-24
Criando classes e objetos

Definindo atributos

Os atributos de uma classe definem a estrutura dos objetos. Estes atributos


são declarados dentro do bloco que define a classe. Veja no exemplo abaixo a
declaração de alguns atributos para a classe produto:

public class Produto {


String descricao;
float preco;
boolean emVenda;
}

Código 3-32: Atributos da classe produto

Observe que na declaração acima os atributos ficam dentro do bloco de código


da claase. Estes atributos são atributos com visibilidade default. Logo
estudaremos os tipos de visibilidade que podem ser aplicadas aos atributos.

Estes atributos especificados acima são conhecidos como atrubutos de


intância e para serem acessados precisam que um objeto da classe que os
contém seja criado.

Um atributo pode ser inicializado com valores já no momento da sua


declaração como mostrado no código abaixo. Isto fará com que quando um
objeto da classe que o contém for criado o mesmo já possua um determinado
valor. O atributo emVenda irá possuir o valor true, neste caso, para todos os
objetos criados

public class Produto {


String descricao;
float preco;
boolean emVenda = true;
}

Código 3-33: Inicialização do atributo

3-25
Criando classes e objetos

Visibilidade aplicada a atributos

Os atributos são elementos que podem conter os quatro tipos de


visibilidades definidos pela orientação a objetos. Vejamos agora o significado
destes tipos de visibilidade aplicadas aos atributos.

public

Esta visibiliade irá tornar o acesso ao atributo disponível para qualquer


outra classe que estiver em qualquer outro pacote. Isto significa que objetos
gerados a partir desta classe que define o atributo público poderão ter os valores
destes atributos modificados por qualquer outro objeto. Este tipo de
encapsulamento/visibilidade não é o recomendado para atributos. Veja o
exemplo abaixo com os atributos para a classe Produto sendo públicos:

public class Produto {


public String descricao;
public float preco;
public boolean emVenda;
}

Código 3-34: Visibilidade pública para os atributos

Veja agora como seria o acesso a estes atributos em um pseudo-código.


Observe que a palavra “prod” é utilizada para referenciar um objeto da classe
Produto criado.

public class Consulta {

public static void main(String[] args) {


Produto prod = new Produto();
prod.descricao = “Notebook”;
prod.preco = 3500.0f;
prod.emVenda = false;
}

Código 3-35: Acessando atributos com visibiliade pública

3-26
Criando classes e objetos

3-27
Criando classes e objetos

private

A visibilidade provate é a visibilidade recomendada pela orientação a


objetos para os atributos de uma classe. Você estudou estes tipos de visibilidade
e a importância do uso das mesmas nos cursos anteriores, então vejamos agora
como ficaria esta visibilidade aplicada aos atributos.

public class Produto {


private String descricao;
private float preco;
private boolean emVenda;
}

Código 3-36: Definindo atributos provados

O acesso agora aos atributos não poderia mais ser feito de forma direta
como na visibilidade pública. Veja o código abaixo e observe que nas linhas
onde tentamos acessar os atributos teríamos um erro sendo reportado pelo
compilador dizendo que o atributo é privado e não pode ser acessado desta
forma.

public class Consulta {

public static void main(String[] args) {


Produto prod = new Produto();

prod.descricao = “Notebook”;
prod.preco = 3500.0f;
prod.emVenda = false;

Código 3-37: Acessando atributos privados

Como então deve ser feito o acesso a atributos privados. O acesso a


atributos com visibilidade privada deve ser feito através de operações públicas
que serão especificadas na classe. Já vimos como definir operações, então veja
agora como ficaria o acesso a estes atributos.

Primeiramente teríamos que modificar a classe Produto adicionando


métodos públicos para fazer este acesso. Veja abaixo:

3-28
Criando classes e objetos

public class Produto {


private String descricao;
private float preco;
private boolean emVenda;

public void setDescricao(String novaDescricao) {


descricao = novaDescricao;
}

public String getDescricao() {


return descricao;
}

...
}

Código 3-38: Definindo operações para acesso aos atributos

Logo depois de modificarmos a classe Produto podemos fazer acesso a


seus atributos através das operações. Veja abaixo:

public class Consulta {

public static void main(String[] args) {


Produto prod = new Produto();

prod.setDescricao(“Notebook”);
prod.setPreco(3500.0f);
prod.setEmVenda(false);

Código 3-39: Utilizando as operações para acesso aos atributos

3-29
Criando classes e objetos

package/default

Um atributo com visibilidade de pacote/default é um atributo que pode ser


acessado diretamente como se fosse público por classes que estiverem no memo
pacote. Para definir um atributo com esta visibilidade não é necessário colocar
modificador antes do mesmo. Este é o modificador padrão para visibilidades
quando as mesmas não forem definidas.

Abaixo veja a nova versão da classe Produto:

package com.targettrust.java;

public class Produto {


String descricao;
float preco;
boolean emVenda;
}

Código 3-40: Visibilidade default

Observe que a classe acima está dentro de um pacote chamado


com.targettrust.java e que a mesma será utilizada por outra classe que está no
mesmo pacote.

package com.targettrust.java;

public class Consulta {

public static void main(String[] args) {


Produto prod = new Produto();

prod.descricao = “Notebook”;
prod.preco = 3500.0f;
prod.emVenda = false;

Código 3-41: Acesso a visibilidade defalut

3-30
Criando classes e objetos

Agora se movermos a classe Consulta para outro pacote e fizermos a


importação do pacote com.targettrust.java para tentar acessar os atributos nõa
teremos sucesso.

package com.targettrust.exemplo;

import com.targettrust.java.*;

public class Consulta {

public static void main(String[] args) {


Produto prod = new Produto();

prod.descricao = “Notebook”;
prod.preco = 3500.0f;
prod.emVenda = false;

Código 3-42: Acessando atributo default fora do pacote

O código que temos para acessar os atributos é o mesmo que temos


quando a classe é pública, mas agora como a classe Consulta está em pacote
diferente, e os atributos de Produto tem a visibilidade de pacote/default o acesso
não pode mais ser feito de forma direta. Neste caso se tivéssemos interesse em
acessar os atributos a partir de outra classe fora do pacote teríamos que fornecer
métodos públicos para isto.

protected

Esta visibiliade para ser entendida necessita o estudo do mecanismo de


herança entre classes. Vamos deixar para mais tarde quando mostrarmos aqui
este mecanismo para então dar exemplos de códigos com protected.

Um atributo protegido é um atributo que terá visibilidade de pacote, mas


também irá permitir o acesso direto a ele para classes que estiverem em pacotes
diferentes daquele pacote onde está a classe que o define, desde que a classe
fora do pacote seja filha da que contém o atributo.

3-31
Criando classes e objetos

Acessando atributos

O acesso a atributos de uma classe deve ser feito somente por operações
definidas na mesma classe que contém este atributo. Desta forma devemos
tornar os atributos de uma classe por padrão privados e as operações que irão
acessá-los públicas.

Cada atributo irá possuir um par de métodos que chamamos de métodos


de leitura e escrita ou getter e setter. Veja abaixo como ficaria a classe produto:

public class Produto {

private int codigo;


private String descricao;
private float preco;

public void setCodigo(int novoCodigo) {


codigo = novoCodigo;
}

public int getCodigo() {


return codigo;
}

public void setDescricao(String novaDescricao) {


descricao = novaDescricao;
}

public String getDescricao() {


return descricao;
}

public void setPreco(float novoPreco) {


preco = novoPreco;
}

public float getPreco() {


return preco;
}

Código 3-43: Definição da classe Produto

3-32
Criando classes e objetos

Comentários no código fonte

Os comentários de código fonte estão presentem em todas as linguagens


de programação, e como java não poderia ficar de fora o mesmo possui 3 tipos
de comentários. Os comentários são importantes pois são um mecanismo para o
programador explicar dentro do fonte com as suas próprias palavras o que
significa ou faz os elementos que o mesmo está escrevendo.

Java possui um tipo de cometário que pode ser utilizado por um gerador de
documentação a fim do mesmo criar um conjunto de páginas HTML com
informações sobre a classe. O programador interessado em utilizar aquela clase
não precise olhar o fonte da mesma para saber do que ele dispõe, mas sim basta
olhar em qualquer browser esta documentação em formato HTML.

Na linguagem java existem os seguintes tipos de comentários:

1 – Comentário de linha

2 – Comentário de múltiplas linhas

3 – Comentário de documentação

Comentário de linha

O comentário de linha é um comentário bastante prático e muito utilizado.


Seve para podermos colocar em qualquer ponto do código uma
documentação. No entanto esta documentação é feita somente em uma linha,
não podendo haver múltiplas linhas como no anterior. Veja o exemplo abaixo:

public class Produto {

public float calcularPreco() {


// para calcular o preço deve ser descontado o desconto
preco = preco – desconto;
...
}
}

Código 3-44: Comentário de linha

3-33
Criando classes e objetos

Comentário de múltiplas linhas

O comentário de múltiplas linhas pode ser utilizado em qualquer ponto do


código. Este tipo de comentário não fará parte da documentação gerado pelo
javadoc. O programador pode escrever mais de uma linha documentando o
código. Os caracteres /* e */ representam o início e fim deste tipo de comentário.

public class Produto {

public void imprimir() {


/* Mostra os dados do produto.
Estes dados mostrados são: nome, descrição e preço
*/
System.out.println(“Dados do produto:”);
...
}
}

Código 3-45: Comentário de múltiplas linhas

Comentário de documentação

Este tipo de cometário deve ser colocado antes do elemento que se quer
documentar, ou seja antes da definição de uma classe ou operação. As
marcações /** e */ indicam o início e fim de um comentário de documentação.
Todo o conteúdo colocado dentro destas marcações será capturado pelo
utilitário javadoc para montar a documentação da classe em formato HTML.
/**
Classe para representar um produto da loja.
Um produto deve conter sempre um código e uma descrição
Criada dia 05-05-2005
*/
public class Produto {

/**
Método para mostrar os dados de um produto
*/
public void imprimir() {

}
}

Código 3-46: Comentário de documentação para classe e operação

3-34
Criando classes e objetos

Os comentários de documentação podem ter algumas marcações


especiais para serem capturadas pelo javadoc e adicionadas na
documentação HTML da classe. Estas marcação são as seguintes:

@return Representa o tipo de retorno de uma operação. Pode conter texto


para explicar o que é o tipo de retorno
@param Representa o parâmetro de uma operação. Deve conter uma
descrição do que significa este parâmetro.
Tabela 3-1: macros para documentação

Veja código de exemplo abaixo:

public class Produto {

/**
Calcula o custo de um produto.
@param arg um argumento a ser utilizado para calcular o custo
@param cod código do produto a ser utilizado no calculo
@return float indicando o custo do produto
*/
public float calcularCusto(String arg, int cod) {

}

Código 3-47: Comentário de documentação para a classe Produto

3-35
Criando classes e objetos

Escopo das variáveis

Este é um assunto muitas vezes não abordado em materiais sobre java,


porém de extrema importância. Nesta linguagem as variáveis quando forem
declaradas irão estar visíveis dentro de um determinado escopo de código. Este
escopo é delimitado por chaves que representam blocos de códigos. Isto
significa que uma variável declarada dentro de um bloco de código é visível
somente aquele bloco de código bem como a outros blocos que estiverem
dentro deste.

Normalmente existe um bloco de código delimitado por chaves para


representar o corpo de código de uma classe (veja na figura abaixo o escopo de
classe) e de uma operação (veja abaixo escopo de método). Isto significa que
se declararmos uma variável dentro do bloco da classe a mesma será visível a
códigos que se encontrarem dentro daquele escopo de classe (não estamos
aqui levando em consideração os tipos de encapsulamento). Dentro de um
corpo de código para classes podemos ter operações definidas e estas
operações possuem também seus blocos de códigos (escopo de método). Isto
significa que se dentro do bloco de código de uma classe definirmos uma
variável, esta variável poderá ser acessada normalmente dentro de qualquer
bloco de código de uma operação que foi definido dentro do bloco da classe.
Isto tudo pode ser resumido da seguinte maneira: uma operação pode chamar
qualquer outra operação da classe ou mesmo acessar uma variável desta classe.

Podemos também definir dentro do escopo de um método outros blocos de


código. Isto pode ser feito através do uso de chaves. Não é normal definirmos
blocos de código desta maneira, mas esta mesma idéia pode ser utilizada para
os comandos que possuem blocos de códigos como o “if”, “for”, “while”, etc...

Veja abaixo na figura a representação dos escopos.

Figura 3-1: Escopos de implementação.

3-36
Criando classes e objetos

public class Produto {

private float preco;

public void setPreco(float novoPreco) {


preco = novoPreco;
}

Código 3-48: Acessando a variável preco

Observe no código acima o escopo onde foi definido o preço. Esta


variável (podemos também chamar de atributo) foi definido dentro do escopo
da classe, ou seja, dentro das chaves da classe. Pode, portanto, ser acessado por
qualquer operação que estiver também definda dentro do escopo de classe,
como a operação setPreco(...).

Veja agora o código abaixo ao qual adicionamos mais variáveis e outro


escopo.

public class Produto {

private float preco;

public void setPreco(float novoPreco) {


preco = novoPreco;
x = 34; // Erro
}

public void foo(int y) {


int x = 10;
if ( x > y) {
int res = x + y;
}
System.out.println( res ); // Erro
}

Código 3-49: Acessando variáveis fora de escopo

No código acima temos a declaraçõ de uma variável inteira x dentro do


bloco de código da operação foo(...). Isto significa que esta variável somente
poderá ser acessada por outros códigos que estiverem dentro desta mesma
operação. Este tipo de variável é conhecida como variável local. Observe que
na linha onde é atribuído o valor 34 ao x teremos um erro.

3-37
Criando classes e objetos

Neste mesmo código mostrado acima podemos observar que foi


declarado um comando condicional if dentro da operação e que dentro deste
comando declaramos uma variável res. Esta variável é uma variável local ao
comando condicional, ou seja, é válida somente dentro deste comando, não
podendo ser acessada fora do mesmo. Veja a linha onde tentamos imprimir o
conteúdo da variável res, nesta linha teremos um erro de acesso.

Podemos definir “x” e “res” como sendo variáveis locais, enquanto preço
pode ser definido como sendo uma variável de instância. Par6ametros de
métodos também são considerados como sendo variáveis locais.

3-38
Criando classes e objetos

Passando Tipos Primitivos para Métodos

Quando um valor de tipo primitivo (int, char, etc.) é passado para o


método, uma cópia deste valor preencherá o respectivo parâmetro do método.
Se o método alterar o valor de seus parâmetros, esta alteração terá um efeito
local: alterará apenas a cópia (o parâmetro formal ou argumento), mas o valor
original permanecerá o mesmo.

Quando o método termina, todos os seus parâmetros são descartados e as


variáveis originais presentes na chamada permanecem inalteradas.

O exemplo abaixo ilustra a maneira com que tipos primitivos são passados
por valor para os métodos.
int preco = 100;
obj.foo(preco); preco
100
System.out.println("preço = " + preco);

Código 5-20: Escopo de execução da variável preco.

public void foo(int arg) {


arg
if(arg > 0 && arg < 2000)
100
arg = 55;
System.out.println("arg = " + arg);
}

Código 3-50: Escopo de execução da variável arg

O código à esquerda declara uma variável int chamada preco e atribui o


valor 150 a esta variável. Quando preco é passada para foo(), uma cópia de
seu valor corrente preencherá o parâmetro arg, que terá o valor inicial de 100.

Dentro de foo(), arg recebe o valor 55. Quando foo() termina, o


parâmetro arg é descartado e o fluxo de execução retorna ao chamador.

A variável preco, definida no contexto do chamador, não foi afetada e tem


ainda o valor de 100.

A saída do programa será, portanto, a seguinte:

arg = 55
preco = 100

3-39
Criando classes e objetos

Passando Referências para Métodos

Quando você passa um objeto para um método, a referência para o objeto


original é passada como argumento, e não uma cópia do objeto. Qualquer
alteração que o método fizer valendo-se da referência recebida, alterará o
objeto original. Quando o método terminar, todas as alterações feitas pelo
método no objeto permanecerão.

O exemplo abaixo ilustra a maneira pela qual referências para objetos são
passadas para métodos.

O código à esquerda cria um objeto Produto e armazena a referência para


este objeto na variável p1. Neste exato instante, o título de p1 é "DVD” e o código
é "1".

Quando p1 é passada para foo(), o método recebe a referência para o


objeto Produto original. Dentro de foo(), através da variável de referência ref,
o nome e o código são alterados. Quando foo() termina, o objeto Produto
original referenciado por prod tem o título "CD" e o código "2".

Produto prod = new Produto( “CD” ); nome:"CD"


prod codigo: 2
prod.setCodigo( 1 );
obj.foo( prod );

Código 3-51: Escopo de execução do objeto prod

public void food(Produto ref) {


ref
ref.setCodigo( 2 );
ref.setNome( "CD" );
}
Código 3-52: Escopo de execução do objeto prod pela referência
ref.

3-40
Criando classes e objetos

Espaço para anotações

3-41
Criando classes e objetos

Exercícios

1. Neste exercício você irá criar uma classe para representar um Produto com
a estrutura (atributos) descrita abaixo. Esta classe estará associada com
outra classe, a ItemPedido. Item pedido possui o produto e a sua
quantidade. No final você irá criar uma classe com método main para
testar sua aplicação. Siga os passos abaixo para realizar o exercício.

Passo 1: Crie uma classe chamada Produto dentro de seu projeto. Esta
classe deverá pertencer ao pacote com.targettrust.java.

Passo 2: Defina nesta classe os seguintes atributos privados:

private String codigo


private String descricao
private float preco
private boolean emVenda
private float desconto

Passo 3: Crie operações públicas para acessar estes atributos permitindo


que os mesmos possam ser lidos e modificados.

Passo 4: Documente o código escrito por você. Use comentário de


documentação para mostrar o que as operações realizam bem como seus
parâmetros e tipo de retorno.

Passo 5: Crie uma outra classe pública com o nome ItemPedido. Esta
classe deve pertencer ao pacote com.targettrust.venda.

Passo 6: Para esta classe declare os seguintes atributos privados:

private int quantidade


private Produto produto

Passo 7: Crie operações públicas para acessar estes atributos de forma


que seja possível modificar e ler os seus valores.

Passo 8: Crie uma nova classe chamada Aplicação no pacote


com.targettrust.java. Dentro desta classe declare o método main mostrado
anteriormente na apostila. O método main é aquele que permite executar a
classe.

3-42
Criando classes e objetos

Passo 9: Dentro do método main crie objetos da classe ItemPedido e


Produto e em seguinda associe estes objetos. Lembre-se que um ItemPedido
possui uma referência para um Produto. Defina valores para os outros atributos
dos objetos.

Passo 10: Crie na classe Aplicacao uma operação que seja capaz de
receber como parâmetro um ItemPedido e faça a impressão das informações do
objeto que esta operação receber.

Passo 11: Gere a documentação utilizando o javadoc para as classes


criadas no exercício.

3-43
Java Fundamentals

4. Comandos da Linguagem

4-1
Comandos da Linguagem

Objetivos

• Conhecer os comandos da linguagem java


• Compeender os comandos e saber o contexto no qual podem ser
utilizados
• São estudados os seguintes comandos:
• switch
• while
• do
• for
• for-each
• break
• continue
• if
• else if
• Operador ternário

4-2
Comandos da Linguagem

Comandos

Os comandos da linguagem são comandos comuns a maioria das


linguagens de programação. O objetivo deste capítulo é adaptar o aluno a
sintaxe destes comandos na linguagem java. Para isto abxaixo aprensentamos os
comandos:: switch, while, do, for, for-each, break, continue,
return, if e else if.

4-3
Comandos da Linguagem

Comando if / else / else if

O comando if é o principal comando de controle de fluxo. Ele possibilita


ao programador Java tomar decisões ou, mais precisamente, executar um
comando somente quando uma determinada condição for verdadeira.

O comando if sempre vem sempre acompanhado de uma expressão


(que é a condição que pende sobre a execução do comando) e um comando
(que somente será executado se a expressão anterior for avaliada para
verdadeiro). Se a expressão for verdadeira o comando é executado. Se a
expressão for falsa, o comando é saltado.

Por exemplo:

if(usuario == null) {
usuario = "admin";
}

Código 4-1: Comando if simples.

Embora pareça um pouco estranho, os parêntesis que envolvem a


expressão são exigidos pela sintaxe do comando. Como já foi mencionado, um
bloco de comandos delimitados por chave (comando composto) comporta-se
como um comando simples, e pode ser sempre colocado onde um comando
simples é esperado. Desta forma, o comando if também pode ser escrito assim:

if( (endereco == null) || (endereco.equals("") ) ) {


endereco = "[desc.]";
System.out.println("Endereço não encontrado");
}

Código 4-2: Comando if com expressão lógica e bloco de comandos.

Um comando if pode opcionalmente incluir a palavra reservada else


seguida de um comando (simples ou composto). Neste caso, a expressão
condicional é avaliada e, se for verdadeira, o comando que segue a palavra
reservada if é executado e o comando que segue a palavra else é pulado.

Se a expressão for falsa o contrário ocorrerá: o comando que segue a


palavra reservada if é pulado e o comando que segue a palavra reservada
else é executado.

4-4
Comandos da Linguagem

if(usuario != null) {
System.out.println("Olá! " + usuario);
}else {
usuario = getNomeUsuario();
System.out.println("Olá! " + usuario + "Esta é sua primeira sessão" );
}

Código 4-3: Comando if/else simples.

Quando há vários comandos if/else aninhados é necessário alguma


precaução para garantir que a cláusula else se refere ao comando if correto.

Considere as seguintes linhas:

if(i == j)
if(j == k)
System.out.println("i é igual a k");
else
System.out.println("i não é igual a j"); // ERRADO

Código 4-4: Comando if mal identado.

Infelizmente não está claro com qual if a cláusula else faz par. A
identação sugere ser o primeiro if o par da cláusula else. E sugere
erradamente, levando o programador a uma situação de erro.

A regra para o casamento dos pares if/else é bem simples. A cláusula


else sempre faz par com o comando if mais próximo. Corrigindo a identação,
para que represente corretamente os pares if/else, teremos:

if(i == j)
if(j == k)
System.out.println("i é igual a k");
else
System.out.println("i não é igual a j"); // ERRADO

Código 4-5: Comando if mal identado.

4-5
Comandos da Linguagem

Muito cuidado deve ser tomado, pois não há erro de sintaxe nos dois
exemplos acima. São construções é perfeitamente legal. Só não ocorre o que o
programador deseja. Ao trabalhar com comandos ifs aninhados use chaves a
fim de tornar o código mais legível.

if(i == j) {
if(j == k) {
System.out.println("i é igual a k");
}
}else {
System.out.println("i não é igual a j"); // CORRETO
}

Código 4-6: Comando if identado corretamente.

4-6
Comandos da Linguagem

Seqüência de Cláusulas else if


O comando if/else é útil para testar uma condição e escolher qual
comando ou bloco de comandos executar. Mas, e se devemos escolher não um
bloco entre dois, mas sim um entre vários? Para resolver este tipo de problema é
comum usarmos uma seqüência de cláusulas else if.

Cláusulas else if não são uma novo comando da linguagem Java, ou


mesmo uma sintaxe alternativa de um comando existente. Estas cláusulas são
apenas o emprego inteligente do conhecido comando if/else associado a
uma identação que facilite o entendimento.

Veja o exemplo abaixo:

if(n == 1) {
// executa bloco de código 1
}else if(n == 2) {
// executa bloco de código 2
}else if(n == 3) {
// executa bloco de código 3
}else {
// se todas as condições acima falham, então
// executa bloco de código 4
}

Código 4-7: Comando if em sequência identado a esquerda.

Não há nada de especial no código acima. É apenas uma seqüência de


comandos ifs, onde cada if é parte da cláusula else do comando anterior.
No entanto, escrito desta forma o código fica bem mais legível e claro do que se
fosse escrito com sucessivos aninhamentos (basta dar uma olhada no exemplo

if(n == 1) {
// executa bloco de código 1
}
else {
if(n == 2) {
// executa bloco de código 2
}
else {
if(n == 3) {
// executa bloco de código 3
}
else {
// se todas as condições acima falham, então
// executa bloco de código 4
}
}
}

abaixo).

4-7
Comandos da Linguagem

Código 4-8: Comando if em sequência identado em relação a blocos de código.

4-8
Comandos da Linguagem

Operador ternário

Java também define um operador ternário, chamado freqüentemente de


operador condicional. Funciona como um comando if dentro de uma
expressão. Seus três operandos são separados por um ponto de interrogação ‘?’
e por dois pontos ‘:’. O segundo e o terceiro operando devem ser do mesmo
tipo.

Veja o código abaixo:


int x, y;
...
String res = “”;
if ( x > y ) {
res = “X é maior que Y”;
}else {
res = “Y é maior que X”;
}
...
System.out.println( res );

Código 4-9: Comando if else a ser transformado em operador ternário

Agora vamos ver o mesmo código fazendo usto do operador ternário:

int x, y;
...
String res = (x > y)?“X é maior que Y”:“Y é maior que X”;
...
System.out.println( res );
Código 4-10: Expressão com operador ternário

Observe que o código é bem mais enxuto para este caso de if..else

4-9
Comandos da Linguagem

Comando switch
O comando if provoca um desvio no fluxo de execução de um programa.
Você pode usar múltiplos comandos ifs, como mostrado na seção anterior, para
executar desvios múltiplos de execução. Entretanto, esta nem sempre é a melhor
solução especialmente quando todos os desvios dependem do valor de uma
única variável.

Neste caso é ineficiente conferir repetidas vezes o valor de uma mesma


variável em múltiplos comandos ifs.

Uma solução melhor é usar o comando switch. Embora a sintaxe deste


comando não possua a mesma elegância apresentada por outros comandos da
linguagem, a praticidade deste tipo de construção o torna extremamente útil. Se
você não está familiar com este comando, talvez já conheça, no entanto, a
idéia que está por detrás dele.

O comando switch possui uma expressão inteira e um corpo que contém


vários pontos de entrada numerados. A expressão é avaliada e o fluxo de
execução salta para o ponto de entrada especificado com o valor avaliado. O
comando switch seguinte é equivalente ao uso repetido de comandos if e

switch(n) {
case 1:
// começa aqui se n == 1
// executa bloco de código 1
break; // para aqui

case 2:
// começa aqui se n == 2
// executa bloco de código 2
break; // para aqui

case 3:
case 4:
// começa aqui se n == 3
// executa bloco de código 3
break; // para aqui

default:
// se todas as condições acima falham, então
// executa bloco de código 4
break; // para aqui
}

else/if, como mostrado na seção anterior.


Código 4-11: Comando switch padrão de inteiros utilizando break

Como você pode ver a partir deste exemplo, os vários pontos de entrada
do comando switch são rotulados ou com a palavra reservada case seguida de
um valor inteiro e dois pontos, ou então da palavra reservada default seguida
de dois pontos.
4-10
Comandos da Linguagem

Quando o comando switch é executado, o interpretador Java calcula o


valor da expressão entre parêntesis e então procura pelo rótulo case que tenha
especificado este valor.

Se ele consegue encontrar tal rótulo, o fluxo de execução é transferido


para o bloco de código que tem início no primeiro comando após o rótulo.

Se ele não encontra um rótulo com o valor da expressão avaliada, o fluxo


de execução salta para o primeiro comando após o rótulo especial default. Ou,
caso um rótulo default não tenha sido definido, o fluxo de execução salta
totalmente para fora do comando switch.

Observe o uso da palavra reservada break ao final de cada rótulo case no


exemplo anterior. O comando break será descrito mais adiante, mas, neste caso,
ele faz com que o fluxo de execução salte para fora do corpo do comando
switch.

Os rótulos case em um comando switch especificam somente o ponto de


entrada para o bloco de código a ser executado. Os rótulos cases não são
blocos independentes de código, e não possuem um ponto de término implícito.
Portanto, você deve especificar o fim de cada rótulo com um comando break
ou equivalente.

Na ausência de um comando break, a expressão é avaliada e o fluxo de


execução salta para o bloco de código que tem seu início logo após o rótulo
que define a expressão avaliada, e a execução continua até o final do
comando switch.

Em raras ocasiões se revela útil escrever código em que a execução


atravessa mais de um rótulo. Em 99% dos casos, depois de encontrado o rótulo e
executado o bloco de código associado a este, a execução deve deixar o
comando switch por força de um comando break. Lembre-se, portanto, de
sempre verificar se há um comando break após cada rótulo, garantindo a saída
correta do comando e impedindo que o fluxo de execução passe por todos os
rótulos do comando.

Um comando switch pode ter mais de uma cláusula case rotulando um


mesmo comando.

Considere o seguinte método:

boolean respostaSimOuNao( char resposta ) {


switch( resposta ) {
case 's':
case 'S': return true;

case 'n':
case 'N': return false;

default: return false; //por default a resposta será false


}
}
4-11
Comandos da Linguagem

Código 4-12: Comando switch de char sem utilização de break e com comando return.

Há algumas importantes restrições sobre o comando switch e os rótulos


cases. A expressão associada com o comando switch deve ter um tipo byte,
char, short ou int. Valores booleanos ou reais não são suportados, nem o tipo
long, apesar de ser um tipo inteiro.

Os valores associados com cada rótulo case devem ser ou constantes, ou


expressões constantes avaliáveis em tempo de compilação. Um rótulo case não
pode conter expressões avaliáveis em tempo de execução, tais como variáveis
ou chamadas de métodos.

Os valores presentes nos rótulos cases devem estar compreendidos na faixa


de valores do tipo usado na expressão do comando. Por fim, dois rótulos cases
não podem ter o mesmo valor, nem um comando switch pode ter mais de uma
cláusula default.

4-12
Comandos da Linguagem

Comando while

Assim como o comando if é o principal comando de controle de fluxo,


permitindo ao programador tomar uma entre várias decisões, o comando while
é o principal comando de iteração da linguagem Java e permite ao
programador executar um bloco de código reiteradas vezes.

Ele possui a seguinte sintaxe:

while(expressao) {
comandos
}

Código 4-13: Estrutura do comando while.

O comando while começa por avaliar a expressão entre parêntesis. Se


esta expressão é avaliada para falso, o fluxo de execução pula o corpo do
comando while e passa a executar o próximo comando definido no programa.
Se, no entanto, a expressão é verdadeira, o corpo do laço while é executado e
a expressão entre parêntesis é reavaliada. Este ciclo continua enquanto a
avaliação da expressão resultar verdadeiro.

Quando a expressão se tornar falsa, a execução do comando while


termina e o fluxo de execução passa para o próximo comando. Você pode criar
um laço infinito com a sintaxe while(true) (pois a expressão sempre é
verdadeira).

Um exemplo de um laço que imprime os números de 0 a 9:

int i = 0;
while(i < 10) {
System.out.println(i);
i++;
}

Código 4-14: Comando while com teste lógico para impressão de inteiros entre 0 e 9.

Como você pode ver, a variável cont começa em 0, neste exemplo, e é


incrementada cada vez que o corpo do laço é executado. Após o laço ter sido
executado 10 vezes, a expressão do comando while se torna falsa (i assume o
valor 10), o comando while termina, e o fluxo de execução passa para o
próximo comando do programa.

A maior parte dos laços tem uma variável contadora, como i. Os nomes
de variáveis: i, j e k são usados, na maioria das vezes, para tais variáveis
contadoras, embora você possa escolher um nome mais claro.

4-13
Comandos da Linguagem

Comando do

O comando do é bastante similar ao comando while. A diferença é que,


no comando do, o teste da expressão é feito no final do laço.

Isto significa que o corpo do laço é executado pelo menos uma vez.
Eis a sintaxe do comando:
do {
comandos
}
while(expressao);

Código 4-15: Estrutura do comnado do/while.

Há algumas diferenças a se observar entre o laço presente no comando do


e o laço presente no comando while. O laço do comando do requer tanto a
palavra reservada do para marcar seu início, quanto a palavra reservada while
para marcar o seu término e introduzir a expressão condicional do laço.

Ao contrário do laço do comando while, o laço do comando do sempre


termina com um ponto e vírgula. Isto obviamente ocorre pelo fato de a
expressão condicional ser colocada no final do comando.

O laço abaixo gera a mesma saída que o código apresentado para o


comando while:

int i = 0;
do {
System.out.println( i );
i++;
} while( cont < 10 );

Código 4-16: Comando do/while com teste lógico no final para impressão de inteiros entre 0 e 9.

Observe que o comando do é bem menos usado que o comando while.


Isto acontece porque, na prática, é incomum encontrarmos situações em que se
deseje que o corpo do laço seja executado pelo menos uma única vez.

4-14
Comandos da Linguagem

Comando for

O comando for fornece uma construção de iteração que é, na maioria


das vezes, mais conveniente que os laços while e do. O comando for tira
vantagem de haver um padrão de iteração bastante freqüente. A maior parte
dos laços possui um contador, ou algum tipo de variável de estado, que é
inicializado antes da execução do laço e incrementado, ou de alguma forma
alterado, após o término da execução do corpo do laço e antes da reavaliação
da expressão condicional associada ao laço.

Os passos de inicialização, teste e atualização constituem as principais


manipulações feitas sobre as variáveis do laço e integram a sintaxe do comando

for( declaração e inicialização; teste; atualização ) {

for.

Código 4-17: Estrutura do comando for

Colocar as expressões de inicialização, teste e atualização no início do


laço é algo que facilita a compreensão do que está ocorrendo no corpo do
laço, e evita erros tais como esquecer de inicializar as variáveis do laço.

O interpretador Java descarta os valores das expressões de inicialização e


atualização, logo, para que estas expressões sejam úteis, elas devem ter efeitos
colaterais.

A expressão de inicialização é tipicamente uma expressão de atribuição,


enquanto a expressão de atualização é usualmente uma expressão de
incremento, decremento ou atribuição.

O seguinte laço imprime os números de 0 a 9, como os exemplos


anteriormente colocados para os comandos while e do:

for( int i = 0; i < 10; i++ ) {


Sytem.out.println( i );
}

Código 4-18: Comando for com teste lógico e incremento para impressão de inteiros entre 0 e 9.

Observe como esta sintaxe coloca todas as informações importantes sobre


a variável do laço em uma única linha, facilitando a compreensão do que
ocorre no corpo do laço.

4-15
Comandos da Linguagem

Colocar a expressão de atualização da variável do laço na sintaxe do


próprio comando for simplifica também o corpo do laço, permitindo que este
possa ser expresso, muita das vezes, através de um único comando simples, sem
a necessidade do uso de um bloco de código entre chaves.

O comando for suporta sintaxes alternativas que ajudam ainda mais


facilitar o seu uso. Pelo fato da maioria dos laços usar as variáveis de laço apenas
dentro do corpo do laço, o comando for permite que a expressão de
inicialização contenha uma declaração completa de variáveis.

O escopo das variáveis declaradas na expressão de inicialização de um


comando for se restringe ao corpo do laço e tais variáveis não mais podem ser
acessadas fora do comando.

for( int i = 0; i < 10; i++ ) {


Sytem.out.println( i );
}

Código 4-19: Comando for com escopo de variável local (i).

Além do mais, a sintaxe do comando não restringe a utilização do


comando a laços com apenas uma variável. Tanto a expressão de inicialização
quanto a de atualização podem conter, em verdade, a inicialização e
atualização de mais de uma variável, desde tais variáveis venham separadas por
vírgulas.

Por exemplo:

for( int i = 0, j = 10; i < 10; i++, j-- ) {


sum += i * j;
}

Código 4-20: Comando for com expressão utilizando o índice e variáveis locais.

Muito embora todos os exemplos de utilização do comando apresentem


contadores numéricos, os laços construídos com o comando for não se
restringem apenas à utilização de contadores.

As expressões de inicialização, teste e atualização do comando for são


todas elas opcionais. O caractere de ponto e vírgula que separa tais expressões
é, no entanto, obrigatório. Se a expressão de teste é omitida, presume-se que ela
é sempre verdadeira.

Com isto, um laço infinito pode ser escrito desta forma:

for( ;; ) // for infinito

Código 4-21: Comando for com loço infinito.

4-16
Comandos da Linguagem

4-17
Comandos da Linguagem

Comando “for-each”

O novo for é um laço de repetição mais fácil e bonito de escrever. Este


substitui o laço for atual de uma forma mais elegante para iterar sobre coleções.
Observe o código abaixo que nos permite iterar uma coleção de alunos através
do laço for tradicional:

Collection<Aluno> c = new ArrayList<Aluno>();


...
for (Iterator<Aluno> i = c.iterator(); i.hasNext(); ) {
System.out.println( i.next().getNome() );
}
Código 4-22: Comando for a ser transformado em for-each

Veja que uma simples tarefa de iterar uma coleção demanda um volume
considerável de código. Agora observe a mesma versão deste código utilizando
o laço “for-each” juntamente com o novo “import static”:

Collection<Aluno> c = new ArrayList<Aluno>();


...
for ( Aluno a : c ) {
out.println( a.getNome() );
}
Código 4-23: Comando for-each, mais simples que for

O sinal de “:” é lido com “em”. Desta forma o código acima pode ser lido
da seguinte forma: “Para cada aluno a em c”.

For-each também pode ser aplicado a arrays. Neste caso você pode iterar
um array da seguinte forma:

int[] notas = {2, 5, 7, 9, 4, 18};


int total = 0;
for( int n : notas) {
total += n;
}
out.println( total );
Código 4-24: For-each para somar notas

4-18
Comandos da Linguagem

Comando break
O comando break é utilizado para transferir o controle para fora do corpo
do comando de laço ou switch mais próximo, transferindo imediatamente o
controle para o primeiro comando após o corpo do laço ou switch. É usado,
assim, para prematuramente sair de qualquer tipo de laço.

Este comando é muito útil para abortar um laço quando algum evento
ocorre durante a execução do corpo deste laço.
break;

Código 4-25: Estrutura do comando break

O seguinte trecho de código utiliza o comando break junto ao comando


while. Ele imprime números que juntos somam um valor menor que 200, iniciando

int i = 0;
int total = 0;
while( i < 100 ) {
total += i; // total = total + i;
if( total >= 200 ) {
break;
}
System.out.println( i );
i++;
}

em 0.
Código 4-26: Comando break finalizando um loço while

O comando break é também utilizado junto ao comando switch, para sair


do corpo do comando. O trecho a seguir demonstra este uso.

switch( resposta ) {
case 's':
case 'S': System.out.println("Resposta SIM.");
break;

case 'n':
case 'N': System.out.println("Resposta NÃO.");
break;

default: System.out.println("Resposta não reconhecida.");


}

Código 4-27: Comando break finalizando um comando switch

O comando break pode ser utilizado também seguido por um label que
especifica um comando presente no método. Com o uso do comando break
4-19
Comandos da Linguagem

associado a um label, é possível interromper qualquer laço dentro da definição


do método, e não somente o laço mais próximo.

Para utilizar o comando break com label, o label deve ser inserido antes do
comando que se deseja interromper. O label pode ser qualquer identificador
válido, seguido por “:”.

Um comando break especificando este label causará um pulo para o


primeiro comando após o fim do corpo do comando com este label.

label:
comando {
...
break label;
...
}

Código 4-28: Estrutura de comando break para quebra de laços nomeados

O trecho de código a seguir exemplifica este uso. Após o múltiplo dos inteiros
i e j atingir um valor maior que 200, o comando for associado ao label
comandoFor é interrompido (comando for mais externo) e a linha indicando que
o múltiplo foi atingido será impressa.
comandoFor:
for( int i = 0; i < 100; i++ ) {

for( int j = 0; j < 100; j++ ) {


System.out.println(i*j);
if (i * j >= 200) {
break comandoFor;
}
}
}
System.out.println( "Fim do Comando FOR" );

Código 4-29: Comando break para quebra de laço for externo “comandoFor”.

O comando break com label é muito utilizado para interromper laços


aninhados. Contudo, embora este seja um mecanismo válido, ele pode levar a
um código que não é claro e de difícil manutenção, sendo melhor evitá-los
repensando a lógica do laço.

4-20
Comandos da Linguagem

Comando continue

O comando continue é usado para transferir o controle para o fim do


corpo do laço, antes da re-avaliação da expressão de teste. Causa, assim, a
interrupção da interação do laço corrente e o pulo para a próxima interação do
laço.

continue;

Código 4-30: Estrutura do comando continue.

O trecho a seguir exemplifica o seu uso. Neste exemplo, o comando


continue é utilizado para interromper a execução do resto do corpo do
comando for em todos os números pares.

Ao final, a soma de todos os números ímpares positivos menores que 10 é


apresentada.

int total = 0;
for( int i=0; i < 10; i++ ) {
if( i % 2 == 0 ) {
continue;
}
total = total + i;
}
System.out.println( total );

Código 4-31: Comando continue reiniciando o laço for na próxima iteração de i

O comando continue deve ser utilizado com cuidado, pois o algoritmo


resultante se torna pouco estruturado e de difícil manutenção.

O comando continue pode, assim como o break, ser utilizado com um


label. Um comando continue com label causará a interrupção da interação
corrente e o pulo para a próxima interação do laço associado ao label.

forExterno:
for( int i = 0; i < 100; i++ ) {

for( int j = 0; j < 100; j++ ) {


System.out.println( i*j );
if( i * j >= 200 ) {
continue forExterno;
}
}
}
System.out.println( "Fim do FOR Externo" );

4-21
Comandos da Linguagem

Código 4-32: Comando continue para reinício de laço for externo “forExterno” no próximo índice
de i

4-22
Comandos da Linguagem

Espaço para anotações

4-23
Comandos da Linguagem

Exercícios

1. Este exercício fará com que você altere as classes criadas no capítulo
anterior adicionando algumas validações as suas operações.

Passo 1: Na classe Produto faça uma validação do parâmetro passado


para operação que altera o preço do Produto. Não permita preços negativos.
Valide também o código do Produto. Este deve possuir no máximo 6 caracteres.

Passo 2: Na classe ItemPedido, não permita atribuir através do método


“set” um item do pedido ao produto se este não estiver em vendas. Faça o
objeto item pedido “recusar” (não realizar a atribuição) o produto.

Passo 3: Agora faça uma alteração na classe Aplicacao para que a


mesma ao imprimir os atributos booleanos imprima as palavras “Sim” e “Não” ao
invés de true/false. Para isto utilize o operador ternário. Compile e teste a
aplicação.

Passo 4: Altere um dos objetos produto que você criou para que o
mesmo não esteja mais em vendas. Rode novamente a sua aplicação e verifique
que para o pedido com o qual você tentou associar um produto que não estava
em venda não sairá dados do produto. Nesta execução a JVM lancará também
uma exception (NullPointerException).

Passo 5: Altere o código agora que faz a impressão das informações de


um ItemPedido na classe Aplicacao para que quando um ItemPedido não tenha
um produto seja impresso uma string mostrando isto.

2. Crie uma classe com o nome GeradorSenhas para que nesta seja
possível gerar senhas através de um laço de repetição.

Passo 1: Crie uma classe pública chamada GeradorSenhas no pacote


com.targettrust.java e nesta classe declare o método main para que a mesma
possa ser executada.

Passo 2: Utilizando um laço de repetição a sua escolha (for, while, ..) faça
a geração de 10 senhas randômicas com no máximo 8 díditos.

Dica para gerar números randomicamente:

Utilize a classe Math do pacote java.lang. Esta possui um método


chamado random() que gera números aleatórios entre zero e um, excluindo o
inteiro um. Veja a documentação JAVADOC para mais detalhes.
4-24
Comandos da Linguagem

4-25
Comandos da Linguagem

4-26
Java Fundamentals

5. Aprofundando o estudo sobre


Classes

5-1
Aprofundando o estudo sobre classes

Objetivos

• Estudar overloading e overriding


• Como utulizar métodos construtores
• Compreender a referência this
• Estudar o método destrutor finalize
• Criar operações com escopo de classe e instância
• Compreender o mecanismo de herança
• Utilizar o recurso de varargs
• Aplicar o polimorfismo
• Compreender o modificador final
• Estudar as enumerations

5-2
Aprofundando o estudo sobre classes

Visão Geral

Métodos de instância são o fundamento para o encapsulamento de classes


e peças chaves para fornecer uma interface consistente. Classes definidas
através de técnicas adequadas de encapsulamento fornecem métodos de
instâncias como o único meio de se acessar e alterar o estado de um objeto.

Permitir o acesso direto às variáveis de instância de uma classe, sem que seja
necessário invocar métodos para tal, é uma técnica de programação perigosa,
pois não garante um estado sempre consistente para o objeto (o usuário não fica
restrito às regras de negócios definidas na manipulação do objeto).

A sobrecarga permite que uma chamada a um método possua diferentes


comportamentos de acordo com os parâmetros passados. Suponha, por
exemplo, a classe Cliente.

O método comprar() desta classe pode ter comportamentos distintos


dependendo dos parâmetros recebidos.

Se a chamada for comprar(dinheiro), o comportamento invocado será


provavelmente um pagamento à vista, enquanto se for comprar(cheque), outro
será o comportamento invocado, fazendo com que o débito de pagamento
possa ser realizado em um dia posterior.

Sobrecarga é uma técnica poderosa, ao permitir que a classe tenha uma


aparência uniforme para o mundo externo.

Construtores garantem que, a despeito de quem cria o objeto, o objeto terá


as características esperadas para a classe.

Ao se criar um objeto, o método construtor é implicitamente chamado,


oferecendo um local adequado para as rotinas de inicialização. Isto é ponto
chave para programação orientada por objetos, já que é impossível saber quem
criará novos objetos das classes que você definiu.

5-3
Aprofundando o estudo sobre classes

Overloading – sobrecarga de operação

Dois ou mais métodos em uma classe podem ter o mesmo nome, desde que
tenham assinaturas diferentes. A assinatura de um método é formada por seu
nome, juntamente com o número, tipo e ordem dos parâmetros. O tipo de
retorno do método não é considerado como parte da assinatura.

A definição de dois ou mais métodos com o mesmo nome, mas com


assinaturas diferentes é conhecida como sobrecarga de métodos (method
overloading). Esta técnica é útil, pois oferece uma interface unificada da classe
para o mundo externo.

O método exato a ser chamado é determinado pelos parâmetros presentes


na chamada, ou seja pela assinatura. Sem a técnica de sobrecarga, cada
método exigiria um nome único, tornando mais difícil a codificação. Se você
quisesse, por exemplo, recuperar informações sobre um cliente usando como
chave de pesquisa o ID do cliente ou o nome, teria de escrever métodos com
nomes distintos: getClientePorNome(nome) e getClientePorID(id).

Usando sobrecarga, os dois métodos podem ter o mesmo nome


getCliente(), um esperando o parâmetro id e o outro esperando o parâmetro
nome, como mostrador abaixo:
public class ClienteDB {

public Cliente getCliente(String nome) {


...
}

public Cliente getCliente(int id) {


...
}
}
Código 5-1 : Overloading da operação getCliente()

Quando o usuário de uma classe chamar um método sobrecarregado, o


compilador escolherá o método correto a ser chamado analisando os
parâmetros passados na chamada e comparando tais parâmetros com os
esperados por cada um dos métodos com aquele nome na definição da classe.

Se o compilador não conseguir um “casamento compatível”, mesmo após


efetuar as conversões implícitas permitidas pela linguagem, um código de erro
será retornado. Do contrário, se mais de um casamento é possível, o compilador
reclamará e assinalará erro de ambigüidade na definição do método.

Métodos sobrecarregados não podem ser diferenciados exclusivamente


pelo tipo de retorno. Se a única diferença entre a declaração de dois métodos

5-4
Aprofundando o estudo sobre classes

for o tipo de retorno, o compilador retornará um erro e não aceitará a


construção.

A distinção entre métodos sobrecarregados deve sempre ter por base o


número e os tipos de parâmetros esperados.

public class Produto {


private float preco;

public float getPreco() { sobrecarga de


... método
}

public float getPreco( float desconto ) {


...
}
}
Código 5-2: Sobrecarga de métodos.

5-5
Aprofundando o estudo sobre classes

Métodos construtores

Métodos construtores são chamados quando um objeto da classe estiver


sendo criado. Podem ser utilizados para realizar algo no momento de criação do
objeto, como por exemplo, inicializar atributos do mesmo para determinados
valores.

O construtor é um método especial chamado automaticamente pelo


ambiente de execução Java quando um objeto é criado. Um construtor sempre
tem o mesmo nome que a classe. Ele pode esperar nenhum, um ou vários
parâmetros, mas não pode ter nenhum tipo de retorno. Veja no código abaixo a
declaração de um construtor:

public class Produto {

Produto( ) {
...
}

}
Código 5-3: Definindo construtores

O construtor acima é conhecido como construtor default. O que acontece


se um construtor não é fornecido? Caso nenhum construtor seja fornecido, Java
supre esta ausência com um construtor default. Este construtor não recebe
nenhum parâmetro e não executa nenhuma tarefa de inicialização.

É possível colocar dentro deste construtor um código a ser executado no


momento de criação do objeto. Assim quando o objeto da classe produto for
criado utilizando o construtor acima este código será executado. Veja como
pode ser criado um objeto utilizando o construtor acima:

public class CriaObjeto {


public static void main(String[] args) {
Produto prod = new Produto();
}
}
Código 5-4 : Criando um objeto e chamando o construtor

Observe que o código acima já havia sido utilizado anteriormente, mas


agora você pode perceber que estamos chamando o construtor default no
momento da criação do objeto.

5-6
Aprofundando o estudo sobre classes

Se você desejar, poderá fornecer mais construtores na mesma classe. Desta


maneira se fornecer algum construtor o compilador não mais ir;a gerar o
construtor default.

Entretanto, na maioria das vezes, será necessário declarar um ou mais


construtores para a classe, a fim de permitir diferentes formas de criação do
objeto, como mostrado no código abaixo a criação de quatro objetos da classe
produto de quatro formas diferentes:

Produto prod1 = new Produto( 1, "CD", 45.50f );


Produto prod2 = new Produto( 2, "DVD" );
Produto prod3 = new Produto( "VHS" );
Produto prod4 = new Produto();

Código 5-5: Sobrecarga de construtores de Produto

Para permitir que o usuário crie objetos desta maneira, será necessário
fornecer os contrutores na classe Produto e que inicialize o estado do objeto
Produto com as opções fornecidas.

Construtores geralmente são declarados como public, salvo quando se


deseja restringir quem pode criar objetos da classe. Veja abaixo um exemplo de
construtor para a primeira e a segunda inicialização acima:

public class Produto {

private String descricao;


private int codigo;
private float preco;

public Produto(int c, String d, float p ) {


codigo = c;
descricao = d;
preco = p;
}

public Produto(int c, String d) {


codigo = c;
descricao = d;
}
}
Código 5-6 : Construtores da classe Produto

Observe o código acima e veja que além de termos definido construtores


na classe Produto para dentro dos mesmos fazer a inicialização de um produto,
também temos overloading de construtores.

5-7
Aprofundando o estudo sobre classes

5-8
Aprofundando o estudo sobre classes

Referência this

Esta referência representa um objeto da própria classe. O this referencia o


objeto corrente. Todos os objetos “possuem” o this.

Todos os métodos de instância recebem um parâmetro implícito chamado


this, que pode ser usado dentro do método de instância para se referir ao
objeto corrente. O objeto corrente é aquele cujo método foi invocado. O
parâmetro this é uma referência implícita para objeto chamado e, como tal,
não é necessário na maioria dos casos.

public class Produto {


public void setNome(String nome) { this
this.nome = nome;
}
}
p1 nome:"CD"
public void metodo() {
Produto p1 = new Produto();
Produto p2 = new Produto();

p1.setNome( "CD" ); p2 nome:"DVD"


p2.setNome( "DVD" );
}

Código 5-7: Referência implícita this do objeto invocado.

Dentro de um método de instância, todas as referências não qualificadas


para variáveis de instância ou métodos de instância estão implicitamente
associadas com a referência this. Os dois comandos dentro do método
setNome(), apresentados abaixo, são equivalentes:

public void setNome( String nome ) {


nome = nome; // ERRO!
this.nome = nome; // Forma correta
}

Código 5-8: Referência implícita this para representar variáveis de instância.

5-9
Aprofundando o estudo sobre classes

Há duas situações em que você deve usar explicitamente a referência this:

• O nome de uma variável de instância é escondido por uma parâmetro


formal, de mesmo nome, do método de instância. Suponha, por exemplo,
uma classe que tenha uma variável de instância nome, e um método de
instância que recebe uma variável também chamada nome. O parâmetro
formal nome deste método esconde a variável de instância nome.
Qualquer referência para nome, dentro deste método, acessará o
parâmetro formal, não à variável de instância. Para acessar a variável de
instância, você deve usar this.nome.

• Quando você precisa passar uma referência para o objeto corrente


como parâmetro de um outro método

5-10
Aprofundando o estudo sobre classes

Compartilhando código entre Construtores

Um construtor pode chamar outro construtor da mesma classe usando a


sintaxe this().

public class Produto {


private String nome;

public Produto() {
this( “” ); Um construtor pode
} chamar outro construtor
usando a sintaxe this()
public Produto(String nome) {
this.setNome( nome );
}
}

Código 5-9: Referência this invocando o construtor da própria classe de acordo com os tipos de
parâmetros.

O primeiro construtor chama o segundo construtor passando “vazio” como


parâmetro. O segundo construtor copia a referência para o objeto String na
variável nome. Esta técnica garante que o nome default para todos os objetos
Produto seja uma string vazia sem a necessidade de se duplicar código em
múltiplos construtores.

A sintaxe this() minimiza a necessidade de se duplicar código nos


construtores. Esta técnica é especialmente útil se a rotina de inicialização é
complexa. Toda a complexidade vai em um construtor que é chamado pelos
demais.

Ao usar a sintaxe this() algumas regras de sintaxe devem ser observadas:


• A chamada para this() deve ser o primeiro comando do construtor
• Os parâmetros de this() devem casar com os parâmetros esperados por
um construtor que não seja o chamador.

5-11
Aprofundando o estudo sobre classes

Método destrutor – finalize()

Em algumas linguagens como C++, uma classe pode fornecer um destrutor.


Um destrutor é similar a um construtor, sendo chamado automaticamente logo
antes de o objeto ser destruído.

Um destrutor é normalmente empregado para liberar recursos mantidos pelo


objeto, tais como memória secundária alocada, arquivos abertos, etc.

Java gerencia automaticamente a memória, logo um objeto não precisa


explicitamente liberar memória que tenha alocado. Conseqüentemente, Java
não suporta destrutores. A fim de permitir que um objeto libere outro recurso que
não memória (pois esta já é automaticamente gerenciada), tais como arquivos
abertos, Java permite que uma classe forneça um método finalize().

O método finalize() é chamado automaticamente quando um objeto é


coletado pelo sistema coletor de lixo. Infelizmente, como vimos antes, não há
como saber quando isto ocorrerá, ou mesmo que ocorrerá antes de o programa
terminar.

A falta de previsibilidade de quando o método finalize() será chamado


é inaceitável se os recursos são escassos.

A única solução é gerenciar tais recursos manualmente. A fim de assumir o


controle do processo, você pode definir um método público dispose() em sua
classe, que os usuários da classe terão de chamar quando tiverem terminado de
usar um objeto desta classe.

Você pode ainda manter o método finalize() se quiser, como um último


esforço de liberar os recursos.

public class Produto {

public void finalize() {


// Libera todos os recursos alocados pela classe
System.out.println( "Instância sendo coletada pelo GC!" );
}
}
Código 5-10: Método finalize para liberação de recursos alocados pela objeto.

5-12
Aprofundando o estudo sobre classes

Variáveis de instância

Variáveis de instância são definidas dentro do bloco de código da classe.


As variáveis de instância são variáveis que irão guardar valores específicos para
cada instância da classe. Normalmente definimos variáveis/atributos como sendo
de instância. Pode-se chamar os atributos que declaramos até o momento como
variáveis de instância. O padrão quando declaramos atributos é que os mesmos
sejam de instância.

Veja o exemplo abaixo:

public class Cliente {

/* Variáveis/atributos de instância */
private String codigo;
private String nome;
private char plano;

/* Variável de classe */
public static float desconto;
}
Código 5-11 : Declarando vaiáveis de instância

Uma variável/atributo de instância pode ser acessado por um método de


instância somente, ou pelo construtor da classe.

Observe que no código acima termos três variáveis de instância e uma de


classe declaradas no fonte. Cada variável de instância terá um valor específico
para o atributo. Veja a figura abaixo:

codigo = 98 codigo = 100


nome = Rafael nome = Walter
plano = A plano = B

obj01 obj02

Figura 5-1 : Objetos com variáveis de instância

5-13
Aprofundando o estudo sobre classes

O código que cria estes objetos é o seguinte:

public class CriaCliente {


public static void main(String[] args) {
Cliente obj01 = new Cliente();
obj01.setCodigo(98)
obj01.setNome(“Rafael”);
obj01.setPlano(‘A’);

Cliente obj02 = new Cliente();


obj02.setCodigo(100)
obj02.setNome(“Walter”);
obj02.setPlano(‘B’);

}
}
Código 5-12 : Criando objetos Cliente

5-14
Aprofundando o estudo sobre classes

Métodos de instância

De maneira análoga às variáveis de instâncias, você utiliza o operador


ponto para chamar um método de instância de um objeto. A sintaxe é a

referenciaObjeto.nomeMetodo(parâmetros ...);

seguinte:

Código 5-13: Estrutura de chamada de um método de instância com parâmetros.

Se o método não espera nenhum argumento, você deve se lembrar apenas


de colocar os parêntesis (uma lista vazia de parâmetros):

referenciaObjeto.nomeMetodo();

Código 5-14: Estrutura de chamada de um método de instância sem parâmetros.

No exemplo abaixo temos declaros na classe três métodos de instância.


Observe estes e veja que os mesmos acessam os atributos de instância da classe.

public class Cliente {

/* Variáveis/atributos de instância */
private String codigo;
private String nome;
private char plano;

/* Métodos de instância */
public void setCodigo(String codigo) {
this.codigo = codigo
}

public String getCodigo() {


return this.codigo;
}

public boolean validar() {


...
}
}

Código 5-15: Definindo métodos de instância

5-15
Aprofundando o estudo sobre classes

Variáveis de classe

Variáveis de classe são variáveis comparadas as variáveis globais. Estas ao


serem definidas irão possuir o mesmo valor para todos os objetos da classe.

Uma variável de classe, também chamada variável estática (variável


static), é aquela que pertence à classe e é comum a todas as instâncias desta
classe. Em outras palavras, há somente uma instância de uma variável de classe,
não importa quantas instâncias da classe existam.

Observe o código que declaramos na sessão anterior quando estávamos


estudando variáveis de instância. Havia uma variável/atributo de classe. O
código é o seguinte:

public class Cliente {

/* Variáveis/atributos de instância */
private String codigo;
private String nome;
private char plano;

/* Variável de classe */
public static float desconto;
}
Código 5-16 : Variável de classe definida

Agora veja como seria o desconto para os objetos da classe:

codigo = 98 codigo = 100


nome = Rafael nome = Walter
plano = A plano = B

desconto = ...
obj01 obj02

Figura 5-2 : Objetos compartilhando variável de classe

5-16
Aprofundando o estudo sobre classes

O desconte seria o mesmo para ambos os objetos. Em outras palavras


podemos dizer que um atributo/variável de classe é compartilhado por todos os
objetos da classe. Veja como ficaria o código para acesso a este
atributo/variável:

public class CriaCliente {


public static void main(String[] args) {
Cliente.desconto = 5.4f;

Cliente obj01 = new Cliente();


obj01.setCodigo(98)
obj01.setNome(“Rafael”);
obj01.setPlano(‘A’);

Cliente obj02 = new Cliente();


obj02.setCodigo(100)
obj02.setNome(“Walter”);
obj02.setPlano(‘B’);

}
}
Código 5-17: Acessando variável de classe

Quando formos nos referencia a um atributo/variável de classe devemos


utilizar o nome da classe para tal e não o nome do objeto. Neste exemplo
estamos acessando diretamente o atributo, porém iremos mostrar mais adiante
como deve ser feito o acesso a ele via uma operação.

Em Java variáveis de classe são declaradas usando a palavra reservada


static. No exemplo abaixo, nós declaramos a variável de classe precoMinimo
porque, obviamente, o preço mínimo se aplica a todas as instâncias da classe
Produto.

Observe que precoMinimo foi declarada private, porque ela precisa ser
acessada apenas pelos métodos da classe Filme. Neste exemplo, a variável
precoMinimo é compartilhada por todos objetos, ou seja, é igual para todas as
instâncias de Filme, muito embora cada instância possua sua própria descrição
e código.

public class Produto {

private static double precoMinimo; // var. de classe


private String descricao; // var. de instância
private int codigo; // var. de instância

Código 5-18: Variáveis de classe

5-17
Aprofundando o estudo sobre classes

Figura 5-3: Variáveis de classe e variáveis de instância.

5-18
Aprofundando o estudo sobre classes

Inicializando Variáveis de Classe

Variáveis de classe são inicializadas quando a classe é carregada. Como


construtores são utilizados para construir instâncias (e, portanto, invocados toda
vez que uma nova instância é criada), você não pode utilizar construtores para
inicializar variáveis de classe.

Variáveis de classe possuem os mesmos valores de inicialização default que


as variáveis de instância (na verdade qualquer variável definida e não
inicializada recebe um valor de inicialização default): inteiros recebem o valor 0,
reais o valor 0.0, booleanos o valor false, caracteres o valor '\u0000' e
referências o valor null.

Assim como as variáveis de instância, as variáveis de classe também podem


ser inicializadas com valores diferentes do valor default. Basta, para isto, utilizar

public class Produto {


private static double precoMinimo = 30.50;
private String nome = "CD";
private int codigo = 1234;
...
}

inicializadores.
Código 5-19: Declaração de variáveis de classe

Rotinas complexas de inicialização de variáveis de classe devem ser


colocadas no bloco de inicialização estático (também conhecido como
inicializador estático).

Um inicializador estático não possui nome, nem valor de retorno e começa


com a palavra reservada static seguida por um bloco de código delimitado
por chaves (static {...}).

Funciona de maneira similar a um construtor, com a diferença de que é


executado apenas uma única vez e não depende das variáveis de instância de
uma classe (não pode sequer referenciá-las).

public class Produto {


private static double precoMinimo;
static {
Date dataAtual = new Date();
precoMinimo = getPrecoMinimoDia( dataAtual );
}
}

Código 5-20: Bloco static para inicialização de estruturas da classe

5-19
Aprofundando o estudo sobre classes

Métodos de classe
Um método de classe, também conhecido como estático (método static)
, é um método que pertence à classe e é compartilhado por todas as instância
desta classe. Ao contrário de um método de instância, uma método de classe
não atua sobre um único objeto. Estes métodos não recebem, portanto, a
referência implícita this.

Um método de classe pode acessar somente variáveis de classe e invocar


métodos de classe.

Métodos de classe são ideais para acessar variáveis de classe. De fato, eles
são a única maneira, caso não exista nenhuma instância da classe (lembre-se:
mesmo não existindo nenhuma instância de uma classe, as variáveis de classe já
existem e têm um valor associado, pois são criadas quando a classe é carregada
pelo ambiente de execução.

Não é necessário ter uma instância da classe criada para poder acessar
variáveis de classe) No exemplo abaixo, o valor do preço mínimo é alterado
para todos os produtos, mesmo que nenhum objeto produto tenha sido criado
public class Produto {
private static double precoMinimo;
public static void setPrecoMinimo( float precoMinimo ) {
Produto.precoMinimo = precoMinimo;
}
}

ainda.
Código 5-21: Declaração de método de classe

Veja o código que usaria esta classe definida acima:

public class TestaStatic {


public static void main(String[] args) {
Produto.setPrecoMinimo( 17.5 );
// ou
Produto p1 = new Produto();
p1.setPrecoMinimo( 19.23 ); // Não recomendnado!
}
}
Código 5-22 : Acessando métodos de classe

Objetos de classe são chamados com a seguinte sintaxe:

NomeClasse.nomeMetodo(parametros);

Código 5-23: Estrutura de chamada de métodos de classe

5-20
Aprofundando o estudo sobre classes

Você pode chamar métodos de classe usando uma referência para um


objeto e o operador ponto (da mesma maneira usada para métodos de
instância), mas ainda assim, o método de classe só poderá acessar as variáveis
de classe.

5-21
Aprofundando o estudo sobre classes

Exemplos de variáveis e métodos estáticos


Quando você executa uma aplicação Java, a JVM localiza e chama o
método main() desta classe. Muito embora tudo em um programa Java deva
estar definido dentro de uma classe, você não precisa criar uma instância da
classe principal se main() chama apenas métodos de classe e manipula apenas

public class ApenasMetodosEstaticos {

public static void main( String args[] ) {


double num, raiz;
// ...
raiz = Math.sqrt( num ); // Usando uma biblioteca
System.out.println("A raiz de " + num + " é " + raiz);
}

variáveis de classe.

Código 5-24: Exemplos de métodos estáticos.

Se, no entanto, main() acessa variáveis ou métodos de instância da própria


classe em que se encontra definido, é necessário, antes de mais que ele crie
instâncias desta classe.

A classe Math fornece métodos de classe para o cálculo de muitas funções


matemáticas, tais como funções trigonométricas e logaritmos.

Fornece também várias constantes de classe tais como e (2.71828) e π


(3.1415926).

A classe System fornece vários métodos de classe que representam o


estado de todo o sistema (ambiente computacional). System.out é uma
variável de classe que se refere ao objeto PrintStream.

Esta variável de classe representa a saída padrão de dados. println() é


um método de instância de PrintStream.

5-22
Aprofundando o estudo sobre classes

O mecanismo de herança entre classes

Herança define uma relação entre classes, onde uma classe (subclasse)
toma de empréstimo as estruturas de dados e os comportamentos de outra
classe (superclasse).

Herança é uma valiosa técnica porque estimula e possibilita a reutilização


de software ao permitir criar uma nova classe baseada nas propriedades de uma
classe existente. Como resultado, o desenvolvedor é capaz de alcançar grande
produtividade que, de outra forma, seria impossível.

Construtores são blocos de código executados quando um objeto de uma


classe é criado. Já vimos isto anteriormente. Ao usar o modelo de herança, cada
subclasse tem acesso ao construtor da superclasse. Qualquer código comum
relativo a inicialização do objeto, pode ser colocado na superclasse e invocado
pela subclasse.

Esta técnica minimiza a necessidade de se duplicar código e garante


consistência na criação de objetos.

Polimorfismo descreve a capacidade de Java executar um método


específico com base na referência a um objeto utilizada na chamada. Usando
esta técnica, você pode definir um método na superclasse e sobrescrevê-lo na
subclasse apropriada.

Você pode invocar os métodos da superclasse e, caso o método tenha


sido sobrescrito na subclasse, Java automaticamente chamará o método
apropriado. Isto é uma construção muito poderosa que possibilita definir métodos
na superclasse sem mesmo conhecer detalhes de qualquer subclasse específica.

Somente use herança para modelar uma genuína relação “é um tipo de”.
Em outras palavras, não use herança senão quando todos os métodos herdados
se aplicam à subclasse.

Se você não pode substituir um objeto da superclasse por um objeto da


subclasse, então você não tem uma genuína relação “é um tipo de”. Neste caso,
as classes podem se relacionar, mas não hierarquicamente.

Se você realmente usa herança, deve explorar a natureza polimórfica dos


métodos de instância na hierarquia de classes. Por exemplo, se você acha que
deve testar o tipo de um objeto na árvore de herança, use polimorfismo para
evitar ter de escrever código separado para tratar objetos de cada classe. Isto
maximiza a reutilização de seu código, e o torna mais fácil de manter no futuro.

Vamos agora ver como estes conceitos mostrados acima são aplicados em
java

5-23
Aprofundando o estudo sobre classes

Herdando estrutura e comportamento

Uma relação de herança faz com que a classe filha herde toda a estrutura
e comportamento dos pais.

A classe Produto define os atributos e métodos que são relevantes para


todos os itens de inventário. Entre os atributos e métodos prováveis:
• Atributos tais como data de aquisição, custo de aquisição, e condição.
• Métodos tais como cálculo de quantia a ser paga pelo item, mudança
de condições, e definição de preço.

Dependendo do que quiser fazer no programa, você precisará definir um


tipo específico de Produto. Você pode usar herança para definir uma subclasse
de itens de inventário para cada tipo distinto de item.

Você poderia definir, por exemplo, as seguintes subclasses: CD, DVD e VHS.

Cada uma destas subclasses automaticamente herdaria todos os atributos


e métodos de Produto e atributos e métodos adicionais poderiam ser fornecidos
se necessários.

A classe CD, por exemplo, poderia definir os seguintes atributos e métodos


adicionais:
• Atributos tais como título, produtor, duração.
• Métodos tais como play, stop, etc.

Subclasses podem também sobrescrever métodos da superclasse se


desejarem fornecer um comportamento mais especializado para o método.

Figura 5-4: Herança entre Produto e CD.

5-24
Aprofundando o estudo sobre classes

Especificando herança em Java

Quando você define uma subclasse, você precisa somente fornecer


código para as facilidades da subclasse que diferem das existentes na
superclasse. Efetivamente, a subclasse apenas estende a superclasse.

A sintaxe para especificar herança em Java faz uso da palavra reservada


extends.

Por exemplo:

public class Produto extends Object {


// Definição da classe Produto
}

public class CD extends Produto {


// Métodos e atributos adicionais para distingui-la
// dos outros tipos de itens de produto
}

Código 5-25: Especificando herança em Java.

Se você tem experiência com outras linguagens OO (orientada por


objetos) tais como C++, observe que Java permite somente herança simples.

Em outras palavras, uma classe pode especificar somente uma única


superclasse da qual herda.

Lembre-se também que todas as classes em Java herdam


automaticamente de uma classe raiz conhecida como Object, que se situa
sempre no topo da árvore de herança.

Se uma classe não especifica explicitamente uma superclasse, como no


caso de Produto, então esta classe é forçada a herdar diretamente de Object,
sendo sua definição (implícita) equivalente à seguinte definição explícita:

public class Produto extends Object {


// Definição da classe Produto
}

Código 5-26: Utilizando a cláusula extends.

5-25
Aprofundando o estudo sobre classes

Objetos de subclasses

A superclasse define as variáveis que são relevantes para todos os tipos de


Produto, tais como nome e codigo. A subclasse CD herda estas variáveis sem ter
de fazer qualquer esforço, e precisa somente especificar as variáveis que lhe são
específicas, tais como duração.

Suponha as seguintes classes:

Figura 5-5: Herança entre as classes CD e Produto.

Se você cria um objeto Produto, ele apenas contém as variáveis definidas


para Produto:

Produto produto = new Produto();

Código 5-27: Criando um novo Produto.

Entretanto, se você cria um objeto CD, ele conterá cinco variáveis de


instância: as três herdadas de Produto mais as duas que ele próprio CD definiu.

CD cd = new CD();

Código 5-28: Criando um novo CD

Variáveis de instância devem normalmente ser declaradas private, o que


significa que instâncias de subclasses herdam os valores, mas não podem acessá-
los diretamente.

Como visto anteriormente, você deve definir, métodos para acessar


variáveis privadas. Você pode definir métodos na subclasse ou herdá-los da
superclasse.
5-26
Aprofundando o estudo sobre classes

Chamando construtores da superclasse

A superclasse e a subclasse geralmente terão construtores que esperam


parâmetros. Suponha, por exemplo, que Produto tenha um construtor que
espera como parâmetros os valores iniciais de preco, nome e codigo.

Da mesma forma, a classe CD tem um construtor que espera parâmetros


suficientes para inicializar seus atributos. As coisas aqui começam a ficar
interessantes.Um objeto CD tem cinco atributos: preco, nome e codigo herdados
de Produto, e produtor e duracao definidos na própria classe CD.

O construtor de CD terá, portanto, cinco argumentos:

public CD(float preco, String nome, int codigo, String produtor, int duracao)

Código 5-29: Utilizando o contrutor de CD com parâmetros.

Ao invés de inicializar preco, nome e codigo explicitamente, tudo o que o


construtor de CD deve fazer é chamar o construtor de sua superclasse. Isto pode
ser feito usando a palavra reservada super. A chamada super(...) deve ser o
primeiro comando do construtor.

public CD(float preco, String nome, int codigo, String produtor, int duracao) {
super( preco, nome, codigo );
setProdutor( produtor );
seDuracao( duracao );
}

Código 5-30: Invocando o construtor da superclasse a partir da referência super.

Se você não chamar explicitamente super(...) o compilador chamará


automaticamente o construtor sem parâmetros. Se a superclasse não tiver um
construtor sem parâmetros, o compilador acusará um erro.

public Produto(float preco, String nome, int codigo) {


setPreco( preco );
setNome( nome );
setCodigo( codigo );
}

public CD(float preco, String nome, int codigo, String produtor,


int duracao) {
super( preco, nome, codigo );
setProdutor( produtor );
seDuracao( duracao );
}

5-27
Aprofundando o estudo sobre classes

Código 5-31: Referenciando o construtor da superclasse.

5-28
Aprofundando o estudo sobre classes

Overloading e Overriding de métodos

Overloading e overriding são freqüentes na programação orientada a


objetos. Overloading é conhecido como sobrecarga enquanto overriding é
conhecido como sobreescrita. Vejamos abaixo a definição destes dois conceitos:

• Sobrecarga de métodos é quando você define múltiplos métodos com


mesmo nome e diferentes assinaturas. Métodos sobrecarregados são
resolvidos em tempo de compilação (resolver um método é descobrir
qual versão do método deve ser chamada com base nas informações
dadas pelo programador), com base no número e tipo dos parâmetros
fornecidos.

• Sobrescrita de métodos é quando você fornece um método com


exatamente o mesmo nome e assinatura que este método possui em
uma superclasse. Métodos sobrescritos são resolvidos em tempo de
execução. Esta técnica é conhecida como polimorfismo e será discutida
mais adiante.

5-29
Aprofundando o estudo sobre classes

Redefinindo métodos – overriding

Quando uma classe herda operações de outra classe, estas operações


passam a fazer parte da estrutura da classe filha. Nem sempre desejamos manter
a mesma implementação para a operação nos filhos, desta forma podemos
redefinir tal operação.

Vejamos os códigos de exemplo abaixo:

public class Produto {


public float getPreco();
public String getNome();

} public class CD extends Produto {


public int getDuracao();
public String getProdutor();
}

Código 5-32: Especificando métodos adicionais na


subclasse.

O exemplo acima mostra alguns dos métodos declarados pela subclasse e


pela superclasse.

A superclasse define métodos que são relevantes para todos os tipos de


Produto.

A subclasse CD herda estes métodos da superclasse, e tem de adicionar


apenas os métodos que lhe são específicos (relevantes para todos os objetos CD),
tais como recuperação do produtor do CD e duração.

Quando você cria um objeto, você pode chamar qualquer um dos seus
métodos públicos (public), bem como qualquer método declarado público em
uma de suas superclasses.

Se você, por exemplo, cria um objeto Produto, você pode chamar os


métodos públicos definidos em Produto, bem como quaisquer métodos públicos
de sua superclasse Object:

Produto produto = new Produto();


float preco = produto.getPreco(); // método public de Produto
Class classe = produto.getClass(); // método public de Object

Código 5-33: Criando um Produto e acessando métodos herdados.

5-30
Aprofundando o estudo sobre classes

Se você cria um objeto CD, você pode chamar métodos públicos definidos
em CD, Produto e Object.

CD cd = new CD();
int duracao = cd.getDuracao() // método public de CD
float preco = cd.getPreco(); // método public de Produto
Class classe = cd.getClass(); // método public de Object

Código 5-34: Criando um CD e acessando métodos herdados

Sobrescrevendo métodos da superclasse

Uma subclasse herda todos os métodos de sua superclasse. Uma subclasse


pode modificar o comportamento de um método de sua superclasse
sobrescrevendo-o, como mostra o exemplo abaixo:

public class Produto {


public float getPreco() {
return preco;
}
}
public class CD extends Produto {
public float getPreco() {
float x = getCustoCD() + calculaImpostos();
return x;
}
}

Código 5-35: Sobreescrevendo o método getPreco()

Para sobrescrever um método de uma superclasse, a subclasse define o


método exatamente com a mesma assinatura e tipo de retorno que o método
possui quando declarado na superclasse.

O método da subclasse efetivamente esconde o método declarado na


superclasse. É importante ter certeza de que o método na subclasse mantém a
mesma semântica que o método que está sendo sobrescrito.

Qual método é chamado? No exemplo acima, a classe Produto fornece


um método getPreco() e a classe CD sobrescreve este método com uma versão
mais especializada.

Se você criar um objeto Produto e chamar getPreco(), o método


chamado será a versão definida em Produto.

5-31
Aprofundando o estudo sobre classes

Se você criar um objeto CD e chamar getPreco(), o método chamado


será a versão especializada definida em CD.

5-32
Aprofundando o estudo sobre classes

Referência super

A referência super é útil quando a classe possui um ancestral. Uma


subclasse herda todos os métodos e variáveis de sua superclasse, bem como
define ela própria. Métodos da superclasse podem ser sobrescritos na subclasse,
bastando a subclasse declará-los novamente e redefinir o seu comportamento
(declarar novamente significa manter a assinatura do método tal qual se
encontra na superclasse alterando apenas o corpo do método).

A palavra reservada super permite que você acesse métodos da


superclasse sobrescritos pela subclasse.

Um dos usos mais comuns de super é chamar os construtores da classe pai.


Quando a superclasse foi projetada, um construtor provavelmente foi declarado
para garantir a inicialização correta de qualquer novo objeto. Como a subclasse
herda todas as variáveis da superclasse, tais variáveis precisarão ser inicializadas
para objetos da subclasse também. A palavra reservada super permite que
você use o código do construtor definido na superclasse sem ter que duplicar o
código em cada subclasse.

Adicione a referência super no construtor da subclasse a fim de acessar o

class SubClasse extends SuperClasse {


...
public SubClasse(int param) {
super(param);
/* coloque aqui o código específico do construtor da subclasse */
}
}

construtor da superclasse:
Código 5-36: Estrutura de chamada explícita do construtor da superclasse

A regra de sintaxe é que a palavra reservada super deve ser, neste caso, a
primeira linha do construtor da subclasse. A palavra reservada super pode ser
usada também para acessar os métodos da superclasse.

Exemplo de uso da referência super:

public class Produto {


private String nome;
public Produto( String nome ) {
super();
this.nome = nome;
}
}

5-33
Aprofundando o estudo sobre classes

public class Filme extends Produto {


private int ano;
public Filme( String nome, int ano ) {
super( nome );
this.ano = ano;
}
Código 5-37: Referência super invocando o construtor da superclasse

No exemplo acima, há rotinas de inicialização que devem ocorrer para


todos os objetos Produto. Estas rotinas são colocadas nos construtores de
Produto. Tais rotinas devem sempre ser usadas, não interessando o tipo do
Produto sendo construído, seja ele um CD, um Filme ou um Livro (subclasses de
Produto).

Há também construtores em cada uma das subclasses encarregados de


rotinas de inicialização específicas destas subclasses.

O construtor de Filme reutiliza o construtor de Produto ao referenciá-lo com


a palavra reservada super. Este comando é o primeiro comando do construtor
de Filme e pode ser seguido por quaisquer comandos que se fizerem necessários
para uma adequada inicialização de objetos Filme.

5-34
Aprofundando o estudo sobre classes

Invocando métodos da superclasse

Como mencionado anteriormente, quando uma subclasse sobrescreve um


método de sua superclasse, ela esconde o método da superclasse.

public class Produto {

public float getPreco() {


return preco;
}
public class CD extends Produto {
}
public float getPreco() {
return super.getPreco() + calculaImpostos();
}

Código 5-38: Sobreescrevendo e invocando o método getPreco()

Se, por exemplo, o programa criar um objeto CD e chamar o método


getPreco(), ele sempre estará executando a versão de getpreco() definida

CD cd = new CD(); // Cria objeto CD


float v = cd.getPreco(); // Executa versão getPreco() de CD

em CD.
Código 5-39: Executando o método getPreco()

Dentro do método getPreco() definido na classe CD pode-se invocar o


método escondido de mesmo nome e assinatura, definido na superclasse
Produto, através da palavra reservada super.

A palavra reservada super é similar a this, salvo que atua como


referência para o objeto corrente como uma instância da superclasse.

Chamar um método escondido da superclasse usando super evitamos


duplicar o código contido no método escondido. Ao reduzir o volume de código
duplicado, facilita-se a tarefa de manutenção do software.

5-35
Aprofundando o estudo sobre classes

Visibilidade protected

A visibilidade protected não foi demonstrada anteriormente pois o uso da


mesma envolve o processo de herança. Agora que já vimos este recurso estamos
aptos a estudar esta visibilidade.

Vejamos o código abaixo:

package com.targettrust.java;
public class Funcionario {
private String nome;
protected float salario;
...
}
Código 5-40: Visiblidade protected aplicada a atributos

O código acima define uma classe Funcionário, a qual possui na sua


estrutura um nome e um salário. Perceba que o nome é privado enquanto o
salário é protected. O atributo nome poderá ser acessado somente por
operações definidas dentro da classe, porém o atributo salário poderá ser
acessado diretamente por outras classes que forem filhas de Funcionario. Isto
poderá ser feito mesmo com a classe filha em outro pacote diferente do pacote
onde estiver o Funcionário. Veja exemplo abaixo:

package com.targettrust.rh;
public class Vendedor extends Funcionario {
private float comisssao;
...
public void calcularSalario() {
...
float total = salario + comissao;
}
}
Código 5-41: Acessando um atributo protected

Veja que o acesso ao atributo é acessado na classe filha como se fosse


declarado na mesma classe. Já o atributo privado não pode ser acessado.

Esta visibilidade também pode ser aplicada a operações da classe, porém


o uso dela nestes casos não é comum.

5-36
Aprofundando o estudo sobre classes

Varargs

Varargs é um recurso popular em algumas linguagens como C, C++ e


linguagens derivadas. Este recurso permite passar um número variável de
parâmetros a um método sem a necessidade de encapsulá-los manualmente
em um array. Junto com varargs será utilizado o recurso de autoboxing. Veja o
exemplo abaixo utilizando este recurso:

public class Varargs {

public static void main(String[] args) {

foo("Java", "Oracle", "Linux");


foo("Porto Alegre", "Ijui", "São Leopoldo", "Cruz Alta");

public static void foo(String... args) {


for (int i=0; i<args.length; i++) {
System.out.println(args[i]);
}
}
}
Código 5-42: Usando varargs para receber mais de um parâmetro

A declaração acima “String... args” equivale a “String[] args”, mas se


utilizado este recurso deve ser o último parâmetro do método. Quando este
método for chamado na chamada podem ser passados vários parâmetros
separados por vírgula desde que os tipos sejam sempre Strings. O enpasulamento
dos parâmetros String em um vetor será feito de forma automática pelo
compilador. Se o tipo de parâmetro é um Object, então os parâmetros se forem
tipos primitivos serão transformados em objetos com o recurso de autoboxing.
Para evitar este overhead da transformação de tipos primitivos nos objetos
correspondentes utilizando o recurso de autoboxing, você pode utilizar, por
exemplo “int... args”.

Se você tentar passar parâmetros com tipo incorretos, portanto não


esperados pelo método, o compilador irá perceber e avisar. Veja exemplo
abaixo:

public class Varargs {

public static void main(String[] args) {


foo("Java", "Oracle", "Linux", 88); // Erro nesta linha!
}

public static void foo(String... args) {


for (int i=0; i<args.length; i++) {
System.out.println(args[i]);
}
}
}

5-37
Aprofundando o estudo sobre classes

Código 5-43: Erro pois esta pasando inteiro e não String

5-38
Aprofundando o estudo sobre classes

Polimorfismo
Usando a técnica de polimorfismo visualize a seguinte hierarquia de classes.

Figura 5-6: Hierarquia de classes representando herança e polimorfismo do método getPreco().

Ao projetar a aplicação de venda, não era sabido todo o tipo de produtos


que seriam vendidos em longo prazo. Em programação não orientada por
objetos, isto criaria um problema, que seria resolvido alterando o código sempre
que um novo tipo de item fosse adicionado. Em Java, nós podemos usar
polimorfismo para resolver este problema. Eis como:

O método getPreco() da classe Produto é sobrescrito pelas classes CD e


DVD, cada qual fornecendo uma versão especializada do método.

A classe CarrinhoCompras possui um método addItem(Produto


produto) que chama o método getPreco() através da referência recebida a
um objeto Produto.

Em tempo de execução Java interroga o parâmetro de addItem() para


descobrir o tipo real do objeto e se o objeto possui alguma função sobrescrita. Se
sim, Java usa o método sobrescrito pela subclasse (CD ou DVD), ao invés de usar o
método definido pela superclasse (Produto).

5-39
Aprofundando o estudo sobre classes

Por exemplo, se a variável cd do tipo CD e dvd do tipo DVD forem


adicionadas:
addItem(cd); // O método addItem chamará a versão de getPreco
// definida em CD e não em Produto

addItem(dvd); // O método addItem chamará a versão de getPreco


// definida em DVD e não em Produto

Código 5-44: Adicionando CD e DVD ao método que espera Produtos.

O mais importante é que as classes CarrinhoCompras e Produto não


precisam ser modificadas quando novos tipos de itens de inventários forem sendo
adicionados ao negócio.

5-40
Aprofundando o estudo sobre classes

Modificador final

Por default todas as variáveis e métodos podem ser sobrescritos. Especificar


uma variável como final evita que seu valor seja alterado. Isto é útil para
garantir que um determinado valor seja consistente entre todos os usuários de
uma classe.

Você pode também declarar uma classe como final. Uma classe final
não pode ser superclasse de nenhuma outra classe. Em outras palavras, não se
pode herdar de uma classe final.

Ao declarar uma classe como final você está tomando uma importante
decisão de projeto, pois está dizendo que tal classe é completa o suficiente para
atender todas as expectativas presentes e futuras de seus usuários, e que,
portanto, nunca será necessário estendê-la para fornecer alguma funcionalidade
adicional.

Um método final não pode ser sobrescrito por nenhuma subclasse. Em outras
palavras, se o programador herda de uma classe, não será permitido que ele
forneça uma versão alternativa de um método declarado como final na
superclasse.

Esta é uma técnica útil para evitar que programadores inadvertidamente ou


maliciosamente redefinam métodos essenciais que devem funcionar de uma
maneira esperada.

Resumindo:
• Uma variável final é uma constante
• Uma variável final não pode ser modificada
• Uma variável final deve ser inicializada
• Uma variável final é geralmente pública (public), permitindo o acesso
externo

public final class Color {


public final static Color BLACK = new Color(0,0,0);
// ...
}

Código 5-45: Variáveis final

Métodos e classes são declarados final por suas razões principais:


segurança e otimização.

5-41
Aprofundando o estudo sobre classes

Se um método executa alguma operação vital, tal como validação de


identidade ou verificação de autorização, ele deve ser declarado final, a fim
de evitar que seja sobrescrito por um método mal-intencionado que esteja
procurando se esquivar das verificações de segurança.

Muitos dos métodos definidos nas classes do pacote java.net são


declaradas final.

Se você declara uma classe como final, ela não poderá nunca mais ser
estendida por nenhuma outra classe. Esta é uma decisão de projeto importante,
pois afirma que a classe atende a todas as necessidades presentes e futuras de
seus usuários. Esta implicação é clara: você não precisa.

public final class Criptografia{


private String senha;

public final void getSenha(String senha) {


getCriptografia(senha, 128) // Chama Criptografia de 128 bits
}
}

Código 5-46: Definindo uma Classe e um método final.

5-42
Aprofundando o estudo sobre classes

Enums

Enums é uma forma mais fácil e segura para representar um conjunto de


valores que são conhecidos em tempo de compilação e que não mudam com o
passar do tempo. Estes valores são muitas vezes conhecidos como constantes de
classe. A partir da nova versão do java, a versão 1.5, foi incluído este novo tipo
que trás algumas vantagens listadas abaixo. Veja inicialmente o padrão que se
costuma utilizar para representar constantes e guardar estações do ano:

public static final int ESTACAO_INVERNO = 0;


public static final int ESTACAO_PRIMAVERA = 1;
public static final int ESTACAO_VERAO = 2;
public static final int ESTACAO_OUTONO = 3;
Código 5-47: Constantes de classe

Este padrão tem os seguintes problemas:


• Não é seguro – Uma vez que uma estação é um int, você pode passar
qualqeur outro valor diferente dos especificados nas constantes acima, ou
mesmo somar estas constantes, o que não faria sentido.

• Prefixar constantes – Você precisa prefixar as constantes com strings para


evitar colisões de nomes semelhantes. Este caso foram prefixadas com a
palavra ESTACAO.

• Os valores impressos não são informativos – Por serem constantes do tipo


int estas constantes quando impressas não tem significado claro. Ao
imprimi-la teremos um int e não um nome.

Com enums o mesmo código acima ficaria da seguinte maneira:


enum Estacao { INVERNO, PRIMAVERA, VERAO, OUTONO }
Código 5-48: Definindo uma enumeration

Tipos enum são cosiderados como objetos, acima temos a definição de um tipo
“Estacao”. Uma enumeration herda da classe java.lang.Enum possuindo desta
forma vários métodos para se poder manipular a estrutura. Métodos de Object
também estão presentes em uma Enum. Comparable e Serializable são interfaces
implementadas pelas enumerations, permitindo que as mesmas portanto possam
ser comparadas e serializadas.

5-43
Aprofundando o estudo sobre classes

Veja abaixo uma versão completa de um código que utiliza enumerations:


enum Estacao { INVERNO, PRIMAVERA, VERAO, OUTONO }

public static void main(String[] args) {


foo( Estacao.INVERNO );
}

public static void foo(Estacao x) {


out.println( x.ordinal() +" "+ x.name() ); // 0 INVERNO
}
Código 5-49: Usando uma enumeration

Neste código acima observe que definimos um tipo “Estacao” e o mesmo


é utilizado mais abaixo para passar valores ao método foo. Quando desejarmos
imprimir o nome da estação passada como parâmetro através de enum
podemos utilizar o método name(), a ordem dos valores é adicionada de forma
automática e começa em 0.

É possível pegar de uma enumeration todos os seus valores através de um


método estático values(). Este método facilita bastante a impressão dos valores
quando desejarmos. Veja o código abaixo:

for(Estacao s : Estacao.values() ) {
out.println( s.name() + " " +s.ordinal() );
}
Código 5-50: Percorrendo uma enumeration

5-44
Aprofundando o estudo sobre classes

Espaço para anotações

5-45
Aprofundando o estudo sobre classes

Exercícios

1. Neste exercício você deve criar uma classe para representar um Curso.
Esta classe terá métodos sobrecarregados (overloading) e redefinidos
(overriding), bem como será uma classe filha da classe Produto e terá
construtores para que seja possível criar objetos da mesma já passando
informações na hora da criação.

Passo 1: Crie uma classe pública Curso no pacode com.targettrust.venda e


faça com que esta classe extenda (herde) a classe Produto. Na classe Curso
defina os seguines atributos e constante privados:

private int cargaHoraria;


private char turno;
private final float VALOR_HORA = 100f;

Passo 2: Defina na classe Curso um método para calcular o preço do curso


( public float getPreco() {...} ). Observe que este tem o mesmo nome do
método que está sendo herdado da classe Produto. Na assinatura deste faça
com que o mesmo possa receber um valor hora ( public float getPreco(float
valorHora) {...} ) a ser levado em consideração no cálculo do preço do curso.
O preço do curso deve ser calculado multiplicando-se a carga horária pelo valor
hora passado como parâmetro no método.

Passo 3: Crie outro método na classe Curso que seja capaz de redefinir o
comportamento (overriding) do método public float getPreco() herdado da
classe Produto. Esta operação deve retornar o preço levando em consideração
o valor hora e a carga horária do curso, para isto utilize o valor hora da
constante.

Passo 4: Na classe Produto, bem como na classe Curso, defina um


construtor que permita criar um objeto destas classes passando valores para
todos os seus atributos. Isto irá fazer com que o construtor default não seja
adicionado pelo compilador java. Desta forma adicione também ele a estas
classes.

Passo 5: No construtor da classe Curso faça com que o mesmo repasse os


parâmetros que este receber e que devem ser atribuídos para os atributos da
classe Produto para o construtor da classe Produto. Use para isto a referência
super(...).

Passo 6: Agora você irá modificar as classes que criou até o momento para
que as operações set’s da classe Produto, ItemPedido e Curso possam receber
parâmetros com o mesmo nome dos atributos. Use a referência this nestas classes
para referenciar o atributo na hora da atribuição e defina o nome do parâmetro

5-46
Aprofundando o estudo sobre classes

com o mesmo nome do atributo. Gere novamente a documentação para estas


classes e verifique as alterações.

Passo 7: Na classe Curso defina o método destrutor finalize() e dentro deste


faça a impressão de uma string sinalizando que o mesmo está sendo executado.

public void finalize() { ... }

Passo 8: Crie agora uma classe pública TestaCurso no pacote


com.targettrust.venda, declare nesta classe o método main e dentro deste
método crie um objeto da classe Curso. Atribua informações para este objeto
através do método construtor. Logo em seguida mostre os dados do objeto
através dos seus métodos de leitura, os get’s.

Passo 9: Atribua null para a referência criada que representa o curso e logo
em seguida chame o método System.gc() para ativar o coletor de lixo e
constatar a execução do método destrutor.

Passo 10: Na classe Curso crie um bloco estático de código para sinalizar
quando a classe está sendo carregada pela JVM. Neste bloco estático imprima
uma mensagem.

5-47
Java Fundamentals

6. Coleções, Arrays, Strings, e


Wrapper Classes

6-1
Coleções, Arrays, String e Wrapper Classes

Objetivos

• Estudar a API Collection


• Utilizar classes StringBuffer e StringBuilder
• Converter e encapsular dados com Wrapper Classes
• Utilizar arrays
• Compreender o mecanismo de autoboxing
• Utilizar generics

6-2
Coleções, Arrays, String e Wrapper Classes

Strings
Assim como na maioria das linguagens de programação, strings são usadas
intensivamente em Java. Desta forma, a API Java fornece uma classe String
para ajudá-lo a manipular seqüências de caracteres.

Literais de string são transformados pelo compilador Java em objetos


String. Eles podem então ser usados diretamente, passados como argumentos
para métodos ou atribuídos a variáveis do tipo String.

System.out.println( "Hello World!" );


String str = "Matrix";

Código 6-1: Manipulação de Strings.

A classe String representa uma string imutável.

Isto significa que, uma vez criado um objeto String, você não poderá mais
alterá-lo. Se você quiser modificar o conteúdo de uma string, você deverá usar a
classe StringBuffer. Esta classe será estudada logo adiante.

6-3
Coleções, Arrays, String e Wrapper Classes

Criando Strings

A maneira mais fácil de se criar uma string é a partir de uma constante


colocada entre aspas duplas, como mostra o exemplo abaixo:

String produto = "Caneta";

Código 6-2: Atribuição de valor String.

Você pode usar o operador mais (+) para concatenar dois objetos String.
Isto é explicado em maiores detalhes adiante. Veja um exemplo do uso do
operador mais (+) quando aplicado a objetos String:

String nomeEmp = primeiroNome + " " + ultimoNome;

Código 6-3: Concatenação de Strings entre variáveis e String fixa.

A classe String fornece vários construtores. Eis aqui alguns dos construtores
mais úteis:
• String() cria uma string vazia, com o valor ""
• String(String str) cria uma cópia do objeto String referenciado por
str
• String(char[] arr) cria uma string a partir dos caracteres presentes no
vetor arr

// uso de construtores
String nomeEmp = new String( "Maria Isabel" );

Código 6-4: Criação de String utilizando o construtor da classe String.

Você encontrará uma lista de construtores na documentação do JDK para


a classe String. A classe String é parte do pacote java.lang.

java.lang é um pacote automaticamente importado por todas as classes


Java. Não é preciso, portanto, declarar um comando explícito de import para
poder usar a classe String em seu código.

6-4
Coleções, Arrays, String e Wrapper Classes

Concatenando Strings

Java usa o operador + para concatenação de strings. O método concat()


de String é outra forma de se concatenar strings.

O seguinte código produz strings equivalentes:

// Concatenação
String nome = "Carlos Silva";
nome = "Carlos " + "Silva";
nome = "Carlos ".concat("Silva");

Código 6-5: Exemplos de concatenação de Strings.

O exemplo abaixo mostra um tipo primitivo (no caso um int) sendo


concatenado com uma String.

O tipo primitivo é convertido implicitamente para String:

int codigo = getCodigo();


System.out.println("Código: " + getCodigo() + ".");

Código 6-6: Conversão de tipos primitivos para String.

Literais de string não podem se estender por mais de uma linha, mas você
pode concatená-los e produzir o mesmo efeito:

String soneto = "De tudo ao meu amor serei atento\n" +


"Antes e com tal zelo, e sempre, e tanto\n" +
"Que mesmo em face de maior encanto\n" +
"Dele se encante mais meu pensamento.";

Código 6-7: Concatenação de Strings utilizando caracteres de escape.

6-5
Coleções, Arrays, String e Wrapper Classes

Executando operações em objetos String

O método length() retorna o número de caracteres de uma string:

String s = "Maria";
int tam = s.length(); // tam = 5

Código 6-8: Tamanho de uma String.

O método charAt() retorna o caractere especificado pelo índice passado


como argumento para o método (os índices sempre começam em 0).

// 01234
String s = "Maria";
char c = s.charAt(2); // c = 'r'

Código 6-9: Recebendo um caracter a partir de uma String.

O método substring() retorna uma substring específica (dois argumentos


são fornecidos a este método: o índice do primeiro caractere da substring e o
índice do caractere após o último caractere da substring desejada).

// 01234
String s = "Maria";
String sub = s.substring(2,4); // sub = "ri"
String sub = s.substring(2); // sub = "ria"

Código 6-10: Produzindo substrings.

O método toUpperCase() retorna uma nova string contendo uma versão


da anterior com todos os caracteres convertidos para a forma maiúscula. O
método toLowerCase() retorna uma nova string contendo uma versão da
anterior com todos os caracteres convertidos para a forma minúscula.

String s = "Maria";
String M = s.toUpperCase(); // M = "MARIA"
String m = s.toLowerCase(); // m = "maria"

Código 6-11: Transformando uma String para maiúsculo e minúsculo.

O método trim() retorna uma nova string contendo uma cópia da string
original com espaços em branco removidos tanto no início quanto no final.

String s = " Cadastro de Clientes ";


String t = s.trim(); // t = "Cadastro de Clientes"

Código 6-12: Truncando espaços de uma String.

6-6
Coleções, Arrays, String e Wrapper Classes

O método indexOf() retorna o índice de uma determinada substring. O


método lastIndexOf() retorna o índice da última ocorrência de uma string
determinada.

// 0 1 2
// 012345678901234567890
String s = "fábrica de brinquedos";
int iof = s.indexOf("bri"); // iof = 2
int liof = s.lastIndexOf("bri"); // liof = 11

Código 6-13: Busca índices de ocorrências de substring dentro de uma String.

Há várias versões para cada um destes métodos. Dê uma olhada na


documentação da classe String para maiores detalhes sobre cada uma delas.

6-7
Coleções, Arrays, String e Wrapper Classes

Comparando duas Strings

O método equals() retorna true se as strings especificadas contêm o


mesmo texto. Caso a string passada como parâmetro seja null, equals()
retorna false.

Importante: o método equals() diferencia maiúsculas de minúsculas!

String senha = getSenha();


if(senha.equals("brasil2010"))

Código 6-14: Comparação de Strings case sensitive.

O método equalsIgnoreCase() é similar a equals(), exceto que ignora


diferença entre maiúsculas e minúsculas.

String cat = getCategoria();


if(cat.equalsIgnoreCase("Drama"))

Código 6-15: Comparação de Strings case insensitive.

Não use o operador == para comparar objetos String! O operador == retorna


true dependendo da JVM quando ambas as variáveis referenciarem o mesmo
objeto.

6-8
Coleções, Arrays, String e Wrapper Classes

Obtendo strings a partir de objetos

Se sua classe tem um método toString(), você pode incluir o seu objeto
em expressões de concatenação de strings. E você pode imprimir seu objeto
como se este fosse uma string. O método toString() é invocado
automaticamente sempre que você usa um objeto em uma expressão de
concatenação de strings ou passa este objeto para System.out.println().

No exemplo abaixo, a classe Produto fornece uma implementação de


toString() que imprime o nome e o código do produto.

public class Produto {


public String toString() {
return getCodigo() + " - " + getNome();
}
}
...
Produto p1 = new Produto( 1, "DVD" );
System.out.println( "Produto: " + p1 ); // "1 – DVD"

Código 6-16: Produzindo String a partir de uma referência.

O que acontece quando a classe não fornece o método toString()? Se a


classe não fornece o método toString(), ela herda um da classe Object.

A string produzida por Object.toString() não é muito amigável. Consiste


do nome da classe da qual o objeto é uma instância e um número hexadecimal
representando uma entrada hash.

6-9
Coleções, Arrays, String e Wrapper Classes

Convertendo tipos primitivos em strings


A classe String fornece um método estático valueOf() que retorna uma
string representando o tipo primitivo. Há uma versão de valueOf() para cada
tipo primitivo.

O exemplo abaixo usa duas versões:

String sete = String.valueOf(7); // chama valueOf(int) "7"


String umPontoZero = String.valueOf(1.0f); // chama valueOf(float) "1.0"

Código 6-17: Convertendo tipos primitivos em String.

Quando um tipo primitivo é concatenado com uma string, ele é


automaticamente convertido para String através de String.valueOf().

Quando um tipo primitivo é passado para System.out.println(), a versão


apropriada de System.out.println() é chamada. Há uma versão para cada
tipo primitivo.

Na versão do J2SDK 1.5 é permitido a seguinte codificação:


System.out.printf("Nome: ");
String user = "James Gosling";
int total = 53;
System.out.printf("%s possui %d anos.\n", user, total);

Código 6-18: Convertendo tipos primitivos em String utilizando printf no J2DSK 1.5.

As expressões “%s” e “%d” formatam as saídas de “James Gosling” e “53”.

6-10
Coleções, Arrays, String e Wrapper Classes

Wrapper Classes
Para cada tipo primitivo, Java fornece uma classe invólucro (wrapper class)
correspondente. Estas classes permitem que um tipo primitivo seja manipulado
como se fosse um objeto.

Cada classe invólucro fornece um método estático para converter uma


string para o tipo primitivo correspondente.

Outros usos para Wrapper Classes


• Wrapper Classes são úteis quando você precisa tratar um tipo primitivo
como um objeto. Java, por exemplo, define uma classe Vector, que
implementa um vetor dinâmico de objetos (o tamanho do vetor pode
ser alterado dinamicamente). Você não pode armazenar tipos primitivos
em Vector. Neste caso, você precisará usar uma classe invólucro. Para
armazenar, por exemplo, variáveis do tipo int em um Vector, você
precisará criar um Integer para cada int: a classe Integer possui um
construtor que faz isto.
• Wrapper Classes fornecem um local adequado para métodos de
conversão relacionados ao tipo. A classe Integer, por exemplo, possui
vários métodos, incluindo aí Integer.parseInt() para converter um
tipo int em outro tipo.
• Wrapper Classes fornecem um local adequado para variáveis
relacionadas ao tipo. Em Integer, por exemplo, Integer.MAX_VALUE
representa o maior valor que um inteiro pode alcançar.

Tipo Classe invólucro


Primitivo correspondente

boolean Boolean

char Character

byte Byte

short Short

int Integer

long Long

double Double

float Float

Tabela 6-1: Tipos primitivos e Wrapper Classes de apoio.

6-11
Coleções, Arrays, String e Wrapper Classes

Conversões com Wrapper Classes

O exemplo abaixo mostra como usar os métodos de conversão para


processar os campos de um formulário. Todos os campos textos guardam valores
do tipo String. Temos, então, que ler os valores dos campos em variáveis do tipo
String. No exemplo, é esperado um valor inteiro no primeiro campo e um valor
real no segundo.

Métodos de conversão são necessários para converter um valor String


para int e float respectivamente.

Observe que os métodos de conversão são chamados sem a necessidade


de se instanciar um objeto Integer ou Float. As classes invólucros são um local
adequado para métodos de conversão e não precisam ser instanciadas quando
tais métodos são usados.

qtdCamp
Quantidad 17

Preço: 425.00 prcCampo

String qtdVal = qtdCampo.getText();


String prcVal = prcCampo.getText();
int qtd = Integer.parseInt(qtdVal);
float prc = Float.parseFloat(prcVal);
Código 6-19: Convertendo String para o tipo primitivo correspondente de acordo com a Wrraper
Class.

O que acontece se o usuário entra com um valor não inteiro no primeiro


campo?

Caso isto aconteça, parseInt() irá falhar e levantar uma exceção. Para
tratar esta situação, seria necessário colocar código adicional para capturar e
tratar a exceção levantada, tanto por parseInt(), quanto por parseFloat().
Exceções serão estudadas mais adiante. O código acima poderia ser feito da

int qtd = Integer.parseInt(qtdCampo.getText());


float prc = Float.parseFloat(prcCampo.getText());
// Suprimimos a declaração de variáveis

seguinte forma:
Código 6-20: Convertendo String para o tipo primitivo correspondente de acordo com a Wrraper
Class.

6-12
Coleções, Arrays, String e Wrapper Classes

StringBuffer e StringBuilder

StringBuffer e StringBuilder representam strings que podem ser


modificadas e estendidas em tempo de execução. A primeira deve ser utilizada
quando mais de uma thread estiver utilizando o objeto já a segunda quando
somente uma thread estiver o utilizando pois o acesso a a mesma é mais rápida.
Estas estruturas permitem você adicionar dados a um objeto sem que o mesmo
precise ser recriado como é o caso da String. Estas estruturas são estruturas
mutáveis ao contrário da String que é imutável.

O seguinte exemplo cria três novos objetos String e copia todos os


caracteres cada vez que um novo objeto String é criado. Isto gera um
overhead grande e deixa muitos objetos na memória para que o garbage
collector os recolha.
String texto = "O texto começa com uma linha\n";
texto = texto + "E ganha outra linha\n";
texto = texto + "E mais uma...\n";

Código 6-21: Concatenando Strings com o operador +.

É mais eficiente usar um objeto StringBuffer e seu método append():

StringBuffer texto = new StringBuffer( "O texto começa com uma linha\n" );
texto.append( "E ganha outra linha\n" );
texto.append( "E mais uma...\n" );

Código 6-22: Concatenando Strings utilizando um StringBuffer

StringBuffer também fornece um comportamento semelhante a


StringBuilder porém esta é uma estrutura que possui os seus métodos
sincronizados, o que deixa o acesso aos mesmos mais lento quando temos
somente uma thread acessando o objeto.

Você deve procurar utilizar StringBuilder em vez de StringBuffer. Veja abaixo


um exemplo de StringBuilder e observe que a forma de uso dos mesmos é igual.

StringBuilder sb = new StringBuilder();

sb.append("Java J2EE\n");
sb.append("Oracle\n");
sb.append("PostgreSQL\n");

System.out.println(sb);
Código 6-23: Utilizando StringBuilder

6-13
Coleções, Arrays, String e Wrapper Classes

Arrays
Um vetor é uma coleção de variáveis do mesmo tipo. Cada elemento
pode armazenar um único item. Os itens de um vetor podem ser de tipos
primitivos ou referências para objetos.

O comprimento de um vetor é fixo, quando criado.

Vetores são úteis quando você deseja um grupo de objetos que possam ser
manipulados como um todo. Caso você, por exemplo, escreva um programa
para permitir a pesquisa por um filme, você provavelmente armazenará a lista de
categorias em um vetor.

Figura 6-2: Estrutura de um vetor de Strings.

6-14
Coleções, Arrays, String e Wrapper Classes

Arrays de tipos primitivos


1. Declaração: Declare uma variável de referência para o vetor

2. Criação: Crie um vetor de objetos do tipo e tamanho desejados, e


armazene a referência para este vetor na variável de referência (do
passo 1)

3. Inicialização: Atribua os valores desejados para os elementos do vetor. Isto


é opcional para um vetor de tipos primitivos porque os elementos
recebem valores default quando o objeto vetor é criado.

De maneira esquemática, temos:

1. Declare a variável de referência que irá receber a referência para o


objeto vetor

Figura 6-3: Referência inicial do vetor.

2. Cria o vetor (alocação de memória e inicialização default dos elementos


do vetor)

Figura 6-4: Referência com vetor padrão int.

3. Atribuição de valores iniciais aos elementos do vetor

Figura 6-5: Referência com vetor padrão int inicializado.

6-15
Coleções, Arrays, String e Wrapper Classes

Declarando arrays de tipos primitivos


Há duas maneiras de se declarar um vetor: (tanto faz!!!)

Sintaxe Exemplo

tipo[] nome; int[] nums;

tipo nome[] int nums[];

Tabela 6-2: Declaração de vetores.

A maioria dos programadores Java usa o primeiro estilo porque ele separa o
tipo da variável (no exemplo acima int) do nome da variável, tornando o
código mais legível.

Quando você declara uma variável de referência para vetor, ela


inicialmente recebe o valor null, até que o vetor seja efetivamente criado
usando new.

Figura 6-6: Declaração do vetor nums.

6-16
Coleções, Arrays, String e Wrapper Classes

Criando arrays
Vetores devem ser criados usando o operador new. O tamanho do vetor
deve ser especificado entre colchetes.O tamanho deve ser um inteiro, mas não
precisa ser necessariamente uma constante. Pode ser uma expressão avaliável
somente em tempo de execução. Uma vez que um vetor é criado, o seu
tamanho permanece o mesmo por toda a existência do vetor.

Figura 6-7: Referência com vetor padrão int.

Todos os elementos de um novo vetor de tipos primitivos recebem


automaticamente o valor default para o tipo:
• Elementos char recebem '\u0000';
• Elementos byte, short, int e long recebem 0;
• Elementos float e double recebem 0.0;
• Elementos boolean recebem false.

// CONSTRUÇÕES VÁLIDAS

// exemplo 1: tamanho é uma constante


final int TAM = 4;
int[] nums = new int[TAM];

// exemplo 2: tamanho conhecido apenas em tempo de execução


int[] notasExame;
int n = getTotalAlunos();
notasExame = new int[n];

Código 6-24: Contruções válidas de vetores.

// CONSTRUÇÕES INVÁLIDAS

// exemplo 1: o tamanho não faz parte da declaração do vetor


int nums[4];

// exemplo 2: n não foi devidamente inicializada


int n;
int[] nums = new int[n];

Código 6-25: Contruções inválidas de vetores.

6-17
Coleções, Arrays, String e Wrapper Classes

Inicializando arrays

Primeiro Método: Atribua um valor para cada elemento


Para se referir a um elemento do vetor use um índice entre colchetes como
mostrado no exemplo abaixo.

Os elementos de um vetor são indexados de 0 a n-1, onde n é o número de


elementos do vetor. Em outras palavras, o índice do primeiro elemento de um
vetor é sempre 0 e não 1.

Figura 6-8: Atribuição de valor int para o vetor

Segundo Método: Inicializadores de vetor


Como mostrado no exemplo abaixo, há uma construção simplificada para a
criação e inicialização de vetores de tipos primitivos. Aqui não há necessidade
de usar o operador new e o comprimento do vetor é automaticamente
detectado.

Observe o uso de chaves e lembre-se de colocar o ponto e vírgula no final.

Figura 6-9: Declaração, criação e inicialização de vetor a partir de lista de valores int.

Inicializadores de vetor são muito úteis para criar tabelas de pesquisa, como
mostrado no seguinte exemplo:

int [] diasMes = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };

Código 6-26: Declaração, criação e inicialização de vetor a partir de lista de valores String.

6-18
Coleções, Arrays, String e Wrapper Classes

Este método é útil quando o valor de cada elemento é conhecido quando


o vetor é criado.

6-19
Coleções, Arrays, String e Wrapper Classes

Arrays de objetos
Os passos para criar um vetor de referências são os mesmos que para criar
um vetor de tipos primitivos, com uma única exceção: você deve sempre
inicializar os elementos do vetor porque isto não é feito automaticamente.

1. Declaração: A sintaxe é a mesma para vetores de tipos primitivos. Por

String [] nomes;

exemplo:
Código 6-27: Declaração de vetor String.

2. Criação: A sintaxe é a mesma para vetores de tipos primitivos. Por

nomes = new String [3];

exemplo:
Código 6-28: Criação do vetor String com tamanho 3

A linha acima cria um objeto vetor do tipo String e de tamanho 3. Todos os


elementos, no início, recebem o valor null.

3. Inicialização: Inicialize os elementos do vetor para o valor que desejar. Isto


será visto com mais detalhes logo adiante.

6-20
Coleções, Arrays, String e Wrapper Classes

Declarando arrays de objetos


De maneira esquemática, temos:

1. Declare a variável de referência que irá receber a referência para o


objeto vetor

Figura 6-9: Referência de vetor null.

2. Cria o vetor (alocação de memória e inicialização default dos elementos


do vetor)

Figura 6-10: Criação do vetor de instâncias com tamanho 4 e


valores null.

3. Atribuição de valores iniciais aos elementos do vetor

Figura 6-11: Inicialização do vetor com Strings.

6-21
Coleções, Arrays, String e Wrapper Classes

Inicializando arrays de objetos


Assim como para vetores de tipos primitivos, há duas maneiras de se
inicializar um vetor de referências para objetos.

Você pode inicializá-lo, atribuindo um valor para cada elemento do vetor,


ou declarando um inicializador ao criá-lo.

// Inicializa elemento por elemento


// Cria um vetor de 4 Strings vazias
String[] array = new String[4];
for(int i=0; i<array.length; i++) {
arr[i] = new String();
}
Código 6-29: Código para inicialização de vetor.

// Inicializadores
String[] produtos = {"CD", "DVD", "VHS", "MP3"}

Código 6-30: Criando vetor de String a partir de uma lista de Strings.

A Propriedade length
Cada vetor tem um atributo length que contém o número de elementos do
vetor. Ao usar length, você evita ter de armazenar o tamanho do vetor em outra
parte de seu código.

A classe System fornece um método útil para copiar todo ou parte de um


vetor para outro vetor. Para maiores informações, consulte System.arrayCopy()
na documentação do J2SDK.

6-22
Coleções, Arrays, String e Wrapper Classes

Utilizando arrays de objetos


Um vetor se comporta como objetos. Sendo assim, quando um vetor é
passado para um método, na verdade, uma referência para este vetor é
passada. Portanto, se o método altera o conteúdo do vetor, estas mudanças
alterarão o vetor original (e não uma cópia).

Qualquer elemento de um vetor pode receber um objeto do tipo correto, e


também ser atribuído a uma variável de tipo compatível. Cada elemento de um
vetor pode ser tratado como um objeto individual.

Um elemento de vetor pode ser passado para um método, e neste caso, por
ser um objeto, a referência para o objeto é que será passada.

String[] produtos = new String[4];


// ...
String produto = produtos[0];
produtos[1] = "Caneta";

System.out.println("Length: " + produtos[0].length());

Código 6-31: Vetor de referência para objetos String.

6-23
Coleções, Arrays, String e Wrapper Classes

Arrays e Exceções
Exceções são levantadas dependendo de como vetores são manipulados.
Caso você tente acessar uma posição inválida de um vetor, o programa será
interrompido pela exceção ArrayIndexOutOfBoundsException.

Caso tente acessar um elemento de um vetor que não tenha sido


inicializado, a exceção NullPointerException será levantada.

// Exceção ArrayIndexOutOfBoundsException
String[] lista = new String[4];
System.out.println(lista[5]);

// Exceção NullPointerException
Produto[] listaProdutos = new Produto[3];
String nomeProduto = listaProdutos[0].getNome();

Código 6-32: Exceções comuns a partir de vetores.

6-24
Coleções, Arrays, String e Wrapper Classes

Arrays multidimensionais
Java suporta vetores multidimensionais, isto é, vetor de vetores.

int[][] matriz = new int[4][2];

Código 6-33: Declarando e criando uma matriz.

A linha acima declara e cria uma matriz bidimensional: a matriz contém


cinco linhas e cada uma das linhas possui quatro colunas. Elementos individuais
podem ser acessados da seguinte maneira:

matriz[indiceLinha][indiceColuna] = valor;

Código 6-34: Esquema de acesso a uma matriz.

O seguinte exemplo cria um vetor multidimensional com dez linhas, mas o


número de colunas por linha é diferente. A primeira linha possui apenas um
elemento, a segunda dois, a terceira três e assim por diante.

int[][] a = new int[10][];


for(int i=0; i<a.length; i++) {
a[i] = new int[i+1];
}

Código 6-35: Criação de matriz com número de colunas diferentes.

Exemplo de um vetor multidimensional:

Figura 6-12: Visão geral de estrutura e implementação de matrizes.

6-25
Coleções, Arrays, String e Wrapper Classes

O método main(String[] args)


Uma referência para um vetor pode ser passada para qualquer método. Um
bom exemplo sito é o método main() usado nas aplicações Java. Quando você
inicia uma aplicação Java, o sistema localiza e chama o método main() para
esta classe.

O método main() recebe um único parâmetro, que é uma referência para


um vetor de objetos String. Cada objeto String guarda um parâmetro da linha
de comando.

O primeiro elemento do vetor contém o primeiro parâmetro da linha de


comando, e não o nome do programa, como ocorre em C ou C++.

É importante notar que os parâmetros da linha de comando são sempre


representados por objetos String. Dentro do método main(), você pode
converter os parâmetros para tipos primitivos.

Se um dos parâmetros, por exemplo, representar um número, você deverá


convertê-lo para um int para que possa realizar operações aritméticas com ele.
Métodos de conversões foram vistos anteriormente.

Na versão do J2SDK 1.5 temos a seguinte possibilidade de receber o vetor de

public class Test {


public static void main(String... args) {
System.out.println(args.length + " argumentos");
}
}

Strings:
Código 6-36: Recepção de parâmetros utilizando J2SDK 1.5.

Utilizamos a expressão “...” para representar uma String dinâmica.

6-26
Coleções, Arrays, String e Wrapper Classes

API Colletion

Em Java a API Collection é referenciada como um framework. Este


framework fornece um conjunto bem defindo de interfaces e classes para
armazenar e manipular grupos de dados, conhecidos como coleções. Este
framework fornece uma conveniente API para muitos dos tipos de dados
abstratos das estruturas: Map, Set, List, Tree, Array, Hashtable e outras coleções.

Este frameword está dentro do pacote java.util, desta forma este pacote
deve ser sempre importado quando se desejar trabalhar com coleções.

Uma simples collection não coloca nenhuma restrição sobre os tipos de


elemento, ordem dos elementos, ou repetição dos elementos dentro da
collection.

Em java,a interface java.util.Collection define a collection básica de


framework para todos tipos de collections. A Interface Collection possui métodos
que permitem você adicionar itens, remover itens, pesquisar, e contar o número
de elementos na collection.

Principais métodos:

- boolean add(Object element)


- boolean remove(Object element)
- void clear()
- int size()
- boolean isEmpty()
- Object[] toArray()

6-27
Coleções, Arrays, String e Wrapper Classes

A interface Iterator

Um Iterator é um objeto que pode ser utilizado para percorrer coleções.


Com os métodos da interface Iterator, você pode percorrer uma collection do
início ao fim e de forma segura remover os elementos da Collection. O iterator
geralmente usa operações de busca.

Métodos:

- remove()
- hasNext()
- next()

Veja abaixo o código exemplo para percorrer uma coleção do tipo


ArrayList utilizando a interface iterator

ArrayList lista = new ArrayList();


...
Iterator it = lista.iterator();
while ( it.hasNext() ) {
String s = (String)it.next();
...
}
Código 6-37: Iterator

6-28
Coleções, Arrays, String e Wrapper Classes

A interface Enumeration

A interface Enumeration permite você percorrer todos os elementos de


uma collection. Percorrer uma collection com uma Enumeration é semelhante a
percorrer com um Iterator. Emumeration não oferece suporte para remover
elementos, coisa esta que você pode fazer com um Iterator.

Métodos:

- boolean hasMoreElements();
- Object nextElement();

Veja abaixo um exemplo de código para percorrer uma Enumeration:

Hashtable tabela = new Hashtable();


...
Enumeration elementos = tabela.elements();
while ( elementos.hasMoreElements() ) {
Produto p = (Produto)elementos.nextElement();
...
}
Código 6-38 Enumeration

6-29
Coleções, Arrays, String e Wrapper Classes

Interfaces do framework

Neste framework existem várias interfaces que podem ser utilizadas, estas
interfaces são estruturas que permitem armazenar objetos de formas específicas.
Vamos ver algumas delas abaixo.

Interface Set
No conceito matemático, um conjunto é justamente um grupo de itens
únicos, sem elementos duplicados.

A interface Set estende a interface Collection. Set não permite duplicatas


dentro da collection. Na implementação Set, null é uma entrada válida, mas
permitida somente um por vez.

Interface List

Como o próprio nome já nos diz, representa uma lista que permite
duplicatas. A interface List estende a interface Collection. Existem duas List
implementations disponíveis na Collections Framework: ArrayList e LinkedList.

A Interface Map
Um map é um tipo especial de grupo sem duplicatas. Dentro da Collections
API, java.util.Map define esta interface. Ele mapeia os valores chaves para os
objetos armazenadao. O valores chaves são usados para procurar, ou indexar os
dados armazenados.

A interface Map não é uma extensão da interface Collection , ele possui


sua própria hierarquia. Map não permite duplicatas dentro da collection. Na
implemantação Map , null é uma entrada válida, mas só é permitido uma vez.
Vamos estudar aqui uma implementação desta interface: Hashtable

6-30
Coleções, Arrays, String e Wrapper Classes

A classe ArrayList

Implementa a interface java.util.List e usa array para armazenamento. Um


armazenamento de array é geralmente mais rápido, mas possui limitações como,
não poder inserir, e apagar entradas no meio da lista. Para realizar este tipo de
adição e exclusão precisamos de um novo array, e isto gera portanto um
overhead muito grande. Você pode acessar qualquer elemento
aleatoricamente.

Esta estrutura não é sincronizada, devendo ser utilizada quando o acesso a


um objeto desta classe tiver uma única thread.

package com.targettrust.exemplos;

import java.util.*;

/**
* - Elementos não ordenados
* - Permite duplicados
* - null É permitido
* - Semelhante a classe Vector
* - Acesso aleatório
*/

public class ArrayListExemplo {


public static void main(String[] args) {
List lista = new ArrayList();
lista.add("Java J2EE");
lista.add("Microsoft Net");
lista.add("Linux");
lista.add("Oracle");
lista.add("Web Designer");
lista.add("Java J2EE");
lista.add(null);

System.out.println( lista.get(0)+", "+lista.get(4) );


// [Java J2EE, Web Designer]
}
}

Código 6-39: Exemplo de ArrayList

6-31
Coleções, Arrays, String e Wrapper Classes

A classe Vector
Uma vez que o tamanho do array é definido você não pode mudar o
tamanho do array. Mas em java isso é possivel utilizando o mecanismo de
refleção do Java que foi introduzido no Java 1.1, mas ele possui suas próprias
limitações.

Esta classe possui os seus métodos sincronizados desta forma deve ser
utilizada quando o acesso ao objeto for concorrente. Já se o acesso não tiver
esta característica então deve ser utilizada a classe ArrayList que não é
sicronizada e é, portanto, mais rápida.

Para lidar com este tipo de situação em Java utilize Vector, ele cresce e
reduz seu próprio tamanho automáticamente. Isso permite somente objetos que
não são primitivos. Para enviar primitivas, converta as primitivas à um objeto e
envie elas para o vector.

O vector realoca e redefine o tamanho automáticamente . Veja o exempo


seguinte.

Vector vt = new Vector(3, 10);

As linha acima representam a capacidade inicial de três, e cresce em


aumento de 10 em cada realocação quando se tenta adicionar o quarto
elemento. Depois de realocado a capacidade do vector se torna a capacidade
inicial + a capacidade aumentada (capacity Increment).

Construtores:

- Vector(int initialCapacity);
- Vector(int initialCapacity, int capacitIncrement);
- Vector()

Métodos:

- void addElement(Object obj);


- int size();
- void setSize(int n);
- void trimToSize();

6-32
Coleções, Arrays, String e Wrapper Classes

Vejamos agora abaixo um exemplo com a classe Vector:

package com.targettrust.exemplos;
import java.util.*;
/**
* - Elementos não ordenados
* - Permite duplicados
* - null É permitido
* - Acesso aleatório
*/
public class VectorExemplo {
public static void main(String[] args) {
Vector lista = new Vector();
lista.add("Java J2EE");
lista.add("Microsoft Net");
lista.add("Linux");
lista.add("Oracle");
lista.add("Web Designer");
lista.add("Java J2EE");
lista.add(null);

System.out.println( lista.get(0) );
System.out.println( lista );
// [Java J2EE]
// [Java J2EE, Microsoft Net, Linux, Oracle, Web Designer, Java J2EE,
null]
}
}

Código 6-40: Exemplo de Vector.

6-33
Coleções, Arrays, String e Wrapper Classes

A classe Hashtable

Hashtable é uma estrutura que possui os elementos armazenados


internamente baseados em chave. Todo objeto guardado dentro desta estrutura
ficará associado a uma chave para posterior recuperação do mesmo.

Esta estrutura é uma das mais rápidas quando o assunto é pesquisa, uma
vez que o acesso ao objeto armazenado é direto através de chaves.

Veja um exemplo abaixo:

package com.targettrust.exemplos;
import java.util.*;
/**
* - Elementos não ordenados
* - NÃO permite duplicados
* - null NÃO é permitido
* - Acesso baseado em chave
*/
public class HashtableExemplo {
public static void main(String[] args) {
Hashtable lista = new Hashtable();
lista.put(new Integer(1), "Java J2EE");
lista.put(new Integer(2), "Microsoft Net");
lista.put(new Integer(3), "Linux");
lista.put(new Integer(4), "Oracle");
lista.put(new Integer(5), "Web Designer");
lista.put(new Integer(6), "Java J2EE");

System.out.println( lista.get(new Integer(1)) );


System.out.println( lista.get(new Integer(4)) );
// [Java J2EE]
// [Oracle]
}
}
Código 6-41: Hashtable

6-34
Coleções, Arrays, String e Wrapper Classes

A classe LinkedList

Implementa a interface java.util.List e usa linked list para armazenamento.


Uma linked list permite que elementos sejam adicionados, removidos da
collection em qualquer posição dentro do container. Com esta implementação
você somente pode acessar os elementos sequencialmente.

Veja abaixo um exemplo de uso desta estrutura:

package com.targettrust.exemplos;

import java.util.*;

/**
* - Elementos não ordenados
* - Permite duplicados
* - null É permitido
*/

public class LinkedListExemplo {


public static void main(String[] args) {
List lista = new LinkedList();
lista.add("Java J2EE");
lista.add("Microsoft Net");
lista.add("Linux");
lista.add("Oracle");
lista.add("Web Designer");
lista.add("Java J2EE");
lista.add(null);

System.out.println( lista );
// [Java J2EE, Microsoft Net, Linux, Oracle, Web Designer, Java J2EE,
// null]
}
}

Código 6-42: Exemplo de LinkedList.

6-35
Coleções, Arrays, String e Wrapper Classes

Generics

Os tipos genéricos representam uma das maiores modificações na


linguagem java já feitas até os dias de hoje. Tipos genéricos trazem a facilidade
de parametrizar o tipo de classes, variáveis ou métodos. Na API do JDK 5.0 todas
as collection, por exemplo, foram parametrizadas. Esta parametrização tornou
classes como, por exemplo, ArrayList capazes de armazenar elementos de um
tipo “E” e não mais Object. A declaração de ArrayList, por exemplo, agora pode
ser feita da seguinte forma:

1 ArrayList<Aluno> alunos = new ArrayList<Aluno>();


2
3 Aluno a = new Aluno("Rafael");
4 alunos.add( a );
5
6 Aluno x = alunos.get( 0 );
7 System.out.println("Nome: "+ x.getNome() );
Código 6-43: Generics

Observe o código acima e perceba que na linha 6 não foi preciso realizar
um “casting” para converter o objeto retirado da coleção. O mesmo havia sido
armazenado internamente como um objeto Aluno e não como sendo do tipo
“Object”.

Além de simplificar a codificação, os tipos genéricos também são uma


grande melhoria de robustez, evitando a possibilidade de typecasts errados e,
portanto a ocorrência de ClassCastException. Isto também, é claro, aumenta a
produtividade, pois perde-se menos tempo com depuração e testes – se o
código compila, é porque vai funcionar, pelo menos no que diz respeito a tipos.

Com os tipos genéricos, o programador pode formalizar restrições


adicionais dos tipos que constrói, por exemplo, determinar que determinada lista
só possa conter instâncias de Produto, e não objetos quaisquer. Violações dessas
regras de tipagem não são críticas para a linguagem (pois não possibilitam perda
de dados nem corrupção de memória), mas são críticas para o desenvolvedor,
pois poderão ocultar erros de lógica. O resultado pode ser uma
ClassCastException, ou – ainda pior – o bug pode ficar oculto por muito tempo,
pois o valor opaco (Object) é convertido para o tipo que deveria ter.

import static java.lang.System.*;


...
...
Aluno a = new Aluno("João", "(51) 3325-2596" );
Map<String, Aluno> hash = new HashMap<String, Aluno>();

// Adiciona um aluno na Hash


hash.put(a.getNome(), a);

// Recupera o Aluno
Aluno x = hash.get( a.getNome() );

6-36
Coleções, Arrays, String e Wrapper Classes

// Mostra dados
out.printf( "Dados: %s %s", x.getNome(), x.getTelefone() );
Código 6-44: Usando generics

Acima você tem um código onde criamos uma HashMap para adicionar
objetos do tipo Aluno associados a chaves do tipo String. Na declaração de
HashMap especificamos que a chave será do tipo String bem como os objetos a
serem armazenados serão Alunos, isto evita qualquer necessidade de “casting”
posterior para recuperar os dados da estrutura, bem como fortalece o sistema de
tipos proibindo que sejam utilizados tipos diferentes dos especificados na
declaração. A linha abaixo não compilaria pois o tipo da chave esta sendo
violada.

hash.put(new Integer(1), a); //ERRO ao compilar!


Código 6-45: Adicionando inteiro como chave

Podemos também cria uma classe com tipos parametrizados. A classe


abaixo representa um grupo no qual podemos guardar objetos. Os objetos a
serem armazenados neste grupo são sempre do tipo especificado no parâmetro
“T” da classe Grupo. Veja o código da classe:

public class Grupo<T> {

private ArrayList elementos;

public Grupo() {
elementos = new ArrayList();
}

public void add(T obj) {


elementos.add( obj );
}

public void remove(T obj ) {


elementos.remove( obj );
}
}
Código 6-46: Criando uma classe de um determinado tipo

Podemos criar um objeto da classe grupo da seguinte forma:

Grupo<String> g = new Grupo<String>();


g.add( "Java J2ME" );
g.add( "Java J2EE" );
g.add( new Integer(3) ); // ERRO!
Código 6-47: Criando grupo de Strings

Observe que na linha onde tentamos adicionar um inteiro teremos


problemas, pois o grupo foi criado como sendo capaz de armazenar somente
Strings. O tipo do grupo pode ser trocado conforme o código abaixo:

Grupo<Aluno> g = new Grupo<Aluno>();

6-37
Coleções, Arrays, String e Wrapper Classes

Aluno a = new Aluno("João", "(51) 3325-2596" );


g.add( a );
g.add( "Java J2EE" ); // ERRO!
g.add( new Integer(3) ); // ERRO!
Código 6-48: Violando acesso ao grupo

O código acima foi alterado para receber objetos aluno e armazenar


dentro do grupo. Veja que a linha onde adicionamos agora uma String ou um
Integer irá sinalizar erro.
Neste exemplo acima os parâmetros do método add(...) bem como do
remove(...) estão parametrizados. Teríamos outra opção para não utilizar os
parâmetros genéricos, bastaria definir o tipo dos métodos como Object. Desta
forma poderia ser criado um grupo e passado qualquer tipo de objeto para ele,
mas isto permitiria que o grupo pudesse armazenar tipos diferentes de objetos.
Para evitar isto você também poderia tornar os tipos dos parâmetros doa
métodos fixos (String, por exemplo), mas desta forma você teria somente um
grupo de strings e não poderia criar grupos de outros objetos. Observe que neste
caso o objetivo é justamente permitir que se possa criar um grupo de tipos
variáveis, mas uma vez definido um grupo como sendo de um tipo, o mesmo não
poderá receber elementos de outros tipos.

6-38
Coleções, Arrays, String e Wrapper Classes

Autoboxing

Autoboxing diz respeito a uma conversão automática que é feita entre os


tipos primitivos da linguagem java (int, float, long, byte, etc...) e suas wrapper
classes (Integer, Float, Long, Byte, Character, etc...). O programador não precisa
mais se preocupar com estas conversões ficando a cargo agora da linguagem.
Este recurso é interessante e simplifica a escrita de códigos porém introduz alguns
efeitos colaterais listados mais abaixo. Deve desta forma ser utilizado de forma
controlada.

Integer i = 10;
System.out.println( i );
Código 6-49: Autoboxing

O código acima atribui um valor primitive (10) a um objeto do tipo Integer.


Logo em seguida o objeto integer é impresso.
Um exemplo:

Quando trabalhamos com Collections sabemos que à uma coleção pode


ser adicionado somente objetos, não sendo possível portanto adicionar um tipo
primitivo a esta. Para isto o tipo primitivo deve ser encapsulado em uma wrapper
class e então o objeto desta wrapper adicionado a Collection. Veja código
abaixo:

ArrayList lista = new ArrayList();

// Criando os objetos
Integer um = new Integer(1);
Integer dois = new Integer(2);
Integer tres = new Integer(3);

// Adicionando os objetos a Collection


lista.add ( um );
lista.add ( dois );
lista.add ( tres );
Código 6-50: Adicionando dados em uma coleção

A mesma dificuldade de manipulação ocorre quando temos que retirara o


valor da coleção, pois se precisarmos manipular o valor como um tipo primitivo
isto exigirá, além de um casting, uma chamada a um método do objeto retirado
para o transformar novamente em um tipo primitivo. Todo este processo de
encapsular e desencapsular é conhecido como: boxing e unboxing. Isto deixa o
código maior e mais complicado de ser escrito.

6-39
Coleções, Arrays, String e Wrapper Classes

Veja abaixo a versão do mesmo agora para retirar os elementos:

// Retira os objetos da coleção


Integer x = (Integer)lista.get(0);
Integer y = (Integer)lista.get(1);
Integer z = (Integer)lista.get(2);

// Mostrar os valores em formato int


System.out.println( x.intValue() );
System.out.println( y.intValue() );
System.out.println( z.intValue() );
Código 6-51: Mostrando dados da coleção sem autoboxing

Podemos utilizar autoboxing para resolver e simplificar a escrita deste


código. Veja abaixo a versão do código que adiciona os três valores dentro da
collection. Observe que utilizamos para isto um tipo de collection que comporta
somente Integer. Esta forma de representação de collection faz uso de Generics.

ArrayList<Integer> lista = new ArrayList<Integer>();

// Adicionando dados a Collection


lista.add ( 1 );
lista.add ( 2 );
lista.add ( 3 );

// Retira os dados da collection


int x = lista.get(0);
int y = lista.get(1);
int z = lista.get(2);

// Mostra os dados da collection


System.out.println( x );
System.out.println( y );
System.out.println( z );
Código 6-52: Utilizando autoboxing para mostrar dados

Comparando valores encapsulados:

Ao compararmos valores que foram encapsulados através do recurso de


autoboxing, temos que tomar muito cuidado. Veja o código abaixo e os tipos de
retorno.

int i = 2;
int j = 2;

ArrayList <Integer> lista = new ArrayList<Integer>();

lista.add(i);
lista.add(j);

System.out.println( (i==j) );
System.out.println( lista.get(0)==lista.get(1) );

6-40
Coleções, Arrays, String e Wrapper Classes

System.out.println( lista.get(0).equals( lista.get(1) ) );


Código 6-53: Comparando dado s de uma coleção

As saídas para o código acima são as seguintes:

true
true
true

A primeira comparação esta comparando tipos primitivos e como já


sabemos a saída deveria ser true. Já no segundo caso são comparados os
valores que foram enpasulados (“autoboxed”), retornando também true. No
terceiro caso são comparados os valores dos objetos criados através de
autoboxing.

Se o valor das variáveis inteiras forem alteradas para um valor superior a


127, o retorno para a comparação lista.get(0)==lista.get(1) será false! Note
que estamos neste caso comparando os valores enpasulados como objetos e
não os valores primitivos diretamente. Para as demais comparações o retorno
continua sendo true. Abaixo a saída para os valores alterados e superiores a 127:

true
false
true

6-41
Coleções, Arrays, String e Wrapper Classes

Espaço para anotações

6-42
Coleções, Arrays, String e Wrapper Classes

Exercícios

1. Neste exercício você irá criar uma classe nova para representar um
pacote de cursos. Um pacote de cursos contém vários cursos. Teremos que saber
quantos há neste pacote, bem como o valor total do pacote. Este valor será a
soma do preço de todos os cursos. Veja abaixo os passos para isto:

Passo 1: Crie uma classe pública chamada PacoteCurso no pacote


com.targettrust.java e defina os seguintes atributos:

private ArrayList<Curso> cursos


private Date dataCriacao

6-43
Coleções, Arrays, String e Wrapper Classes

Observe no atributo acima o uso de generics para definir o tipo de objetos


da coleção

Passo 2: Crie no método construtor o ArrayList.

Passo 3: Defina nesta classe uma operação que possa receber objetos da
classe curso e os adiciona a coleção cursos. Quando esta coleção atingir um
número de 10 cursos deve ser impresso um aviso.

Passo 4: Crie nesta classe uma operação capaz de calcular o valor total do
pacote. Para fazer este cálculo você deverá percorrer a coleção e obter o
preço de cada curso adicionado a ela. Use for-each para percorrer o ArrayList.

Passo 5: Crie uma operação na classe PacoteCurso para que a mesma


retorne uma StringBuilder contendo o nome de todos os curos que fazem parte
do pacote.

2. Agora você irá praticar o mecanismo de autoboxing do java. Vamos criar


um ArrayList com números inteiros e percorrer o mesmo somando estes valores.

Passo 1: Crie uma classe chamada ExemploAutoboxing no pacote


com.targettrust.java e declare nesta classe o método main.

Passo 2: Na classe declare um atributo privado, chamado lista, estático, do


tipo ArrayList. Utilize Generics na declaração do atributo ( ArrayList<Integer> ) No
método main crie este objeto que representa a lista e adicione no mesmo 5
números do tipo int.

Passo 3: Percorra a lista com um for-each e some todos os números da lista


mostrando o total ao término do laço. Observe que não haverá casting para a
soma dos números!

6-44
Java Fundamentals

7. Tratamento de Exceções

7-1
Tratamento de Exceções

Objetivos
• Compreender as Vantagens do tratamento de Exceções em Java;
• Manipular, Tratar, Propagar, Capturar e Criar Exceções.

7-2
Tratamento de Exceções

Introdução
Uma exceção é um evento que ocorre durante a execução de um
programa que interrompe o fluxo normal das instruções. Muitos tipos de erros
podem causar exceções, tais como tentar acessar um elemento de um vetor
fora dos limites ou tentar dividir um número por zero.

Quando uma exceção ocorre dentro de um método Java, o método cria


um objeto Exception e deixa que o ambiente de execução (runtime system) se
encarregue do assunto. Na terminologia Java, este processo é chamado levantar
uma exceção (throwing an exception).

O objeto Exception criado contém informações sobre a exceção, tais


como seu tipo e o estado do programa quando o erro ocorreu.

Após o método levantar uma exceção, o ambiente de execução é então


responsável por encontrar um código que manipule o erro. Para isso, o ambiente
de execução entra em ação procurando o código de tratamento para o erro no
conjunto de métodos da pilha de chamada do método em que o erro ocorreu.

O ambiente de execução procura de trás para frente na pilha de chamada,


começando com o método onde o erro ocorreu, até encontrar um método que
contenha a manipulação apropriada da exceção.

Uma manipulação de exceção é considerada apropriada se o tipo de


exceção levantada é o mesmo tipo de exceção manipulada pelo manipulador
(pelo código de tratamento da exceção).

Assim, a exceção cruza a pilha de chamadas até que uma manipulação


apropriada é encontrada e um dos métodos chamados trata a exceção. Na
terminologia Java, se diz que o manipulador escolhido para tratar a exceção
capturou (catch the exception) a exceção.

Se o ambiente de execução procura exaustivamente em todos os métodos


da pilha de chamadas sem descobrir um manipulador de exceções apropriado,
o ambiente de execução (e conseqüentemente, o programa Java) termina.

A utilização de exceções para manipular erros tem as seguintes vantagens


sobre as técnicas de gerenciamento de erros tradicionais:

1. Separa o código para manipular erros do código regular (do fluxo


normal) do programa

2. Propaga erros na pilha de chamadas

3. Agrupa tipos de erros e os diferencia

4. Não podem ser ignoradas

7-3
Tratamento de Exceções

1a Vantagem: Separação de Código


Na programação tradicional, a detecção e manipulação de erros tornam o
código freqüentemente mais difícil de compreender. Como exemplo, suponha
que se tenha uma função que leia a primeira linha de um arquivo.

Um esquema do código para esta função seria:

int leituraArquivo {
abre o arquivo;
le o arquivo;
fecha o arquivo;
}

Código 7-1: Exemplo de código estruturado para tratamento de arquivos.

Esta função poderia ter diversos erros potenciais, tais como erro na abertura
do arquivo, erro na leitura da primeira linha, erro no fechamento do arquivo.

No modo de programação tradicional para detectar os potenciais erros


para esta função, cada erro deve ser testado e atribuído para um código de
erro. Isto leva a uma grande quantidade de código adicional para a detecção e
manipulação dos possíveis erros.

Por exemplo, a função anterior se tornaria uma função tal como:

int leituraArquivo {
int codigoErro = 0;
abre o arquivo;
if (erroAberturaArquivo)
codigoErro = -1;
else {
le o arquivo;
if (erroLeituraArquivo) codigoErro = -2;
fecha o arquivo;
if (erroFechamentoArquivo) codigoErro = -3;
}
return codigoErro;
}

Código 7-2: Exemplo de código condicional para tratamento de arquivos com retorno de valor.

Na linguagem Java, o problema de tratamento de erros é realizado a partir


exceções.

As exceções permitem que o programador escreva o código do fluxo


principal e manipule os casos excepcionais em outro local.

7-4
Tratamento de Exceções

A função exemplo anterior se tornaria uma função tal como:

leituraArquivo {
try {
abre o arquivo;
le o arquivo;
fecha o arquivo;
}
catch (erroAberturaArquivo) {
manipula erro
}
catch (erroLeituraArquivo) {
manipula erro
}
catch (erroFechamentoArquivo) {
manipula erro
}
}

Código 7-3: Exemplo de código estruturado para tratamento de arquivos utilizando Java.

Como pode ser visto, as exceções não dispensam a detecção e


manipulação dos erros.

O que elas fornecem são meios de separar da lógica principal todos os


detalhes do que fazer quando algum evento fora do normal ocorre. Com isso, o
código se torna mais claro e menos propenso a erros.

7-5
Tratamento de Exceções

2a Vantagem: Propagação de Erros


A segunda vantagem do uso de exceções é a habilidade de propagar a
informação do erro através dos métodos da pilha de chamadas.

Uma exceção em Java é enviada imediatamente para o manipulador


apropriado, sem a necessidade do comando if em cada nível para transmitir o
erro acima na pilha de chamadas.

Suponha que o método do exemplo anterior, leituraArquivo, é o quarto


método em uma série de chamadas de métodos aninhados feitas pelo
programa principal: metodo1 chama o metodo2, que chama o metodo3, que
chama leituraArquivo.

O metodo1 é o único método interessado nos erros que ocorrem dentro do


método leituraArquivo, e é o método que manipula estes erros. Na técnica de
programação tradicional, o metodo2 e o metodo3 teriam que propagar os
códigos de erros retornados por leituraArquivo adiante na pilha de
chamadas até que os códigos de erros finalmente atingissem metodo1, o único
método interessado nestes erros.

Assim, na programação tradicional, seria necessário:

1. leituraArquivo tenta executar sua função e retorna um código de


erro para metodo3

2. metodo3 verifica a existência de erros e transmite o código de erro para


metodo2

3. metodo2 verifica a existência de erros e transmite o código de erro para


metodo1

4. metodo1 verifica a existência de erros e trata os erros ocorridos.

Na manipulação de exceções em Java, contudo, o ambiente de execução


procura de trás para frente na pilha de chamadas, procurando descobrir algum
método que está interessado em manipular uma exceção em particular.

Um método Java pode levantar qualquer exceção dentro dele, permitindo


assim que um método adiante na pilha de chamadas possa capturar esta
exceção. Com isso, apenas os métodos interessados devem se preocupar em
detectar erros.

Deste modo, manipulando exceções, é necessário:

1. leituraArquivo levanta uma exceção

2. metodo1 captura a exceção

7-6
Tratamento de Exceções

Além disso, as exceções que podem ser levantadas dentro de um método


são parte da interface de programação pública do método e devem ser
especificadas na cláusula throws do método.

Com isso, o método informa aos seus chamadores sobre as exceções que
ele pode levantar e estes métodos podem decidir de modo inteligente o que
fazer com tais exceções.

O trecho a seguir demonstra como ficariam os métodos metodo1, metodo2 e


metodo3 manipulando exceções. Como pode ser visto, este código é mais
compacto que na programação tradicional.

public void metodo1() {


try { chamada de metodo2; }
catch (exception) { trata erro; }
}

public void metodo2() throws exception {


chamada de metodo3;
}

public void metodo3() throws exception {


chamada de lePrimeiraLinhaArquivo;
}

Código 7-4: Propagação de exceções utilizando a cláusula throws.

7-7
Tratamento de Exceções

3a Vantagem: Agrupar Tipos de Erros


As exceções freqüentemente se enquadram em categorias ou grupos. Por
exemplo, os erros que podem ocorrer na manipulação de um vetor (tais como
índice fora do intervalo do tamanho do vetor e inserção de elemento de um tipo
errado) podem ser facilmente imaginados como um grupo de exceções, cada
exceção representando um dos tipos de erro.

Do mesmo modo, os erros que podem ocorrer em operações de E/S (tais


como tentar abrir um arquivo com nome inválido e interrupção da operação de
E/S) são facilmente imaginados como um grupo de exceções.

Além disso, é fácil imaginar que alguns métodos queiram fazer o tratamento
das exceções que se enquadrem numa categoria (por exemplo, todas as
exceções referentes a vetores), e outros métodos queiram manipular apenas
exceções específicas (por exemplo, apenas a inserção de um elemento de tipo
errado no vetor).

Todas as exceções que são levantadas dentro de um programa Java são


objetos first-class. Deste modo, o agrupamento de exceções é um resultado
natural da hierarquia de classes. As exceções em Java devem ser instâncias de
Throwable, ou qualquer descendente de Throwable.

Assim como em outras classes, subclasses da classe Throwable podem ser


criadas. Cada classe sem subclasses (folha) representa um tipo específico de
exceção e cada classe com uma ou mais subclasses (nodo) representa um
grupo de exceções.

No esquema abaixo, IOException é uma subclasse de Exception (uma


subclasse de Throwable) e possui duas subclasses:
• FileNotFoundException
• InterruptedIOException.

A classe IOException é uma classe geral de exceções produzidas por falha


ou interrupção de operações de E/S. As suas duas subclasses representam dois
tipos de erro específicos que podem ocorrer em operações de E/S.

7-8
Tratamento de Exceções

Figura 7-1: Hierarquia de classes para tratamento de arquivos.

Um exemplo de manipulador de exceção que trata somente exceções


geradas ao tentar abrir um arquivo com nome inválido

catch (FileNotFoundException e) {
tratamento da exceção
}

(FileNotFoundException) é:

Código 7-5: Bloco catch para tratamento de exceção específica.

Um método pode também capturar uma exceção baseada em seu grupo


ou tipo geral. Isto é feito especificando qualquer uma das superclasses de
exceções no comando catch. Para capturar todas as exceções de E/S,
independente do seu tipo, a manipulação de exceções poderia especificar o
argumento IOException, como no exemplo abaixo.

Esta manipulação iria capturar todas as exceções deste grupo. Para


descobrir exatamente que tipo de exceção ocorreu, o parâmetro e poderia ser

catch (IOException e) {
tratamento
}

usado.
Código 7-6: Bloco catch para tratamento de exceção de grupo (IOException).

É possível também utilizar um manipulador de exceções que trate qualquer


catch (Exception e) {
tratamento
}

exceção:
Código 7-7: Bloco catch para tratamento de qualquer exceção.

Manipuladores de exceções que são muito genéricos, como o mostrado


acima, podem tornar o código mais propenso a erros por capturar e tratar
exceções que não foram previstas e que, portanto, não serão corretamente
tratadas dentro do manipulador.

Deste modo, manipuladores de exceções gerais não devem ser utilizados


como uma regra.

7-9
Tratamento de Exceções

4a Vantagem: Exceções não são Ignoradas


Após um método levantar uma exceção, ela não pode ser ignorada: ela
precisa ser capturada e manipulada em algum local.

7-10
Tratamento de Exceções

Manipulando Exceções
A classe Throwable é a superclasse de todos os erros e exceções na
linguagem Java, possuindo duas subclasses: Error e Exception.

Figura 7-2: Hierarquia de erros Java.

Subclasse Error

Erros são extensões da classe Error, subclasse de Throwable. Subclasses de


Error, diferente das subclasses de Exception, geralmente não devem ser
capturadas, e geralmente causarão o término do programa.

Exemplos deste tipo de erro são falta de memória durante a execução ou


não estar apto a carregar uma classe.

Exceções não verificadas (Unchecked Exceptions)


Exceções não verificadas (exceções de execução) são extensões da classe
RuntimeException. Estas exceções são aquelas que ocorrem dentro do
ambiente de execução Java (JVM). Possíveis exemplos são exceções aritméticas
(tal como divisão por zero), exceções de ponteiro (tal como tentar acessar um
objeto utilizando uma referência nula) e exceções de indexação (tal como tentar
acessar um elemento de um vetor utilizando um índice muito grande).

As exceções de execução podem ocorrer em qualquer local do programa


e o custo para testar se elas realmente ocorrerão é freqüentemente maior que o
benefício de capturá-las. Assim, o compilador não requer que o programador
capture estas exceções, embora isso seja possível.

Porém, embora estas exceções não precisem ser capturadas, elas não
podem ser ignoradas. Se uma exceção de execução ocorre e o código não as
7-11
Tratamento de Exceções

manipula, a máquina virtual irá terminar o programa e imprimir o nome da


exceção e um rastro da pilha.

Exceções Verificadas (Checked exceptions)


Exceções verificadas são extensões da classe Exception. Estas exceções
não são exceções de execução e são verificadas pelo compilador, que se
certifica de que tais exceções serão capturadas e manipuladas em algum outro
local no programa. Exceções criadas pelo programador devem estender a classe
Exception.

7-12
Tratamento de Exceções

Tratando Exceções
Quando um método utilizado pelo programa levantar uma exceção, o
programador possui três opções:

- Capturar a exceção e tratá-la;

- Capturar a exceção e levantar uma exceção diferente, que será


manipulada em outro local;

- Deixar a exceção passar através do método; algum manipulador deve


então capturá-la em outro local.

Para identificar se um método em particular de uma classe padrão do Java


pode levantar uma exceção, a documentação do JDK pode ser utilizada. Todas
as classes padrão do Java são documentadas e uma parte da documentação
de cada método é a lista das exceções que este método pode levantar.

Por exemplo, na classe Integer, para o método parseInt (que transforma


um argumento String em um número decimal com sinal):
public static int parseInt(String s) throws NumberFormatException

7-13
Tratamento de Exceções

Manipulando Exceções
O mecanismo para manipulação de exceções em Java é formado pelos
comandos try/catch/finally.

Para manipular uma exceção, o código deve ser organizado nos seguintes
blocos:

- Incluir as chamadas dos métodos que podem levantar exceções em um


bloco try;

- Inserir um ou mais blocos catch, onde cada um irá capturar uma


exceção em particular;

- Adicionar um bloco finally se desejado. O bloco finally é


executado sempre, mesmo que nenhuma exceção tenha sido
levantada.

O exemplo a seguir demonstra a sintaxe para manipular três exceções


(excecaoTipo1, excecaoTipo2 e excecaoTipo3) que poderiam ser levantadas
por método chamado.

try {
...
chamada do método que pode levantar exceção
...
}catch (excecaoTipo1){
trata excecaoTipo1
}catch (excecaoTipo2){
trata excecaoTipo2
}catch (excecaoTipo3){
trata excecaoTipo3
}finally {
...
}

Código 7-8: Tratamento de exceções utilizando os blocos try/catch/finally.

7-14
Tratamento de Exceções

O Bloco try

O comando try delimita o bloco de código que contem as chamadas para


os métodos que podem levantar exceções. Se uma exceção ocorre dentro de
um bloco try, esta exceção é capturada pelo manipulador de exceções
apropriado associado a este bloco try.

O Bloco catch

Um bloco try pode ser seguido por zero ou mais blocos catch, que
especificam como tratar os vários tipos de exceções. Cada cláusula catch é
declarada com um argumento, que deve ser do tipo Throwable ou uma
subclasse deste.

Quando uma exceção ocorre, o primeiro catch que possui o argumento do


tipo apropriado é ativado. Este argumento deve ser o tipo da exceção do objeto
ou uma superclasse da exceção, e é valido somente dentro do bloco catch.

Não é necessário que haja um bloco catch para todas as exceções


possíveis. Em alguns casos, o tratamento correto é permitir que a exceção seja
propagada acima e possa ser capturada por outro método que esteja na pilha.

O Bloco finally

O bloco finally contém o código responsável pela finalização do método


(tais como fechamento dos arquivos, liberação dos recursos) antes que o
controle seja transferido para outra parte do programa.

O código dentro deste bloco é sempre executado se uma porção do bloco


try é executada, independente de como o código no bloco try é completado.
Em um fluxo normal, o controle atinge o final do bloco try e passa então ao
bloco finally que executa a finalização necessária.

Se o controle deixa o bloco try pelo uso de um comando return ou break,


o conteúdo do bloco finally é executado antes do controle ser transferido
para o novo destino.

Se uma exceção ocorre no bloco try e há um bloco catch local para


manipular esta exceção, o controle é transferido para o bloco catch e
posteriormente para o bloco finally. Se a exceção ocorre e não há um bloco
catch para manipular a exceção, o controle é transferido para o bloco finally
e então propagado para a cláusula catch mais próxima que manipula esta
exceção.

É possível existir um bloco finally sem um ou mais blocos catch anteriores.


Contudo, um bloco try deve sempre existir antes do bloco finally.

7-15
Tratamento de Exceções

Manipulando Exceções: Exemplo Prático


O código a seguir apresenta um exemplo de uma classe, converteNumero,
onde são capturadas duas exceções: ArrayIndexOutOfBoundsException e
NumberFormatException. Esta classe converte o argumento lido no formato
String para um inteiro.

public class ConverteNumero {


public static void main(String argv[]) {
int num;
try {
System.out.println("String lida: " + argv[0]);
num = Integer.parseInt(argv[0]);
System.out.println("Numero: " + num);
}catch (ArrayIndexOutOfBoundsException e) {
System.out.println("Não foi fornecido argumento.");
return;
}catch (NumberFormatException e) {
System.out.println("Não foi possível converter a string" +
"para inteiro. Verifique a string fornecida.");
}finally {
System.out.println("Bloco finally.");
}
System.out.println("Encerrando método");
}
}

Código 7-9: Exemplo de método utilizando tratamento de exceções.

Observe o código acima e descreva o que é impresso na tela nas seguintes


situações:

1. A classe é chamada utilizando: C:\java ConverteNumero 38

2. A classe é chamada utilizando: C:\java ConverteNumero palavra

3. A classe é chamada utilizando: C:\java ConverteNumero

Imagine agora o exemplo acima sem o bloco catch que trata a exceção
ArrayIndexOutOfBoundsException. O que aconteceria se a classe fosse
chamada utilizando apenas converteNumero?

7-16
Tratamento de Exceções

Propagando Exceções
Quando uma exceção não pode ser tratada localmente ou se deseja que
ela seja tratada em outro local, ela pode ser propagada para o código que
chamou o método.

Para propagar uma exceção para o método acima, a exceção deve ser
indicada na declaração do método chamado. Isto é feito utilizando o comando

tipo metodo(parametros) throws excecaoTipo1 {


...
}

throws.
Código 7-10: Propagação de exceções utilizando a cláusula throws.

Observe o exemplo abaixo. O método converteStringParaNumero não


trata a exceção NumberFormatException localmente. Ao invés disso, a exceção
passa automaticamente para o método que chamou
converteStringParaNumero.

O método que chamou converteStringParaNumero pode capturar a


exceção NumberFormatException ou propagar a exceção para um método
acima. Caso ele deseje propagar a exceção, ele deve também conter throws
NumberFormatException em sua declaração.

private void converteStringNumero(String str) throws NumberFormatException {


int num;
try {
System.out.println("String lida: " + str);
num = Integer.parseInt(str);
System.out.println("Numero: " + num);
}finally {
System.out.println("Bloco finally método converteStringNumero.");
}
System.out.println("Encerrando método converteStringNumero");
}

Código 7-11: Tratamento de exceções utilizando a cláusula throws para o contexto anterior.

7-17
Tratamento de Exceções

Lançando Exceções
O programador pode lançar exceções em seu código para indicar alguma
condição anormal. Estas exceções podem ser exceções padrões do sistema ou
exceções criadas pelo programador.

Ao levantar uma exceção, o que está sendo feito na realidade é a criação


de um objeto e a transmissão deste para um método superior. Assim, o objeto
exceção deve ser criado usando o operador new.

O exemplo a seguir apresenta um método que lança uma exceção do tipo


ArrayIndexOutOfBoundsException se verificar que o índice não é válido.

private void testaArgumento(int indice, String vetor[]) throws


ArrayIndexOutOfBoundsException {
if (indice >= vetor.length) {
throw new ArrayIndexOutOfBoundsException();
}
}
Código 7-12: Antecipando uma exceção e criando a exceção correspondente.

7-18
Tratamento de Exceções

Criando Exceções
Para criar as suas próprias exceções, o programador deve estender a classe
Exception. A classe RuntimeException não deve ser estendida, pois esta classe
é utilizada para exceções comuns que não precisam ser verificadas.

O exemplo a seguir cria uma exceção chamada


ExcecaoArgumentoInvalido. Esta exceção possui um único construtor que

public class ExcecaoArgumentoInvalido extends Exception {


public ExcecaoArgumentoInvalido (String message) {
super(message);
}
}

apenas chama o construtor da superclasse.


Código 7-13: Criando uma classe do tipo Exception.

7-19
Tratamento de Exceções

Capturando Exceções e Levantando Exceções


Diferentes
Quando uma exceção é levantada, o método pode capturá-la e levantar
uma exceção diferente para outros métodos.

O trecho a seguir exemplifica esta situação.

Neste método, a exceção ArrayIndexOutOfBounds é capturada e uma


nova exceção do tipo ExcecaoArgumentoInvalido é criada.

public void testaArgumento(int indice, String strvet[]) throws


ExcecaoArgumentoInvalido {
try {
String str = strvet[indice];
}
catch (ArrayIndexOutOfBoundsException e) {
throw new ExcecaoArgumentoInvalido("# elementos incorretos!");
}
}

Código 7-14: Capturando exceções e lançando uma exceção diferente (exceção particular).

7-20
Tratamento de Exceções

Espaço para anotações

7-21
Tratamento de Exceções

Exercícios

1. Neste exercício você irá criar uma exceção personalizada para tratar
erros de validação que podem ser então lançados quando estiver
executando a aplicação e um valor incorreto for passado, por exemplo,
para o método que atribui valores a um produto.

Passo 1: Crie uma classe pública chamada ValidacaoException, esta


classe deve ser criada em um arquivo fonte novo e deve extender a
classe RuntimeException.

Passo 2: Defina um método construtor nesta classe que possa receber


uma String como parâmetro e repasse esta string para a classe pai.

Passo 3: Nas operações de modificação do atributo preco da classe


Produto bem como na operação que atribui um produto a um
ItemPedido faça uso desta classe para se a validação der algum erro
você lançar uma exceção com uma mensagem personalizada para
quem chamou esta operação.

7-22
Java Fundamentals

8. Classes abstratas e Interfaces

T@rgetTrust Treinamento e Tecnologia 8-1


Classes abstratas e interfaces

Objetivos

• Criar classes abstratas


• Definir interfaces

8-2
Classes abstratas e interfaces

Abstração
Em Java você pode definir classes que representem, na definição de um
projeto, um nível mais alto de abstração. Ao usar estas classes o projetista terá
uma visão melhor de como as subclasses devem se parecer e mesmo quais
métodos são obrigatórios em todas as subclasses.

Figura 8-1: Definindo a classe Produto como abstrata para não ser instanciada.

Classes Abstratas
Uma classe abstrata é simplesmente uma classe que não pode ser
instanciada. Somente suas subclasses podem ser instanciadas. Por exemplo,
Produto não contém detalhes suficientes para fornecer algo útil para o negócio.

Um item deve ser um CD ou um DVD. Produto serve, no entanto, como uma


coleção de dados e comportamentos que são comuns a todos os itens
disponíveis para alugar.

Métodos Abstratos
Métodos abstratos vão um passo além da herança padrão. Um método
abstrato definido dentro de uma classe abstrata deve ser implementado pelas
subclasses destas. Esta técnica permite que o projetista de classes decida
exatamente quais comportamentos as subclasses devem ter.

O projetista de uma classe abstrata não consegue determinar como estes


comportamentos serão implementados, mas somente que eles serão
implementados.

Interfaces
Interface é a especificação de um conjunto de métodos, similares a uma
classe abstrata. Além do que uma classe abstrata pode oferecer, uma interface
pode efetivamente permitir herança múltipla.

Uma classe pode implementar um número ilimitado de interfaces, mas pode


estender somente uma única superclasse.

8-3
Classes abstratas e interfaces

Definindo classes abstratas


Java fornece a palavra reservada abstract que indica que uma classe é
abstrata. Por exemplo, a classe Produto no exemplo abaixo foi declarada como

sendo abstrata:
Figura 8-2: Definições das classes Produto, DVD e CD. Herança e Abstração da classe Produto.

Produto é declarado como uma classe abstrata porque não possui


informação ou comportamento suficientes para representar um objeto
autônomo.

O usuário não deveria ser capaz de criar objetos Produto, porque Produto
é apenas uma classe parcial e intermediária. Produto existe somente para que
possa ser estendida por classes mais especializadas, tais como CD ou DVD.

O que acontece se você tentar instanciar uma classe abstrata?

Produto produto = new Produto(); // Erro de compilação

Código 8-1: Erro de compilação na criação de uma instância de uma classe abstrata.

Se você tentar criar um objeto Produto em seu programa, o compilador


acusará um erro. O usuário pode criar somente objetos de subclasses concretas:

CD cd = new CD(); // Herdando Produto


DVD dvd = new DVD(); // Herdando Produto

Código 8-2: Criação com sucesso de instâncias especializadas.

O modificador abstract pode ser aplicado somente a classes e métodos.


Classes abstratas provem um modo de adiar a implementação de métodos para
8-4
Classes abstratas e interfaces

subclasses. Uma classe abstrata não pode ser instanciada, ou seja, não podemos
chamar seus construtores.

8-5
Classes abstratas e interfaces

Métodos Abstratos
Ao projetar uma hierarquia de classes, há provavelmente algumas
operações que todas as classes deverão ter, cada qual de sua própria maneira.
Por exemplo, em um negócio de aluguel de fitas, o vendendo deve saber se um
item pode ser alugado ou não.

Cada tipo de item, entretanto, determina se pode ser alugado de uma


maneira específica.

Para representar este conceito em Java, o método “este item pode ser
importado” é definido na classe Produto. Entretanto, não há uma
implementação sensata para este método em Produto, porque cada tipo de
item tem suas próprias exigências.

Uma abordagem, seria deixar o método vazio na classe Produto:

public abstract class Produto {


public boolean isImportado() {
return true;
}
}

Código 8-4: Definindo métodos concretaos em classes abstratas.

Esta abordagem não é boa o suficiente porque não força cada uma das
subclasses concretas a sobrescrever o método. Suponha, por exemplo, que a
classe DVD se esqueça de sobrescrever o método isImportado(), o que
aconteceria se o usuário chamasse este método a partir de uma referência para
DVD?

O método isImportado() definido em Produto seria chamado e retornaria


true. Este não é o resultado desejado. A solução é declarar o método como
abstrato, como mostrado logo a seguir.

8-6
Classes abstratas e interfaces

Definindo métodos abstratos


Para declarar um método abstrato em Java, você deve colocar antes do
nome do método a palavra reservada abstract. Veja o exemplo:

public abstract class Produto {


public abstract boolean isImportado();
}

Código 8-5: Definindo métodos abstratos em classes abstratas.

Ao declarar um método abstrato, você deve fornecer somente a assinatura


do método, que compreende: o nome do método, a lista de parâmetros
esperados e o tipo de retorno. Você não fornece um corpo para o método.

Cada subclasse concreta deverá sobrescrever este método e fornecer o seu


próprio corpo.

Agora que o método é declarado abstrato, todas as subclasses devem


fornecer uma implementação para ele.

Subclasses abstratas podem conter métodos que não são declarados


abstratos. Estes métodos podem ser sobrescritos pelas subclasses, mas isto não pé
necessariamente obrigatório.

8-7
Classes abstratas e interfaces

Interfaces
Uma interface é similar a uma classe abstrata, exceto que ela não possui
nenhum método concreto ou variáveis de instância. É apenas uma coleção de
declarações de métodos abstratos e constantes  isto é, variáveis declaradas
como static public final.

Uma interface é como um contrato que a subclasse deve obedecer.

Qualquer classe que implemente uma interface deve implementar todos os


métodos especificados na interface. Uma classe pode implementar muitas
interfaces, mas pode estender apenas uma única classe. Java não suporta
herança múltipla, mas permite a implementação de múltiplas interfaces.

Como já foi dito anteriormente, CD herda todos os atributos e


comportamentos de Produto. Além disto, deve fornecer implementação para
cada um dos métodos definidos em cada interface que implementar.

Estes métodos podem ser usados por outras classes para implementar
comportamentos específicos como, por exemplo, uma rotina de ordenamento.

8-8
Classes abstratas e interfaces

Exemplos de interfaces
Interfaces descrevem um aspecto de comportamento que muitas classes
possuem. O nome de uma interface é geralmente um comportamento como:
Compra, Venda, etc. Difere, portanto, do nome de uma classe, que é usualmente
um substantivo, como cliente ou produto.

A interface java.sql.Connection possui os métodos a seguir:

- void commit()

- void rollback()

- void close()

- void clearWarnings()

- Statement createStatement()

- void setAutoCommit(boolean)

- boolean isClosed()

Qualquer classe que precise trabalhar com conexão de bando de dados


pode implementar a interface Connection.

As classes que implementam uma interface podem ser completamente


distintas e não ter uma nada a ver com as demais.

A única coisa que devem ter necessariamente em comum é a necessidade


de todas serem móveis.

8-9
Classes abstratas e interfaces

Definindo Interfaces

Você pode definir uma interface usando a palavra reservada interface.


Todos os métodos especificados em uma interface são implicitamente públicos e
abstratos (public e abstract). Quaisquer variáveis definidas em uma interface
são implicitamente públicas, estáticas e constantes (public, static e final).

public interface Connection {


public void commit();
public void rollback();
public void close();
public void clearWarnings();
public Statement createStatement();
public void setAutoCommit(boolean b);
public boolean isClosed();

// entre outros métodos ...


}
Código 8-6: A interface Conncetion do pacote java.sql.

Portanto, a definição acima equivaleria à seguinte definição:


public interface Connection {
public abstract void commit();
public abstract void rollback();
public abstract void close();
public abstract void clearWarnings();
public abstract Statement createStatement();
public abstract void setAutoCommit(boolean b);
public abstract boolean isClosed();

// entre outros métodos ...


}
Código 8-7: A interface Conncetion do pacote java.sql equivalente.

Porque os métodos de interface são implicitamente públicos e abstratos é


prática geralmente aceita não especificar estes modificadores de acesso. O
mesmo é verdade para variáveis.

Como são implicitamente públicas, estáticas e finais, geralmente estes


modificadores não são especificados. Cada interface pode ser representada em
um arquivo Java separado com o nome da interface. (neste caso poderíamos
criar um arquivo chamado Connection.java do pacote java.sql)

8-10
Classes abstratas e interfaces

Implementando Interfaces
O exemplo abaixo mostra o exemplo da classe OracleConn que implementa
a interface Conncetion. OracleConn deve implementar todos os métodos de
todas as interfaces que ele declare implementar (interfaces presentes na cláusula
implements).

Neste caso, OracleConn deve implementar os métodos contidos na


interface Connection.
public class OracleConn implements Connection {

public void commit() {


//... código
}

public void rollback() {


//... código
}

Código 8-8: A classe OracleConn deve implementar TODOS os métodos da interface Conncetion.

Uma classe pode implementar mais de uma interface se ela desejar,


bastando para tal especificar uma lista de interfaces separadas por vírgula.

Considere o seguinte exemplo:

public class Aplicacao extends JFrame implements InterfaceI, InterfaceII {

Código 8-9: A classe Aplicacao pode implementar mais de uma interface.

Aqui, Aplicacao implementa duas interfaces: InterfaceI e InterfaceII.


Isto significa que a classe Aplicacao deve implementar todos os métodos
declarados em InterfaceI e InterfaceII. A classe Aplicacao terá o
comportamento definido nas duas interfaces.

8-11
Classes abstratas e interfaces

Espaço para anotações

8-12
Classes abstratas e interfaces

Exercícios

1. Crie uma interface para representar uma fila de impressão

Passo 1: Declare uma interface em um arquivo novo com os seguintes


comportamentos:

boolean imprimir()
void parar()
boolean remover(Documento doc)
void refresh()
void pausa()

Passo 2: Crie uma classe para implementar esta fila de impressão e


codifique as operações nela

2. Trabalhando com classes abstratas

Passo 1: Torne a classe Produto abstrata adicionando a palavra reservada


abstract na assinatura da classe.

Passo 2: Tente criar um objeto desta classe agora. É possível?

Passo 3: Transforme a operação que retorna o preço abstrata uma vez que
a forma de calcular o preço irá depender de cada uma das subclasses.

8-13