Você está na página 1de 209

André L. N.

Campos
Artur F. Campos

JAVA START
1ªEdição

SMI

www.sistemamoderno.com.br
Copyright © 2018 by André L. N. Campos
Copyright © 2018 by Artur F. Campos

Nenhuma parte desta publicação poderá ser reproduzida sem autorização


prévia e escrita dos detentores dos direitos autorais da obra, acima descritos.
Este livro publica nomes comerciais e marcas registradas de produtos
pertencentes a diversas companhias. Os autores utilizaram essas marcas para
fins exclusivamente editoriais e em benefício dos proprietários das marcas, sem
nenhuma intenção de atingir seus direitos.
Abril de 2018

Produção: Sistema Moderno de Informação


Editor responsável: Deise Farias
Capa: André Campos
Diagramação/Design: Sistema Moderno de Informação
Revisão final: Sistema Moderno de Informação

Dados Internacionais de Catalogação na Publicação (CIP)

C186j Campos, Andre


C186j Campos, Artur
Java Start / Andre L. N. Campos, Artur F. Campos. 1.ed.
– Rio De Janeiro, 2018.
212 p. : il.;23cm.

ISBN: 978-85-45563-00-6

1. Linguagem de programação. 2. JAVA. 3. Certificação. I.


Título.

CDU 004.4

Direitos reservados por André L. N. Campos e Artur F. Campos.


Serviço ao cliente: www.sistemamoderno.com.br/contato.aspx
Sumário
1. JAVA – Uma (muito) breve história ............................................................ 1
SUN ..................................................................................................................................... 1
Oracle .............................................................................................................................. 1
WORA................................................................................................................................... 2
Conclusão ....................................................................................................................... 3
Desafio ............................................................................................................................ 4
2. Ambiente de desenvolvimento e primeiros programas ....................... 5
Primeiros programas ................................................................................................ 9
Erros de gravação ................................................................................................... 13
Conclusão ..................................................................................................................... 13
Desafio .......................................................................................................................... 13
3. Tipos de dados...................................................................................................... 14
Dados primitivos ..................................................................................................... 14
String ............................................................................................................................ 17
4. Classes – Primeira visita ............................................................................ 18
Conceitos utilizados no Java.......................................................................... 20
Criando e utilizando uma classe ................................................................... 20
Utilizando uma classe .......................................................................................... 22
Atributos ..................................................................................................................... 24
Pacotes e visibilidade ....................................................................................... 26
Protegendo os atributos ..................................................................................... 29
5. Ambiente de desenvolvimento integrado ................................................. 32
Instalando o Eclipse ............................................................................................ 32
Usando o Eclipse ..................................................................................................... 34
6. Adicionando Inteligência ............................................................................... 43
if/else .......................................................................................................................... 43
Switch ............................................................................................................................ 48
7. Arrays ........................................................................................................................ 52
8. Repetições ............................................................................................................... 59
while .............................................................................................................................. 59
do/while ....................................................................................................................... 60
for ................................................................................................................................... 62
for e Array................................................................................................................. 64
Pare o loop, eu quero descer .......................................................................... 66
9. Classes - Revisita ............................................................................................ 70
Encapsulamento .......................................................................................................... 72
Polimorfismo .............................................................................................................. 75
Herança .......................................................................................................................... 77
10. Interfaces ............................................................................................................... 83
Criando uma interface .......................................................................................... 83
Implementando uma interface ............................................................................ 84
Por que são importantes? ................................................................................... 86
11. Coleções ................................................................................................................... 87
Interfaces ................................................................................................................... 87
Usando coleções ....................................................................................................... 88
Tipos de coleções ................................................................................................... 93
Coleção de classes................................................................................................. 93
Criando um construtor .......................................................................................... 95
12. Tratamento de exceções ................................................................................... 99
Classes de exceção............................................................................................... 100
Pegando o objeto ................................................................................................... 101
Entrando no jogo ................................................................................................... 104
Conclusão ................................................................................................................... 108
Desafio ........................................................................................................................ 108
13. Interface gráfica ............................................................................................. 109
JavaFX .......................................................................................................................... 109
Primeira aplicação JavaFX............................................................................... 112
Conclusão ................................................................................................................... 119
Desafio ........................................................................................................................ 119
14. Construindo telas ............................................................................................. 120
Controles essenciais .......................................................................................... 120
Label, TextField e Button............................................................................... 121
Organizando na tela ............................................................................................ 122
Pane .......................................................................................................................... 123
FlowPane ................................................................................................................. 125
HBox e VBox .......................................................................................................... 126
BorderPane ............................................................................................................. 129
AnchorPane ............................................................................................................. 132
TilePane ................................................................................................................. 133
StackPane ............................................................................................................... 135
GridPane ................................................................................................................. 136
Conclusão ................................................................................................................... 138
15. Colocando o motor no carro ........................................................................ 139
Eventos ........................................................................................................................ 139
Calculando o IMC ................................................................................................... 143
Jogando dados .......................................................................................................... 146
Conclusão ................................................................................................................... 151
Desafio ........................................................................................................................ 151
16. Interface gráfica – Uma mão na roda ................................................... 152
Scene Builder .......................................................................................................... 152
Instalando o Scene Builder ............................................................................ 152
Integrando o Scene Builder ao Eclipse ................................................... 154
Estrutura de uma aplicação com Java FX Scene Builder ................. 156
Uma aplicação do Java FX Scene Builder ................................................. 157
Conclusão ................................................................................................................... 166
Desafio ........................................................................................................................ 166
17. Strings.................................................................................................................... 167
Classe ou tipo de dados? ................................................................................. 167
String pool............................................................................................................... 168
Métodos da classe String ................................................................................. 170
length ...................................................................................................................... 171
indexOf.................................................................................................................... 171
charAt ...................................................................................................................... 171
substring ............................................................................................................... 172
trim .......................................................................................................................... 173
replace.................................................................................................................... 173
Eu nasci assim, assim vou ficar ................................................................. 173
Criptógrafo............................................................................................................... 174
Conclusão ................................................................................................................... 181
Desafio ........................................................................................................................ 181
18. Classes – Visita final ................................................................................. 182
Pacotes ........................................................................................................................ 182
Modificadores de acesso ................................................................................... 184
Modificadores de comportamento ................................................................... 190
Abstract ................................................................................................................. 192
final ........................................................................................................................ 194
static ...................................................................................................................... 194
Conclusão ................................................................................................................... 200
Desafio ........................................................................................................................ 200
19. Partiu?.................................................................................................................... 201
1. JAVA – Uma (muito) breve história

A história do JAVA é razoavelmente bem conhecida. Ela é fundamental para o


aprendizado da linguagem? Não. Mas acho que ter um conhecimento elementar dessa
história pode ser um motivador para o aprendizado. Então falemos brevemente sobre ela.

SUN

A empresa que criou o JAVA foi a SUN Microsystems, criada em 1982 por 4 alunos da
Stanford University. Por isso o nome SUN: Stanford University Network. A empresa
entrou inicialmente no mercado de desktops com processadores comprados da Motorola
e mais tarde passou a construir seus próprios processadores, os famosos Sparc. Alguns
anos depois passou a se concentrar em equipamentos servidores, mas rodou outros
projetos paralelos de pesquisa e implementação, tanto na área de hardware quanto na
área de software. Inclusive chegou a comprar outras companhias e tecnologias, como o
MySql que foi comprado por cerca de um bilhão de dólares em 2008.
Muitas das interessantes criações da SUN nasceram do SUN Microsystems Laboratories,
incluindo a linguagem Java. A linguagem foi criada com o objetivo de ser compatível com
diversas plataformas que já rodavam em equipamentos eletrônicos na época, ou seja, em
1991. O objetivo seria algo como: “escreva uma vez e rode em qualquer lugar”. Isso era
bem melhor do que ter que escrever um programa diferente para cada uma das
plataformas existentes ou que poderiam vir a existir.
Isso mesmo, o Java não foi originalmente criado pensando em computadores, mas sim em
dispositivos eletrônicos. O primeiro projeto seria o de uma TV interativa. Mas não deu
muito certo. Muito avançado para a época. O projeto foi meio que engavetado.
Mas, poucos anos depois, por volta de 1994/1995, a coisa voltou à tona. Desde 1990 a
Internet começava a dar seus primeiros passos no mundo, digamos, mais comercial
(embora tivesse sido criada mesmo em 1969 para fins militares). E computadores diversos
seriam utilizados para acessar a Internet, com plataformas diferentes e sistemas
operacionais diferentes. O Java parecia agora uma ideia muito interessante. Em 1995 a
empresa dona do maior navegador Internet da época, a Netscape, fez uma parceria com a
SUN para rodar applets2 Java dentro de seu navegador. Daí para a popularização da
linguagem Java foi uma questão de tempo (pouco tempo).

Oracle

No entanto, a SUN não resistiu ao tempo e acabou sendo comprada pela Oracle em
2010. A Oracle manteve as patentes e os projetos que valiam a pena, inclusive o da
linguagem Java, é claro.
É claro que o Java foi muito além dos applets para Internet. Graças ao seu conceito de
“escreva uma vez e rode em qualquer lugar” o Java hoje está rodando em todo lugar,

2
Pequenos componentes de software que executam atividades específicas e rodam dentro de softwares
maiores que os contém.

1
desde equipamentos eletrônicos, eletrodomésticos, carros, celulares, computadores, até os
servidores corporativos com aplicações gigantescas e muito complexas.

WORA

Write once, run anywhere. Ou seja, escreva uma vez, rode em qualquer lugar. Esta é a base
inicial do sucesso do Java e certamente o motivo fundamental de sua disseminação,
virtualmente para todas as direções. Mas o que é isso na prática?
Imagine a quantidade de equipamentos eletrônicos diferentes que existiam na década de
1990. Com diversos fabricantes, os sistemas operacionais desses equipamentos poderiam
ser também diversos. Quando o Java foi originalmente pensado o objetivo era resolver
exatamente este tipo de problema. Naquela época um software construído em C era
compilado para o sistema operacional específico. Na verdade, é assim até hoje.
Por exemplo, imaginemos que tenhamos construído um software em linguagem C para
monitorar diversos aspectos do computador, tais como a temperatura do processador, a
velocidade do cooler, a velocidade de rotação do disco rígido, entre outras. Considerando
que as plataformas rodam sistemas operacionais diferentes e até com processadores de
linhas diferentes, provavelmente os programas deverão considerar essas diferenças. Isso
provavelmente significa código ligeiramente diferente para cada plataforma.
Adicionalmente, estes programas precisam ser compilados para suas respectivas
plataformas. Veja o exemplo na Figura 1. Agora imagine que não se trata apenas do
Monitor.c, mas de 30 programas construídos nesta linguagem. Seriam 90 versões a serem
gerenciadas. Isso não tem nada a ver com write once, run anywhere.

Figura 1. Programa específico para cada plataforma.

Agora imagine se fosse possível escrever apenas um Monitor.c e o compilador se virasse


para gerar um software que rodasse em todas as plataformas existentes. Neste caso, seria
necessário gerenciar apenas uma versão de cada um dos programas e não uma versão para
cada plataforma para cada programa. Mas foi exatamente isso que pensou a equipe que
bolou o Java. No caso deles, eles poderiam construir um código que rodaria em qualquer

2
equipamento eletrônico, ou seja, em qualquer plataforma. Mas como eles conseguiram
essa mágica?
Eles tiveram a seguinte ideia: e se a gente construísse um “executor” para cada plataforma
e que executasse nosso código único, realizando os ajustes necessários para cada uma
dessas plataformas? E foi isso o que fizeram. Eles construíram um componente de
software que conhecia cada uma das plataformas, de modo que ele pudesse receber um
mesmo código de programa e executar “traduzindo” para as especificidades de cada uma
das plataformas. Eles batizaram este componente de Java Virtual Machine (máquina
virtual Java), ou JVM.
Com esta solução não seria mais necessário manter uma versão e realizar uma compilação
para cada plataforma diferente, como pode ser visto na Figura 2. Seria necessário escrever
o código uma vez em Java, compilar e depois rodar em qualquer sistema operacional que
contivesse previamente instalada uma Java Virtual Machine. Em outras palavras, write
once, run anywhere.

Figura 2. Programa único para todas as plataformas.

Essa solução acabou sendo revolucionária e deu muito certo para a popularização e
consolidação do Java. Considerando que a linguagem Java foi criada a bem menos tempo
que linguagens como Cobol, Pascal, C, SQL, C++ e em relativamente pouco tempo
alcançou tamanha popularidade e aplicação real, é preciso concordar que essa estratégia
realmente deu certo. Claro, há outros importantes fatores envolvidos, como o tipo de
licenciamento. Mas o WORA foi fundamental para o sucesso da plataforma Java.

Conclusão

Você conheceu neste capítulo um pouco da história da plataforma Java. Chamamos de


plataforma porque não se trata apenas de uma linguagem e seu compilador, mas também
de um conjunto de outras tecnologias que inclui a famosa JVM (Java Virtual Machine). E
foi graças a esta JVM que o Java implementou o conceito WORA e alcançou tantos
equipamentos diferentes em todo o mundo.

3
Desafio

Conheça um pouco mais sobre a história do Java. Sabia que o primeiro nome da
linguagem era Oak? Descubra o motivo. Por que a linguagem passou a ser chamada de
Java? Por que a SUN faliu? Por que a IBM não comprou a tecnologia, ao invés da Oracle?

4
2. Ambiente de desenvolvimento e primeiros programas

O que precisamos para começar a escrever nossos códigos em Java? Considerando o que
foi visto no capítulo anterior, precisaremos minimamente do seguinte ambiente: algo com
o que escrever nosso código, o compilador Java e a máquina virtual Java para executar
nossos programas, conforme Figura 3.

Figura 3. Elementos necessários para construir software em Java.

A maioria dos computadores já possui o Java Runtime Environment (JRE) instalado por
alguma necessidade anterior. Este ambiente de execução inclui a máquina virtual Java
(JVM), as classes que são utilizadas durante a execução de programas Java e alguns outros
componentes para esta execução, como pode ser visto na Figura 4. Este ambiente é tudo o
que se precisa para se executar software construído em Java.

Figura 4. Ambiente para execução de aplicações em Java.

No entanto, para construirmos software utilizando a plataforma Java precisaremos de um


ambiente de desenvolvimento, que incluirá o JRE e mais alguns componentes, tais como

5
o compilador Java, ferramentas de interface e de debug do código (falaremos sobre isso
mais adiante). Este ambiente, chamado de Java Development Kit (JDK), está representado
na Figura 5.

Figura 5. Ambiente para desenvolvimento de aplicações Java.

Então, o primeiro passo é instalar o JDK. Ele adicionará o compilador e demais


ferramentas necessárias para o desenvolvimento de software com Java. Para isso, baixe o
JDK mais recente (ready for use) a partir do site:
http://www.oracle.com/technetwork/java/javase/downloads/jdk9-
downloads-3848520.html
Por ocasião da publicação deste livro o JDK mais recente era o JDK 9. Utilize sempre o
JDK mais recente. E não se preocupe porque a plataforma Java mantém compatibilidade
com versões anteriores. Assim, todo o conteúdo deste livro será plenamente aplicável ao
Java 9 ou a versões superiores (10, 11, 12, etc). No entanto, alguns dos exemplos poderão
não rodar adequadamente em versões anteriores ao Java 9.
Selecione o JDK mais adequado para o seu sistema operacional, ou seja, tanto ao sistema
operacional quanto ao modelo de implementação deste sistema, se é de 32 ou de 64 bits.
Os exemplos utilizados neste livro serão compilados e executados em sistema operacional
Windows. Mas, graças ao conceito de WORA eles funcionarão perfeitamente em
qualquer sistema operacional.
No caso da instalação do JDK em sistema operacional Windows será necessário criar uma
variável de ambiente3, a JAVA_HOME e alterar a variável de ambiente Path de modo

3
Variável de ambiente é uma variável do sistema operacional que contém informações tais como caminhos
de pastas para uso do sistema e outras preferências. Estas variáveis podem ser criadas ou alteradas.

6
que ela aponte também para a pasta onde estão os executáveis do Java que utilizaremos,
ou seja %JAVA_HOME%\bin.
Não sabe como alterar essas variáveis de ambiente? Clique no botão Iniciar, digite path e
selecione a opção “Editar as variáveis de ambiente para sua conta” que vai aparecer na
tela. Crie a variável JAVA_HOME, clicando no botão Novo. O valor da JAVA_HOME
deve conter o caminho para a pasta do JDK que você acabou de instalar. Deve ser algo
parecido com “C:\Program Files\Java\jdk-9.0.4”. Para ter certeza chame o Windows
Explorer, vá no disco C: (ou no disco de sistema), na pasta Program Files (ou Arquivo de
Programas), depois na pasta Java e veja ali o nome correto da pasta do JDK. Esta é a pasta
para a qual o JAVA_HOME deve apontar.
Em seguida altere a variável de ambiente Path para conter também o valor
%JAVA_HOME%\bin, conforme apresentado na Figura 6. Observe que nesta figura a
JAVA_HOME foi apontada para o jdk-9.0.4, utilizado neste livro.

Figura 6. Configuração das variáveis de ambiente.

7
Com isso configurado vamos começar a programar! Por enquanto, vamos criar uma pasta
para guardar e compilar nossos primeiros programas. Pode ter qualquer nome e estar em
qualquer lugar. Para os trabalhos com este livro criei uma pasta chamada Codigo Java
(sem acento) diretamente na raiz da unidade de disco C:. Você poderá fazer o mesmo.
Agora preciso fazer um alerta: utilizaremos um prompt de comando por algum tempo
para compilar e executar nossos códigos. Um prompt de comando é aquela janela de
fundo preto onde podemos digitar comandos diretamente. Para carregar o seu prompt de
comando clique no botão Iniciar do Windows e digite prompt. Clique na opção de menu
Prompt de Comando e pronto, é só usar.
Você não sabe usar um prompt de comando? Sem problemas, vou te passar uns poucos
comandos simples e fáceis que utilizaremos por um tempo na Tabela 1.
Tabela 1. Lista de comandos de prompt.

Comando Função Exemplos


cls Limpa a tela cls (só isso mesmo 😊)

dir
Exibe lista de dir c:\ (mostra arquivos e pastas que estão na raiz da
dir
arquivos e pastas unidade c:).
dir *.java (mostra arquivos com a extensão java).

cd \ (vai para a raiz da unidade atual).


cd \Program Files (vai para dentro da pasta Program
cd Muda de pasta
Files a partir da raiz da unidade atual).
cd .. (volta para a pasta anterior)

md \Codigo Java (Cria uma pasta Codigo Java a partir


md Cria uma pasta
da raiz da unidade atual).

rd \Codigo Java (Exclui a pasta Codigo Java a partir


da raiz da unidade atual, apenas se não houver
rd Exclui uma pasta arquivos e pastas dentro da pasta a ser excluída).
Obs: Cuidado com este comando, não vá excluir pastas que
não deveria, Ok?

Além dos comandos apresentados na Tabela 1 também usaremos bastante dois programas
executáveis que foram adicionados pelo JDK: o javac.exe e o java.exe. Estes programas
estão dentro da pasta bin que fica dentro da pasta do JDK. O javac.exe é o compilador
Java e o java.exe é o runtime que executa os códigos java compilados.

8
Primeiros programas

Nosso primeiro programa se chamará, coincidência ou não, PrimeiroPrograma.java e será


editado no Notepad mesmo. Utilize qualquer editor padrão TXT, como o Notepad ou o
Notepad++ (prefira o Notepad++, excelente e gratuito). O código será o seguinte:
class PrimeiroPrograma {
public static void main (String args[]) {
System.out.println("Este programa está exibindo qualquer coisa na
tela");
}
}

Depois de salvar o programa na pasta criada, carreguei o prompt de comando fui até a
pasta digitando Cd C:\Codigo Java. E seguida exibi o conteúdo da pasta com o comando
dir, conforme a Figura 7.

Figura 7. Pasta “Codigo Java” com primeiro programa.

O próximo passo é compilar o código e para isso utilizaremos o javac.exe, o compilador


que foi instalado pelo JDK e está na pasta bin, dentro da pasta indicada por
JAVA_HOME. A compilação ocorrerá conforme apresentado na Figura 8.

9
Figura 8. Programa compilado e arquivo .class gerado.

É importante que o nome do programa esteja exatamente igual ao nome da classe que
escrevemos dentro do código, ou seja class PrimeiroPrograma e PrimeiroPrograma.java.
Note que o Java é case sensitive, ou seja, ele faz toda a diferença entre minúsculas e
maiúsculas. Também é muito importante informar para o javac o nome completo do
arquivo, incluindo sua extensão .java.
Se tudo deu certo o javac gerou um novo arquivo, o PrimeiroPrograma.class. Este arquivo
é a compilação que será executada pela máquina virtual Java, ou JVM. Então, o próximo
passo e enviar este arquivo para execução. A JVM é invocada pelo java.exe, que também
está lá na pasta bin dentro do JAVA_HOME.
A execução do programa pode ser vista na Figura 9. Note que neste casso não é preciso
informar a extensão. A JVM já sabe qual extensão procurar ao tentar executar um
programa Java.

Figura 9. Execução do PrimeiroPrograma.class.

10
Fácil demais né? Então vamos construir nosso segundo programa. Não se preocupe ainda
com a questão do class static void main (String args[]) e nem com os detalhes dos
comandos que estamos utilizando. Vamos ver tudo isso em detalhes. O importante agora
é testar o processo de construção, compilação e execução de programas em Java, Ok?
O SegundoPrograma.java, também construído em Notepad, pode ficar assim;
class SegundoPrograma {
public static void main (String args[]) {
int idade;
idade=35;
System.out.println("A pessoa com esta idade já viveu aproximadamente");
System.out.println(idade*365);
System.out.println("dias");
}
}

Note que ao programar em Java é necessário terminar com um ponto e vírgula no final da
linha. Neste código nós criamos uma variável do tipo inteiro e em seguida atribuímos a
ela o valor 35. Depois usamos alguns comandos para exibir uma mensagem e calcular
quantos dias essa pessoa já viveu, aproximadamente, considerando 365 dias para cada ano
vivido. Vamos compilar e executar? O resultado pode ser visto na Figura 10.

Figura 10. Compilação e execução do SegundoPrograma.java.

Agora que está ficando cada vez mais fácil, vamos para o TerceiroPrograma.java. O código
a seguir cria variáveis do tipo double, que são mais adequadas para representar valores
monetários, pois poderemos precisar das casas decimais. Depois o valor do boleto é
atribuído, uma multa é calculada com base em um fator de 10% e em seguida o valor
final do boleto com a multa é calculado. Finalmente as informações são apresentadas em
tela.
class TerceiroPrograma {

public static void main (String args[]) {

11
double boletoValor;
double valorMulta;
double valorTotal;

boletoValor=450;
valorMulta=boletoValor*0.10;
valorTotal=boletoValor+valorMulta;

System.out.println("Valor do seu boleto: "+boletoValor);


System.out.println("Percentual da multa: 10%");
System.out.println("Valor da multa: "+valorMulta);
System.out.println("Total a pagar: "+valorTotal);
}
}

Depois de salvo com TerceiroPrograma.java na mesma pasta dos dois primeiros, é possível
compilar com o javac e depois executar com o java. Observe a compilação e execução na
Figura 11.
Note na Figura 12 a lista de programas com código fonte em Java e seus respectivos
arquivos com extensão .class. Esses arquivos são compilados pelo javac.exe, produzindo o
que chamamos de arquivos de byte-code. Os byte-codes, por sua vez, são executados pelo
JVM. Por isso, se desejar, pode enviar os arquivos .class para qualquer pessoa que tenha a
JVM instalada em seu computador e essa pessoa poderá executar o seu programa. Ele não
precisará do código fonte. Seu arquivo .class funcionará em qualquer sistema operacional
que tenha a JVM instalada. Lembra? Write once, run anywhere.

Figura 11. Compilação e execução do TerceiroPrograma.java.

12
Figura 12. A lista de programas e de seus respectivos compilados em byte-code.

Erros de gravação

Não fique preocupado se acontecerem erros durante a compilação ou execução dos


programas. Comigo aconteceram vários! É assim mesmo que se programa. O negócio é
voltar no código, ver o que está errado, corrigir, recompilar e executar. Até dar certo. Não
desanime, porque vai ficando mais fácil a cada dia. Que tal fazer umas alterações nestes
exemplos e criar seus próprios primeiros programas em Java?

Conclusão

Este capítulo foi bem legal. Você começou entendendo o ambiente de desenvolvimento e
execução de aplicações em Java. Depois você fez o setup (configuração) do seu ambiente.
E chegou inclusive a construir os primeiros programas em Java. Parabéns, você é um
programador Java! Se já fez uma ou duas aplicações em Java, já é programador. Agora é
seguir para o alto e avante.

Desafio

Agora que você já pode se considerar um programador Java, construa um programa para
calcular o IMC (Índice de Massa Corporal) de uma pessoa.

13
3. Tipos de dados

Assim como todas as linguagens o Java trabalha intensamente com dados. Para isso ele
precisa de um lugar para guardar esses dados enquanto realiza suas operações. Este lugar é
a memória do computador. Mas, para que não tenhamos que acessar diretamente a
memória do computador (e o sistema operacional nem permitiria isso), existem as
variáveis, que são como portas de acesso para esta memória.
Ao programar nós definimos os nomes das variáveis que queremos utilizar. Uma vez
definida, a variável poderá guardar dados e devolver estes dados sempre que precisarmos.
Na verdade, já fizemos isso em nossos primeiros programas. Veja este trecho de código:
int idade; // Criamos uma variável para guardar dados
idade=35; // Guardamos o valor 35 dentro da variável
System.out.println (idade); // Utilizamos o dado
guardado na variável

Em Java poderíamos criar e armazenar o valor em uma única linha. Seria assim:
int idade=35;

Ao criarmos variáveis precisamos definir os seus tipos. No exemplo acima estamos


criando a variável idade e definindo que o tipo dela será inteiro (já falaremos sobre os
tipos existentes). Isso significa que ao longo do código não poderemos atribuir valor com
um tipo diferente disso, pois do contrário receberíamos uma mensagem de erro durante o
processo de compilação do código.
Mas isso não é ruim? Na verdade, não. O Java é uma linguagem fortemente tipada, assim
como outras linguagens como o C#, por exemplo. Isso quer dizer que somos obrigados a
definir o tipo de cada variável. Contudo, isso é muito bom quando estamos construindo
programas maiores e mais complexos. Com tanto código e com tantas variáveis é comum
esquecermos o tipo da variável e acabar atribuindo um valor que não queríamos a uma
delas. Em uma linguagem fortemente tipada como o Java sempre seremos alertados
quando cometermos erros deste tipo. No final, isto permite corrigir os erros logo no
começo e evita muita dor de cabeça.

Dados primitivos

Mas quais são os tipos de dados do Java? Há dois tipos fundamentais: os primitivos e os
de referência. Os tipos primitivos são apresentados a seguir, na Tabela 2.
Tabela 2. Tipos de dados primitivos do Java.

Grupo Tipo Capacidade


Lógico boolean true ou false

14
byte -128 a 127

short -32768 a 32767


Numérico
int -2147483648 a 2147483647
inteiro
long -9223372036854775808 a 9223372036854775807

char 0 a 65535

Numérico de float 1.40239846 e -46 a 3.40282347 e +38


ponto
flutuante (com 4.94065645841246544 e -324 a
double
casas decimais) 1.7976931348623157 e +308

Note que é possível utilizar virtualmente todos os tipos de número com variáveis Java.
Um detalhe interessante sobre isso é que estes tipos estão implementados na JVM, ou
seja, não dependem do sistema operacional e nem do tipo de processador utilizado. A
JVM garante que estes tipos funcionarão como o esperado em qualquer sistema
operacional.
Os tipos de referência são um pouco mais complexos e não entraremos em maiores
detalhes sobre eles ainda, mas são apresentados na Tabela 3. Por enquanto não se
preocupe com estes tipos.
Tabela 3. Tipos de referência.

Tipo Objetivo
String Armazena uma sequência de char.

Tipo de classe Identifica o tipo da classe ou o pacote onde a classe está.

Identifica o tipo da interface ou o pacote onde a interface


Tipo de interface
está.

Identifica o tipo de uma classe, interface, método ou


Tipo de variável
construtor de classe.

Identifica um tipo de dado que contém uma referência para


Array
um objeto.

Agora vamos fazer um teste bem simples com as variáveis do tipo primitivo conforme
apresentado no Código 1. Note que foram definidas variáveis para cada um dos tipos
primitivos e que na mesma linha de declaração um valor foi atribuído. Excetuando a
variável a, do tipo boolean, todas as outras variáveis receberam o valor máximo que elas
podem comportar.

15
Talvez você esteja achando estranho que para as declarações de long, float e double os
valores tenham recebido uma letra no final. Isso acontece porque esses valores literais são
normalmente compreendidos pelo Java como int. Para deixar claro que o valor se refere a
um tipo diferente, no final do valor a gente indica este tipo. Por isso as letras, sendo L-
long, F-Float e D-Double.
Código 1. Teste com tipos primitivos do Java.

public class Variaveis {

public static void main (String args[]){

boolean a = true;
byte b = 127;
short c = 32767;
char d = 65;
int e = 2147483647;
long f = 9223372036854775807L;
float g = 3.40282347e+38F;
double h = 1.7976931348623157e+308D;

System.out.println("boolean..: " + a);


System.out.println("byte.....: " + b);
System.out.println("short....: " + c);
System.out.println("char.....: " + d);
System.out.println("int......: " + e);
System.out.println("long.....: " + f);
System.out.println("float....: " + g);
System.out.println("double...: " + h);
}
}

Mas, por que precisamos colocar as letras L, F e D no final de literais numéricos, mesmo
quando já definimos claramente o tipo da variável? Isto é uma questão da implementação
do Java. Ele sempre tenta trabalhar com inteiros e por isso precisamos indicar
explicitamente o tipo do literal. Mas não se preocupe, isso se aplica apenas a literais, ou
seja, apenas quando a gente informa no código o número (o que é raríssimo). Em cálculos
e outras situações normais isso não acontecerá.
Agora vamos compilar e executar para ver se tudo funciona. A Figura 13 apresenta o
resultado do programa. Reparou no resultado apresentado para a variável do tipo char? É
uma letra e não um número. Isso acontece porque o tipo char retorna o equivalente
ASCII para o número. Não sabe o que é ASCII? Resumindo, é uma correlação entre um
número (que o computador entende) e uma letra (que humanos entendem). Para cada
letra há um número correspondente. Por exemplo, 65 é A, 66 é B, 67 é C, assim por
diante. Dê uma googlada rápida sobre ASCII só para entender isso direitinho.

16
Figura 13. Compilação e execução de Variaveis.java.

String

O tipo String não é um tipo de dado primitivo. Na verdade, String é uma classe criada
para armazenar e manipular literais alfanuméricos, mais conhecidos como strings
(cordas). São sequências de letras, números e símbolos concatenados. Por exemplo, “Meu
nome é João” é uma string. Outra string: “Rua Sobe e Desce, 45, Ap. 54”. Como estes
valores não podem ser armazenados em uma variável do tipo primitivo, uma classe foi
criada para este objetivo.
No entanto, trata-se de uma classe bem fácil de utilizar. Neste momento não entraremos
nos detalhes de implementação desta classe. Apenas veremos como utilizá-la. Para criar
uma variável do tipo string a sintaxe é a seguinte:
String <nome da variável>;

Simples, não? Então vamos ver um exemplo também simples de utilização deste tipo de
variável no seguinte trecho de código:
String a;
a="Meu nome é João";

String b="E meu nome é Maria";

System.out.println(a);
System.out.println(b);

O resultado da execução deste código seria a seguinte:


Meu nome é João
E meu nome é Maria

Imagine se adicionasse o comando System.out.println(a+b). O que aconteceria? Teste e


confira.

17
4. Classes – Primeira visita

Calma, ainda não vamos começar a falar detalhadamente em programação orientada a


objetos. Mas precisamos ter uma ideia básica sobre esse assunto para entender o
funcionamento dos nossos programas em Java. De fato, a primeira declaração de todos os
programas que construímos foi “class”. O arquivo compilado pelo javac.exe tem a
extensão “.class”. Então, se estamos trabalhando com Java estamos trabalhando com
classes. Mas, o que é uma classe afinal?
Ao contrário do que muita gente pensa o conceito de programação orientada a objetos
não é coisa nova. Na verdade, essa filosofia de programação começou a ser pensada ainda
na década de 1960. Porém, naquela época as linguagens eram procedurais e demorou
muito tempo para que os desenvolvedores de software percebessem que o novo modelo
de programação trazia vantagens reais. Para falar a verdade, ainda hoje há aqueles que
questionam se programar orientado a objetos é realmente vantajoso.
Mas o que é linguagem procedural? As linguagens procedurais, também chamadas de
estruturadas, baseiam a codificação em sequencias de código. A iteração (loop), decisão e
a sequência (passos de comandos) caracterizam esse tipo de linguagem. Podemos
comparar o código procedural com um livro de papel, onde há uma sequência de linhas,
uma sequência de parágrafos, uma sequência de capítulos. O leitor segue esta lógica de
leitura.
Bem, se a linguagem procedural pode ser comparada a um livro de papel, ao que
podemos comparar a linguagem orientada a objetos? Apenas como analogia, podemos
utilizar o conceito de páginas WEB com seus hyperlinks. Cada página tem seu conjunto
de informações e o leitor pode clicar em qualquer link para obter a informação que ele
deseja. Não há ordem específica, pois ele é livre para seguir a ordem que achar melhor.
Na linguagem orientada a objetos há blocos de programação que podem ser usadas na
ordem que o programador desejar. Esta é apenas uma analogia para apoiar a compreensão
e não uma definição formal, ok?
A essência do conceito de orientação a objetos é a proposta de construir pequenos blocos
que conteriam tanto código (comportamento) quanto dados (atributos). Estes blocos
poderiam receber e enviar informações e serem integrados de maneira parecida a um
Lego. Cada pequeno bloco teria acesso ao seu conjunto particular de dados. Eles
poderiam receber requisições, trabalhar na resposta consultando e alterando seus próprios
dados e responder ao que foi pedido.
A vantagem deste modelo de programação é a possibilidade de separar bem as
responsabilidades de cada objeto, melhorando a eficiência e a manutenibilidade dos
programas e também de poder herdar blocos de outros blocos (na verdade, classes de
classes), possibilitando maior reuso de código. Outra vantagem é que cada bloco tem seus
comportamentos e dados bem protegidos, evitando o acesso indevido e as consequentes
falhas, além de facilitar a vida de quem os usa.
Mas como esses blocos viriam a existir? Primeiro eles seriam projetados, como se fossem
desenhados em uma folha de papel. Depois eles seriam construídos a partir do projeto.
Ao projeto nós chamamos de classe e a construção a partir do projeto nós chamamos de

18
objeto. Na Figura 14 podemos dizer que a planta da casa é uma classe e a casa construída
a partir da planta é um objeto. Outra forma de dizer é que a casa (objeto) é uma instância
da planta (classe). Isso significa que a casa é a materialização da planta. Então, instanciar e
materializar são sinônimos neste caso.

Figura 14. Projeto de uma casa e a casa construída.

Além da própria construção e instanciação de classes, um outro importante conceito de


orientação a objetos é o de herança. Herança de classes? Mas como assim? O objetivo é
que os programadores possam construir classes para seus próprios usos e para o uso de
outros programadores. Essas classes, além de utilizadas do modo como foram feitas,
poderiam ser herdadas, aprimoradas e utilizadas. Imagine as seguintes heranças de classes:
a. João fez um projeto de monociclo. A partir do projeto ele construiu o monociclo e
começou a utilizar.
b. Marcos herdou o projeto do João, adicionou uma roda e criou um novo projeto.
A partir desse novo projeto ele construiu uma bicicleta e saiu por aí pedalando.
c. Clara viu o Marcos andando de bike e pediu uma cópia do projeto dele. Ela
herdou o projeto e adicionou um motor elétrico, fazendo os devidos ajustes de
transmissão. Terminado os ajustes no projeto, Clara construiu uma bicicleta
elétrica e foi passear com o Marcos.
d. Ana achou o projeto da Clara muito legal, mas ela queria muito mais autonomia e
velocidade. Assim, ela herdou o projeto da bicicleta elétrica e fez ajustes,
adicionando um motor a explosão, carenagem e sistema de transmissão baseado
em correntes. A partir desse projeto ela criou uma motocicleta e foi pegar estrada.
Estes são dois dos maiores benefícios esperados da programação orientada a objetos;
1. Maior reuso de código. É possível utilizar as classes de outros programadores. Já
ouviu o termo “reinventar a roda”? Ninguém precisa ficar construindo de novo as
coisas básicas que a maioria dos programadores precisa. Não é preciso saber cada
detalhe da classe que estamos herdando. Basta herdar e utilizar, sejam classes de
outros ou as nossas próprias de trabalhos anteriores.
2. Evolução das funcionalidades. Melhor ainda: é possível fazer melhorias na classe a
partir da herança e construir classes melhores e mais adequadas a cada
necessidade.

19
Imagino que neste ponto você já tenha uma ideia geral do conceito de classe. Mas talvez
esteja se perguntando: o que tudo isso tem a ver com Java?

Conceitos utilizados no Java

Agora vamos revisitar nosso primeiro programa:


class PrimeiroPrograma {
public static void main (String args[]) {
System.out.println("Este programa está exibindo qualquer coisa na
tela");
}
}

É interessante perceber que nosso próprio programa já é uma classe. Para se criar uma
classe em Java utiliza-se a seguinte estrutura:
<visibilidade> class <nome da classe> {
Aqui ficam os métodos e as propriedades
}

Dentro de todos os nossos programas temos um método chamado main. Este é um


método especial do Java, porque é o método que a JVM procura para executar um
programa. Depois o System é uma classe do Java que contém uma série de outras classes e
métodos. Dentro da classe System temos um atributo especial out que representa o
dispositivo para saída de informação (por padrão a tela do computador) e o método
println que envia informação ao dispositivo padrão.
Ao programar em Java criaremos outros métodos e também novas classes. A construção
de classes e a integração entre elas é basicamente o modelo de programação em Java. Mas
não se preocupe se esses conceitos são novos para você. Teremos muito tempo para
abordá-los e utilizá-los ao longo desse livro.
No entanto, quero destacar que o objetivo deste livro é tratar dos elementos mais
fundamentais da linguagem Java e não se aprofundar nos conceitos de orientação a
objetos.

Criando e utilizando uma classe

O primeiro passo então é criarmos uma classe nossa, de verdade e começar a utilizá-la. Os
programas que já construímos até agora já eram classes. Então criar novas classes não será
difícil.
A primeira coisa a fazer é criar um arquivo de programa separado para a nossa nova
classe. Vamos criar uma classe para conter operações matemáticas simples. Que tal
chamar esta classe de “Matemática”? Aqui vai uma primeira dica: por uma questão de
padronização sempre daremos nomes para as classes começando com maiúsculas. Se for
um nome composto utilizaremos maiúscula no início de cada nome. Por exemplo:

20
MatematicaBasica ou MatematicaAvancada. Se você não seguir essa regra o seu código
será compilado assim mesmo. Mas manter o mesmo padrão utilizado pela comunidade de
programadores é sempre vantajoso. Ao longo do livro falaremos de outras regras deste
tipo.

Voltando para a nossa nova classe, ela pode ser algo assim apresentado no Código 2.
Código 2. Nova classe matemática.

public class Matematica {

public int soma(int valor1, int valor2) {


int r;
r=valor1+valor2;
return r;
}
}

Note que nossa classe não tem um método main como nos programas anteriores. Ao
invés disso ela tem apenas o método soma. Os nomes de método são escritos em
minúsculas. Quando são nomes compostos então o primeiro nome é em minúsculas e
segundo inicia com maiúscula. Por exemplo: somaValores ou calculaMulta.
Note também que antes de class nós adicionamos o modificador public. Bem, isso
aprimora nossa estrutura de criação de classes para um novo formato:
<modificador> class <nome da classe> {
Aqui ficam os métodos e as propriedades
}

Mas o que é esse modificado? Ele define a visibilidade da nossa classe para outras classes.
Se for public então todas as outras classes poderão ter acesso a ela. Como estamos
criando uma classe para ser utilizada por outras, então vamos deixar como public.
Agora vamos falar um pouquinho sobre o método soma. Note que ele também tem um
modificador no começo e que está como public. Vamos deixar assim, porque queremos
que esse método seja utilizado por qualquer outra classe. Depois de public tem um int.
Esse é o tipo de dado que o método vai retornar. Um método pode receber informações,
ou parâmetros e retornar informações. Quando a gente coloca um int antes do nome do
método, estamos dizendo que o método vai retornar informação do tipo inteiro. Não se
preocupe muito com tipos por enquanto. Logo trataremos especificamente deste assunto.
Depois do nome do método, dentro de parênteses, há a definição de tipo e nome dos
parâmetros. Um método pode ter nenhum, um, ou diversos parâmetros. Inclusive cada
um destes parâmetros pode ser de tipos diferentes. Neste caso os dois parâmetros são do
tipo int. Bem, com tudo isso nossa estrutura, ou sintaxe, para a construção de uma classe
já evoluiu para a seguinte:
<modificador> class <nome da classe> {

21
<modificador> <tipo retorno> nomeDoMetodo (<tipo>
parâmetro){
... código ...
return <valor de retorno>
}
}

Nossa classe Matematica é muito simples. Ela tem o método soma que recebe dois valores
do tipo inteiro (int), cria uma variável r também do tipo inteiro, soma os dois valores
recebidos e guarda dentro desta variável e retorna o valor desta variável para quem
chamou o método.

Utilizando uma classe

Como vimos no começo do capítulo uma classe é um bloco que contém comportamento
(código) e dados (neste caso, variáveis). Esse bloco recebe informações e retorna
informações. E nós criamos uma classe assim, que tem uma responsabilidade muito
simples: somar dois valores inteiros. Mas como fazemos para usar este bloco? Como
juntamos este bloco com outros para compor um software?
Na verdade, é muito simples fazer isso. Precisaremos instanciar nossa classe em um
objeto. É como construir algo a partir do projeto, como já vimos. E como isso é feito em
Java? A sintaxe é a seguinte:
<NomeDaClasse> <variável>=new <NomeDaClasse>();

Nós criamos uma classe com o nome Matematica. Agora precisamos criar um programa
que vai utilizar esta classe, ou seja, vai instanciar essa classe e chamar o método soma dela.
Vamos criar um outro programa chamado QuartoPrograma.java e colocar na mesma
pasta junto com nossa classe e com os programas anteriores. O programa ficará assim:
Código 3. Classe QuartoPrograma.

class QuartoPrograma {

public static void main (String args[]) {

Matematica m=new Matematica();


int resultado;
resultado=m.soma(5,10);
System.out.println("O resultado da soma:");
System.out.println(resultado);

}
}

A primeira coisa a fazer é instanciar a classe em uma variável. Isso é feito pela linha
Matematica m=new Matematica(). Está exatamente de acordo com a sintaxe que vimos.
Depois disso é só chamar o método soma passando os parâmetros, ou seja, dois valores
inteiros. Percebeu que depois de instanciar a classe na variável m, a partir de então todo o

22
contato com a classe é feito através dessa variável? É porque m representa o objeto da
classe.
A classe é apenas um projeto no papel, lembra? Queremos falar com o objeto e não com a
classe. E neste caso a porta de entrada para o objeto que acabamos de instanciar é a
variável m. Note que utilizamos um ponto (.) depois da variável para acessar o que tem
dentro dela. Por enquanto lá dentro só tem o método soma, então acessamos assim
m.soma(4,6). Depois teremos mais coisas para acessar dentro dos objetos dessa classe.
Mas como o QuartoPrograma.java sabe “encontrar” a classe que está em
Matematica.java? A JVM faz isso através dos byte-codes, ou seja, dos códigos já
compilados e gerados com a extensão .class.

Figura 15. Compilação de Matematica.java e QuartoPrograma.java.

Note na Figura 15 que foram compilados a classe Matematica, em Matematica.java e o


QuartoPrograma.java. Por isso, foram gerados os byte-codes Matematica.class e
QuartoPrograma.class. Quando o QuartoPrograma.class for executado pela JVM ela
saberá que tem que carregar a classe Matematica que está compilada no arquivo
Matematica.class. E tudo deve funcionar. Vamos ver?

23
Figura 16. Execução do QuartoPrograma.class.

Tudo funcionou conforme o previsto. Mas o que aconteceria se você apagasse o arquivo
Matematica.class? Será que tudo continuaria funcionando? Faça o teste e descubra o
resultado.

Atributos

Além de comportamento uma classe possui atributos. Esses atributos conterão dados
necessários aos métodos (comportamento) ou por eles produzidos. Os atributos de uma
classe são declarados como variáveis no início de uma classe. Então, que tal criarmos um
atributo em nossa classe Matemática para conter o resultado das operações? Isso poderia
ser feito como no Código 4.
Código 4. Classe Matematica com atributo.

public class Matematica {

// AQUI FICARÃO OS ATRIBUTOS DA CLASSE


int resultado;

// AQUI FICARÃO OS MÉTODOS DA CLASSE


public void soma(int valor1, int valor2) {
resultado=valor1+valor2;
}

Podemos notar algumas diferenças neste código em relação ao anterior. Primeiramente,


estamos utilizando duas barras seguidas (//) para indicar uma linha de comentário. Estas
linhas são ignoradas pelo compilador Java e, portanto, podemos escrever nelas o que
desejarmos. Os comentários são muito úteis para relembrarmos o que fizemos em
determinado código, especialmente quando o código é maior e já não trabalhamos nele
há algum tempo.
Também é possível notar uma declaração de variável logo no começo, a variável resultado
do tipo inteiro. Esta declaração é, na verdade, a de um atributo da classe. Ela pode ser
acessada por todos os métodos da classe e até de fora dela. Para acessar um atributo de
um objeto (instância da classe) também utilizamos o ponto (.), que neste caso seria algo
como m.resultado.

24
Adicionalmente o método soma foi simplificado. O resultado do cálculo é armazenado
diretamente no atributo da classe, evitando a criação de outras variáveis dentro do
método. Mas notou que não há mais o retorno do valor? Por que não? Porque o resultado
agora será pego a partir do atributo resultado e não mais por retorno do método. Mas
lembra que antes do nome do método havia um int indicando o tipo de retorno? Bem,
como não há mais retorno é preciso indicar isso antes do nome do método. Para dizer
que não haverá retorno do método utilizamos a palavra reservada void.
Mas como isso afetará nosso programa que utiliza a classe Matematica? Este código
também poderá ser simplificado. Veja como ficou o QuartoPrograma.java no Código 5.
Código 5. Classe QuartoPrograma utilizando atributo da classe Matematica.

class QuartoPrograma {

public static void main (String args[]) {

Matematica m=new Matematica();


m.soma(5,10);
System.out.println("Resultado: "+m.resultado);
}
}

Basicamente este código instancia um objeto da classe Matematica na variável m e


executa o método soma deste objeto. Depois ele exibe o resultado acessando diretamente
o atributo resultado do objeto instanciado. Parece que tudo ficou mais fácil de entender.
Este é um dos objetivos do paradigma (filosofia) da orientação a objetos.
Este atributo resultado pode ser compartilhado por diversos métodos. Que tal criarmos
um método multiplica ? Isso vai ilustrar melhor a integração entre comportamento e
dados dentro de uma classe. O código da classe Matematica ficaria assim, como no
Código 6.
Código 6. Classe Matematica com método multiplica().

public class Matematica {

// AQUI FICARÃO OS ATRIBUTOS DA CLASSE


int resultado;

// AQUI FICARÃO OS MÉTODOS DA CLASSE


public void soma(int valor1, int valor2) {
resultado=valor1+valor2;
}

public void multiplica(int valor1, int valor2) {


resultado=valor1*valor2;
}

25
O método multiplica é idêntico ao método soma. Naturalmente, a única diferença é a
operação matemática realizada entre valor1 e valor2. Mas o resultado é sempre
armazenado no atributo resultado. Agora vamos compilar e executar. O resultado pode
ser visto na Figura 17.

Figura 17. Compilação e execução dos ajustes implementados em Matematica.java e QuartoPrograma.java.

Pacotes e visibilidade

Quando estamos construindo software a quantidade de arquivos de programa costuma


crescer rapidamente. A tendência é criar muitas classes, cada uma delas com poucas
responsabilidades. Dependendo do número de classes pode ficar difícil achar
rapidamente um determinado método ou mesmo uma determinada classe.

Figura 18. Estrutura de pastas do projeto Calculo.

26
Note que criamos uma pasta Calculo para colocar nosso projeto dentro e separar dos
programas construídos anteriormente.
A plataforma Java nos possibilita um mecanismo para organização de todo esse código.
Uma das formas de organizar melhor o código é pela criação de pacotes. O próprio Java
tem suas bibliotecas de classes organizadas dessa maneira. Mas, o que é um pacote?
Falando de maneira muito simples e direta, cada pacote é uma pasta dentro do nosso
projeto.
Então, vamos deixar nosso programa principal na pasta principal do nosso projeto e criar
uma subpasta Apoio para colocar todas as classes da apoio, incluindo a classe
Matematica. Em seguida, vamos mover o arquivo Matematica.java da pasta principal para
dentro da pasta Apoio. O resultado será parecido com o que mostra a Figura 18.
Para que o compilador compreenda nossa estrutura e reconheça a pasta Apoio como um
pacote, todas as classes que forem colocadas dentro da pasta Apoio devem ser marcadas
como parte do pacote Apoio. Isso é feito através da palavra reservada package. Veja como
ficaria o código da classe Matematica dentro do pacote Apoio.

Código 7. Classe Matematica dentro do pacote Apoio.

package Apoio;

public class Matematica {

// AQUI FICARÃO OS ATRIBUTOS DA CLASSE


int resultado;

// AQUI FICARÃO OS MÉTODOS DA CLASSE


public void soma(int valor1, int valor2) {
resultado=valor1+valor2;
}

public void multiplica(int valor1, int valor2) {


resultado=valor1*valor2;
}

public void mostraResultado(){


System.out.println("Resultado: "+resultado);
}
}

E as classes que estiverem fora do pacote Apoio precisam importar a classe Matematica
que está no pacote apoio. Isso é feito pelo uso da palavra reservada import logo no
começo do programa. Note na alteração realizada no QuartoPrograma.java:
Código 8. Classe QuartoPrograma utilizando pacote Apoio.

import Apoio.Matematica;

class QuartoPrograma {

27
public static void main (String args[]) {

Matematica m=new Matematica();


m.soma(5,10);
System.out.println("Resutlado: "+m.resultado);

m.multiplica(5,10);
System.out.println("Resultado: "+m.resultado);
}
}

Agora vamos tentar compilar e executar nosso programa, conforme Figura 19.

Figura 19. Execução do programa estruturado em pacotes.

Mas o que deu errado? Quando o sistema começa a ser organizado em pacotes podem
começar a surgir problemas de visibilidade dos atributos. Por isso, é uma boa prática
declarar explicitamente a visibilidade dos atributos e dos métodos durante a sua criação.
Os atributos e os métodos podem ser basicamente públicos, privados, protected ou
default (este último ocorre quando nada é declarado). Como nós não declaramos a
visibilidade do nosso atributo ele ficou como default, ou seja, ele só pode ser visto por
outras classes que estejam no mesmo pacote, de maneira similar ao que acontece com o
protected. Por isso ele não está sendo visto pela classe main da classe QuartoPrograma,
que está fora do pacote Apoio.
Precisamos mudar isso para public, ou seja, de modo que o atributo possa ser visto por
qualquer outra classe. Então, basta adicionar um public antes do int resultado e o
atributo poderá ser visto de qualquer classe, inclusive da classe QuartoPrograma.

28
Código 9. Visibilidade da classe Matematica.

package Apoio;

public class Matematica {

// AQUI FICARÃO OS ATRIBUTOS DA CLASSE


public int resultado;

// AQUI FICARÃO OS MÉTODOS DA CLASSE


public void soma(int valor1, int valor2) {
resultado=valor1+valor2;
}

public void multiplica(int valor1, int valor2) {


resultado=valor1*valor2;
}

public void mostraResultado(){


System.out.println("Resultado: "+resultado);
}
}

Como pode ser visto na Figura 20, tudo funciona perfeitamente após a implementação
do ajuste de visibilidade do atributo resultado.

Figura 20. Programa corrigido e funcionando com pacotes.

Protegendo os atributos

A classe Matematica está ficando interessante e já tem dois métodos. No entanto, ela
apresenta um problema: o atributo resultado está muito desprotegido. Do jeito que está o

29
programador poderia inadvertidamente alterar o valor do resultado a qualquer momento.
Imagine o seguinte trecho de código:
Matematica m=new Matematica();
m.soma(5,10);
m.resultado=60;
System.out.println("Resutlado: "+m.resultado);

Neste caso, o resultado seria alterado antes de ser apresentado ao usuário. Por mais que
isto seja improvável a melhor coisa a fazer é proteger o atributo para evitar qualquer falha
no uso da classe.
Para implementar isso faremos duas coisas. A primeira será alterar a visibilidade do
atributo para Private, de modo que ele não seja mais acessível de fora da classe
Matematica. A segunda coisa que faremos é criar um método para acesso de leitura do
atributo. Assim o atributo poderá ser lido de fora da classe, mas nunca alterado. Veja
como ficam as classes:
Código 10. Matematica.java.

package Apoio;

public class Matematica {

// AQUI FICARÃO OS ATRIBUTOS DA CLASSE


private int resultado;

// AQUI FICARÃO OS MÉTODOS DA CLASSE


public void soma(int valor1, int valor2) {
resultado=valor1+valor2;
}
public void multiplica(int valor1, int valor2) {
resultado=valor1*valor2;
}
}

Código 11. QuartoPrograma.java

import Apoio.Matematica;

class QuartoPrograma {

public static void main (String args[]) {

Matematica m=new Matematica();


m.soma(5,10);
System.out.println("Resutlado: "+m.getResultado());

m.multiplica(5,10);
System.out.println("Resultado: "+m.getResultado());
}
}

30
Com esta alteração o atributo resultado está protegido, pois não é mais possível acessá-lo
para alteração. A única forma de alterar este atributo é através dos métodos da própria
classe, o que é feito pelos métodos soma e multiplica. No entanto é possível acessá-lo para
consulta, conforme implementado no Código 11, utilizando o método getResultado de
Matematica.
Agora imaginemos que quiséssemos implementar um mecanismo de segurança para os
valores que enviamos para os métodos, um que garantisse que os valores não fossem
superiores a 122. Por que 122? Apenas a título de exemplo imaginemos que os valores
passados para a classe Matematica são idades de pessoas. Por isso não pode passar de 122.
Pense em como fazer isso. Se não conseguir não se preocupe. Voltaremos a este assunto
em capítulo à frente.

31
5. Ambiente de desenvolvimento integrado

Até agora temos trabalhado com poucas classes e chegamos a ver como criar um pacote
(package). Talvez já tenha percebido como é trabalhoso compilar e recompilar seu código,
vez após vez, utilizando o prompt de comando. Se isso já é difícil com umas poucas
classes, imagine como seria com dezenas de classes distribuídas em diversos pacotes.
Simplesmente inviável, não acha? É por isso que precisamos de um ambiente integrado de
desenvolvimento, mais conhecido como IDE (Integrated Development Environment).
Um IDE é uma ferramenta que oferece um conjunto de funcionalidades de apoio ao
desenvolvimento tornando-o mais eficiente. Ele já contém um editor para você escrever
seu código, um compilador e ainda apoia no processo de debug, ou seja, de identificação
e correção de falhas. Tudo em um único lugar. Simples e prático assim. Bem, parece que
era isso mesmo do que precisamos, não concorda?
Existem diversos IDEs que podem ser usados para o desenvolvimento de aplicações Java.
Atualmente, porém, os mais utilizados são o Eclipse, o Netbeans e o IntelliJ IDEA. Se
você googlar sobre esses três IDEs e quiser saber qual deles é o melhor, então encontrará
uma infinidade de opiniões e talvez até uma guerra ideológica. No entanto, pesquisa de
market share (que identifica o espaço ocupado por cada um no mercado) vai mostrar que o
Eclipse e o IntelliJ estão meio que empatados na liderança e o Netbeans fica um pouco
atrás. No entanto, os três produtos são fantásticos e qualquer um deles atenderá
plenamente a suas necessidades.
Apesar disso, precisamos escolher uma ferramenta para apresentar neste livro e utilizar
em nossos exemplos a partir de agora. Como dissemos, qualquer uma delas seria uma
excelente escolha. Porém, optamos por apresentar o Eclipse IDE, tanto por sua
consolidação no mercado quanto por sua infraestrutura que é suportada por diversos
atores da área de software, incluindo a Oracle que também fornece o Netbeans.

Instalando o Eclipse

O primeiro passo é baixar o instalador correto para o seu sistema operacional. É


importante também confirmar se você precisará da versão de 32 bits ou a de 64 bits. Isto
tem a ver com a versão do Java instalada na sua máquina. Se a versão for de 32 bits,
naturalmente precisará instalar o Eclipse de 32 bits. Se sua versão do Java for 64 bits...
Bem, acho que já entendeu. Mas, como saber qual versão está em seu computador?
Simples. Carregue o prompt de comando (De novo?! Calma...) e digite java –version. Isso
te dirá qual é a sua versão do Java. Veja na Figura 21.

32
Figura 21. Verificando a versão do Java.

Sabendo qual é a versão do Java e qual é o seu sistema operacional, visite a página do
Eclipse em https://eclipse.org/downloads/eclipse-packages/. Você terá a
seguinte tela (ou uma muito parecida):

Figura 22. Site do Eclipse.

Selecione a opção mais adequada e baixe sua versão. No caso do sistema Windows poderá
optar por baixar o instalador (Installer) que é a primeira opção oferecida. Esta é
certamente a forma mais prática de realizar a instalação. Enquanto estiver fazendo o
download ou logo após poderá surgir uma página pedindo uma doação. Vale a pena
destacar: o produto é gratuito. Se desejar fazer uma doação estará ajudando ao projeto,
mas não é um requisito para baixar, instalar e usar o Eclipse, Ok?
Se escolher o instalador, será um executável. O processo de instalação é simples. Se
escolher outra opção, receberá um arquivo compactado de extensão .zip. Basta
descompactá-lo na raiz de sua unidade de disco de preferência e utilizar o eclplise.exe que
está dentro da pasta. Neste caso, o Eclipse não precisará ser instalado. Ao ser
descompactado estará pronto para uso.
A Figura 23 apresenta a tela inicial do instalador para a seleção da versão a ser instalada
do Eclipse. Tanto a Eclipse IDE for Java Developers quanto a Eclipse IDE for Java EE
Developers atenderão suas necessidades de aprendizado. Nós preferimos instalar a versão
mais completa, a Eclipse IDE for Java EE Developers.

33
Figura 23. Versões do Eclipse apresentadas pelo instalador.+

Usando o Eclipse

Agora vamos usar o Eclipse. Provavelmente, a primeira coisa que o Eclipse vai fazer é
solicitar a pasta de trabalho (directory as workspace), conforme Figura 24. Em outras
palavras, o Eclipse quer saber qual é a pasta principal a partir da qual ele criará as
subpastas, uma para cada projeto. Ele vai trabalhar do mesmo jeito que estávamos
trabalhando. A diferença é que antes estávamos criando as pastas manualmente e agora o
Eclipse vai realizar esta tarefa.
Selecione a pasta que estiver usando para guardar seus projetos em Java. Para os efeitos
deste livro a pasta será c:\Codigo Java. Esta definição não é imutável. Se mais tarde
escolher outra pasta para guardar os seus projetos Java isto pode ser mudado facilmente
na opção de menu File/Switch Workspace. Na verdade, você poderá ter várias pastas de
projetos Java e ficar alternando entre estas pastas sempre que quiser.

Figura 24. Informando o workspace para o Eclipse.

34
Na tela inicial do Eclipse, conforme Figura 25, haverá uma série de opções, dependendo
da versão instalada. Esta tela de boas vindas pode ser ignorada. Aproveite para desmarcar
o checkbox Always show Welcome at start up.

Figura 25. Tela inicial do Eclipse.

Agora já podemos criar nosso primeiro projeto utilizando o Eclipse. Vamos criar um
projeto bem simples só para testar a ferramenta. Tudo o que é criado no Eclipse pode ser
feito pela opção de menu File/New, ou através das teclas CTRL+N (nós geralmente
usamos esta segunda opção), que aciona a janela apresentada na Figura 26.
Selecione a opção Java Project e clique no botão Next. Agora escolha um nome para o
projeto. Para este exemplo podemos usar o nome TestesMatematicos. Em seguida,
selecione a opção Finish. Veja a janela de criação de projetos na Figura 27

Figura 26. Janela acionada por CTRL+N.

35
Figura 27. Janela para criação de projetos.

O Eclipse é um IDE que suporta o desenvolvimento de software em diversas linguagens,


tais como C, C++, Java, PHP e outras. Por isso ele tem o conceito de perspectiva, como
pode ser visto na Figura 28. Isso se refere apenas a forma como as janelas serão montadas
e apresentadas. No entanto, a forma como as janelas são abertas não necessariamente
interferem na produtividade do desenvolver. Por exemplo, mesmo desenvolvendo em
Java muitos programadores não gostam da perspectiva Java do Ecplise. Nós utilizaremos a
perspectiva Java. A opção Remember my decision apenas faz o Eclispe se lembrar da sua
decisão e repetir sua resposta nas próximas vezes que utilizar o IDE.

Figura 28. Associação de perspectiva visual do Eclipse.

36
Agora, a partir da tela inicial do projeto já criado, conforme Figura 29, já podemos criar
nossa primeira classe. Mais uma vez, pressione CTRL+N e dessa vez escolha a opção
“Class” e selecione Next.

Figura 29. Tela inicial do projeto.

O próximo passo é criar a classe. Com este objetivo é apresentada a janela para a criação
de classes, conforme Figura 30. Vamos informar o nome de nossa classe:
MatematicaAvancada. Note que o Eclipse já oferece uma série de opções que podem ser
adicionadas automaticamente em nossa classe. Depois utilizaremos pelo menos uma
dessas opções. Por enquanto, clique em Finish para criar nossa classe.

Figura 30. Janela para criação de classes.

37
Em seguida é apresentado o editor de código. Já podemos escrever o código de nossa
classe diretamente nele. Este editor já marca todas as palavras reservadas do Java e faz
muito mais do que isso. Veja o código digitado na Figura 31. Reparou que há um erro
nessa linha? Nem foi necessário compilar o programa para perceber que havia um erro!
Isso é um avanço bem vindo, pois agilizará muito o processo de desenvolvimento daqui
para frente. Quando houver erro o Eclipse avisará imediatamente, permitindo corrigir a
falha mesmo antes de compilar o código.

Figura 31. Código inicial de MatamaticaAvancada, com erro.

Mas não é tudo. Tem mais coisa boa aí. Note que o Eclispe colocou uma pequena figura
de lâmpada no canto esquerdo linha 5. Bem, lâmpada costuma ser sinônimo de ideia.
Isso quer dizer que além de avisar do erro antecipadamente o Eclipse tem algumas ideias
sobre como corrigir esse erro. Ele quer te apresentar as opções para que você escolha uma
das opções. Vamos ver que ideias são essas? Clique sobre a lâmpada para saber, ou
simplesmente pressione CTRL+1 quando estiver na linha com o erro. Veja a tela que
aparece na Figura 32.

Figura 32. Propostas do Eclipse para correção.

Ele tem algumas ideias sobre como corrigir. Ele já notou que resultado não existe nem
como variável local do método e nem como atributo da classe (que ele chama de field).
Então ele sugere criar uma variável local chamada resultado ou um atributo de classe com
esse mesmo nome. Outra opção que ele dá é criar um parâmetro chamado resultado. Se
nada disso for aceito por você, ele parte para uma ação mais radical: que tal remover a
atribuição de valor para resultado? Outras opções ainda seriam usar um dos parâmetros
(valor1 ou valor2) para receber o valor no lugar de resultado. Ou ainda mudar o nome de
resultado para outro nome.

38
Veja que o Eclipse não sabe realmente resolver o problema, pois ele não tem como saber
o que estamos pensando ao construir aquele pedaço de código. No entanto, as sugestões
dele podem nos abrir os olhos para a solução. Por exemplo, neste caso, está claro que
esquecemos de criar o atributo de classe resultado. Então, vamos clicar na opção Create
field resultado e ver o que acontece na Figura 33.

Figura 33. Resultado da primeira ajuda do Eclipse.

O Eclipse criou automaticamente um atributo da classe chamado resultado e ele já inferiu


o tipo da variável com base no cálculo. Ou seja, se resultado vai armazenar a soma de dois
valores do tipo double, então este atributo deve ser do tipo double também, certo? E o
Eclipse já sabe disso.
Mas agora ele começa a apresentar outra lâmpada para a gente. Porém, desta vez ele marca
resultado com um sublinhado amarelo em vez de vermelho. Quando o sublinhado é
vermelho ele se refere a um erro que impedirá a compilação do código. Quando é
amarelo isto indica apenas um aviso. Ele quer dizer que o código vai compilar, mas talvez
não funcione como o esperado.
Neste caso em particular o Eclipse está indicando que criamos o atributo resultado,
atribuímos um valor para ele, mas não há como este atributo ser utilizado por alguém
porque ele é private e não há um método que permita o acesso de outras classes a este
atributo. Em outras palavras, assim ele não servirá a nenhum propósito. Vejamos então
quais são as sugestões oferecidas na Figura 34.

Figura 34. Sugestões de correção do segundo erro.

A primeira sugestão dele é meio exagerada, que é a de remover o atributo e pronto. Mas a
segunda sugestão é bem interessante. Ele propõe criarmos métodos para acesso ao
atributo: um para ler o atributo, ou um getter e um para alterar o valor do atributo, ou

39
um setter. E o mais interessante é que ele pode fazer isso automaticamente. Então vamos
selecionar esta opção e ver o que acontece na Figura 35.

Figura 35. Janela de criação de getters e setters.

Ele sugeriu criar um método getResultado (que retornará o valor de resultado) e um


método setResultado (que permitirá alterar o valor de resultado). É um padrão utilizar o
nome do atributo e adicionar um get ou um set antes deste nome. Esta é uma prática
comum e recomendada pela comunidade de desenvolvimento Java em todo o mundo. Já
que é assim, vamos apenas clicar em OK. Veja o resultado na Figura 36.

Figura 36. Setter e Getter criado automaticamente pelo Eclipse.

Agora está tudo certo. O Eclipse não reclama de mais nada. Já podemos compilar e
executar o nosso programa. Lembra que era preciso abrir um prompt de comando para
rodar o javac? Esqueça essa época. Agora o Eclipse vai fazer tudo isso pra gente. Isso pode
ser feito por se pressionar CTRL+F11 ou por se clicar no ícone de play na barra de
comandos, que na verdade de chama Run. Veja o resultado na Figura 37.

40
Run

Figura 37. Executando o primeiro programa no Eclipse. Ainda não foi dessa vez...

Opa, parece que alguma coisa não deu certo. Ele está dizendo que o programa não pode
ser executado. Isto acontece porque nós criamos uma classe para uso por outras classes. E
o projeto não tem nenhuma classe de início de execução, uma que tenha o public static
void main (String args[]). Lembra que nossos primeiros programas tinham sempre este
método na classe principal? A JVM procura por este método para começar a execução do
projeto. Então, precisamos criar uma classe que contenha o ponto de início e que use
nossa classe anterior.

Figura 38. Criando classe com ponto de início para execução.

41
Você já sabe criar uma classe. Pressione CTRL+N e selecione classe. Na janela de criação
de classes escolha o nome TestaSoma para nossa nova classe e selecione a opção public
static void main (String[] args), conforme Figura 38 e em seguida pressione Finish. O
Eclipse vai criar uma classe já com ponto de início para nós.
Já com o editor de código na classe TestaSoma, digite o seguinte código dentro do
método main:
MatematicaAvancada ma=new MatematicaAvancada();
ma.soma(10, 5);
System.out.println("Resultado: "+ma.getResultado());

Neste código nós instanciamos a classe MatematicaAvancada na variável ma, chamamos


o método soma passando os valores 10 e 5 por parâmetro e depois exibimos o conteúdo
do atributo resultado através do método getResultado. Salve e execute. O resultado da
execução do seu código será exibido no console, uma janela que fica na parte de baixo da
tela do Eclipse.
Mais uma dica interessante sobre o editor de código: ele oferece várias ajudas enquanto
está digitando. Uma delas é o auto completar. Depois de instanciar Matemática avançada
em ma, ao digitar ma. no editor de código, logo após o ponto (.) aparecerá uma lista de
atributos e métodos que a instância possui, incluindo soma, setResultado e getResultado.
Ao escolher/digitar soma logo após o ponto o Eclipse mostra os parâmetros e os tipos
que são esperados para o método. Isso é uma mão na roda ao programar.
Outra dica: cansado de digitar System.out.println? Acho que sim. Então, no editor de
código digite sysout e logo em seguida pressione CTLR+Barra de espaço. Legal, né? É o
Eclipse ajudando a gente a programar.
Em suma, o Eclipse é uma ferramenta fantástica e existem literalmente dezenas de outras
ajudas que ele pode nos dar enquanto estamos desenvolvendo. Explicar o Eclispe em
detalhes não faz parte do escopo deste livro. No entanto, ao longo dos capítulos faremos
observações sobre o IDE sempre que for adequado. Enquanto isso, você vai descobrir
pelo uso tudo o que o Eclipse tem a oferecer.

42
6. Adicionando Inteligência

Diversos fabricantes de carros, como a Volvo e a Volkswagen estão começando a


implementar um sistema de frenagem automática de emergência em seus veículos. Um
sensor no para-choque e uma câmera no retrovisor coletam informações e enviam para
um software que analisa e decide frear o veículo em caso de colisão iminente, mesmo que
o condutor continue pisando fundo no acelerador.
As vezes ficamos intrigados sobre como os programas conseguem, de certa forma, tomar
decisões. Eles analisam alguma informação e decidem sobre como se comportar. Outro
exemplo é o próprio Eclipse, que identifica um erro de código e, a partir da análise deste
erro, consegue propor diferentes caminhos de solução.
Na base de toda a “inteligência” dos softwares sempre haverá comandos que avaliam um
dado e decidem se executam um determinado bloco de comandos ou outro. No Java
temos dois comandos fundamentais para este fim: if e switch.

if/else

O comando if, em sua utilização mais elementar, possibilita decidir entre dois caminhos.
A sintaxe dele é bem simples:
if (<condição verdadeira>) <executa este comando>;
else <executa este comando>;

Vemos nesta sintaxe que uma condição é testada para verificar se ela é verdadeira. Uma
condição verdadeira é tipada como true e uma condição falsa é tipada como false. Vimos
anteriormente sobre o tipo de dados boolean, que pode armazenar as informações true
ou false. Considere o seguinte trecho de código, que exibirá Verdadeiro.
if (true) System.out.println("Verdadeiro");
else System.out.println("Falso");

Para analisar expressões são utilizados os operadores relacionais do Java. Então é


importante conhecer esses operadores, que são apresentados
Tabela 4. Operadores relacionais do Java.

Operador Análise da operação


valor1 == valor2 valor1 é igual a valor2

valor1 != valor2 valor1 não é igual a valor2

valor1 > valor2 valor1 é maior que valor2

valor1 < valor2 valor1 é menor que valor2

valor1 >= valor2 valor1 é maior ou igual a valor2

valor1 <= valor2 valor1 é menor ou igual a valor2

43
Então vamos ao Eclipse, no mesmo projeto em que estávamos trabalhando,
TestesMatematicos e vamos criar uma nova classe chamada TestaOperadores, com
public static void main(String[] args) e isso você aprendeu a fazer no capítulo anterior,
conforme Código 12.
Código 12. Classe TestaOperadores .

public class TestaOperadores {

public static void main(String[] args) {

int valor1=10;
int valor2=5;

if(valor1==valor2) System.out.println("valor1 é igual a valor2");

if(valor1!=valor2) System.out.println("valor1 não é igual a valor2");

if(valor1>valor2) System.out.println("valor1 é maior do que valor2");

if(valor1<valor2) System.out.println("valor1 é menor do que valor2");

if(valor1>=valor2) System.out.println("valor1 é maior ou igual a


valor2");

if(valor1<=valor2) System.out.println("valor1 é menor ou igual a


valor2");
}
}

O que acontece ao executar este código? Altere os valores de valor1 e valor2 algumas vezes
e rode o programa. É importante fazer isso para compreender bem o funcionamento dos
operadores relacionais. Esse conhecimento é fundamental para praticamente qualquer
tipo de programação em Java e também em outras linguagens que possuem operadores
idênticos ou similares.
Ainda há um detalhe sobre a sintaxe do if/else. Até agora nós colocamos apenas um
comando após o if. E se quiséssemos colocar vários, como no trecho abaixo? Isso daria
certo?
int idade=17;
if (idade>=18) System.out.println("Você já pode
dirigir.");
System.out.println("Increva-se em uma autoescola.");
else System.out.println("Você ainda precisa esperar.");

Certamente isso não daria certo. Pode testar no Eclipse. Ele nem compila. É porque tanto
o if quanto o else esperam encontrar apenas um comando para executar. Se você quiser
executar um grupo de comandos depois do if ou do else (e a gente faz muito isso) é
preciso utilizar as chaves. Tudo o que estiver entre chaves será executado. Veja a sintaxe:
if (<condição verdadeira>) {
<executa este comando>;
<executa este comando>;

44
<executa este comando>;
}
else {
<executa este comando>;
<executa este comando>;
<executa este comando>;
}

Com base nesta sintaxe podemos escrever o Código 13. Este trecho vai apresentar as
mensagens: Você ainda precisa esperar para entrar na autoescola e na linha seguinte
Tenha um pouco de paciência.
Código 13. Trecho de código utilizando colchetes no if/else.

int idade=17;

if (idade>=18) {
System.out.println("Você já pode dirigir");
System.out.println("Inscreva-se em uma autoescola");
}
else {
System.out.println("Você ainda precisa esperar para entrar na
autoescola");
System.out.println("Tenha um pouco de paciência.");
}

Agora que você já conhece o if/else está pronto para um desafio de programação? Muito
bem, vamos construir um método para testar se um número é par ou ímpar. Se for par o
método vai retornar true e se for ímpar vai retornar false.
Antes, porém, são necessárias algumas dicas sobre tipos de dados. Já falamos sobre os
tipos numéricos e sobre os limites deles. No entanto, não falamos ainda sobre conversão
entre tipos. Por exemplo, se nós temos um double a=1.50, como eu faço para converter
este valor para um inteiro? Será que o trecho a seguir funcionaria?
double a = 1.50;
int b;
b = a;
System.out.println(b);

Se você testou no Eclipse então percebeu que este código nem compila. O Eclipse avisa
que não pode converter um valor que é double para int. Mas nós podemos. Faremos isso
utilizando uma técnica fácil e direta chamada cast e a sintaxe é assim:
variavelTipoB=(<novotipo>) variavelTipoA

Ou seja, se temos uma variável do tipo A e queremos salvar o valor dela em uma variável
do tipo B, devemos colocar entre parênteses o <novotipo>, ou seja, o tipo par ao qual
pretendemos fazer a conversão. Então veja como ficaria o exemplo anterior:
double a = 1.50;
int b;
b = (int) a;
System.out.println (b);

45
Se você testar este novo trecho de código perceberá que tudo funciona e que o resultado
apresentado é 1. Mas, como assim? O valor apresentado não deveria ser 1.50? O valor
original estava em ponto flutuante e por isso apresentava casas decimais. Mas a variável b
é do tipo inteira e não consegue guardar as casas decimais. Por isso, na conversão, a parte
decimal é inteiramente perdida. Apenas o valor inteiro do número é transferido.
Porém, essa característica nos será muito útil para construirmos o nosso próximo método.
Vamos pensar um pouquinho do ponto de vista matemático. Vamos melhorar este trecho
de código e testar informando o numero 2:
double valorDec = 2;
int valorInt = (int) valorDec;

System.out.println("Divisao do decimal: "+valorDec/2);


System.out.println("Divisao do inteiro: "+valorInt/2);

if (valorDec/2==valorInt/2) System.out.println("Mesmo
resultado.");
else System.out.println("Resultados diferentes.");

Qual é o resultado produzido? Este:


Divisao do decimal: 1.0
Divisao do inteiro: 1
Mesmo resultado.

Agora vamos alterar o valor para 3 ao invés de 2. Ou seja, o código agora vai ficar assim:
double valorDec = 3;
int valorInt = (int) valorDec;

System.out.println("Divisao do decimal: "+valorDec/2);


System.out.println("Divisao do inteiro: "+valorInt/2);

if (valorDec/2==valorInt/2) System.out.println("Mesmo
resultado.");
else System.out.println("Resultados diferentes.");

Qual é o resultado? Este:


Divisao do decimal: 1.5
Divisao do inteiro: 1
Resultados diferentes.

Repare que sempre que informarmos um valor par na primeira linha, os resultados serão
diferentes. E sempre que for um valor ímpar os resultados serão iguais. Isso acontece
porque um valor ímpar, quando dividido por dois sempre vai produzir um resultado com
decimal. Na conversão para inteiro esta parte decimal se perde. Por isso a comparação da

46
divisão do double com a divisão do int sempre vai dar diferente quando o valor for
ímpar.
Agora temos tudo o que precisamos para criar nosso método que testa se um número é
par ou ímpar. Vamos carregar o Eclipse e alterar nossa classe MatematicaAvancada em
nosso projeto TestesMatematicos. A nossa classe MatematicaAvancada ficará como
apresentado em Código 14.
Código 14. Classe MatematicaAvancada com método par().

public class MatematicaAvancada {

private double resultado;

public void soma(double valor1, double valor2) {


setResultado(valor1+valor2);
}

public double getResultado() {


return resultado;
}

public void setResultado(double resultado) {


this.resultado = resultado;
}

public boolean par(double valorDec) {

boolean retorno = false;

int valorInt = (int) valorDec;

if (valorDec/2==valorInt/2) retorno=true;

return retorno;
}
}

Criamos um método par() que recebe um valor do tipo double em valorDec e retorna um
valor boolean, ou seja, true ou false. Ele implementa a fórmula simples que acabamos de
discutir para definir se um número é par. Se as divisões resultarem no mesmo valor então
é par e retorno recebe o valor true. Se não, retorno continua com seu valor original
atribuído na declaração da variável, ou seja, false.
Agora vejamos o nosso código ajustado para testar a classe em Código 15.
Código 15. Classe TestaSoma atualizada para testar o método par().

public class TestaSoma {

public static void main(String[] args) {

MatematicaAvancada ma=new MatematicaAvancada();


ma.soma(10, 6);

47
double rsto= ma.getResultado();
System.out.println("Resultado: "+rsto);

if(ma.par(rsto)) {
System.out.println("Este número é par");
}
else {
System.out.println("Este número é ímpar");
}
}
}

Agora estamos armazenando o resultado que retorna do método getResultado() em rsto.


Apenas por curiosidade queremos saber se este resultado é par ou ímpar. Por isso
chamamos o método par() para saber se este resultado é par ou ímpar. Altere os valores
da soma e teste várias vezes este código. Veja como os comandos if/else são importantes
para incluir “inteligência” em nossos programas.

Switch

Como já mencionado, os comandos if/else são amplamente usados em programação Java.


No entanto, as vezes há casos em que queremos fazer um grande número de comparações
em relação a uma mesma variável. Neste caso, seriam necessários vários if para testar
todos os valores. Quando isso acontece uma alternativa interessante é o comando switch.
A sintaxe dele é assim:
Switch (<expressão>) {
case <valor testado 1>:
<comando 1>;
<comando 2>;
<comando N>;
break;
case <valor testado 2>:
<comando 1>;
<comando 2>;
<comando N>;
break;
case <valor testado N>:
<comando 1>;
<comando 2>;
<comando N>;
break;
default:
<comando 1>;
<comando 2>;
<comando N>;
break;
}

Para ilustrarmos o uso deste comando vamos imaginar que precisamos saber qual é o
percentual de desconto que devemos aplicar a um seguro de carro. As seguradoras de

48
carro utilizam um sistema que eles chamam de classe bônus, que vai de 1 a 10. Quanto
maior o valor, maior o desconto. Ao passo que o cliente renova o seguro a cada ano, se
não houve nenhum problema e o seguro não foi acionado, então o cliente sobe de classe,
aumentando também o percentual de desconto na renovação.
Então vamos imaginar que os descontos vão de 5% a 10%, dependendo da classe bônus
do cliente. Nosso programa precisa definir o percentual de desconto a partir da classe do
cliente. Vejamos então como nossa classe MatematicaAvancada ficaria com um método
descontoSeguro() baseado na classe bônus passada. Veja o Código 16.
Código 16. Classe MatematicaAvancada com método descontoSeguro().

public class MatematicaAvancada {

private double resultado;

public void soma(double valor1, double valor2) {


setResultado(valor1+valor2);
}

public double getResultado() {


return resultado;
}

public void setResultado(double resultado) {


this.resultado = resultado;
}

public boolean par(double valorDec) {

boolean retorno = false;

int valorInt = (int) valorDec;

if (valorDec/2==valorInt/2) retorno=true;

return retorno;
}

public double descontoSeguro(int classeBonus) {


double retorno = 0;
switch (classeBonus) {
case 1:
retorno=0.05; // 5%
break;
case 2:
retorno=0.06; // 6%
break;
case 3:
retorno=0.065; // 6,5%
break;
case 4:
retorno=0.07; // 7%
break;
case 5:
retorno=0.08; // 8%

49
break;
case 6:
retorno=0.085; // 8,5%
break;
case 7:
retorno=0.087; // 8,7%
break;
case 8:
retorno=0.090; // 9%
break;
case 9:
retorno=0.096; // 9,6%
break;
case 10:
retorno=0.010; // 10%
break;
}
return retorno;
}
}

Nós utilizamos o comando switch para testar uma expressão, que é a classe bônus
passada. Ela pode ser um valor de 1 a 10. Cada case testa se a variável classeBonus tem o
valor 1, ou 2, ou 3 e assim por diante. O percentual de desconto adequado para a classe
informada é armazenado na variável retorno. Veja em Código 17 a utilização desta classe.
Código 17. Classe TestaSoma utilizando o método descontoSeguro() da classe MatematicaAvancada.

public class TestaSoma {

public static void main(String[] args) {

MatematicaAvancada ma=new MatematicaAvancada();


ma.soma(900, 750);

double valorSeguro=ma.getResultado();
System.out.println("Valor do Seguro.........: "+valorSeguro);

if(ma.par(valorSeguro)) {
System.out.println("------------------------> Este numero é par");
}
else {
System.out.println("------------------------> Este número é ímpar");
}

double fatorDesconto=ma.descontoSeguro(5); // Classe bônus 5


double seguroDesconto=valorSeguro*fatorDesconto;
double seguroFinal=valorSeguro-seguroDesconto;

System.out.println("Fator de desconto.......: "+fatorDesconto);


System.out.println("Valor do desconto.......: "+seguroDesconto);
System.out.println("Seguro com desconto.....: "+seguroFinal);
}
}

50
Como vimos neste capítulo, os comandos de decisão if/else e switch são muito versáteis e
podem ser utilizados em diversas situações. Fique à vontade para fazer diversos testes, para
criar classes e métodos que façam uso intensivo dos comandos de decisão. Como veremos
em próximos capítulos, estes comandos serão fundamentais para testarmos parâmetros
enviados aos nossos métodos, tornando estes métodos mais confiáveis e resistentes a
falhas.

51
7. Arrays

Estamos chegando a um ponto em que precisamos de novas formas de armazenamento


dos dados, o que nos permitirá enfrentar novos desafios. As Arrays são um mecanismo
interessante e útil para armazenarmos dados.
Para compreender este conceito vamos imaginar que você é dono de um grande negócio
de armazenamento, com prédios e mais prédios para estoque de diversos produtos. Então
você decide separar seus prédios por produto, evitando misturar materiais diferentes no
mesmo prédio. Na Figura 39 podemos ver alguns dos seus prédios, cada um armazenando
um tipo de produto.

Figura 39. Prédios para armazenamento de produtos.

As Arrays são como esses prédios. A diferença é que elas armazenam valores ao invés de
armazenar produtos. As Arrays também possuem andares. Em cada andar fica
armazenado um valor. Todos os valores de uma Array são do mesmo tipo. Ou seja,
podemos criar uma Array para armazenar vários valores do tipo int, outra para valores do
tipo double e ainda mais uma para valores do tipo String. Podemos até criar Arrays que
armazenam Arrays em seus andares. Quando criamos uma Array devemos definir o
número de andares que ela vai possuir.
Então vamos ver como criar uma Array. Este é um processo em dois passos: 1) Desenhar
a planta; 2) Construir o prédio. Desenhar a planta é, na verdade, definir uma variável
como sendo do tipo Array e já estabelecendo o tipo de dados que vai ficar nos andares
desta Array. Construir o prédio então é construir os andares da Array, todos vazios,
prontos para receberem os valores. Definir a variável de Array e definir seu tipo de dados
(desenhar a planta) é feito da seguinte forma:
<tipo>[] <nome da array>;

Ou

52
<tipo> < nome da array >[];

Sim, existem duas formas de fazer a mesma coisa. Fique a vontade para escolher a sua.
Vamos ver um trecho de código que define duas Arrays do tipo String:
String[] a;
String b[];

Neste código nós definimos as variáveis a e b como sendo do tipo Array que armazenará
dados do tipo String. Lembre-se, os andares ainda não foram construídos. Nem foram
definidos quantos andares serão.
Para construir o “prédio” com seus andares usaremos a palavra reservada new. Esta
palavra realmente cria a Array, reservando espaço na memória do computador para
guardar todos os valores que esta Array poderá armazenar. A sintaxe é a seguinte:
<nome da array> = new <tipo de dado> [<número de elementos>];

Então vejamos como isso ficaria em código:


a = new String[5];
b = new String[5];

Agora sim, criamos os prédios (Arrays) e seus andares. Neste caso criamos duas Arrays
com 5 elementos, ou 5 andares. Percebeu? Com o comando String[] a nós apenas
desenhamos a planta do prédio, ou seja, dizemos à JVM que a variável a vai ter um Array
do tipo String. Mas nada foi criado ainda. Quem cria o prédio? A linha de comando
a=new String[5], que realmente constrói o prédio e determina o número de andares, ou
seja, aloca espaço na memória do computador para os 5 andares de String.
Mas, tem um detalhe. Assim como em um prédio a contagem geralmente começa do
andar térreo, que corresponde ao zero, também em uma Array a contagem dos andares
começa do 0. Então a estrutura, tanto da Array a quanto da b será como apresentada na
Figura 40.

Figura 40. Estrutura das Array a e b.

53
Mas lembre-se de que os andares estão absolutamente vazios ainda. Agora podemos
colocar um valor do tipo String em cada andar da Array. Para colocar um dado dentro de
um dos andares, a sintaxe é a seguinte:
<nome da array>[índice]=<dado>

Na sintaxe apresentada o índice é o andar do prédio, ou seja, a posição dentro da Array.


O trecho de código a seguir demonstra como adicionar um valor no andar térreo, ou
índice 0, do Array a:
a[0] = "Joao";
a[1] = "Maria";
a[2] = "Marcos";
a[3] = "Clara";
a[4] = "Rogério";

Depois que os valores foram adicionados em cada elemento da Array, ou em cada andar
da Array, veja na Figura 41 como ficou a Array a. Note que a numeração da Array sempre
começa de zero e continua até completar o número de elementos definidos na construção
da Array.

Figura 41. Array a preenchida com valores String.

Bem fácil, não concorda? Agora veja o código a seguir, que define e cria duas Arrays e as
“abastece” com dados.:
public class TestaArray {

public static void main(String[] args) {

// Definindo os Arrays
String[] a;
String b[];

// Criando os Arrays
a = new String[5];
b = new String[5];

// Carregando dados no Array a


a[0] = "Joao";

54
a[1] = "Maria";
a[2] = "Marcos";
a[3] = "Clara";
a[4] = "Rogério";

// Carregando dados no Array b


b[0] = "Silva";
b[1] = "Fernandes";
b[2] = "Pereira";
b[3] = "Lins";
b[4] = "Gomes";

// Exibindo dados do Array a


System.out.println(a[0]);
System.out.println(a[1]);
System.out.println(a[2]);
System.out.println(a[3]);
System.out.println(a[4]);
System.out.println("-");

// Exibindo dados do Array b


System.out.println(b[0]);
System.out.println(b[1]);
System.out.println(b[2]);
System.out.println(b[3]);
System.out.println(b[4]);
System.out.println("-");

// Exibindo dados do Array a e b


System.out.println(a[0]+" "+b[0]);
System.out.println(a[1]+" "+b[1]);
System.out.println(a[2]+" "+b[2]);
System.out.println(a[3]+" "+b[3]);
System.out.println(a[4]+" "+b[4]);
System.out.println("-");
}
}

Este código foi adicionado de comentários para facilitar sua compreensão. Estude-o com
cuidado e faça testes você mesmo. Tente mudar o tamanho das Arrays e preenchê-los com
diferentes valores.
Até agora falamos sobre Arrays de apenas uma dimensão. Voltando à nossa analogia do
prédio é como se tivesse um enorme salão em cada andar do prédio. Mas e se cada andar
fosse composto de 3 cômodos. Então passaríamos a ter duas dimensões, a dimensão do
andar, de 0 a 4 e a dimensão dos cômodos, de 0 a 2 (sim, os cômodos também seriam
numerados a partir do zero).
As Arrays podem ter mais de uma dimensão. Para declarar uma Array com duas
dimensões a sintaxe seria a seguinte:
<tipo>[][] <nome da array>

Ou

55
<tipo> <nome da array>[][]

Agora imaginemos que nosso objetivo é criar uma Array com 3 andares e para cada andar
queremos 4 cômodos. O objetivo é preencher esse prédio da seguinte forma: em cada
andar queremos colocar no primeiro cômodo o nome de um esporte olímpico e nos
outros 3 cômodos colocar o nome do vencedor do ouro em um, da prata em outro e do
bronze no último. Então teríamos uma estrutura de duas dimensões, como apresentado
na Figura 42.

Figura 42. Array com duas dimensões para armazenamento de competições.

O código para a declaração desta Array seria:


String Competicoes[][];

E a declaração para a construção da Array seria:


Competicoes = new String[3][4];

E no trecho a seguir é apresentado o código para preencher o andar térreo da nossa


Array:
Competicoes[0][0] = "Corrida 100m rasos";
Competicoes[0][1] = "Usain";
Competicoes[0][2] = "Florence";
Competicoes[0][3] = "Tyson";

Note que na primeira dimensão, a dos andares, repetimos sempre o 0, porque estamos
colocando dados nos cômodos deste andar apenas. E como ficaria isso em nossa Array?
Veja na Figura 43.

Figura 43. Array Competicoes parcialmente preenchido.

56
Quando temos uma Array de duas dimensões, como é o caso desta, o preenchimento e
consulta de seus elementos é parecido com o jogo batalha naval. Podemos acessar a
qualquer elemento do Array com o que nós estamos chamando de andar e cômodo. A
seguir, veja o código completo de uso da Array de duas dimensões.
public class TestaArrayMulti {

public static void main(String[] args) {

// Definindo os Arrays
String Competicoes[][];

// Criando os Arrays
Competicoes = new String[3][4];

// Carregando dados no Array a


Competicoes[0][0] = "Corrida 100m rasos";
Competicoes[0][1] = "Usain";
Competicoes[0][2] = "Florence";
Competicoes[0][3] = "Tyson";

Competicoes[1][0] = "Salto triplo";


Competicoes[1][1] = "Inessa";
Competicoes[1][2] = "Jonathan";
Competicoes[1][3] = "Françoise";

Competicoes[2][0] = "Natacao 50m";


Competicoes[2][1] = "César";
Competicoes[2][2] = "Ranomi";
Competicoes[2][3] = "Michael";

// Exibindo dados do Array a


System.out.println("Competição: "+Competicoes[0][0]);
System.out.println("Ouro: "+Competicoes[0][1]);
System.out.println("Prata: "+Competicoes[0][2]);
System.out.println("Bronze: "+Competicoes[0][3]);
System.out.println("-");
System.out.println("Competição: "+Competicoes[1][0]);
System.out.println("Ouro: "+Competicoes[1][1]);
System.out.println("Prata: "+Competicoes[1][2]);
System.out.println("Bronze: "+Competicoes[1][3]);
System.out.println("-");
System.out.println("Competição: "+Competicoes[2][0]);
System.out.println("Ouro: "+Competicoes[2][1]);
System.out.println("Prata: "+Competicoes[2][2]);
System.out.println("Bronze: "+Competicoes[2][3]);
}
}

Teste este código e veja que resultado interessante. Faça ajustes, adicione dimensões. Se
divirta um pouco com Arrays para construir um conhecimento sólido sobre o assunto.

57
Mas é possível ter uma Array com 3, 5 ou 10 dimensões? Claro que sim. A Array poderá
ter quantas dimensões você precisar. O conceito de Array é uma ferramenta útil de
programação, pois você poderá aplicar este conceito a estruturas de programação
orientada a objetos, como faremos em capítulos posteriores.

58
8. Repetições

As repetições, também chamadas de loops, são muito necessárias em programação. Esses


loops se referem a trechos de código que são repetidos até que determinada condição seja
atendida. Por exemplo, no caso da frenagem automática dos carros que citamos
anteriormente a leitura e análise dos sensores são feitas em loop, durante todo o período
em que o veículo está ligado. Eles são muito úteis para manipular os dados das Arrays,
como veremos neste capítulo.
Existem três estruturas básicas de repetição (loop) no Java: while, for e do/while.
Veremos exemplos das três estruturas e ainda como interrompê-las antes da hora, quando
necessário.

while

A estrutura de repetição while continuará em loop enquanto determinada condição for


verdadeira. O teste desta condição é realizado logo no início do loop. Isso significa que se
na primeira vez a condição já não for atendida o bloco de código nunca será executado,
nem uma única vez. Este conceito está representado na Figura 44.

Figura 44. Funcionamento da estrutura while.

A sintaxe para utilização desta estrutura de repetição é:


while (<condição>) {
<linha de código 1>;
<linha de código 2>;
<linha de código N>;
}

Crie uma classe chamada TestaLoop, que tenha o método public static void main
(String[] args). E vamos testar a estrutura while com o Código 18.
Código 18. Testando a estrutura while.

public class TestaLoop {

public static void main (String[] args) {

int n=0;

while (n<5) {
System.out.println(n);
n++;
}

59
}
}

Neste código nós utilizamos a variável inteira n como elemento de condição. Ou seja, a
variável n é testada e se o valor dela for inferior a 5 então a entrada no loop é autorizada.
Caso contrário o loop se encerra.
Dentro do bloco de código o valor de n é exibido. Notou a declaração n++? Esta é uma
forma de incrementar em 1 o valor de uma variável, ou seja, a mesma coisa que n=n+1.
Ainda outra forma: n+=1 (n=n+1). Este formato se aplicaria a outros operadores. Por
exemplo: n-=1 (n=n-1), n-- (n=n-1), n*=2 (n=n*2) e etc. Utilize a forma que
considerar mais adequada.
O Código 18 vai produzir o seguinte resultado no console do Eclipse:
0
1
2
3
4

Por que ele não mostrou o número 5? Porque quando o n chegou a valer 5 o loop
repetiu, voltando para o while. O while testou se o valor de n ainda era menor do que 5.
Não era, porque 5 é igual a 5 e não menor do que 5. Neste momento o loop foi
encerrado.

do/while

Da mesma forma que o while o do/while repetirá um bloco de código enquanto


determinada condição for verdadeira. A diferença, como pode ser visto em Figura 45, é
que o teste da condição é feito no final da rodada do loop e não no início da rodada
como acontece no caso do while.

Figura 45. Funcionamento da estrutura do/while.

A sintaxe para utilização desta estrutura de repetição é:


do{
<linha de código 1>;
<linha de código 2>;
<linha de código N>;
} while <condição>;

60
Vamos adicionar um trecho de código em nossa classe TestaLoop para observar o
funcionamento da estrutura do/while. Veja os ajustes que implementamos no Código 19.
Como foi mantido o exemplo anterior adicionamos mensagens por println para indicar
de qual estrutura de repetição são os resultados.
Código 19. Testando a estrutura do/while.

public class TestaLoop {

public static void main (String[] args) {

// Testando a estrutura while


System.out.println("Teste de while");
int n=0;
while (n<5) {
System.out.println(n);
n++;
}

System.out.println("-");

// Testando a estrutura do/while


System.out.println("Teste de do/while");
int i=0;
do {
System.out.println(i);
i++;
} while (i<5);
}
}

Notou que o while fica por fora das chaves neste caso? Ao executar este código o Eclipse
apresenta o seguinte resultado:
Teste de while
0
1
2
3
4
-
Teste de do/while
0
1
2
3
4

É possível perceber que em termos de resultado final as duas estruturas de loop parecem
ser exatamente idênticas. Mas será mesmo? Vamos fazer uma pequena alteração no
código, ajustando os valores iniciais das variáveis de condição dos dois loops para 6. Veja
como fica o Código 20.

61
Código 20. Classe TestaLoop ajustada para iniciar em 6.

public class TestaLoop {

public static void main (String[] args) {

// Testando a estrutura while


System.out.println("Teste de while");
int n=6;
while (n<5) {
System.out.println(n);
n++;
}

System.out.println("-");

// Testando a estrutura do/while


System.out.println("Teste de do/while");
int i=6;
do {
System.out.println(i);
i++;
} while (i<5);
}
}

Note que o valor de i e de n foram ajustados para 6. Isso quer dizer que ambas as variáveis
já começam com um valor superior a 5, o que não será aceito para continuidade do loop.
Mas veja o resultado:
Teste de while
-
Teste de do/while
6

O que aconteceu? Na estrutura while, o primeiro loop, como a condição é testada antes
do bloco de código ele nem chegou a ser executado. Já na estrutura do/while, como a
condição é testada apenas após a execução do bloco de código, este bloco chegou a rodar
uma vez, exibindo o valor 6.

for

A terceira estrutura de repetição é o for, que é amplamente utilizado em programação. É


uma estrutura mais compacta, onde o teste da condição é realizado antes da execução do
bloco, de maneira similar ao while. Veja a Figura 46.

62
Figura 46. Funcionamento da estrutura for.

A sintaxe do for é a seguinte:


for (<tipo> <variável>=<inicial>;<teste>;<alteração>) {
<linha de código 1>;
<linha de código 2>;
<linha de código N>;
}

Então vejamos no trecho de código a seguir como implementar esta sintaxe:


for ( int z=0; z<5; z++)

Esta estrutura de loop tem três partes distintas, separadas por ponto e vírgula (;). Em sua
primeira parte, uma área para declarar e inicializar a variável que será utilizada para
controle. Neste caso, a variável z é declarada como inteira e o valor 0 é atribuído a ela. A
segunda parte é a de teste. Assim como nos exemplos anteriores continuamos testando a
variável é menor do que 5. Quando for igual ou maior o loop será encerrado. E a terceira
parte é onde alteramos o valor da variável de controle. Neste casso estamos
incrementando ela de 1.
Vejamos o código completo ajustada da classe TestaLoop em Código 21.
Código 21. Classe TestaLoop ajustada para testar for.

public class TestaLoop {

public static void main (String[] args) {

// Testando a estrutura while


System.out.println("Teste de while");
int n=0;
while (n<5) {
System.out.println(n);
n++;
}

System.out.println("-");

// Testando a estrutura do/while


System.out.println("Teste de do/while");
int i=0;
do {
System.out.println(i);

63
i++;
} while (i<5);

System.out.println("-");

// Testando a estrutura for


System.out.println("Teste de for");
for(int z=0;z<5;z++) {
System.out.println(z);
}
}
}

E qual será o resultado da execução da classe agora? Veja a seguir:


Teste de while
0
1
2
3
4
-
Teste de do/while
0
1
2
3
4
-
Teste de for
0
1
2
3
4

Como pode ver as três estruturas de loop apresentam o mesmo resultado, apesar do
funcionamento de cada uma dessas estruturas serem um pouco diferentes entre si. Mas,
qual é a melhor? Não há melhor. Cada uma será mais adequada a cada necessidade. O
importante é conhecer as opções e poder escolher a que achar mais apropriada quando a
necessidade surgir.

for e Array

E estrutura de repetição for é bastante utilizada com Array. Quando falamos sobre Arrays
nós utilizamos o System.out.println para exibir o conteúdo de cada elemento da Array,
indicando o índice um a um. Fizemos algo assim:
// Exibindo dados do Array a
System.out.println(a[0]);
System.out.println(a[1]);
System.out.println(a[2]);

64
System.out.println(a[3]);
System.out.println(a[4]);

Agora, imagine se nossa Array tivesse 750 elementos! Teríamos que escrever uma linha de
System.out.println 750 vezes? Isso não parece ter sentido. E não tem mesmo. Uma forma
eficiente de acessar o conteúdo de uma Array é utilizando a estrutura de repetição for.
Nada nos impede de utilizar as outras estruturas de repetição, mas a for parece ser a mais
eficiente para este objetivo. Veja como o código acima poderia ser escrito utilizando for:
// Exibindo dados do Array a
for (int n=0; n<4; n++ ) {
System.out.println(a[n]);
}

O resultado deste trecho de código seria exatamente o mesmo do trecho anterior. Porém,
esta é uma forma muito mais eficiente de escrever. E se houvesse 750 elementos na Array,
sem problema. A única coisa a fazer seria alterar a condição de controle para n<751.
Mas, tem mais vantagens. Como já dissemos a Array não é um tipo de dado primário. Na
verdade, Array é uma implementação em classe. Em outras palavras, Array (assim como
String) se comportam como classes, possuindo métodos e atributos que podemos utilizar.
Um dos atributos de Array é length, que informa o tamanho da Array, ou seja, o número
de elementos (andares) que ela tem. Com esta informação podemos tornar o nosso trecho
de código ainda mais eficiente e flexível:
// Exibindo dados do Array a
for (int n=0; n<a.length; n++ ) {
System.out.println(a[n]);
}

Nosso Array a possui 5 elementos, de 0 a 4. Então, o atributo lenght retornará 5, que é o


número de elementos. Ou seja, escrever na condição do for que n<a.length é a mesma
coisa que escrever n<5, neste caso. Mas a vantagem é que mesmo que em futuras versões
do programa o tamanho do Array a mude, nosso for vai continuar funcionando
normalmente, sempre mostrando todos os elementos do Array, não importa o tamanho
dele.
Veja agora a versão completa ajustada em Código 22.
Código 22. Classe TestaArray ajustada para TestaArrayComFor.

public class TestaArrayComFor {


public static void main(String[] args) {

// Definindo os Arrays
String[] a;
String b[];

// Criando os Arrays
a = new String[5];

65
b = new String[5];

// Carregando dados no Array a


a[0] = "Joao";
a[1] = "Maria";
a[2] = "Marcos";
a[3] = "Clara";
a[4] = "Rogério";

// Carregando dados no Array b


b[0] = "Silva";
b[1] = "Fernandes";
b[2] = "Pereira";
b[3] = "Lins";
b[4] = "Gomes";

// Exibindo dados do Array a


for (int n=0; n<a.length; n++ ) {
System.out.println(a[n]);
}

// Exibindo dados do Array a


for (int i=0; i<b.length; i++ ) {
System.out.println(b[i]);
}

// Exibindo dados do Array a e b


for (int z=0; z<a.length; z++ ) {
System.out.println(a[z]+" "+b[z]);
}
}
}

Divirta-se com as estruturas de repetição. Elas são muito úteis em programação Java.
Contudo, ainda há mais uma forma de usar a estrutura for. Veremos isso em próximos
capítulos.

Pare o loop, eu quero descer

Há situações onde é necessário interromper um loop antes de seu término originalmente


previsto. Imagine um loop em que um carro continua andando com o objetivo de
percorrer uma determinada distância, digamos 500 quilômetros. O tanque do veículo
suporta 50 litros de gasolina e a relação de consumo seja de 8,5 quilômetros por litro.
Será que o veículo consegue percorrer os 500 quilômetros com apenas um tanque de
combustível? Este loop precisa ser interrompido se a gasolina acabar, não concorda?
Mas antes, vejamos como o break é utilizado para interromper os loops que acabamos de
ver. No caso do while, que testamos primeiro, para interrompê-lo basta fazer uma
verificação e usar o comando break, conforme apresentado no trecho abaixo. A
interrupção será feita logo depois da exibição do número 3.

66
System.out.println("Teste de while");
int n=0;
while (n<5) {

System.out.println(n);

if(n==3)
break;

n++;
}

O mesmo acontece com os demais loops, ou seja, do/while e for. Veja a implementação
do break no trecho de código a seguir:
System.out.println("Teste de do/while");
int i=0;
do {
System.out.println(i);

if(i==3)
break;

i++;
} while (i<5);

System.out.println("Teste de for");
for(int z=0;z<5;z++) {
System.out.println(z);

if(z==3)
break;
}

E quanto ao loop do carro, como ficaria? Simples. Veja o trecho de código a seguir:
int distancia=500;
double tanqueGasolina=50;
double quilometrosPorLitro=8.5;
double consumoPorQuilometro=1/quilometrosPorLitro;

for (int decorrido=1; decorrido<=distancia; decorrido++)


{

tanqueGasolina-=consumoPorQuilometro;
System.out.println("Quilometros rodados: "+decorrido);

if(tanqueGasolina<=0) {
System.out.println("Acabou a gasolina.");
break;
}

67
O código acima faz todas as configurações de parâmetros do veículo antes de iniciar o
loop. Trata-se de um veículo com um tanque com capacidade para 50 litros de gasolina e
com um consumo de 8,5 quilômetros por litro. A viagem está configurada para 500
quilômetros. O loop roda sobre o número de quilômetros a viajar, armazenando em
decorrido o número de quilômetros viajados a cada rodada do loop. O consumo por litro
é descontado do tanqueGasolina, que vai reduzindo até chegar a zero. Quando a gasolina
acaba, um break é executado e o loop se encerra.
Brinque com este exemplo. Altere a distância da viagem, o consumo do veículo ou a
capacidade do tanque. Agora veja o novo código completo de testaLoop em Código 23.
Código 23. Classe TestaLoop implementando break.

public class TestaLoop {

public static void main (String[] args) {

// Testando a estrutura while


System.out.println("Teste de while");
int n=0;
while (n<5) {

System.out.println(n);

if(n==3)
break;

n++;
}

System.out.println("-");

// Testando a estrutura do/while


System.out.println("Teste de do/while");
int i=0;
do {
System.out.println(i);

if(i==3)
break;

i++;
} while (i<5);

System.out.println("-");

// Testando a estrutura for


System.out.println("Teste de for");
for(int z=0;z<5;z++) {
System.out.println(z);

if(z==3)
break;
}

68
int distancia=500;
double tanqueGasolina=50;
double quilometrosPorLitro=8.5;
double consumoPorQuilometro=1/quilometrosPorLitro;

for (int decorrido=1; decorrido<=distancia; decorrido++) {

tanqueGasolina-=consumoPorQuilometro;
System.out.println("Quilometros rodados: "+decorrido);
if(tanqueGasolina<=0) {
System.out.println("Acabou a gasolina.");
break;
}

}
}
}

69
9. Classes - Revisita

A filosofia de orientação a objetos é a base de toda a plataforma Java. Em Java, tudo é um


objeto. Então, para ser produtivo com essa linguagem entender bem do conceito e de sua
aplicação prática é essencial.
Mas vamos abordar este conceito de uma maneira divertida. Há muitos exemplos por aí
baseados em contas correntes, contas contábeis, transações administrativas e etc. Esses
exemplos são excelentes e vale a pena estuda-los. Porém, aqui nesta obra, usaremos como
exemplo alguns simples jogos numéricos. Simples, mas até divertidos.
Quando penso em um jogo simples, penso primeiro em um jogo de dados. O que pode
ser mais simples do que isso? Você escolhe um número de 1 a 6, joga o dado e verifica o
número que ficou para cima. Se for o número que você escolheu então ganhou. Se não,
perdeu. Vamos começar a construir nossa classe com base no conceito de jogo de dados.
O primeiro passo é criar um novo projeto no Eclipse. Lembra como é? Pressione
CTRL+N e escolha (ou digite) Java Project. Podemos chamar nosso projeto de
JogosNumericos.
Lembra dos pacotes? Para organizar nosso código vamos criar, dentro do projeto
JogosNumericos, um pacote chamado ClassesBase. No Eclipse, estando sobre o projeto
JogosNumericos pressione CTRL+N e escolha (ou digite) Package.
Dentro do pacote ClassesBase vamos criar nossa primeira classe, a Jogo. No Eclipse, com
o pacote ClassBase selecionado, pressione CTRL+N e escolha (ou digite) Class. Agora
vamos ao código da nossa classe:

Código 24. Classe Jogo dentro do pacote ClassesBase.

package ClassesBase;

import java.util.Random;

public class Jogo {

// ATRIBUTOS
public int aposta;
public int resultado;

// MÉTODOS
public void executaJogo() {
this.resultado=(new Random().nextInt(6)+1);
}

public void mostraResultado() {


System.out.println ("Sua aposta: "+aposta+" Resultado: "+resultado);

if (this.resultado == this.aposta)
System.out.println("Parabens, você acertou!");

70
else System.out.println ("Errou. Mas, continue tentando...");
}

Reparou no this da linha de comando this.resultado == this.aposta? Vamos explicar em


breve.
Por ora, analisemos brevemente este código. Logo no começo são definidos dois atributos
públicos: aposta e resultado. Em seguida temos o método executaJogo(), que instancia
em rnd um objeto da classe Random. Esse objeto acessa o método nextInt() e o resultado
adicionado de um é guardado no atributo resultado. Mas, um momento: de onde veio
essa classe Random?
A linguagem Java já oferece centenas de classes, com seus métodos e atributos, para nosso
uso. Essas classes executam uma infinidade de funções que podem ser muito úteis em
nossos programas. O objetivo é já oferecer funcionalidades sem que a gente precise ficar
criando tudo do zero.
Por exemplo, se você quisesse criar um método que criasse um número inteiro aleatório
entre 1 e 6 para a nossa classe Jogo, como você faria? Sim, tem um jeito. Mas você teria
que pensar muito e fazer vários testes até construir esse método. Mas não precisa! O Java
já te oferece um método que faz isso. É o método nextInt() da classe Random. Assim, em
vez de você gastar tempo criando o que já existe, este tempo será dedicado à construção
dos métodos específicos do seu programa.
Existem realmente muitas classes já oferecidas pelo Java. Com tanta classe e tanto código,
como eles organizaram tudo isso? Sim, do mesmo jeito que a gente está organizando o
nosso código: usando pacotes. Esse enorme conjunto de pacotes com classes e métodos é
chamado de API (Application Programming Interface). Gostaria de conhecer os pacotes,
classes e métodos da API Java? Fácil:
https://docs.oracle.com/javase/9/docs/api/overview-summary.html.
Comece pelos pacotes da sessão Java SE e divirta-se.
Voltando ao Código 24, há ainda o método mostraResultado(), cujo objetivo é mostrar o
resultado de maneira amigável para o usuário. Agora, a partir de src vamos criar uma
classe chamada TestaJogo, que instanciará e usará a classe Jogo. A classe TestaJogo,
naturalmente, terá o método public static void main (String[] args), pois será a classe a
ser executada pela JVM.
Código 25. Classe TestaJogo utilizando a classe Jogo.

import ClassesBase.Jogo;

public class TestaJogo {

public static void main(String[] args) {


Jogo j=new Jogo();
j.aposta=5;
j.executaJogo();
j.mostraResultado();
}
}

71
Observando o código Código 25 é possível notar que a primeira ação é importar a classe
Jogo que está dentro do pacote ClassesBase. No início da execução a variável j é declarada
como sendo do tipo Jogo e na mesma linha é realizada a instância da classe Jogo na
variável j.
Percebeu que quando criamos uma classe é como se estivéssemos criando um tipo de
dado? E na verdade é isso mesmo que acontece. Por isso é que podemos declarar uma
variável assim: Jogo j. A declaração de uma variável envolve indicar o tipo e o nome da
variável. A classe que a gente acabou de criar é um tipo então. Algo similar acontece com
String e Array, que são tipos e classes.
Em seguida o valor 5 é atribuído ao atributo aposta, o método executaJogo() é executado
e o resultado é mostrado pelo método mostraResultado(). Veja o resultado de uma das
execuções da classe TestaJogo:
Sua aposta: 5 Resultado: 4
Errou. Mas, continue tentando...

Repeti várias vezes a execução até conseguir acertar o 5. Veja o resultado:


Sua aposta: 5 Resultado: 5
Parabéns, você acertou!

Como deve ter notado, a cada execução o resultado varia entre 1 e 6. Este é o objetivo no
método nextInt() da classe Random que utilizamos a partir da API do Java.
Então beleza, tudo funcionando direitinho, certo? Mas podia ser melhor e ter mais
diversidade também. O que isso quer dizer? Veremos a partir de agora alguns conceitos
bem básicos de orientação a objetos que nos ajudarão com esta e com as próximas classes
que construirmos.

Encapsulamento

Embora nossa classe Jogo esteja funcionando direitinho, ela apresenta algumas
oportunidades de melhoria. Por exemplo, se reparar direitinho no Código 25 notará que
os atributos aposta e resultado são públicos. Isso significa que intencionalmente ou não o
programador que estiver usando a classe poderá alterar o valor de resultado antes de
mostrar o resultado. Veja o trecho de código a seguir:
Jogo j=new Jogo();
j.aposta=5;
j.executaJogo();
j.resultado=5;
j.mostraResultado();

Desse jeito vai ganhar sempre! Outra coisa que pode acontecer é o atributo aposta receber
valores que nunca aconteceriam, como neste caso:
Jogo j=new Jogo();
j.aposta=7;
j.executaJogo();
j.mostraResultado();

72
Nós sabemos que o método executaJogo() foi construído de um jeito que os resultados
sempre serão um valor entre 1 e 6. Então esta aposta no número 7 nunca vai acontecer.
Estes dois problemas são possíveis apenas porque não foi respeitada uma técnica de
programação orientada a objetos chamada de encapsulamento. Encapsular é esconder
tudo o que não é necessário ser visto e integrar as informações a métodos. Mas, o que é
isso?
Eu entendo muito pouco de eletrônica, mas ainda assim consigo usar diversos aparelhos
eletrônicos. Alguns bem complexos. Como isso é possível? Quem fabricou o aparelho
escondeu toda a complexidade dele dentro de uma caixa e deixou visível para mim apenas
o que eu posso usar com segurança, provavelmente alguns botões (físicos ou virtuais).
Esses botões também oferecem alguma proteção, controlando o máximo e o mínimo de
determinada função.
O encapsulamento é exatamente isso. Temos que esconder as complexidades da classe e
proteger os dados através de mecanismos implementados por métodos. Por exemplo, por
que o atributo resultado precisa ser público? Na verdade, nenhum atributo deve ser
público. Todos devem ser privados da classe e acessíveis apenas através de métodos que
garantam a segurança desses dados.
Então, mãos a obra. A primeira coisa a fazer é mudar a visibilidade dos atributos de
public para private. Desse jeito, apenas a própria classe pode acessar esses dados.
Qualquer acesso a eles será intermediado por um método nosso, garantindo total
segurança. Esses métodos são conhecidos como getters (que retornam os dados) e setters
(que armazenam os dados).
Assim que você alterar o tipo dos atributos aposta e resultado para private, a classe
TestaJogo vai quebrar (apresentar um erro). A linha j.aposta=5 já não vai funcionar. Sabe
por que? Exatamente, aposta não pode mais ser visto fora da classe. Se alguém quiser
acessar vai ter que ser por meio de um método nosso. Então vamos criar um setter para
esse atributo na classe Jogo:
public void setAposta(int aposta) {
this.aposta = aposta;
}

Agora vamos alterar a linha que está dando erro na classe TestaJogo:
Jogo j=new Jogo();
j.setAposta(5);
j.executaJogo();
j.mostraResultado();

Agora tudo voltou a funcionar. Além disso, não é mais possível acessar o atributo
resultado. Ele nem aparece. Será que precisamos criar um setter ou um getter para o
atributo resultado? Ou será que precisamos criar um getter para o atributo aposta? A
resposta é: não. Crie getters e setters apenas para os atributos que precisam ser acessados
de fora da classe. O resultado não precisa ser acessado, pois ele já é exibido pelo método

73
mostraResultado(). Quanto ao atributo aposta, ele precisa apenas ter o valor atribuído.
Não precisa ser consultado. Se não precisa, não crie.
E quanto a proteger o valor de aposta? Se aposta for maior do que 6 ou menor do que 1,
então não haverá chances de vencer o jogo. Devemos criar algum mecanismo de proteção
aqui. Por enquanto vamos apenas avisar que a aposta não valeu, fazendo o seguinte ajuste
no setter de aposta:
public void setAposta(int aposta) {
if (aposta>6 || aposta <1)
System.out.println("APOSTA INVÁLIDA!");
this.aposta = aposta;
}

Neste caso estamos apenas avisando que a aposta é inválida. Em futuros capítulos
abordaremos o tratamento de exceções e poderemos criar uma solução mais rígida para
este problema. Mas, por enquanto, o objetivo é mostrar que os setters poderão
implementar mecanismos de proteção aos dados, evitando que o programador que está
usando a classe atribua valores indevidos. Veja agora os códigos completos e ajustados até
aqui:
Código 26. Classe jogo ajustada para encapsulamento.

package ClassesBase;

import java.util.Random;

public class Jogo {

// ATRIBUTOS
private int aposta;
private int resultado;

// GETTERS E SETTERS
public void setAposta(int aposta) {
if (aposta>6 || aposta <1)
System.out.println("APOSTA INVÁLIDA!");
this.aposta = aposta;
}

// MÉTODOS
public void executaJogo() {
Random rnd = new Random();
this.resultado=rnd.nextInt(6)+1;
}

public void mostraResultado() {


System.out.println ("Sua aposta: "+aposta+" Resultado: "+resultado);

if (this.resultado == this.aposta)
System.out.println("Parabéns, você acertou!");

else System.out.println ("Errou. Mas, continue tentando...");


}

74
Código 27. Classe TestaJogo ajustada para encapsulamento.

import ClassesBase.Jogo;

public class TestaJogo {

public static void main(String[] args) {


Jogo j=new Jogo();
j.setAposta(7);
j.executaJogo();
j.mostraResultado();
}
}

Polimorfismo

A palavra polimorfismo significa múltiplas formas. Em orientação a objetos isso significa


que uma mesma função pode ter várias formas. Isso facilita a vida de quem usa a classe e
cada usuário da classe poderá escolher a forma que melhor atende suas necessidades.
Por exemplo, no caso de nossa classe Jogo alguns poderiam preferir enviar a aposta na
hora de executar o método executaJogo(). Para isso nós teríamos que mudar o código
deste método, da seguinte forma:
public void executaJogo(int aposta) {
this.aposta=aposta;
Random rnd = new Random();
this.resultado=rnd.nextInt(6)+1;
}

Como pode ver, com a alteração o método agora recebe um inteiro aposta e este valor é
atribuído ao atributo aposta da classe Jogo. O resto continua como estava e tudo
funciona. Mas, espere um momento. Duas variáveis aposta? Pode isso?
Claro que não pode haver duas variáveis com o mesmo nome e no mesmo escopo. Mas,
neste caso elas não estão no mesmo escopo. O atributo aposta é da classe Jogo. Quando
queremos nos referir a um atributo ou mesmo a um método da classe corrente podemos
utilizar a palavra reservada this. Assim, this se refere à instância corrente da classe Jogo.
Então this.aposta se refere ao atributo da classe Jogo.
E a variável aposta que recebemos por parâmetro? As variáveis que recebem parâmetros
são variáveis do tipo local, com visibilidade apenas dentro do método onde são utilizadas.
Neste caso, aposta é uma variável local e this.aposta é um atributo da classe.
Mas agora surge um outro problema: se alterarmos o método executaJogo() para receber a
aposta por parâmetro, então não será mais possível utilizar o método antigo, sem
parâmetro. E se outros programadores preferirem daquele jeito? Pior ainda e se já
existirem muitos programas que usam a classe Jogo e que usam o método da forma
original?

75
É aí que entra o polimorfismo. Podemos ter a mesma função com comportamentos
diferentes. Veja o que podemos fazer:
public void executaJogo() {
Random rnd = new Random();
this.resultado=rnd.nextInt(6)+1;
}

public void executaJogo(int aposta) {


this.aposta=aposta;
Random rnd = new Random();
this.resultado=rnd.nextInt(6)+1;
}

Podemos escrever o mesmo método duas ou mais vezes, desde que sua assinatura seja
diferente. Assinatura de um método é o formato de sua declaração, onde se diz o tipo de
retorno, a quantidade de parâmetros e os tipos dos parâmetros. Neste caso temos duas
assinaturas diferentes: uma sem parâmetros e outra com um parâmetro do tipo inteiro.
Dependendo de como o método for chamado, com ou sem parâmetro, um ou outro
método é executado.
Mas ainda tem alguma coisa que não está legal. É que tem código repetido nos dois
métodos. Imagine se tivermos 5 métodos, com 5 trechos de código repetidos. E quando
tiver que fazer um ajuste no código, então vai ter que fazer em todos? E se fizer em quatro
e esquecer de fazer em um? Isso seria um problemão para manter o código depois.
Mas é bem fácil resolver isso. Veja:
public void executaJogo() {
Random rnd = new Random();
this.resultado=rnd.nextInt(6)+1;
}

public void executaJogo(int aposta) {


this.aposta=aposta;
this.executaJogo();
}

Percebeu a mudança? Agora no segundo executaJogo() a gente faz o que tem que fazer, ou
seja, atribui o valor da variável local aposta para o atributo aposta e depois chama o
próprio método executaAposta() original para terminar o trabalho. Desse jeito não há
repetição de código e nenhum problema futuro de manutenção.
Veja a versão completa dos códigos depois dos ajustes para polimorfismo.
Código 28. Classe Jogo depois dos ajustes para polimorfismo.

package ClassesBase;

import java.util.Random;

public class Jogo {

76
// ATRIBUTOS
private int aposta;
private int resultado;

// GETTERS E SETTERS
public void setAposta(int aposta) {
if (aposta>6 || aposta <1)
System.out.println("APOSTA INVÁLIDA!");
this.aposta = aposta;
}

// MÉTODOS
public void executaJogo() {
Random rnd = new Random();
this.resultado=rnd.nextInt(6)+1;
}

public void executaJogo(int aposta) {


this.aposta=aposta;
this.executaJogo();
}

public void mostraResultado() {


System.out.println ("Sua aposta: "+aposta+" Resultado: "+resultado);

if (this.resultado == this.aposta)
System.out.println("Parabéns, você acertou!");

else System.out.println ("Errou. Mas, continue tentando...");


}

Código 29. Classe TestaJogo após ajustes para polimorfismo.

import ClassesBase.Jogo;

public class TestaJogo {

public static void main(String[] args) {


Jogo j=new Jogo();
j.executaJogo(6);
j.mostraResultado();
}
}

Herança

Agora chegamos ao ponto alto deste capítulo: herança. Falamos disso quando
introduzimos o conceito de orientação a objetos. A capacidade de herdar uma classe a
partir de outra e fazer ajustes e aprimoramentos. E você verá que isso é simples e fácil de
fazer. Então, vamos começar logo colocando a mão na massa. Vamos criar uma nova
classe no Eclipse. Ela se chamará Dado. Ao cria-la, ela deverá estar assim:

77
package ClassesBase;

public class Dado {

Ótimo. Agora faremos o seguinte: vamos herdar tudo que classe Jogo. E a palavra mágica
para fazer isso é extends. Usaremos este comando para estender, ou herdar, a classe Jogo.
Veja como ficará o código da classe Dado:
package ClassesBase;

public class Dado extends Jogo {

Tudo pronto. Já podemos usar. Duvida? Então faça ajustes na classe TestaJogo, trocando
a classe Jogo pela classe Dado, conforme Código 30.

Código 30. Ajustes em TestaJogo para usar a classe Dado ao invés da classe Jogo.

import ClassesBase.Dado;

public class TestaJogo {

public static void main(String[] args) {


Dado j=new Dado();
j.executaJogo(6);
j.mostraResultado();
}
}

Repare que agora estamos importando a classe Dado e não mais a classe Jogo. Também
estamos definindo a variável j como sendo do tipo Dado e instanciando um objeto de
Dado nesta variável. Depois, usamos os métodos que eram de Jogo normalmente em
Dado. Por que isso acontece? Porque tudo o que a classe Jogo tinha foi herdado pela
classe Dado. Tudo funciona perfeitamente.
Poxa, isso é até legal e tudo. Mas qual é a vantagem da herança? Ainda não foi possível
perceber a vantagem, mas veremos a seguir. Que tal criarmos agora uma classe Roleta?
Vamos herdar de Jogo, desse jeito:
package ClassesBase;

public class Roleta extends Jogo {

Se você instanciar essa classe que acabamos de criar tudo vai funcionar normalmente,
como aconteceu com a classe Dado. Mas tem um problema com essa nova classe. No jogo

78
de roletas os números vão de 0 a 36 e não de 1 a 6 como acontece no caso dos dados. E
agora, o que fazemos?
Para compreender melhor as nossas classes utilizaremos agora um diagrama chamado
diagrama de classes. Não entraremos nos detalhes técnicos desse diagrama, apenas o
utilizaremos na medida da nossa necessidade.

Figura 47. Diagrama de classes do pacote ClassesBase.

O diagrama apresentado na Figura 47 apresenta a classe Jogo com seus dois atributos e
seus quatro métodos. As classes Dado e Roleta aparecem como vazias, mas na verdade a
seta que as liga à Jogo indica uma herança de tudo que a classe Jogo tem.
O problema que temos é que o método executaJogo() contém uma regra de negócio, ou
seja, ele define qual é a regra do resultado, que sempre será um valor de 1 a 6. O que
precisamos fazer é criar um método executaJogo() específico para Roleta que gere
resultados de 0 a 36. Os demais métodos e atributos continuarão na classe original.
Lembra-se do polimorfismo? Se criarmos um método executaJogo() em nossa classe
Roleta, ele vai sobrescrever o método original. Assim, a nova regra de negócio vai ficar
valendo, mas só para a classe Roleta. Vamos tentar? Veja o Código 31 a seguir.
Código 31. Classe Roleta sobre escrevendo método executaJogo() com nova regra de negócio.

package ClassesBase;

import java.util.Random;

public class Roleta extends Jogo {

public void executaJogo() {


Random rnd = new Random();
this.resultado=rnd.nextInt(37);
}

Opa! Deu problema. O método que a gente criou acabou não funcionando. Por que?
Porque o atributo resultado é private da classe Jogo. Lembra o que a gente faz para dar
acesso aos atributos de nossas classes? Isso mesmo, agente cria getters e setters. Neste caso,

79
precisamos de um setter, já que a classe Roleta precisa guardar um valor no atributo
resultado da classe Jogo. Vamos criar o setter na classe Jogo, com o seguinte trecho de
código:
public void setResultado(int resultado) {
this.resultado=resultado;
}

Em seguida vamos alterar o método executaJogo() que implementamos em nossa classe


Roleta, da seguinte maneira:
public void executaJogo() {
Random rnd = new Random();
this.setResultado(rnd.nextInt(37));
}

E quando executamos a classe TestaJogo, teremos o seguinte resultado no console:


Sua aposta: 6 Resultado: 2
Errou. Mas, continue tentando...
-
Sua aposta: 35 Resultado: 3
Errou. Mas, continue tentando...

Note que na classe Roleta nós implementamos apenas o método executaJogo(). Todo o
resto continuou sendo herdado da classe Jogo. Neste método sobre escrito nós mudamos
a regra de negócio para resultado, que agora pode ser um número inteiro de 0 a 36. Veja
como ficou nosso diagrama de classes na Figura 48. A classe Dado continua usando o
método executaJogo() da classe Jogo, mas a classe Roleta tem seu próprio método
executaJogo() que se sobrepõe ao original.

Figura 48. Diagrama de classes do pacote ClassesBase com método sobre escrito por Roleta.

Veja agora os códigos completos depois de todos os ajustes:


Código 32. Classe Jogo após os ajustes para herança.

package ClassesBase;

import java.util.Random;

80
public class Jogo {

// ATRIBUTOS
private int aposta;
private int resultado;

// GETTERS E SETTERS
public void setAposta(int aposta) {
if (aposta>6 || aposta <1)
System.out.println("APOSTA INVÁLIDA!");
this.aposta = aposta;
}

public void setResultado(int resultado) {


this.resultado=resultado;
}

// MÉTODOS
public void executaJogo() {
Random rnd = new Random();
this.setResultado(rnd.nextInt(6)+1);
}

public void executaJogo(int aposta) {


this.aposta=aposta;
this.executaJogo();
}

public void mostraResultado() {


System.out.println ("Sua aposta: "+aposta+" Resultado: "+resultado);

if (this.resultado == this.aposta)
System.out.println("Parabéns, você acertou!");

else System.out.println ("Errou. Mas, continue tentando...");


}

Código 33. Classe Dados após os ajustes para herança.

package ClassesBase;

public class Dado extends Jogo {

Código 34. Classe Roleta após os ajustes para herança.

package ClassesBase;

import java.util.Random;

public class Roleta extends Jogo {

81
public void executaJogo() {
Random rnd = new Random();
this.setResultado(rnd.nextInt(37));
}

Código 35. Classe TestaJogo após os ajustes para herança.

import ClassesBase.Dado;
import ClassesBase.Roleta;

public class TestaJogo {

public static void main(String[] args) {

Dado dd=new Dado();


dd.executaJogo(6);
dd.mostraResultado();

System.out.println("-");

Roleta rl=new Roleta();


rl.executaJogo(35);
rl.mostraResultado();
}
}

Neste capítulo vimos os conceitos básicos e a aplicação prática de três fundamentos do


paradigma de orientação a objetos: encapsulamento, polimorfismo e herança.
Certamente, há formas ainda mais avançadas de implementação desses conceitos, como
através de classes abstratas, métodos abstratos e interfaces, sobre o que falaremos em
breve.

82
10. Interfaces

Você sabe o que é uma franquia? Imagine uma empresa muito famosa que vende lanches
em todo o país e talvez até em outros países. A marca da empresa é muito conhecida.
Então essa empresa decide franquear sua marca e seu método de fazer lanches. Ela cria
um contrato de franquia. Se você quiser abrir uma lanchonete daquela marca, então vai
precisar assinar um contrato desses. Neste contrato há exigências sobre o tamanho das
instalações, a quantidade de funcionários, o tipo de equipamento e os fornecedores que
você será obrigado a usar. Quem vai comprar tudo e fazer tudo é você, mas as exigências
são do contrato. Se descumprir o contrato, não pode usar a marca. Bem, é mais ou menos
isso.
Mas, por que estamos falando de franquias? Porque as interfaces são como franquias. Elas
estabelecem o que uma determinada classe tem que fazer, como se fosse um contrato. E a
classe precisa implementar as exigências desse contrato. Quando isso acontece dizemos
que a classe está implementando a interface. É porque a classe está implementando os
métodos determinados da interface.

Criando uma interface

O processo para criar uma interface é similar ao processo para criar uma classe. O nome
do arquivo deve ser idêntico ao nome da interface. Vamos criar um novo projeto no
Eclipse chamado UsoDeInterface. Dentro do projeto, pressione CTRL+N e escolha
Interface. Escolha o seguinte nome para a interface: InterfaceJogos. Assim podemos
voltar às nossas classes de jogos.
A sintaxe para criação de uma interface é a seguinte:
<visibilidade> <nome da interface> {
Método1();
Método2();
MétodoN();
}

A interface que acabamos de criar no Eclipse terá a seguinte estrutura inicial:


public interface InterfaceJogos {

Sobre a visibilidade da interface, é interessante que ela seja pública, pois do contrário não
poderá ser implementado pelas classes. Também os métodos que ela contratualiza devem
ser públicos, para que sejam vistos como exigência pelas classes que implementarem a
interface.

83
Se uma interface é um contrato, quais são as regras? As regras deste nosso contrato são os
métodos exigidos pela interface. Estes métodos são apenas exigidos pela interface, mas
não são implementados por ela. Isso quer dizer que na interface só tem a assinatura do
método (cabeçalho) e não o corpo. Afinal, interface é só um contrato de franquia. Quem
tem que implementar tudo é a classe que utiliza a interface. Então vamos estabelecer que
métodos devem existir nas classes que implementares a nossa InterfaceJogos. Veja o
Código 36.
Código 36. Inteface InterfaceJogos com suas regras.

public interface InterfaceJogos {

// GETTERS E SETTERS
public void setAposta(int aposta);
public void setResultado(int resultado);

// MÉTODOS
public void executaJogo();
public void executaJogo(int aposta);
public void mostraResultado();

‘Mas só tem as assinaturas dos métodos’, talvez você esteja pensando. É isso mesmo. Essa
interface está dizendo o seguinte: qualquer um que quiser me implementar vai ter que ter
esses 5 métodos e vai ter que ser do jeito que estou descrevendo aqui, com esses mesmos
retornos, com os mesmos parâmetros e com os mesmos tipos.

Implementando uma interface

Agora vamos pegar a classe Jogo e trazer para o nosso projeto. Pode copiar do projeto
antigo e colocar no nosso projeto UsoDeInterface. E vamos implementar nossa interface
nesta classe. Sabe como se implementa uma interface em uma classe? A sintaxe é a
seguinte:
<visibilidade> class <nome da classe> implements <nome da
interface> {
...
}

Se você parou para observar esta sintaxe, notará que apenas foi adicionada a palavra
reservada implements na criação da classe, seguida pelo nome da interface. Então, veja
em Código 37 como ficou a implementação.
Código 37. Classe Jogo implementando interface InterfaceJogos.

import java.util.Random;

public class Jogo implements InterfaceJogos {

// ATRIBUTOS
private int aposta;

84
private int resultado;

// GETTERS E SETTERS
public void setAposta(int aposta) {
if (aposta>6 || aposta <1)
System.out.println("APOSTA INVÁLIDA!");
this.aposta = aposta;
}

public void setResultado(int resultado) {


this.resultado=resultado;
}

// MÉTODOS
public void executaJogo() {
Random rnd = new Random();
this.setResultado(rnd.nextInt(6)+1);
}

public void executaJogo(int aposta) {


this.aposta=aposta;
this.executaJogo();
}

public void mostraResultado() {


System.out.println ("Sua aposta: "+aposta+" Resultado: "+resultado);

if (this.resultado == this.aposta)
System.out.println("Parabéns, você acertou!");

else System.out.println ("Errou. Mas, continue tentando...");


}

Vamos fazer o teste completo? Copie para o novo projeto as classes TestaJogo, Dado e
Roleta. Talvez seja necessário fazer algum pequeno ajuste, pois agora todos estão no
mesmo pacote, o default e antes estavam em pacotes diferentes. Provavelmente basta
excluir os imports que o Eclipse aponta como falha (casa tenha algum). Agora rode a
classe TestaJogo. Tudo continuou funcionando como antes? Sim. Então, qual foi a
vantagem de usar uma interface?
Tente excluir um método qualquer da classe Jogo. Se o fizer, receberá uma mensagem
semelhante a esta apresentada na Figura 49. Por que isso acontece? Porque o contrato está
sendo desrespeitado. A interface InterfaceJogos exige um conjunto de métodos e a classe
que implementa esta interface, no caso a classe Jogo, deve ter a implementação de todos
os métodos.

85
Figura 49. Mensagem de erro ao excluir método exigido na interface.

Por que são importantes?

Então, pelo que estamos vendo, o objetivo das interfaces é estabelecer um contrato que
seja obedecido por duas ou mais classes. Em outras palavras, é uma forma de criar um
padrão mínimo entre diversas classes de um determinado grupo. Ele garante que cada
uma dessas classes vai implementar minimamente um determinado conjunto de métodos.
Isso é muito útil para quem usa o grupo de classes. Em vez de estudar os métodos de cada
uma das classes, basta estudar as interfaces que são implementadas por aquele grupo.
Talvez você não precise criar e implementar interfaces em seus projetos. Talvez precise.
Independentemente disso o Java implementa muitas interfaces em suas classes. A API
Java está cheia de interfaces que utilizamos no dia a dia. Entender o que é uma interface e
como ela funciona nos ajuda a entender muito melhor a API do Java e a utilizá-la com
mais eficiência. Muito em breve você vai perceber isso.

86
11. Coleções

Nós já falamos a respeito de Arrays e vimos sua importância para a programação Java.
Agora vamos avançar: falaremos sobre Collections, um mecanismo que nos permite
trabalhar com grupos de objetos. Você verá que as Collections são parecidas com Arrays,
porém muito mais avançadas. No entanto, para entender melhor as coleções precisamos
compreender razoavelmente o que é uma interface no Java. Ainda bem que você já
aprendeu sobre esse assunto.
Assim como os Arrays, as coleções permitem guardar grupos de informações similares.
Daí o nome coleções. É como se fossem coleções de informações. Porém, as coleções são
muito mais poderosas e flexíveis do que os Arrays.
Mas, o que Collections tem a ver com interface? Existem diferentes tipos de coleções,
cada tipo mais adequado para um determinado objetivo. Mas, para garantir uma
padronização entre os métodos utilizados nestas coleções, foram criadas interfaces, que
você já sabe que são contratos. Assim, as interfaces garantem um contrato comum de
métodos que são oferecidos pelos diferentes tipos de coleções.
Então, quais são as interfaces e quais são as classes que implementam essas interfaces?

Interfaces

Existe uma rica estrutura de interfaces que contratualizam o uso de coleções no Java,
como pode ser visto na Figura 50. Note que há classes que implementam as interfaces.

Figura 50. Relação de interfaces para coleções que são implementadas por classes abstratas.

87
As classes dessa figura que implementam as interfaces são chamadas de abstratas, pois elas
mesmas não podem ser instanciadas. Outras classes precisam herdar as classes abstratas e
essas sim podem ser instanciadas. A Figura 51 apresenta o conjunto de classes de coleções
que podemos utilizar em nosso código e as relaciona às classes abstratas da figura anterior.
Quando essas classes herdam as classes abstratas, herdam também a implementação das
interfaces que são implementadas por aquelas classes abstratas.

Figura 51. Classes de coleções que herdam as classes abstratas que implementam as interfaces.

Talvez você esteja achando tudo isso muito complicado. Mas, não se preocupe com isso.
O que importa é que podemos instanciar as classes de coleções da Figura 51 e que essas
classes respeitam as interfaces ligadas a elas. Na verdade, a gente vai usar bem poucas
dessas classes, talvez uma ou duas. Naturalmente, dependendo da necessidade você
poderá vir a utilizar mais dessas classes, ou até todas. Mas isso virá com o tempo. Por ora,
vamos nos concentrar em entender como usar as coleções e que benefícios podemos obter
delas.
Mas, então, porque estas figuras? Ao passo que você se envolver mais com o uso de
coleções estas figuras passarão a fazer mais sentido e serão muito úteis para o seu
entendimento delas.

Usando coleções

Uma primeira diferença que podemos destacar entre Arrays e coleções é que uma Array
precisa ter seu número de elementos definidos, enquanto que as coleções não. Os

88
elementos de uma coleção podem continuar a ser adicionados indefinidamente (ou
enquanto houver memória).
Vamos colocar a mão na massa? Crie um novo projeto no Eclipse chamado
TestaColecoes. Dentro deste projeto, crie uma classe com main chamada Teste01.
Dentro desta classe vamos criar nossa primeira coleção. Mas, qual é a sintaxe? Veja a
seguir a sintaxe para declarar uma variável de referência para uma coleção:
<tipo de coleção> <tipo de dado> nomeDaVariavel;

Agora, veja a sintaxe para criar a coleção propriamente dita:


nomeDaVariavel= new <tipo de coleção> <tipo de dado>();

Bem, como isso seria na prática? Veja um trecho de código para criar uma coleção com os
dias da semana:
List<String> diasSemana;
diasSemana = new ArrayList<String>();

Apenas lembrando: List e ArrayList são classes e devem ser importadas para nossa
aplicação com import. Notou que as coleções são instanciadas como classes? E, na
verdade, essas coleções inclusive possuem métodos. É preciso ficar atento a duas coisas: 1)
o tipo de coleção que você pretende usar. Neste exemplo nós usamos a do tipo List (talvez
a mais comum); 2) O tipo de dado que vai colocar dentro da coleção. Neste exemplo nós
utilizamos o tipo String. Mas poderia ser qualquer tipo, inclusive uma classe como tipo.
Veremos isso em breve.
Mas, assim como nas classes, podemos declarar e criar a coleção em uma mesma linha de
código, com a seguinte sintaxe:
<tipo de coleção> <tipo de dado> nomeDaVariavel= new <tipo de
coleção> <tipo de dado>();

O código seria assim:


List<String> diasSemana = new ArrayList<String>();

Antes de tudo, lembre-se de que List é uma interface e que ArrayList é uma classe que
implementa List. Tanto as interfaces quanto as classes precisam ser importadas dos
pacotes da API do Java para funcionar bem. O próprio Eclipse vai te avisar disso. De
qualquer modo, você precisará dos seguintes imports:
import java.util.List;
import java.util.ArrayList;

89
Agora, como adicionamos elementos na lista? Com as Arrays a gente simplesmente
utilizava colchetes para informar o número do elemento que queríamos acessar, para
guardar ou ler uma informação. No caso das coleções nós utilizaremos seus métodos. E os
métodos que uma coleção possui depende de sua interface. Lembra disso? Do contrato de
métodos.
Se você olhar na Figura 51 vai encontrar as classes concretas de coleções que podemos
utilizar. Elas estão em retângulos com a borda mais escura. São: LinkedList, ArrayList,
ArrayDeque, PriorityQueue, TreeSet, EnumSet, HashSet e LinkedHashSet. Mas a
definição dos métodos que utilizaremos estão nas interfaces, como consta na Figura 50.
Muito complicado? Vamos simplificar: ao instanciar uma coleção você pode usar a mesma
classe, mas com interfaces diferentes. E por que você faria isso? Para tornar seu código
mais flexível. Quer um exemplo? Pois bem. Cada tipo de coleção é mais apropriado para
um objetivo. Imagine que você tenha escolhida a HashSet. Todo o seu código foi
construído com base nos métodos específicos da classe HashSet.
Em determinado momento você descobre que a melhor classe de coleção para o seu
objetivo é a ArrayList e não a HashSet. Então você vai lá no início do seu código e altera
a instância da sua coleção de HashSet para ArrayList. É quando descobre que tem vários
métodos de HashSet que não existem em ArrayList e você precisa mexer no código todo
para que ele volte a funcionar.
Neste caso hipotético, se você tivesse usado a interface List e programado tudo com os
métodos dessa interface, não faria diferença instanciar uma coleção do tipo ArrayList ou
HashSet. Tudo continuaria funcionando. Então, o uso de interfaces pode deixar nosso
código mais estruturado, mais flexível e mais fácil de manter.
Notou que fizemos exatamente isso em nosso código? Ao invés de escrever:
ArrayList<String> diasSemana = new ArrayList<String>();

Nós escrevemos:
List<String> diasSemana = new ArrayList<String>();

As duas formas funcionam. Mas a segunda forma utiliza uma interface, a List, trazendo
todas as vantagens que acabamos de mencionar.
Mas, vamos ou não vamos adicionar elementos em nossa lista? Claro que sim. Esta é uma
das grandes vantagens das Collections em relação aos Arrays – o número de elementos
pode aumentar e diminuir livremente. Nas Arrays o número de elementos é fixo,
inflexível. Para as Collections podemos utilizar um método contratualizado pela interface
List, o add(). Veja o código abaixo:

List<String> diasSemana = new ArrayList<String>();

diasSemana.add("Domingo");
diasSemana.add("Segunda");

90
diasSemana.add("Terça");
diasSemana.add("Quarta");
diasSemana.add("Quinta");
diasSemana.add("Sexta");
diasSemana.add("Sábado");

System.out.println(diasSemana);

Se este pequeno trecho de código for executado, apresentará o seguinte resultado no


console:
[Domingo, Segunda, Terça, Quarta, Quinta, Sexta, Sábado]

Nós adicionamos cada dia da semana ao diasSemana, que é uma referência para instância
de uma coleção ArrayList de Strings. Quando imprimimos uma coleção são apresentados
os elementos da coleção separado por vírgulas, sendo o conjunto limitado por colchetes.
Isto acontece independentemente do tipo de dado que será armazenado na coleção. Veja
este exemplo:
List<Double> notas = new ArrayList<Double>();
notas.add(4.5);
notas.add(8.4);
notas.add(9.3);
System.out.println(notas);

Que exibe o seguinte resultado:


[4.5, 8.4, 9.3]

Mas, como podemos obter um valor em uma coleção? Podemos utilizar o método get(),
como no trecho abaixo:
List<Double> notas = new ArrayList<Double>();
notas.add(4.5);
notas.add(8.4);
notas.add(9.3);
System.out.println(notas);
System.out.println(notas.get(2));

Que exibe:
[4.5, 8.4, 9.3]
9.3

Percebeu que o primeiro elemento de uma Collection é o elemento zero? Exatamente


como acontece com as Arrays. Neste aspecto eles são idênticos.

91
Agora já temos elementos para fazer nosso primeiro teste mais concreto em nossa classe
Teste01 do projeto TestaColecoes, como pode ser visto no Código 38.
Código 38. Primeiro teste com coleções usando ArrayList.

import java.util.ArrayList;
import java.util.List;

public class Teste01 {

public static void main(String[] args) {

List<String> alunos = new ArrayList<String>();

alunos.add("Clara");
alunos.add("Carlos");
alunos.add("Sandra");
alunos.add("Roberto");

List<Double> notas;
notas = new ArrayList<Double>();

notas.add(6.5);
notas.add(8.4);
notas.add(9.3);
notas.add(7.9);

for (int a=0; a<alunos.size();a++) {


System.out.println(alunos.get(a) + " - " + notas.get(a));
}

Criamos uma coleção do tipo ArrayList para String, utilizando a interface List na
variável alunos e adicionamos 4 alunos. Depois criamos uma coleção do mesmo tipo para
Double, com o objetivo armazenar as notas desses alunos. As notas são adicionadas. Por
fim, utilizamos um for para percorrer os elementos das duas coleções, pegar seu conteúdo
com get() e exibir no console. O resultado é:
Clara - 6.5
Carlos - 8.4
Sandra - 9.3
Roberto - 7.9

Note que dentro do for utilizamos o método size() da interface List aplicado a uma classe
ArrayList, com o objetivo de descobrir o tamanho desse ArrayList, ou seja, o número
total de elementos. Quanto utilizamos Array nós utilizamos um atributo, o lenght, ao
invés de um método. Por que? Porque a interface List contratualiza uma forma diferente
para obtenção do tamanho de uma lista. Qualquer lista que implemente a interface List
terá sempre um método size() para informar o tamanho da lista.

92
Tipos de coleções

Como já mencionado, cada tipo de coleção serve a um objetivo mais específico. Você não
precisa se preocupar em conhecer os detalhes de cada tipo por agora. Algumas mais
utilizadas são apresentadas muito brevemente na Tabela 5.
Tabela 5. Lista de algumas classes de coleções.

Tipo Objetivo
LinkedList Implementa as interfaces List e Deque, com uma lista duplamente
ligada. Permite a adição e valor null.

ArrayList Implementa interface List, com uma lista no estilo array de tamanho
variável. Permite a adição de valores null.

ArrayDeque Implementa interface Deque, com uma lista no estilo array de


tamanho variável. Não permite a adição de valores null. Pode ser
usada para implementar filas (queue), sendo mais rápida do que
LinkedList para este fim.

HashSet Implementa a interface Set e é muito rápida. Porém, não garante a


manutenção da ordem dos elementos ao passo que eles são
adicionados na lista. Permite a adição de elemento null.

LinkedHashSet Implementa a interface Set, mantendo a lista duplamente ligada


entre seus elementos. É possível estabelecer a ordem dos elementos.

Neste capítulo utilizaremos apenas a classe ArrayList implementando a interface List.

Coleção de classes

Como já mencionando, uma lista tem um tipo de lista e também um tipo de dado.
Utilizamos até agora os tipos de dado String e Double. Mas, será que podemos usar uma
classe com tipo de dado? Claro que sim. Já vimos que String é na verdade uma classe. Se
uma classe é também um tipo dado então podemos armazenar instâncias de classes em
uma coleção. Muito confuso? Calma, vamos ver isso na prática.
Em nosso projeto TestaColecoes vamos criar uma nova classe. Vai ser a classe aluno,
conforme Código 39.
Código 39. Classe Aluno.

public class Aluno {

private String nome;


private Double nota;

public void setNome(String nome) {


this.nome = nome;
}
public String getNome() {

93
return nome;
}
public void setNota(Double nota) {
this.nota = nota;
}
public Double getNota() {
return nota;
}

Muito bem, nada de novo na criação de classes. Criamos uma classe com dois atributos
privados (nome e nota) e os getters e setters para estes atributos. Agora, ainda em nosso
projeto TestaColecoes, vamos criar uma classe Testa02 com main e definir uma coleção
que armazene instâncias de nossa classe Aluno. Veja o Código 40.
Código 40. Armazenando instâncias de Aluno em coleções.

import java.util.ArrayList;
import java.util.List;

public class Testa02 {


public static void main(String[] args) {

Aluno a1=new Aluno();


a1.setNome("Clara");
a1.setNota(6.5);

Aluno a2=new Aluno();


a2.setNome("Carlos");
a2.setNota(8.4);

Aluno a3=new Aluno();


a3.setNome("Sandra");
a3.setNota(9.3);

Aluno a4=new Aluno();


a4.setNome("Roberto");
a4.setNota(7.9);

List<Aluno> alunos = new ArrayList<Aluno>();

alunos.add(a1);
alunos.add(a2);
alunos.add(a3);
alunos.add(a4);

for (int a=0; a<alunos.size();a++) {


System.out.println(alunos.get(a).getNome() + " - " +
alunos.get(a).getNota());
}

}
}

94
Nós criamos 4 instâncias da classe Aluno. Depois criamos uma lista alunos, mas agora de
um tipo diferente. Antes era do tipo String. Agora é do tipo Aluno, a nossa classe. Isso
quer dizer que cada andar (ou elemento) da ArrayList conterá uma instância de classe
aluno (uma referência para a instância).
Utilizamos o método add() da interface List para adicionar as instâncias de Aluno para
dentro da coleção alunos. E depois executamos um loop para ler todos os andares da lista
alunos, utilizando o método size() para descobrir quantos andares tem nossa lista.
Tranquilo, não achou? Mas esse for ainda está parecendo meio complicado. Tem jeito de
ficar um pouco mais simples. Vamos utilizar uma construção específica de for que é
chamada de foreach e que a gente usa para percorrer coleções. Note que não existe o
comando foreach no Java, mas o tipo de for é que é chamado assim. Vamos ver a sintaxe:
For (<tipo do dado> variável : <lista>) {
<comandos>;
}

Esse tipo de for “passeia” por toda uma lista, assim como o nosso for já fez. A diferença é
que a cada andar (elemento) da lista ele faz uma instância daquele andar em uma variável.
A partir disso, a gente pode usar a variável referência para acessar os métodos e atributos
da classe que está naquele andar. Vamos ver na prática que fica mais fácil:
for (Aluno alno : alunos) {
System.out.println(alno.getNome() + " - " +
alno.getNota());
}

Vamos analisar esse trecho. O for define um tipo para a variável alno (escolhi esse nome,
mas poderia ser qualquer um). O tipo é Aluno, a nossa classe. Depois o for percorre
todos os elementos da coleção alunos e em cada elemento ele instancia a classe daquele
elemento (daquele andar) na variável alno. Aí fica fácil. É só usar alno para acessar os
métodos daquela instância. A gente usa o getNome() e o getNota() para exibir as
informações, um andar de cada vez. Legal, né?

Criando um construtor

Mas ainda tem uma coisa que podemos fazer para melhorar o uso da classe Aluno, que é
criar nosso próprio construtor de classe. Quando a gente instancia uma classe a primeira
coisa que acontece é uma chamada ao método construtor da classe. Toda classe tem um
método construtor. Se você não criou um, então o próprio Java cria um método, digamos
invisível, que faz a construção. Mas agora a gente quer criar nosso próprio método. Como
fazemos isso?
Um método construtor é um método parecido com os demais. A diferença é que ele não
tem retorno definido. Ele precisa ser público e pode ou não receber parâmetros. O
método construtor da nossa classe, ainda que invisível, é assim:

95
public Aluno() {

Tanto é assim que se você for na classe Aluno e adicionar esse método tudo vai continuar
funcionando normalmente. É porque ele já está lá, só não estava aparecendo.
Mas agora queremos criar nosso próprio método, um que já receba o nome do aluno e
sua nota. Isso vai facilitar um pouco nossa programação. Altere a classe aluno para ficar
de acordo com o Código 41.
Código 41. Classe Aluno com construtores.

public class Aluno {

// ATRIBUTOS
private String nome;
private Double nota;

// CONSTRUTOR

public Aluno() {

public Aluno(String nome, Double nota) {


this.nome=nome;
this.nota=nota;
}

// GETTERS E SETTERS
public void setNome(String nome) {
this.nome = nome;
}
public String getNome() {
return nome;
}
public void setNota(Double nota) {
this.nota = nota;
}
public Double getNota() {
return nota;
}

Percebeu que nós criamos dois construtores? O primeiro é do jeito que já estava, ou seja,
não recebe nada e não faz nada. O segundo é recebendo o nome e a nota e já guardando
os valores recebidos nos atributos da instância da classe. Precisamos dos dois, porque
assim tanto poderemos instanciar sem passar parâmetros quanto passando parâmetros.
Duas formas de usar o mesmo método, ou seja, polimorfismo. Este tipo de polimorfismo

96
é por sobrecarga de métodos, quando criamos versões do mesmo método mudando
apenas a assinatura do mesmo. Veja a utilização no Código 42.

Código 42. Classe Testa02 utilizando o novo construtor da classe Aluno.

import java.util.ArrayList;
import java.util.List;

public class Testa02 {


public static void main(String[] args) {

Aluno a1=new Aluno("Clara" , 6.5);


Aluno a2=new Aluno("Carlos" , 8.4);
Aluno a3=new Aluno("Sandra" , 9.3);
Aluno a4=new Aluno("Roberto" , 7.9);

List<Aluno> alunos = new ArrayList<Aluno>();

alunos.add(a1);
alunos.add(a2);
alunos.add(a3);
alunos.add(a4);

for (Aluno alno : alunos) {


System.out.println(alno.getNome() + " - " + alno.getNota());
}
}
}

Com uma nova versão de construtor da classe Aluno não é mais preciso usar os setters
para enviar os valores. Na própria instanciação da classe já é possível enviar o nome e a
nota do aluno. Mas, com este novo método construtor é possível simplificar ainda mais.
Quer ver? Observe o Código 43.
Código 43. Classe Teste01 adicionando instâncias da classe Aluno diretamente na coleção.

import java.util.ArrayList;
import java.util.List;

public class Testa02 {


public static void main(String[] args) {

List<Aluno> alunos = new ArrayList<Aluno>();

alunos.add(new Aluno("Clara" , 6.5));


alunos.add(new Aluno("Carlos" , 8.4));
alunos.add(new Aluno("Sandra" , 9.3));
alunos.add(new Aluno("Roberto" , 7.9));

for (Aluno alno : alunos) {


System.out.println(alno.getNome() + " - " + alno.getNota());
}

97
}
}

Na versão anterior deste código nós primeiro criávamos uma instância da classe em uma
variável (a1, a2, a3 e a4) e depois usávamos a referência nestas variáveis para adicionar as
instâncias na lista alunos. Agora isso mudou. A gente já adiciona à lista alunos uma
instância diretamente, sem ter que usar uma variável como intermediária. Assim o código
ficou bem eficiente, fácil de ler e fácil de dar manutenção no futuro. Bem diferente da
primeira versão em Código 40.
Acreditamos que agora você esteja mais bem preparado para programar em Java, pois as
coleções são uma parte importante desta linguagem. Teremos novos desafios pela frente
que exigirão dos seus conhecimentos de coleções. Então, invista um tempinho em fazer
outros exercícios e em conhecer melhor os outros tipos de coleções. Fica um desafio:
descubra as vantagens da classe HashSet sobre a ArrayList e quando seria melhor usar
uma ou outra.

98
12. Tratamento de exceções

Quando estamos construindo software precisamos nos preocupar com eventuais erros
que podem surgir para o usuário durante a utilização deste software. Esta é uma
preocupação constante do desenvolvedor, não importa a linguagem que esteja utilizando.
Afinal, ninguém gosta de receber uma mensagem de erro e ter o programa interrompido
quando está precisando exatamente deste programa para concluir suas tarefas.
Nós criamos um projeto chamado TrataErros e dentro dele uma classe chamada Divisao.
Sugerimos que faça o mesmo para realizar os seus testes. Veja Código 44. Temos dois
métodos estáticos, o principal main() e o que realiza uma divisão, o dividir().
Código 44. Classe Divisao.

public class Divisao {

public static void main (String args[]) {

System.out.println(dividir(10,0));

public static int dividir(int a, int b) {

int r=0;

r=a/b;

return r;

Ao executar este código recebemos o seguinte erro no console do Eclipse:


Exception in thread "main"
java.lang.ArithmeticException: / by zero
at Divisao.dividir(Divisao.java:13)
at Divisao.main(Divisao.java:5)

Por que isso acontece? Embora exista uma boa dose de teoria matemática sobre o assunto,
para simplificar a nossa vida vamos assumir que divisões por zero não são possíveis na
matemática. Para deixar nossa afirmação ou pouco menos errada, vamos assumir que
divisões de números inteiros por zero não são possíveis na matemática. Por isso
recebemos um erro no código acima.
Se um usuário estivesse utilizando nossa aplicação ele não estaria no Eclipse. Neste caso,
imagine a surpresa dele quando, de repente, a aplicação fosse interrompida e ele recebesse
uma tela parecida com aquela apresentada na Figura 52. Absolutamente desagradável.

99
Figura 52. Tela de erro recebida da execução de Divisao.

Então, queremos evitar que surpresas assim aconteçam. Por isso, vamos tratar os
potenciais erros de nossas aplicações. O Java nos oferece um mecanismo para tratar esses
erros, que são chamados de Exception, como pode ser observado na mensagem da
própria Figura 52. Mas, o que é essa Exception?

Classes de exceção

Uma exceção (Exception) é tudo aquilo que foge à regra, não concorda? Podemos dizer
que não é uma regra que erros devam acontecer durante a execução de um software.
Quando isto acontece, é uma exceção. Estas exceções devem ser tratadas. O Java oferece
um conjunto de classes para cuidarmos destas exceções, como pode ser visto na Figura 53.

Figura 53. Estrutura das classes para tratamento de exceções.

100
Toda vez que uma exceção acontece uma dessas classes é instanciada pela JVM e é jogada,
ou lançada para sua aplicação, como se fosse uma bola de baseball. Só que, em vez de
uma bola, um objeto instanciado a partir de uma das classes de exceção é que é jogado
para sua aplicação. Sua aplicação tem duas escolhas: 1) pegar o objeto e trata-lo da melhor
forma possível ou 2) deixar cair.
Se a aplicação deixar cair a JVM pega de volta, interrompe sua aplicação e, como último
ato, mostra qual foi o objeto que ela pegou. Exatamente o que aconteceu na Figura 52.
Naquela figura a JVM mostra que foi gerado um objeto de exceção durante a execução da
instância de Divisao. O objeto de exceção era do tipo ArithmeticException. Como ele
não foi pego pela aplicação a JVM pegou de volta e interrompeu a aplicação. A mensagem
enviada pelo objeto de exceção foi: “/ by zero”, ou seja, divisão por zero.

Pegando o objeto

Bem, não queremos que esse objeto de exceção fique voando por aí feito uma bola de
baseball. Embora baseball não seja muito a nossa praia, vamos pegar esse objeto de
exceção e trata-lo de forma adequada.
Para isso, o Java nos oferece a estrutura de tratamento de exceções try/catch/finally. A
sintaxe é assim:
try {
seu código;
}

catch (TipoClasseExceção <variável>) {


seu código de tratamento do erro;
}

catch (OutroTipoClasseExceção <variável>) {


seu código de tratamento do erro;
}

finnaly {
seu código que vai rodar sempre;
}

É tudo muito simples. Dentro do bloco try fica nosso código com potencial para erros. Se
o erro acontecer a JVM vai instanciar uma das classes de exceção da Figura 53 e jogar para
nossa aplicação. O catch pega o objeto de exceção e trata ele dentro do bloco. Esse catch
pega o objeto apenas se for do tipo declarado dentro dos parênteses. Se não for ele deixa
passar. É por isso que você pode ter uma sequência de catches para pegar pelos tipos que
quiser.
Se não quiser pegar um tipo bem específico, pode pegar por um bem genérico como a
classe Exception, que pega todas as exceções. A vantagem de pegar um tipo mais
específico, como ArithmeticExepection, é que você sabe mais precisamente o erro que
aconteceu ao entrar naquele bloco de tratamento. Se for um Exception, pode ser
qualquer erro. Vamos tratar o erro da classe Divisao?

101
Código 45. Classe Divisao tratando o erro de maneira genérica.

public class Divisao {

public static void main (String args[]) {

int v1 = 10;
int v2 = 0;

System.out.println("Resultado da soma de "+v1+" e "+v2+


" -> "+dividir(10,0));

public static int dividir(int a, int b) {

int r = 0;

try {
r = a/b;
}
catch (Exception e) {
System.out.println("Aconteceu um erro. Não sei exatamente qual.
Mas recebi a seguinte mensagem:");
System.out.println(e.getMessage());
}

return r;

Note em Código 45 que demos uma pequena incrementada na classe Divisao. Agora ela
mostra os fatores da soma e o método dividir() agora trata o erro. A operação que pode
causar o erro ficou dentro do bloco try. Durante a execução, quando o erro acontece a
JVM joga um objeto de exceção do tipo ArithmeticException para cima de nossa
aplicação, como já vimos antes. Mas nosso bloco catch pega uma exceção do tipo
Exception que, como pode ser visto em Figura 53, é bem mais genérico e por isso pega
qualquer exceção, inclusive a nossa ArithmeticException. Ao executarmos a aplicação
recebemos o seguinte resultado:
Aconteceu um erro. Não sei exatamente qual. Mas recebi a
seguinte mensagem:
/ by zero
Resultado da soma de 10 e 0 -> 0

Como o catch pegou uma exceção muito ampla e genérica, não é possível saber que erro
aconteceu. Uma opção seria analisar com if ou switch a mensagem de erro retornada pelo
objeto de exceção. Mas isso não é muito eficiente e a própria estrutura de classes para
tratamento de exceções do Java já nos ajuda com isso. Por exemplo, poderíamos mudar o
código do método dividir() da seguinte maneira:

102
public static int dividir(int a, int b) {

int r = 0;

try {
r = a/b;
}

catch (ArithmeticException e) {
System.out.println("Aconteceu um erro aritmético.
Detalhes: "+e.getMessage());
}

catch (Exception e) {
System.out.println("Aconteceu um erro. Não sei exatamente
qual. Mas recebi a seguinte mensagem:");
System.out.println(e.getMessage());
}

return r;

Neste novo trecho temos dois catch, um bem específico e um mais genérico. Se o objeto
de exceção jogado pela JVM for especificamente do tipo ArithmeticException ele vai ser
pego pelo primeiro catch. Se for de qualquer outro tipo, será pego pelo segundo catch.
Ao executar este código, o que acontece? Veja:
Aconteceu um erro aritmético. Detalhes: / by zero
Resultado da soma de 10 e 0 -> 0

Como o primeiro catch só pega erros aritméticos é possível enviar uma mensagem de erro
bem mais precisa para o usuário. Mas o que acontece se ocorrer um outro tipo de erro?
Vamos introduzir um erro em nosso código só para testar essa hipótese. Vamos criar uma
Array e tentar acessar um andar que não existe. Veja o código:

public static int dividir(int a, int b) {

int r = 0;

try {

String[] s=new String[5];


s[6]="Nao existe este elemento da Array";

r = a/b;
}

catch (ArithmeticException e) {
System.out.println("Aconteceu um erro aritmético.
Detalhes: "+e.getMessage());
}

103
catch (Exception e) {
System.out.println("Aconteceu um erro. Não sei exatamente
qual. Mas recebi a seguinte mensagem:");
System.out.println(e.getMessage());
}

return r;

Agora veja o resultado da execução:


Aconteceu um erro. Não sei exatamente qual. Mas recebi a
seguinte mensagem:
6
Resultado da soma de 10 e 0 -> 0

Aconteceu um erro dentro do bloco try, só que foi uma exceção do tipo
ArrayIndexOutOfBoundsException e não temos um catch específico para isso. Mas
temo um cacth genérico do tipo Exception que pegou nosso erro. A mensagem exibida
foi genérica, mas ainda melhor do que ter a aplicação interrompida com uma mensagem
incompreensível para o usuário.

Entrando no jogo

Vimos que quando acontecem erros em uma aplicação a JVM gera um objeto que é
instância de uma das classes de exceção e joga para a nossa aplicação. A gente já sabe
como pegar essa “bola” e tratá-la. Tudo ótimo. Mas e se a gente quiser jogar também?
Nossa aplicação pode gerar uma instância de exceção e jogar para ela mesma pegar? Pode
sim e vamos ver agora como fazer isso.
Você talvez esteja se perguntando: mas, por que cargas d’água eu iria querer jogar um
objeto de exceção para mim mesmo, quer dizer para minha própria aplicação? Pode até
parecer estranho, mas não é. Por exemplo, você pode estar criando um método e precisa
garantir algumas condições para a execução deste método. Se as condições não forem
atendidas você quer gerar um erro para quem chama o método.
Veja o caso no nosso método dividir(). Ele recebe dois inteiros e divide o primeiro pelo
segundo. O que aconteceria se o primeiro inteiro fosse menor do que o segundo? Isso
resultaria em um decimal que, por ser inteiro, seria sempre arredondado para zero. Neste
caso, sempre que o primeiro valor fosse menor do que o segundo o resultado seria zero. Já
que é assim não queremos que o chamado de nosso método envie o primeiro valor sendo
menor do que o segundo. Mas como fazer isso?
Se catch é pegar (em inglês) então utilizaremos o throw, que é lançar. O throw lança uma
exceção e podemos escolher uma das classes de exceção para lançar. Se quisermos lançar
uma exceção bem genérica, sem problemas, utilizamos a Exception mesmo. A sintaxe é:
throw <instância de uma classe de exceção>

104
É simples assim. No entanto, quando utilizamos um throw dentro de um método
precisamos informar ao compilador que faremos isso e que tipo de classe de exceção será
utilizada para fazer o throw. Isso tem que ser dito na assinatura do método através da
palavra reservada throws. A sintaxe é assim:
<método>(<parâmetros>) thows <Exceção 1>,<Exceção N> {

Como você pode ver na sintaxe, se o método for lançar mais de um tipo de classe de
exceção, cada tipo deve ser informado na assinatura do método através de throws. Então,
vamos entender bem: throw lança uma exceção e throws informa (avisa) o tipo de
exceção que será lançada. Não queremos confundir as duas declarações.
Toda vez que a gente coloca um throws em um método é como se a gente dissesse: olha,
vai ter uma exceção desse tipo aqui, mas não vamos tratar isso aqui dentro não, vamos
devolver o erro para quem chamou o método. Quem chamou que trate. É como aquela
brincadeira da batata quente; um passa pro outro. Se ninguém pegar para tratar, acaba na
mão da JVM, que interrompe a aplicação e envia um erro para o usuário. Isso pode ser
visto na Figura 54.

Figura 54. Exception sendo devolvida para os chamadores.

Vamos ver como isso funciona na prática. No Código 46 nós passamos a prever o erro
que mencionamos, ou seja, passamos a considerar um erro quando o primeiro valor é
menor do que o segundo. Quando isso acontece a gente lança uma exceção através de um
throw. Note que o argumento de throw é uma instância da classe de exceção e não
simplesmente a classe. Por isso é que precisamos fazer um new da classe e passar para o
throw. A gente usou uma classe bem genérica, a Exception e passou uma mensagem para
dentro dela.

105
Código 46. Classe Divisao fazendo throws para todos os chamadores.

public class Divisao {

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


int v1 = 3;
int v2 = 4;

System.out.println("Resultado: "+v1+" + "+v2+" = "+dividir(v1,v2));


}

public static int dividir(int a, int b) throws Exception {

if (a < b) {
throw new Exception("Primeiro valor deve ser maior que segundo.");
}

int r = 0;

try {
r = a/b;
}

catch (ArithmeticException e) {
System.out.println("Erro aritmético: "+e.getMessage());
}
catch (Exception e) {
System.out.println("Erro genérico: "+e.getMessage()); }

return r;
}
}

A cláusula throws na assinatura do método dividir() manda o erro para ser tratado por
quem chamou, ou seja, o método main(). Por sua vez, o main() também não trata e
através de um throws ele manda para quem chamou, ou seja, a JVM. A JVM interrompe a
aplicação e exibe a seguinte mensagem:
Exception in thread "main" java.lang.Exception: Primeiro
valor deve ser maior que segundo.
at Divisao.dividir(Divisao.java:13)
at Divisao.main(Divisao.java:7)

Olha que legal, a nossa mensagem de erro foi recebida pela JVM e exibida: “Primeiro
valor deve ser maior do que segundo”. Muito bom, mas não é legal essa mensagem que
pode parecer estranha. É melhor a gente evitar que a JVM trate o erro e envie a
mensagem para o usuário final. Se não queremos que a JVM trate o erro, então nós
devemos tratar.

106
Figura 55. Exception sendo tratada em main().

E para fazer isso, interceptaremos e trataremos o erro no método main(), antes de chegar
na JVM, conforme apresentado na Figura 55. E como implementarmos esse tratamento
em nosso código? Veja como fazer isso no Código 47.
Código 47. Classe Divisao tratando o erro em main().

public class Divisao {

public static void main (String args[]) {


int v1 = 3;
int v2 = 4;
try {
System.out.println("Resultado: "+v1+" + "+v2+" = "+dividir(v1,v2));
}
catch (Exception x) {
System.out.println("Sinto muito, ocorreu o seguinte erro: "+
x.getMessage());
}

public static int dividir(int a, int b) throws Exception {

if (a < b) {
throw new Exception("Primeiro valor deve ser maior que segundo.");
}

int r = 0;

try {
r = a/b;
}

catch (ArithmeticException e) {
System.out.println("Erro aritmético: "+e.getMessage());

107
}
catch (Exception e) {
System.out.println("Erro genérico: "+e.getMessage()); }

return r;
}
}

Nesta nova versão da classe Divisao já podemos ver que o erro foi tratado com um
try/catch no método main(). Utilizamos a variável x para capturar o objeto de exceção
apenas para lembrar que você pode usar qualquer nome de variável para este fim. E agora
que nós estamos tratando a exceção no método main(), ele não precisa mais fazer um
throws para a JVM; por isso o tiramos da assinatura do método.

Conclusão

Neste capítulo nós vimos a importância de se tratar erros, evitando que as aplicações
sejam interrompidas de forma abrupta e que exibam mensagens incompreensíveis para os
usuários finais. Para evitar que isso aconteça você aprendeu a usar o try/catch e tratar os
potenciais erros de uma aplicação. Aprendeu também a lançar erros que devem ser
tratados por quem chama seus métodos, utilizando a cláusula throws. Parece que agora
você é um mestre do tratamento de erros. Parabéns.

Desafio

Você viu que já existe um conjunto de classes de exceção que são importantes para
detectar e tratar erros com maior precisão. Mas elas não dão toda a precisão necessárias.
Imagine que quiséssemos detectar erros desses tipos:
• O valor informado precisa estar entre 1 e 6 (para a classe de dados)
• O valor informado precisa estar entre 1.40 e 2.20 (para a classe de IMC)
O Java viabiliza a criação de suas próprias classes de exceção customizadas, por fazer
extends (herança) a partir das classes existentes. Então, este é o seu desafio. Pesquise sobre
o assunto e construa algumas classes personalizadas para tratamento de exceções.

108
13. Interface gráfica

Até agora temos utilizado intensamente o sysout do Eclipse, ou seja, o


System.out.println, para exibir tudo o que a gente precisa no console. Chato isso, né?
Pois tudo vai mudar a partir deste capítulo. Começaremos a utilizar comandos de
interface gráfica para implementar interação em nossos programas. As coisas vão ficar
ainda mais divertidas.
O Java tem uma história de estruturas para lidar com a Graphic User Interface (GUI).
Esses frameworks começaram com o AWT, passaram pelo Swing e agora chegaram ao
JavaFX. Nos concentraremos no JavaFX, que é mais moderno, mais flexível, mais
sofisticado e, apesar de tudo, mais fácil de utilizar. Se quiser saber mais sobre AWT e
Swing há um vasto material disponível na Internet e em outras mídias.

JavaFX

O JavaFX começou como um framework independente da API do Java, dentro do qual a


codificação era feita através de scripts. Porém, a partir do Java 8 ele foi, digamos assim,
incorporado efetivamente ao Java e passou a ser chamado de JavaFX 2.0. Vale a pena
destacar que o JavaFX ainda é filho direto da SUN Microsystems, pois começou a ser
desenvolvido em 2007 e teve sua primeira versão publicada em 2008. E como vimos, a
Oracle comprou a SUN apenas em 2010.
O JavaFX é disponibilizado no Java por meio de classes organizadas em pacotes, assim
como acontece com as demais classes da API Java. Neste caso, as classes estão dentro de
diversos pacotes, todos começando com javafx, como por exemplo:
import javafx.application.Application;
import javafx.stage.Stage;
import javafx.scene.Scene;
import javafx.scene.Group;

Todos os pacotes do framework javafx podem ser analisados na documentação do Java,


em https://docs.oracle.com/javase/9/docs/api/overview-
summary.html, na seção JavaFX. Você perceberá que o framework contem dezenas de
pacotes. Mas, não se preocupe em ter que aprender tudo isso de uma vez, pois
utilizaremos apenas alguns destes pacotes.
No entanto, há algumas classes e tipos de classes que precisamos entender bem. A boa
notícia é que elas são bem simples e fáceis de compreender e utilizar. Na Figura 56 são
apresentadas as classes Application, Stage e Scene. Também são apresentados os tipos de
classe primary node, branch node e node. Por que estamos chamando estes últimos 3 de
tipos e não de classes? Por que eles não são propriamente classes, mas categorias de
classes. Em outras palavras, há várias classes que podem ser um primary node, várias
classes que podem ser um branch node e várias classes que podem ser um node. Mas que
classes podem ser essas?

109
Figura 56. Inter-relação das classes Java FX.

Vamos começar de baixo para cima. Um node é um elemento final, que não pode ter
filhos. Por exemplo, um botão, uma caixa de texto ou um rótulo (label). Veja a seguir
algumas das classes visuais do JavaFX do tipo node:
• Button • CheckBox
• Accordion
• ColorPicker • ComboBox
• ChoiceBox
• Label • ListView
• DatePicker
• MenuBar • PasswordField
• Menu
• RadioButton • Slider
• ProgressBar
• SplitMenuButton • SplitPane
• Spinner
• TabPane • TextArea
• TableView
• TitledPane • ToggleButton
• TextField
• TreeTableView • TreeView
• ToolBar

Mas e se quisermos agrupar um conjunto de nodes de modo que possamos controlar os


eventos e os comportamentos deste conjunto? Então utilizamos os branch nodes, ou seja,
classes que funcionam como containers para os nodes. Veja algumas das classes que
podem ser containers para nodes:

110
• Group • Region

• Pane • HBox

• VBox • FlowPane

• BorderPane • BorderPane

• StackPane • TilePane

• GridPane • AnchorPane

• TextFlow

Agora chegamos à classe Scene. Esta classe é um grande container para todos os
elementos visuais que compõem uma interface gráfica, ou seja, ela guarda todas as classes
que acabamos de ver. Mas, espere um momento. Não pulamos o primary node?
O primary node (também chamado de root node) é na verdade um branch node que deve
ser escolhido como o pai (mãe, se preferir) de todas as demais classes, tanto de outros
branch nodes quanto dos diversos nodes comuns. Dizendo de outra forma: podemos ter
um Group como primary node, que contém outros Group, alguns Label, um Button e
um TextField, por exemplo. Então, qualquer node pode ser um primary node? Não.
Todos os nodes que possuem a capacidade de agrupar outros nodes dentro de si podem
ser um primary node.
Para ilustrarmos o conceito, imagine o conjunto de funcionários de uma empresa e vamos
classificar cada uma dessas pessoas como um node. O Presidente é um node, o Diretor é
um node, o Gerente é um node, o Chefe de Seção é um node e o funcionários que não
exercem nenhuma função de chefia também são nodes (eletricistas, compradores,
programadores, etc). Neste caso, todos os funcionários comuns são nodes que não podem
mandar em outros nodes, ou seja, são nodes que não podem ter filhos. Todos aqueles que
chefiam grupos de pessoas seriam branch nodes, ou seja, o Presidente, o Diretor, o
Gerente ou o Chefe de Seção. Estes são nodes que podem ter filhos.
Agora imagine que esta empresa ganhou um prêmio e precisa enviar um representante
para receber o prêmio em um evento. Eles mandariam um programador ou um
eletricista? Provavelmente não. Talvez o Presidente fosse. Se ele não pudesse ir, mandaria
um Diretor. Se este não pudesse ir, mandaria um Gerente ou um Chefe de Seção. Este
representante seria o primay node da organização. Isso quer dizer que qualquer branch
node pode fazer o papel de primary node.
O que a classe Scene precisa saber é quem é o primary node, que pode ser chamado
também de root node, algo com o node raiz ou principal. Por favor, veja mais uma vez a
Figura 56 para compreender bem este ponto.
A classe Stage é o maior container do JavaFX. Ele conterá a classe Scene. Um detalhe
importante é que uma aplicação pode conter mais do que uma Stage e cada Stage pode
conter diversas Scene. Apesar de podermos criar várias classes Stage, a própria JVM cria
uma classe Stage inicial para a aplicação e passa para a aplicação utilizar. Geralmente

111
utilizamos esta classe padrão em nossas aplicações. Podemos imaginar a classe Stage como
uma grande tela de pintura, em branco, onde jogamos nossas Scenes.

Estes conceitos são muito importantes e fundamentais. Eles


serão necessários nos próximos capítulos também. Então,
faça uma nova leitura desse tópico e entenda
profundamente os conceitos apresentados na Figura 56.

Primeira aplicação JavaFX

Muito bem, agora que você já entendeu os conceitos fundamentais da programação de


interfaces gráficas com JavaFX, chegou a hora divertida: vamos colocar a mão na massa.
A primeira coisa a fazer é criar um novo projeto Java no Eclipse, que chamaremos de
ClassesJavaFX. O objetivo deste primeiro projeto é exemplificar os conceitos
apresentados até aqui.

Figura 57. Configuração do Eclipse para os pacotes JavaFX.

Depois de criado o projeto vamos ter certeza de que ele consegue “enxergar” os pacotes
JavaFX. Na verdade, vamos configurar o projeto para utilizar o ambiente Java Runtime
(JRE) padrão, que já instalamos com o JDK 9.

112
O processo é muito simples, como pode ser visto na Figura 57. Selecione a opção de
menu Project, a opção Properties, depois a aba Libraries, o botão Edit e selecione a
opção Workspace Default JRE. Depois disso é só clicar em Finish e tudo estará
configurado da forma com precisamos.
Agora, vamos criar a classe ClassesJavaFX. A primeira coisa a lembrar é que nossa classe
herda a classe Application do Java FX. Se é assim, precisaremos adicionar um extends
para implementar a herança, como neste trecho:
public class ClassesJavaFX extends Application

Pronto, agora todos os atributos e métodos da classe Application já estão à nossa


disposição na classe ClassesJavaFX, incluindo o método start(), que é onde colocamos
toda a nossa programação. Só que ao invés de utilizar o método start() padrão nós
criaremos o nosso próprio, sobrescrevendo o original. Como já vimos, isto é uma forma
de polimorfismo.
Quando um método precisa ser sobrescrito o Eclipse sinaliza ele com um @Override. Não
se preocupe com isso. Trata-se apenas de uma anotação para o compilador verificar se o
método é realmente uma sobrescrita de outro, uma forma de reduzir erros e falhas de
programação. Seja como for, precisamos sobrescrever o método start() assim:
@Override
public void start(Stage telaEmBranco)

Ao sobrescrevermos o método start() nós estamos recebendo um parâmetro do tipo Stage.


Mas, Stage não é uma classe do Java FX? Exatamente. Lembra que dissemos que a JVM já
nos entrega uma classe Stage padrão? Então, é essa daí. A gente recebe ela em uma
variável de instância da classe e pode usar na aplicação. Optamos por usar o nome
telaEmBranco para a variável, mas sinta-se livre para usar o nome que considerar mais
adequado.
Um detalhe importante a ser mencionado é que para herdar a classe Java FX Application
e utilizar seus métodos é necessário importar o pacote Application, que está em
application, que por sua vez está em javafx. Utilize o seguinte import:
import javafx.application.Application;

Outros imports serão necessários ao passo que utilizar as classes JavaFX. O Eclipse vai
apontar a necessidade de importação de cada uma dessas classes e vai te ajudar a colocar o
import no lugar certo.
Agora precisamos colocar alguma coisa nessa tela em branco. Como vimos, precisamos
colocar pelo menos uma classe Scene dentro da Stage, instanciada em telaEmBranco. E
dentro da Scene uma classe do tipo primary node (ou root node, se preferir). Dentro da
primary node, as classes dos tipos branch node e node. Então vamos começar de trás para
frente, criando uma classe simples do tipo node. Utilizaremos a classe Label, cujo objetivo
é simplesmente conter um texto, uma mensagem. É simples assim:

113
Label labelA=new Label("Eu sou um label isolado");

Lembre-se de que apenas criamos um objeto, ou seja, uma instância da classe Label na
variável labelA. Até agora nada apareceu na tela do computador. Está tudo apenas na
memória. Mais uma coisa: a classe Label também está dentro de um pacote do conjunto
de pacotes javafx e por isso precisamos importá-lo. Adicione uma linha à nossa seção de
imports:
import javafx.application.Application;
import javafx.scene.control.Label;

O próximo passo é criar um branch node, ou seja, um node que tenha filhos. Como
vimos, diversas classes do Java FX podem ter filhos. Vamos utilizar uma dessas classes, a
Group. Criaremos outras classes do tipo node, usando a própria Label e as colocaremos
dentro da classe branch node.
Você já sabe criar uma classe Label, mas agora adicionaremos uma coisa simples, que é a
questão do posicionamento. Nós podemos indicar uma posição X (horizontal) e uma
posição Y (vertical) para posicionar visualmente uma Label dentro de um grupo. Se não
fizermos isso, na hora da exibição todos os textos são colocados na mesma posição,
sobrepondo-se um ao outro e ficando ilegível. Então vamos garantir apenas que eles não
se sobreponham. Não se preocupe muito com esta questão por agora porque logo
falaremos sobre como organizar a exibição dos controles na tela, Ok? Então criaremos
dois objetos Label da seguinte forma:
Label labelB=new Label("Eu sou o primeiro label do grupo A");
labelB.setLayoutX(0);
labelB.setLayoutY(0);

Label labelC=new Label("Eu sou o segundo label do grupo A");


labelC.setLayoutX(0);
labelC.setLayoutY(20);

Note que utilizamos os métodos setLayoutX() e setLayoutY() da classe Label para


posicionar os objetos que serão colocados dentro do mesmo grupo, de modo que eles não
se sobreponham visualmente. Mas e quanto ao grupo? Vamos instanciar uma classe
Group e colocar estes dois Labels dentro dele:
Group grupoA=new Group();
grupoA.getChildren().add(labelB);
grupoA.getChildren().add(labelC);

Opa, esse método getChildren() é novidade. Para que ele serve? Calma, é muito simples.
A classe Group, assim como outras classes do tipo branch node, precisam guardar todos
os itens que estão dentro delas. Como a classe faz isso? Ela possui um atributo do tipo
Collection, ou seja, que implementa esta interface. Esse atributo é privado e não temos
acesso direto a ele, seguindo as boas práticas de encapsulamento que já aprendemos.

114
Então a classe oferece um método get para termos acesso a esta coleção com a lista de
controles da classe. Esse método é o getChildren().
É por isso que quando utilizamos o getChildren() passamos a ter uma referência para a
Collection que tem dentro do objeto instanciado de Group. E todos os métodos
contratualizados pela interface Collection estarão à nossa disposição, incluindo o método
add() que já utilizamos várias vezes com os testes de coleções. Viu como foi importante
aprender sobre as coleções? Elas são amplamente utilizadas em outras classes do Java.
Ainda falando sobre a classe Group, precisamos importar o pacote desta classe para
dentro de nossa aplicação. Adicione mais uma linha em nossa seção de imports:
import javafx.application.Application;
import javafx.scene.control.Label;
import javafx.scene.Group;

Que tal adicionar mais um grupo, ou seja, mais um branch node com seus nodes dentro?
Veja o trecho de código a seguir:
Label labelD=new Label("Eu sou o primeiro label do grupo B");
labelD.setLayoutX(0);
labelD.setLayoutY(0);

Label labelE=new Label("Eu sou o segundo label do grupo B");


labelE.setLayoutX(0);
labelE.setLayoutY(20);

Group grupoB=new Group();


grupoB.getChildren().add(labelD);
grupoB.getChildren().add(labelE);

Muito bem, já temos os nodes e os branch nodes. Mas ainda não temos um primary
node. Lembra da Figura 56? Pois então, precisamos de um primary node, o objeto que vai
conter todos os nossos objetos node e todos os nossos objetos branch node. Qualquer
classe que possa ter filhos pode ser um primary node, incluindo a própria classe Group.
Ou seja, poderíamos colocar o Label isolado e os dois Group dentro de outro Group
maior e dizer que este último seria o primary node. Sem problemas. No entanto,
utilizaremos, neste caso, outra classe como primary node, a classe FlowPane.
A classe FlowPane faz parte do pacote layout, que faz parte do pacote scene, que está
dentrodo pacote javafx. O pacote layout contém classes que podem ser utilizadas como
primay node e que nos ajudam com estratégias de organização visual de nossas aplicações.
Esse pacote conta com várias classes deste tipo, tais como GridPane, HBox, VBox,
TilePane, BorderPane, StackPane e a própria FlowPane que utilizaremos. Utilizaremos
algumas dessas classes nesse livro, então não fique preocupado por não conhecer tudo
ainda.
A classe FlowPane oferece uma estrutura visual para organizar os objetos que serão
jogados na tela. Nós podemos configurá-lo para distribuir os objetos sequencialmente na
horizontal ou na vertical e para isso precisamos apenas definir a orientação com a ajuda

115
da classe Orientation, que na verdade é uma enumeração. Em outras palavras,
Orientation é uma lista com opções definidas entre as quais podemos escolher.
No trecho de código a seguir nós criamos uma classe do tipo FlowPane e como ela pode
ter filhos colocamos dentro dela o labelA, o grupoA e o grupoB. Naturalmente os Labels
dentro de cada grupo são levados por esses grupos para dentro do FlowPane.
FlowPane primaryNode=new FlowPane(Orientation.VERTICAL);
primaryNode.getChildren().add(labelA);
primaryNode.getChildren().add(grupoA);
primaryNode.getChildren().add(grupoB);

Não podemos esquecer de adicionar os imports para a classe FlowPane para a


enumeração Orientation. Veja as últimas duas linhas do trecho a seguir:
import javafx.application.Application;
import javafx.scene.control.Label;
import javafx.scene.Group;
import javafx.scene.layout.FlowPane;
import javafx.geometry.Orientation;

Voltando à Figura 56, precisamos de uma classe Scene para receber o primary node, ou
seja, a nossa FlowPane. Sem problemas, vamos fazer isso agora. Aproveitamos para dizer a
dimensão da nossa Scene, que será considerada para definir o tamanho da janela a ser
exibida pela aplicação. Veja o trecho de código a seguir:
Scene cena = new Scene(primaryNode, 400, 300);

Nós criamos uma instância da classe Scene na variável cena, jogamos para dentro do
objeto cena o objeto primaryNode, que é a nossa instância de FlowPane e ainda dizemos
que nossa cena terá 400 pixels de largura e 300 pixels de altura. E mais uma vez
precisamos adicionar uma linha em nossa seção de imports:
import javafx.application.Application;
import javafx.scene.control.Label;
import javafx.scene.Group;
import javafx.scene.layout.FlowPane;
import javafx.geometry.Orientation;
import javafx.scene.Scene;

Finalmente vamos jogar tudo o que construímos até aqui em uma tela em branco, que
será mais tarde exibida para o usuário da aplicação. Já sabemos que nossa tela é uma
instância padrão de Stage, que nos foi cedida pela própria JVM. Mas temos que jogar
nossa cena no Stage e fazer algumas pequenas configurações. Veja o código a seguir:
telaEmBranco.setTitle("Inter-relação entre classes JavaFX");
telaEmBranco.setScene(cena);
telaEmBranco.sizeToScene();
telaEmBranco.show();

116
Neste código nós definimos um título para a janela que será exibida ao usuário, por meio
do método setTitle(). Jogamos a cena para dentro do Stage com o método setScene.
Determinamos para telaEmBranco que seu tamanho deverá ser aquele informado na
cena com o método sizeToScene() e finalmente exibimos a instância do Stage com o
método show(). Para utilizar a classe Stage e seus métodos precisamos importar a classe
para nossa aplicação. Adicione a última linha em nossa seção de imports:
import javafx.application.Application;
import javafx.scene.control.Label;
import javafx.scene.Group;
import javafx.scene.layout.FlowPane;
import javafx.geometry.Orientation;
import javafx.scene.Scene;
import javafx.stage.Stage;

Então, tudo funcionando, certo? Só que não. Se você digitou esse código todo e tentou
executar, percebeu que nada aconteceu. Se não tentou, já ficou sabendo agora. Mas, por
que não funciona?
Nossa classe ClassesJavaFX não possui métodos estáticos. Ela precisa ser instanciada por
alguém para ser usada, ou conter um método estático que faça isso. Lembra do famoso
método public static void main? Então, precisamos de um desses para fazer nossa
aplicação rodar. Então, na própria classe, vamos adicionar este método e a partir dele
chamar o método padrão da classe Application, que é o launch(). Esse método vai fazer
nossa aplicação rodar:
public static void main(String args[]) {
Application.launch(args);
}

Uma vez tudo explicado em seus mínimos detalhes, vamos ao código completo, em
Código 48. O resultado da aplicação rodando pode ser visto na Figura 58.
Código 48. Primeira aplicação Java FX.

import javafx.application.Application;
import javafx.scene.control.Label;
import javafx.scene.Group;
import javafx.scene.layout.FlowPane;
import javafx.geometry.Orientation;
import javafx.scene.Scene;
import javafx.stage.Stage;

public class ClassesJavaFX extends Application {

@Override
public void start(Stage telaEmBranco) {

// NODES ISOLADOS
Label labelA=new Label("Eu sou um label isolado");

117
// BRANCH NODE A
Label labelB=new Label("Eu sou o primeiro label do grupo A");
labelB.setLayoutX(0);
labelB.setLayoutY(0);

Label labelC=new Label("Eu sou o segundo label do grupo A");


labelC.setLayoutX(0);
labelC.setLayoutY(20);

Group grupoA=new Group();


grupoA.getChildren().add(labelB);
grupoA.getChildren().add(labelC);

// BRANCH NODE B
Label labelD=new Label("Eu sou o primeiro label do grupo B");
labelD.setLayoutX(0);
labelD.setLayoutY(0);

Label labelE=new Label("Eu sou o segundo label do grupo B");


labelE.setLayoutX(0);
labelE.setLayoutY(20);

Group grupoB=new Group();


grupoB.getChildren().add(labelD);
grupoB.getChildren().add(labelE);

// PRIMARY NODE
FlowPane primaryNode=new FlowPane(Orientation.VERTICAL);
primaryNode.getChildren().add(labelA);
primaryNode.getChildren().add(grupoA);
primaryNode.getChildren().add(grupoB);

// SCENE
Scene cena = new Scene(primaryNode, 400, 300);

// JOGANDO TUDO NA STAGE


telaEmBranco.setTitle("Inter-relação entre classes JavaFX");
telaEmBranco.setScene(cena);
telaEmBranco.sizeToScene();
telaEmBranco.show();
}

public static void main(String args[]) {


Application.launch(args);
}

118
Figura 58. Tela produzida pela primeira aplicação utilizando Java FX.

Conclusão

Vimos muita coisa neste capítulo. Aprendemos que todos os controles visuais que podem
ser colocadas em uma tela (interface gráfica) são chamados de nodes. Podem ser
simplesmente nodes para o caso de controles que não tem filhos, como TextField, Label
e Button, ou branch nodes para o caso dos controles que tem filhos, como Group e Pane
(este você vai aprender no próximo capítulo).
Vimos também que o objeto Scene precisa ter um node principal, que seja pais de todos
os nodes visuais. A este node principal nós chamamos de primary node, mas ele é
chamado também de root node (é a mesma coisa). Este primary node é um dos branch
nodes, geralmente um Pane. Depois que tá tudo organizadinho dentro de Scene, a gente
joga o Scene dentro do Stage e temos uma bela tela aparecendo no monitor do usuário.
Esse primeiro resultado é muito legal, não acha? Mas é pouco perto do que ainda vamos
fazer neste livro em termos de interface gráfica. Pronto para os próximos desafios? Então
se prepare para passar de fase nos próximos capítulos.

Desafio

Você conheceu alguns nodes bem úteis, como Label, TextField e Button. Que tal
ampliar seu conhecimento e prática? Pesquise sobre outros controles visuais do Java FX e
tente montar telas que os utilizem. Há diversos, como TextArea, ListView e Menu que
serão muitos úteis ao longo de seu aprendizado.

119
14. Construindo telas

A interface com o usuário é um elemento fundamental na


maioria dos programas de computador, seja para desktop,
web ou dispositivos móveis.

Você já aprendeu os princípios fundamentais da construção de uma interface gráfica com


o usuário (GUI). Agora chegou a hora de construir algumas dessas interfaces. Mas, o que
está faltando para isso? Conhecer as classes para a implementação de controles tais como
caixas de texto, botões, listas de combinação e etc. E algo muito importante é saber
organizar esses controles na tela, ou seja, estruturar o seu layout. Então, se é isso o que
falta aprender, vamos aprender.

Controles essenciais

Nós veremos neste livro apenas os controles essenciais para nossos exemplos. Estes
controles também são os mais usados para se construir aplicações em geral. No entanto,
existem diversos controles que podem ser utilizados em uma interface com o usuário, tais
como:

• Accordion • Button • ButtonBase

• Cell • CheckBox • CheckMenuItem

• ChoiceBox • ColorPicker • ComboBox

• ContextMenu • Control • DatePicker

• HTMLEditor • Hyperlink • IndexedCell

• Label • Labeled • ListCell

• ListView • Menu • MenuBar

• MenuButton • MenuItem • MenuItemBase

• Pagination • PasswordField • ProgressBar

• ProgressIndicator • RadioButton • RadioMenuItem

• ScrollBar • ScrollPane • Separator

• Spinner • Slider • SplitMenuButton

• SplitPane • TabPane • TableColumnHeader

120
• TableView • TextArea • TextInputControl

• TextField • TitledPane • ToggleButton

• ToolBar • Tooltip • TreeCell

• TreeTableCell • TreeTableView • TreeView

• WebView

Que tal a gente começar construindo uma aplicação para calcular o índice de massa
corporal (IMC) de uma pessoa? A Figura 59 apresenta uma proposta de interface para essa
aplicação.

Figura 59. Proposta da tela de aplicação para calcular IMC.

Para construirmos esta aplicação precisaremos basicamente das classes Label (que você já
aprendeu a usar), TextField e Button. Então vamos ver como utilizar as classes TextField
e Button. Depois a gente organiza tudo em um layout parecido com o proposto, Ok? Para
isso vamos criar um novo projeto chamado CalculaIMC e uma classe chamada
InterfaceIMC. Esta classe, naturalmente, será no mesmo padrão da classe anteriormente
criada, a ClassesJavaFX, ou seja, estendendo Application e tendo seu método start().

Label, TextField e Button

Criar os controles visuais que colocaremos em nossa tela é algo simples, como já vimos
quanto utilizamos a classe Label. É necessário apenas instanciar a classe do tipo de
controle que queremos utilizar. Lembra-se como fizemos isso para Label? Agora
precisaremos de 3 labels em nossa tela:
Label lblPeso = new Label("Peso:");
Label lblAltura = new Label("Altura:");
Label lblMensagem = new Label("Seu IMC é: ");

121
Assim nós instanciamos 3 objetos da classe Label, cada uma com seu respectivo texto.
Depois precisaremos adicionar isso em nossa tela. Não podemos esquecer de importar a
classe da API Java:
import javafx.scene.control.Label;

Para os demais controles não é muito diferente. Veja como instanciar os TextField, que
serão as nossas caixas de texto:
TextField txtPeso = new TextField();
TextField txtAltura = new TextField();

Este TextField são as caixas onde o usuário vai digitar as informações que estamos
precisando, ou seja, o peso e a altura da pessoa. Para utilizar esta classe precisamos fazer a
seguinte importação:
import javafx.scene.control.TextField;

E por fim temos o botão a ser pressionado para fazer o cálculo. Por enquanto não faremos
o cálculo, apenas colocaremos o botão na tela. Para instanciar o botão podemos utilizar o
seguinte código:
Button btnCalcular = new Button("Calcular");

É também necessário importar a classe da API Java:


import javafx.scene.control.Button;

Organizando na tela

Para distribuir nossos controles visuais, ou nodes, na tela precisamos de um layout, de


uma estrutura visual para organizar esses controles. Do contrário, eles seriam colocados
um sobre o outro, produzindo uma interface ilegível.

O Java FX disponibiliza algumas classes para nos ajudar com


o layout de nossas telas.

Essas classes são painéis (panes) que possibilitam diferentes layouts para seus filhos, ou
seja, para os controles que forem jogados dentro deles. Quando o controle é adicionado a
um painel, este painel passa a gerenciar o layout deste controle. Portanto, estas classes
funcionam como branch nodes (que podem conter outros nodes) e certamente com
primary nodes (root node utilizado pela classe Scene).

122
Os painéis que podemos utilizar são Pane, FlowPane, HBox, VBox, AnchorPane,
StackPane, TilePane e GridPane. Inclusive, nós chegamos a utilizar o FlowPane no
capítulo anterior. Cada um destes painéis oferece uma estratégia de layout diferente e
alguns deles podem ser combinados. Por exemplo, o BorderPane pode conter um
FlowPane, um HBox, ou um GridPane, ou todos eles. Vejamos como eles funcionam.

Pane
A classe Pane é a mãe das demais classes que consideraremos a seguir. Ela oferece um
painel onde podemos adicionar nossos nodes, mas não oferece exatamente uma estratégia
de layout. De fato, esta classe nos permite colocar objetos em qualquer ponto dentro do
painel utilizando uma coordenada, sendo X para a posição horizontal e Y para a posição
vertical. Então, vamos criar uma classe chamada ExemploPane, naturalmente estendendo
a classe Application do Java FX, instanciar a classe Pane e adicionar um círculo e alguns
retângulos dentro desta instância.
Código 49. Exemplo de uso da classe Pane.

import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;

public class ExemploPane extends Application {

@Override
public void start(Stage telaEmBranco) {

// PAINEL E SEUS NODES

/* Criar o painel */
Pane painel=new BorderPane();

/* Criar o node círculo */


Circle cabeca = new Circle(135,60, 35);

/* Criar o nodes de retângulo */


Rectangle tronco = new Rectangle(100,100,70,90);
Rectangle bracoEsquerdo = new Rectangle(70,100,25,75);
Rectangle bracoDireito = new Rectangle(175,100,25,75);
Rectangle pernaEsquerda = new Rectangle(100,195,30,80);
Rectangle pernaDireita = new Rectangle(140,195,30,80);

/* Que tal um pouco de cor? */


cabeca.setFill(Color.RED);
tronco.setFill(Color.ORANGE);
bracoEsquerdo.setFill(Color.BLUE);
bracoDireito.setFill(Color.BLUE);

/* Adicionar nodes no painel */


painel.getChildren().add(cabeca);

123
painel.getChildren().add(bracoEsquerdo);
painel.getChildren().add(tronco);
painel.getChildren().add(bracoDireito);
painel.getChildren().add(pernaEsquerda);
painel.getChildren().add(pernaDireita);

// SCENE
Scene cena = new Scene(painel, 280, 330);

// JOGANDO TUDO NA STAGE


telaEmBranco.setTitle("Exemplo de Pane");
telaEmBranco.setScene(cena);
telaEmBranco.sizeToScene();
telaEmBranco.show();

public static void main(String[] args) {


Application.launch(args);
}

Neste código apresentado na Código 49 temos um exemplo de uso da classe Pane com a
adição de alguns nodes. Primeiramente, a seção de import carrega todos os pacotes e
classes que são necessários para a aplicação. Depois a classe ExemploPane é criada,
estendendo a classe Application, que traz com ela o método start(), que nós
sobrescrevemos. Dentro deste método, que recebe a classe Stage padrão da JVM, nós
criamos nossos nodes e adicionamos a uma Scene, que é colocada dentro da Stage, nossa
tela em branco.
Criamos um painel do tipo Pane, um círculo e alguns retângulos. Note que a criação
destes nodes é bem parecida com a criação dos nodes Label, TextField e Button, exceto
pelos parâmetros passados aos construtores. No caso da classe Circle, são passados três
números. Os dois primeiros são as coordenadas X e Y de onde deve ficar o centro do
círculo e o terceiro número se refere ao raio deste círculo.

Figura 60. Tela exibida a partir do exemplo da classe Pane.

124
Para a classe Rectangle são passados quatro números. Os dois primeiros são as
coordenadas X e Y de onde deve ficar o canto esquerdo superior do retângulo. O terceiro
número é a largura e o quarto número a altura. Todos esses números são referentes a
pixels. E todos estes números são relativos à janela dentro da qual eles estarão sendo
exibidos, ou seja, dentro da Scene. O resultado de nosso código é apresentado na Figura
60.
Assim como colocamos um círculo e alguns retângulos, poderíamos ter colocado também
botões, caixas de texto, rótulos e qualquer outro node. No entanto, a classe Pane
raramente é utilizada, exatamente porque ela não oferece uma estratégia de layout. Com
esta classe temos que informar coordenadas o tempo todo, o que pode ser muito
trabalhoso para criar e depois manter, especialmente para telas mais complexas. Para isso
utilizamos combinações dos painéis que veremos a seguir.

FlowPane
A classe FlowPane possibilita a estratégia de espalhamento horizontal ou vertical dos
nodes que forem adicionados a ela. Independentemente da orientação, quando os nodes
excedem o limite da tela eles começam a montar uma nova linha (para orientação
horizontal) ou uma nova coluna (para orientação vertical).
Código 50. Exemplo da classe FlowPane.

import javafx.application.Application;
import javafx.geometry.Orientation;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.FlowPane;
import javafx.stage.Stage;

public class ExemploFlowPane extends Application {

@Override
public void start(Stage telaEmBranco) {

// PAINEL E SEUS NODES

/* Criar o painel */
FlowPane painel=new FlowPane();
painel.setOrientation(Orientation.HORIZONTAL);
painel.setVgap(4);
painel.setHgap(10);

/* Colocar nodes dentro do pane */

for (int i=1; i<51; i++) {


Button b=new Button("B"+i);
b.setPrefWidth(50);
painel.getChildren().add(b);
}

// SCENE
Scene cena = new Scene(painel, 300, 300);

// JOGANDO TUDO NA STAGE

125
telaEmBranco.setTitle("Exemplo de FlowPane");
telaEmBranco.setScene(cena);
telaEmBranco.sizeToScene();
telaEmBranco.show();

public static void main (String[] args) {


Application.launch(args);
}

No Código 50 nós instanciamos a classe FlowPane na variável painel e estabelecemos sua


orientação como horizontal. Em seguida utilizamos o método setVgap() para definir um
espaço vertical de 4 pixels entre os nodes e o método setHgap() para definir um espaço
horizontal de 10 pixels entre os nodes.
Com um for nós adicionamos 50 botões dentro do objeto FlowPane, cada botão com
uma largura de 50 pixels. Adicionamos o FlowPane em Scene e Scene em Stage. Veja o
resultado da execução desta aplicação na Figura 61.

Figura 61. Resultado do exemplo de FlowPane.

O que aconteceria se mudássemos a orientação para vertical? Experimente mudar o


código e ver o resultado. Faça a seguinte mudança:
painel.setOrientation(Orientation.VERTICAL);

HBox e VBox
As classes HBox e VBox são especializadas em suas funções, ou seja, em distribuir os
nodes horizontalmente, no caso da HBox, ou verticalmente, no caso da VBox. Elas não
produzem novas linhas ou novas colunas como faz a FlowPane. Elas existem para criar
uma única linha ou uma única coluna.

126
No entanto, essas classes tentam ao máximo encaixar todos os nodes que forem
adicionados a elas dentro do espaço que elas têm. Por isso, os nodes são reduzidos para
caber. Isso não acontece com FlowNode, que mantem o tamanho dos nodes e
simplesmente muda de linha ou de coluna para continuar exibindo.
Código 51. Exemplo de HBox e VBox.

import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;

public class ExemploVBoxHBox extends Application {

@Override
public void start(Stage telaEmBranco) {
// PAINEL E SEUS NODES

/* Criar o painel */
HBox painel=new HBox();
painel.setSpacing(4);

/* Colocar nodes dentro do pane */

for (int i=1; i<6; i++) {


Button b=new Button("x"+i);
b.setPrefWidth(50);
painel.getChildren().add(b);
}

// SCENE
Scene cena = new Scene(painel, 300, 300);

// JOGANDO TUDO NA STAGE


telaEmBranco.setTitle("Exemplo de FlowPane");
telaEmBranco.setScene(cena);
telaEmBranco.sizeToScene();
telaEmBranco.show();

public static void main (String[] args) {


Application.launch(args);
}
}

No exemplo apresentado no Código 51 criamos uma janela idêntica a criada para o


exemplo da classe FlowPane, porém com apenas 5 botões, conforme apresentado na
Figura 62. Os tamanhos dos botões foram mantidos, pois cabiam no espaço de HBox.

127
Figura 62. Resultado de exemplo de HBox com 5 botões.

Contudo, ajustamos o código para adicionar 7 botões ao HBox, alterando o for da


seguinte maneira:
for (int i=1; i<8; i++) {
Button b=new Button("x"+i);
b.setPrefWidth(50);
painel.getChildren().add(b);
}

O resultado pode ser visto na Figura 63. Mesmo com o botão sendo setado para 50 pixels
de largura, o HBox diminuiu a largura dos botões para caber na mesma linha,
considerando o espaço disponível na janela.

Figura 63. Resultado do exemplo de HBox com 7 botões.

128
A Figura 64 é o resultado do nosso exemplo, porém alterado para exibir 20 botões. É
possível perceber que o HBox reduziu o máximo que pode a largura dos botões e mesmo
assim não conseguir exibir todos os 20.

Figura 64. Resultado do exemplo de HBox com 20 botões.

Faça alguns testes com VBox. Apenas substitua HBox por VBox na instância da classe e
faça o import da casse VBox.

BorderPane
A classe BorderPane já oferece uma estrutura mais completa para organização visual de
nossas telas. Esta classe divide a tela em 5 partes, conforme apresentado na Figura 65.
Desse modo, os nodes podem ser posicionados em cada uma dessas partes. Podemos
colocar nodes simples, como caixas de texto ou botões, ou branch nodes, como
FlowPane, HBox, VBox e outros.

Figura 65. Organização dos nodes filhos em BorderPane.

O posicionamento dentro de cada uma dessas áreas pode ser configurado. A classe
BorderPane oferece métodos estáticos para este fim. Mas, o que são métodos estáticos?

129
São aqueles oferecidos pela classe e não por sua instância. Por isso, eles são chamados
diretamente a partir da classe e não da instância da classe. Por exemplo:
BorderPane root = new BorderPane();
root.setTop(opcoes);
BorderPane.setAlignment(opcoes, Pos.CENTER);

Repare que temos um objeto opções, que é um node qualquer e está sendo colocado na
área de topo de um objeto BorderPane. Em seguida, estamos alinhando este node no
centro desta área. No entanto, ao invés de codificarmos root.setAlignment nós
codificamos BorderPane.setAlignment. Fizemos isto porque estamos utilizando um
método estático diretamente da classe e não de sua instância. Mas não se preocupe tanto
com este conceito por enquanto.
Código 52. Exemplo de BorderPane.

import javafx.application.Application;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;

public class ExemploBorderPane extends Application {

@Override
public void start(Stage telaEmBranco) {

// FILHOS DE BorderPane
/* HBox para o topo */
HBox opcoes=new HBox(4);
opcoes.getChildren().add(new Button("Opção 1"));
opcoes.getChildren().add(new Button("Opção 2"));

/* Botões para as laterais */


Button btnEsquerda=new Button("Esquerda");
Button btnDireita=new Button("Direita");

/* Caixa de texto para o centro */


TextField txtCentro = new TextField();

/* Rótulo para o rodapé */


Label lblRodape = new Label("Sistema construido em: Século XXI");

// PAINEL PRINCIPAL
/* Primary Node, também chamado de Root Node */
BorderPane root = new BorderPane();
root.setTop(opcoes);
root.setLeft(btnEsquerda);
root.setRight(btnDireita);
root.setCenter(txtCentro);

130
root.setBottom(lblRodape);

/* Alinhando alguns nodes */


BorderPane.setAlignment(btnEsquerda, Pos.CENTER);
BorderPane.setAlignment(btnDireita, Pos.CENTER);
BorderPane.setAlignment(txtCentro, Pos.CENTER);
BorderPane.setAlignment(lblRodape, Pos.CENTER);

// SCENE
Scene cena = new Scene(root, 300, 300);

// JOGANDO TUDO NA STAGE


telaEmBranco.setTitle("Exemplo de BorderPane");
telaEmBranco.setScene(cena);
telaEmBranco.sizeToScene();
telaEmBranco.show();

public static void main (String[] args) {


Application.launch(args);
}
}

Em Código 52 vemos um exemplo de utilização da classe BorderPane. Neste código nós


criamos alguns nodes, sendo um deles um branch node do tipo HBox e depois
adicionamos estes nodes em nosso root node, um objeto BorderPane. Há um método
específico para adicionar nodes em cada uma das 5 áreas do BorderPane: setTop(),
setLeft(), setCenter(), setRight() e setBottom(). Há também como alinhar os nodes
dentro dessas áreas, como fizemos através do método estático setAlignment() da classe
BorderPane. O resultado deste exemplo pode ser visto na Figura 66.

Figura 66. Resultado do exemplo de BorderPane.

131
AnchorPane
A classe AnchorPane também alinha suas classes filhas nas bordas. No entanto, ela não
divide a tela em 5 áreas específicas. Na verdade, é possível ancorar objetos nas bordas da
tela, informando um valor da distância entre a borda e a posição do objeto. É um método
diferente de distribuir os objetos na tela, mas acreditamos que, para esta estrutura de
distribuição visual, a classe BorderPane seria mais adequada. Contudo, segue exemplo de
utilização no Código 53 e o resultado é apresentado na Figura 67.
Código 53. Exemplo de AnchorPane.

import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;

public class ExemploAnchorPane extends Application {

@Override
public void start(Stage telaEmBranco) {

// FILHOS DE AnchorPane
/* HBox para o topo */
HBox opcoes=new HBox(4);
opcoes.getChildren().add(new Button("Opção 1"));
opcoes.getChildren().add(new Button("Opção 2"));

/* Botões para as laterais */


Button btnEsquerda=new Button("Esquerda");
Button btnDireita=new Button("Direita");

/* Caixa de texto para o centro */


TextField txtCentro = new TextField();

/* Rótulo para o rodapé */


Label lblRodape = new Label("Sistema construido em: Século XXI");

// PAINEL PRINCIPAL
/* Primary Node, também chamado de Root Node */
AnchorPane root = new AnchorPane();
AnchorPane.setTopAnchor(opcoes, 10.0);
AnchorPane.setLeftAnchor(btnEsquerda,10.0);
AnchorPane.setRightAnchor(btnDireita,10.0);
AnchorPane.setTopAnchor(txtCentro,80.0);
AnchorPane.setBottomAnchor(lblRodape,10.0);

root.getChildren().add(opcoes);
root.getChildren().add(btnEsquerda);
root.getChildren().add(btnDireita);
root.getChildren().add(txtCentro);
root.getChildren().add(lblRodape);

132
// SCENE
Scene cena = new Scene(root, 300, 300);

// JOGANDO TUDO NA STAGE


telaEmBranco.setTitle("Exemplo de AnchorPane");
telaEmBranco.setScene(cena);
telaEmBranco.sizeToScene();
telaEmBranco.show();
}

public static void main (String[] args) {


Application.launch(args);
}
}

Figura 67. Resultado do exemplo de AnchorPane.

TilePane
A classe TilePane funciona de maneira muito parecida com a FlowPane, possuindo
inclusive o método setOrientation(). No entanto, a diferença de TilePane é que essa
classe organiza os nodes em espaços exatamente iguais, considerando o espaço necessário
para o maior node filho que ela contiver. Este layout é também chamado de azulejo, por
se tratar de diversos blocos de tamanho igual. Um exemplo do uso desta classe pode ser
visto em Código 54 e o resultado na Figura 68.
Código 54. Exemplo de TilePane.

import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
import javafx.scene.layout.TilePane;
import javafx.stage.Stage;

public class ExemploTilePane extends Application {

133
@Override
public void start(Stage telaEmBranco) {

// PAINEL PRINCIPAL
/* Primary Node, também chamado de Root Node */
TilePane root = new TilePane();
root.setVgap(5);
root.setHgap(5);

for (int i=1; i<6; i++) {


root.getChildren().add(new Button("Botão "+i));
root.getChildren().add(new TextField("Caixa de Texto "+i));
root.getChildren().add(new Label("Rótulo "+i));
}

// SCENE
Scene cena = new Scene(root, 500, 300);

// JOGANDO TUDO NA STAGE


telaEmBranco.setTitle("Exemplo de TilePane");
telaEmBranco.setScene(cena);
telaEmBranco.sizeToScene();
telaEmBranco.show();

public static void main (String[] args) {


Application.launch(args);
}
}

Figura 68. Resultado do exemplo de TilePane.

134
StackPane
A classe StackPane possibilita a criação de composições visuais. Ele, por assim dizer,
empilha os nodes um sobre o outro, sobrepondo eles. É o conceito de camadas utilizado
em muitos softwares de edição de imagens, como o Adobe Photoshop. Neste caso, cada
node é uma camada colocada sobre o node anterior. Veja um exemplo de composição
utilizando StackPane no Código 55 e o resultado na Figura 69.

Código 55. Exemplo de StackPane.

import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;

public class ExemploStackPane extends Application {

@Override
public void start(Stage telaEmBranco) {

// PAINEL PRINCIPAL
/* Primary Node, também chamado de Root Node */
StackPane root = new StackPane();

/* Nodes dentro do root node */


root.getChildren().add(new Rectangle(200,200,Color.BLUE));
root.getChildren().add(new Circle(100,Color.RED));
root.getChildren().add(new Rectangle(140,140,Color.YELLOW));

// SCENE
Scene cena = new Scene(root, 500, 300);

// JOGANDO TUDO NA STAGE


telaEmBranco.setTitle("Exemplo de BorderPane");
telaEmBranco.setScene(cena);
telaEmBranco.sizeToScene();
telaEmBranco.show();

public static void main (String[] args) {


Application.launch(args);
}
}

135
Figura 69. Resultado do exemplo de StackPane.

GridPane
A classe GridPane é provavelmente a mais indicada para organizar telas mais complexas,
como os formulários de entrada de dados. Isto porque ela estrutura os nodes em um grid.
Lembra daquele jogo, batalha naval? Neste jogo há as linhas e colunas para guiar os
ataques. Lembra também um tabuleiro de damas ou de xadrez. Podemos adicionar os
nodes nas linhas e colunas. Também podemos definir um espaço entre as linhas e um
espaço entre as colunas, para separar um pouquinho os nodes dentro de GridPane.
Figura 70. Exemplo de GridPane.

import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
import javafx.scene.layout.GridPane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;

public class ExemploGridPane extends Application {

@Override
public void start(Stage telaEmBranco) {

// PAINEL PRINCIPAL
/* Primary Node, também chamado de Root Node */
GridPane root = new GridPane();
root.setVgap(10);

/* Nodes dentro do root node */


root.add(new Rectangle(50,50,Color.BLUE), 0, 0);
root.add(new Circle(25,Color.RED),0,0);
root.add(new Rectangle(30,10,Color.YELLOW),0,0);

136
root.add(new Label(" CADASTRO DE CLIENTES "), 1, 0);

root.add(new Label("Nome:"), 1, 2);


root.add(new Label("Endereco:"), 1, 3);
root.add(new Label("Telefone fixo:"), 1, 4);
root.add(new Label("Telefone celular:"), 1, 5);
root.add(new Label("Possui Whatsapp:"), 1, 6);

root.add(new TextField(), 2, 2);


root.add(new TextField(), 2, 3);
root.add(new TextField(), 2, 4);
root.add(new TextField(), 2, 5);
root.add(new TextField(), 2, 6);

root.add(new Button("Cancelar"), 1, 8);


root.add(new Button("Salvar"), 2, 8);

// SCENE
Scene cena = new Scene(root, 400, 320);

// JOGANDO TUDO NA STAGE


telaEmBranco.setTitle("Exemplo de GridPane");
telaEmBranco.setScene(cena);
telaEmBranco.sizeToScene();
telaEmBranco.show();
}

public static void main (String[] args) {


Application.launch(args);
}
}

Podemos ver o uso de GridPane no Código 55. Assim como FlowPane e TilePane, é
possível ajustar o espaçamento entre as linhas, com o método VGap() e as colunas, com o
método HGap(). No código utilizamos apenas o primeiro.
As linhas e colunas do GridPane são, a princípio, imaginárias. Ao passo que vamos
adicionando os nodes nas coordenadas as linhas e colunas vão se formando para
acomodar os nodes que chegam. Se quiser dar uma olhada nas linhas e colunas que se
formam pode utilizar o método setGridLinesVisible(), adicionando a seguinte linha de
código logo após instanciar o GridPane:
root.setGridLinesVisible(true);

É interessante notar que o GridPane pode também fazer o papel do StackPane. Neste
código nós criamos uma espécie de logo utilizando as classes Rectangle e Circle e
adicionando os nodes na mesma coluna e linha. Assim eles se sobrepõem, como no caso
da StackPane.
Os nodes podem ser adicionados ao objeto GridPane pelo método add(), diretamente da
classe sem passar por getChildren(), o que é bem mais prático. Mas é possível adicionar

137
também utilizando getChildren(). Em nosso código adicionamos diretamente, passando o
node que queremos adicionar, a coluna e a linha onde este node deve ser posicionado.
Veja o resultado na Figura 71.

Figura 71. Resultado do exemplo de GridPane.

Conclusão

Uau, vimos muita coisa neste capítulo. Começamos por classes básicas para controles de
interface, como Label, TextField e Button e depois ingressamos nas estratégias de layout
oferecidas pelo Java FX. Vimos que as classes para layout trabalham de maneira integrada
para compor uma interface adequada. Como dissemos, acreditamos que a classe
GridPane é a mais flexível e por isso a utilizamos mais em nossos códigos. Mas é claro
que você pode ter uma opinião diferente. O importante é utilizar os recursos que o Java
FX oferece para produzir o melhor resultado possível.

138
15. Colocando o motor no carro

Trabalhamos bastante com classes para construção de interfaces com o usuário e agora
não dependemos mais do console para exibir nossas informações. Reparou que já tem um
tempinho que a gente não usa o sysout do Eclipse? Mas tem uma coisa faltando: exibir
resultados na tela. Nós montamos telas, colocamos botões, rótulos e caixas de texto, mas
não apresentamos nenhuma informação na tela recentemente.
Sim, nós conseguimos montar diversas interfaces com o usuário, mas nada que
“funcione”. Parece um belo carro, só que sem motor. Então, chegou a hora de
colocarmos o motor no carro. E para isso precisaremos tratar os eventos de nossas
interfaces. Mas, que eventos são esses?

Eventos

O movimento do mouse sobre a tela, um clique em um botão, o preenchimento de uma


caixa de texto; todos estes são eventos que acontecem em nossa interface.

Quer estejamos cientes disso ou não, esses eventos


continuam acontecendo durante todo o tempo que uma
aplicação está rodando.

Porém, o mais interessante é que podemos detectar estes eventos e escrever código para
ser executado quando os eventos ocorrerem. De fato, existem algumas formas de fazer
isso, mas nos concentraremos na que mais nos interessa neste momento.
As classes que utilizamos para compor nossas interfaces já foram preparadas para capturar
e tratar (handle) esses eventos. Na verdade, existe uma interface que podemos utilizar para
instanciar um objeto dentro das nossas classes nodes e esta interface é o nosso suporte
para o tratamento dos eventos. Observe a Figura 72.

Figura 72. Estrutura conceitual para tratamento de eventos.

139
As classes Java FX que adicionamos a Stage, tais como Scene, FlowPane, GridPane,
TextField e etc, possuem um método que nos permite instanciar uma classe anônima
(sim, uma classe sem nome) que terá apenas um método: handle(). E dentro deste
método teremos o nosso código para tratar o evento. Vamos ver um exemplo?
Código 56. Exemplo de tratamento de eventos.

import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.layout.GridPane;

import javafx.stage.Stage;

public class ExemploHandle extends Application {

@Override
public void start(Stage telaEmBranco) {

// PAINEL PRINCIPAL
/* Primary Node, também chamado de Root Node */
GridPane root = new GridPane();
root.setVgap(10);

/* Nodes dentro do root node */


Label lblTitulo = new Label("Tratando eventos");
Label lblMensagem = new Label();
Button btnOk = new Button("Ok");
Button btnApagar = new Button("Apagar");

root.add(lblTitulo, 0, 0);
root.add(lblMensagem, 1, 3);
root.add(btnOk, 1, 5);
root.add(btnApagar, 1, 6);

/* Tratando os eventos dos nodes */


btnOk.setOnAction(new EventHandler<ActionEvent>() {
public void handle (ActionEvent ae) {
lblMensagem.setText("Opa, quem clicou no botão [Ok]?");
}
});

btnApagar.setOnAction(new EventHandler<ActionEvent>() {
public void handle (ActionEvent ae) {
lblMensagem.setText("");
}
});

// SCENE
Scene cena = new Scene(root,300, 200);

// JOGANDO TUDO NA STAGE


telaEmBranco.setTitle("Exemplo de Handle");
telaEmBranco.setScene(cena);

140
telaEmBranco.sizeToScene();
telaEmBranco.show();
}

public static void main (String[] args) {


Application.launch(args);
}
}

Este código é parecido com os que utilizamos até agora para construir nossas interfaces.
Estamos usando uma classe GridPane para organizar nossos nodes na tela e temos 4
desses nodes: dois Label e dois Button.
Porém, há uma diferença nesse código. Criamos uma seção para implementar o
tratamento de eventos. Note o seguinte trecho:
btnOk.setOnAction(new EventHandler<ActionEvent>() {
public void handle (ActionEvent ae) {
lblMensagem.setText("Opa, quem clicou no botão
[Ok]?");
}
});

Estamos utilizando o método setOnAction do objeto btnOk para adicionar uma classe
anônima que possui apenas um método: handle(). Dentro deste método, escrevemos o
código que será executado quando o botão for pressionado, ou seja, quando o evento do
clique do mouse sobre o botão for detectado por nosso método handle(). Olhe mais uma
vez para a Figura 72 e compare com este trecho de código. Procure compreender bem a
relação entre a figura e o código.
Uma vez dentro do handle() nós utilizamos o método setText() do objeto lblMensagem
para exibir nossa mensagem na tela. Também o utilizamos para apagar a mensagem no
tratamento de eventos do botão btnApagar.
Nós utilizamos o tipo de evento ActionEvent, útil para detectarmos quando um botão foi
pressionado, ou uma caixa de combinação foi exibida ou escondida, ou quando um item
de menu foi selecionado.

Mas existem outros tipos de eventos que podem ser


detectados e para os quais podemos criar uma ação
correspondente, por meio de um evento handler().

141
Uma lista de eventos por classe e tipo pode ser observada na Tabela 6.
Tabela 6. Eventos de interface que podem ser detectados e tratados.

Classes Evento na interface Tipo


Tecla pressionada no KeyEvent
teclado.

Mouse é movido ou um
botão do mouse é MouseEvent
pressionado.

Uma operação de pegar,


arrastar e soltar é MouseDragEvent
realizada.

Uma entrada de dado


alternativa é gerada, InputMethodEvent
alterada, removida ou
atualizada.

Quando drag and drop


(arrastar e soltar) é DragEvent
Node e Scene executado.

Um objeto é rolado. ScrollEvent

Gesto de rotação é
realizado sobre um RotateEvent
objeto.

Gesto de swipe é
realizado sobre um SwipeEvent
objeto.

Um objeto é tocado. TouchEvent

Gesto de zoom é
realizado sobre um ZoomEvent
objeto.

Menu de contexto é ContextMenuEvent


requisitado.

Um botão é
ButtonBase, ComboBoxBase,
pressionado, uma caixa ActionEvent
ContextMenu, MenuItem,
TextField de combinação é exibida
ou escondida, ou um

142
item de menu é
selecionado.
ListView Um item de lista, tabela ListView.EditEvent
TableColumn ou árvore é editada. TableColumn.CellEditEvent
TreeView TreeView.EditEvent

MediaView
Erro encontrado no MediaErrorEvent
media player.

Menu é exibido ou
Menu escondido. Event

PopupWindow
Janela popup é Event
escondida.

Tab
Uma Tab é selecionada Event
ou fechada.

Window
Uma janela é fechada, WindowEvent
exibida ou escondida.

Em nosso código nós tratamos o evento do tipo ActionEvent que é detectado pela classe
ButtonBase (entre outras) e acontece quando o usuário pressiona um botão, seja com o
mouse ou com o teclado. A classe ButtonBase é a superclasse (mãe) das classes Button,
CheckBox, Hyperlink, MenuBotton e ToggleBotton. Por isso é que conseguimos
escrever um código para o handle() do Button.
O importante é que você aprendeu tratar eventos, a escrever código para ser executado
quando determinado evento ocorrer. Com o tempo você vais desenvolver um
conhecimento mais profundo e detalhado sobre o tratamento de eventos (handling).

Calculando o IMC

Agora que a gente aprendeu a escrever código para tratar eventos podemos colocar motor
em alguns carros que já construímos até agora. O que acha?
Vamos começar pelo cálculo de IMC, que a gente construiu a tela mas não escreveu
código para fazer funcionar. Veja o Código 57.
Código 57. Cálculo de IMC funcionando.

import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;

143
import javafx.scene.control.TextField;
import javafx.scene.layout.GridPane;
import javafx.stage.Stage;
import javafx.geometry.Insets;
import javafx.geometry.Pos;

public class InterfaceIMC extends Application {

@Override
public void start(Stage telaEmBranco) throws Exception {

// PRIMARY NODE - GRID

GridPane primaryNode = new GridPane();


//primaryNode.setGridLinesVisible(true);
primaryNode.setAlignment(Pos.CENTER);
primaryNode.setHgap(20);
primaryNode.setVgap(15);
primaryNode.setPadding(new Insets(5));

Label lblPeso = new Label("Peso:");


Label lblAltura = new Label("Altura:");
Label lblMensagem = new Label("Seu IMC é: ");

TextField txtPeso = new TextField();


TextField txtAltura = new TextField();

Button btnCalcular = new Button("Calcular");

primaryNode.add(lblPeso, 0, 0);
primaryNode.add(txtPeso, 1, 0);

primaryNode.add(lblAltura, 0, 2);
primaryNode.add(txtAltura, 1, 2);

primaryNode.add(btnCalcular, 1, 4);
primaryNode.add(lblMensagem, 1, 6);

// SCENE
Scene cena = new Scene(primaryNode, 280, 300);

// JOGANDO TUDO NA STAGE


telaEmBranco.setTitle("Cálculo de IMC");
telaEmBranco.setScene(cena);
telaEmBranco.sizeToScene();
telaEmBranco.show();

// ADICIONANDO OS HANDLES PARA OS EVENTOS


/* Botão Calcular */
btnCalcular.setOnAction(new EventHandler<ActionEvent>() {
public void handle (ActionEvent ae) {
double peso;
double altura;
double resultado;

144
peso = Double.parseDouble(txtPeso.getText());
altura = Double.parseDouble(txtAltura.getText());
resultado = peso/(altura*altura);
lblMensagem.setText("Seu IMC é "+resultado);
}
});
}

public static void main(String args[]) {


Application.launch(args);
}
}

Este código é essencialmente o mesmo que já tínhamos em nosso projeto CalculaIMC


com nossa classe InterfaceIMC. A diferença está no evento do tipo ActionEvent que
detectamos e tratamos no botão btnCalcular, conforme o trecho de código a seguir:
btnCalcular.setOnAction(new EventHandler<ActionEvent>()
{
public void handle (ActionEvent ae) {
double peso;
double altura;
double resultado;

peso = Double.parseDouble(txtPeso.getText());
altura = Double.parseDouble(txtAltura.getText());
resultado = peso/(altura*altura);
lblMensagem.setText("Seu IMC é "+resultado);
}
});

Nós utilizamos o método setOnAction para adicionar ao node btnCalcular a referência


para uma classe anônima que tem um método handle(). Este método é acionado quando
um evento do tipo ActionEvent acontece sobre o btnCalcular, ou seja, quando este botão
é pressionado. Dentro do método handle() nós escrevemos o nosso código.
Dentro do handle() nós declaramos 3 variáveis do tipo Double: peso, altura e resultado.
É importante lembrar que as variáveis do tipo Double e Float são do tipo ponto
flutuante, quer dizer, possuem casas decimais. No Java (e na vasta maioria das linguagens)
as casas decimais são indicadas por um ponto (.) e não por uma vírgula (,). Por isso é que
a altura deve ser informada assim: 1.60 e não assim: 1,60.
Os valores informados nos TextField ficam armazenados em um atributo private desta
classe e pode ser acessado por seu getter e settter. O método getter getText() retorna o
valor em tipo String e nós sabemos que este tipo não nos permite realizar cálculos
aritméticos. Precisamos converter o tipo String para o tipo Double, porque o valor String
“1.60” não pode ser, por exemplo, dividido por dois, mas o valor Double 1.60 pode ser
utilizado em qualquer cálculo matemático.
A classe Double possui um conjunto de métodos estáticos que podemos utilizar. Um
deles é o parseDouble(), que converte um valor que está no tipo String para o tipo
Double. É tudo o que precisamos. E foi exatamente o que utilizamos para colocar valores
numéricos dentro de peso e altura. Depois foi só fazer o cálculo simples de IMC, cuja

145
fórmula é amplamente conhecida e difundida na Internet e depois usar o método setter
do Label lblMensagem para exibir o resultado para o usuário. Veja o resultado na Figura
73.

Figura 73. Resultado do projeto IMC.

Muito legal, não acha? Mas vamos treinar mais um pouquinho com interfaces gráficas.
Lembra do nosso jogo de dado que exibia os resultados no console? Que tal a gente
colocar isso numa telinha um pouco mais bonitinha?
Já ia esquecendo de dizer: para o Java o padrão de marcador decimal é o ponto e não a
vírgula. De fato, o padrão americano para identificar as casas decimais é o ponto e não a
vírgula.

Jogando dados

Vamos criar um novo projeto chamado Dados. Para que o projeto já reconheça os
pacotes Java FX sem problemas, durante a criação do projeto selecione a opção Use
default JRE, conforme Figura 74.

Figura 74. Criando um novo projeto Dados.

146
Dentro deste projeto vamos organizar um pouco melhor o nosso código utilizando
package. Lembra, são como pastas dentro do nosso projeto para guardar nossos códigos.
Na verdade, são pastas mesmo. Então vamos criar 3 pacotes: Classes, Visual e Img. Em
Classes (que original!) colocaremos as classes utilizadas por nossa interface gráfica, em
Visual colocaremos nossa classe de interface gráfica e em Img colocaremos uma imagem
para uso no projeto.
Em projetos reais você terá muitas classes de interface gráfica e muitas classes de apoio.
Provavelmente você terá que organizar seu código em vários pacotes e subpacotes. Então,
o objetivo aqui é começar a te treinar para projetos reais. Você deverá ter um projeto no
Eclipse parecido com este da Figura 75.

Figura 75. Estrutura inicial do projeto Dados.

Localize na internet uma imagem do tipo png, baixe e salve com o nome dados.png
dentro da pasta (pacote) Img.
Precisamos agora das classes que já criamos para nosso jogo de dados. No próprio Eclipse
abra o antigo projeto JogaDados, selecione as classes JogoNumerico e Dados, copie
(CTRL+C) e cole (CTRL+V) dentro do nosso pacote Classes. Pronto, já temos as nossas
classes. Agora é criar a Interface gráfica.
Nós poderíamos começar fazendo alguns ajustes em nossa classe JogoNumerico ou na
classe Dado, mas não faremos nada disso. Utilizaremos as classes do jeito que elas foram
projetadas. Este é um dos objetivos da orientação a objetos, o reuso. Poder reutilizar
código sem ter que mexer nele economiza tempo e nos deixa focados em nosso objetivo
original. Então vamos logo para o nosso Código 58.
Código 58. Classe JogaDados.

package Visual;

import Classes.Dado;
import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.GridPane;
import javafx.stage.Stage;

public class JogaDados extends Application {

// ATRIBUTOS DA CLASSE

147
int acertos=0;
int tentativas=0;

public static void main(String args[]) {


Application.launch(args);
}

@Override
public void start(Stage telaEmBranco) {

// Nodes
/* Imagem */
Image img = new Image("img\\dados.png");
ImageView imgDado = new ImageView(img);
imgDado.setPreserveRatio(true);
imgDado.setFitWidth(80);

/* Labels */
Label lblTitulo = new Label(" JOGANDO DADOS ");
Label lblOrientacao = new Label("Escolha um número:");
Label lblResultado1Titulo = new Label("Dado 1");
Label lblResultado2Titulo = new Label("Dado 2");
Label lblResultado1 = new Label();
Label lblResultado2 = new Label();
Label lblResultadoMensagem = new Label();
Label lblDesempenho = new Label();

/* Caixas de texto */
TextField txtAposta = new TextField();

/* Botões */
Button btnJogarDados = new Button("Jogar Dados!");
btnJogarDados.setDefaultButton(true);

/* Root node */
GridPane grdTela = new GridPane();
grdTela.setHgap(10);
grdTela.setVgap(10);

grdTela.add(imgDado, 1, 0);
grdTela.add(lblTitulo, 2, 0);
grdTela.add(lblOrientacao, 2, 1);
grdTela.add(txtAposta, 2, 2);
grdTela.add(btnJogarDados, 2, 3);
grdTela.add(lblResultado1Titulo, 1, 4);
grdTela.add(lblResultado2Titulo, 3, 4);
grdTela.add(lblResultado1, 1, 5);
grdTela.add(lblResultado2, 3, 5);
grdTela.add(lblResultadoMensagem, 1, 6, 3, 1);
grdTela.add(lblDesempenho, 1, 8, 3, 1);

// SCENE
Scene cena = new Scene(grdTela, 310, 300);

// JOGANDO TUDO NA STAGE


telaEmBranco.setTitle("Jogo de Dados");
telaEmBranco.setScene(cena);

148
telaEmBranco.sizeToScene();
telaEmBranco.show();

// TRATANDO OS EVENTOS
btnJogarDados.setOnAction(new EventHandler<ActionEvent>() {
public void handle (ActionEvent ae) {
Dado d1 = new Dado();
Dado d2 = new Dado();
int resultado;

d1.executaJogo();
lblResultado1.setText(String.valueOf(d1.getResultado()));

d2.executaJogo();
lblResultado2.setText(String.valueOf(d2.getResultado()));

resultado=d1.getResultado()+d2.getResultado();

tentativas++;

if(resultado==Integer.parseInt(txtAposta.getText())) {
lblResultadoMensagem.setText("Parabéns, o resultado foi "+
resultado+". Voce acertou!");
acertos++;
}
else {
lblResultadoMensagem.setText("Que pena, o resultado foi "+
resultado+". Continue tentando.");
}

lblDesempenho.setText("Sua proporção de acertos é de "+acertos+


" para "+tentativas);
}
});
}

A primeira coisa que podemos notar neste código é que ele tem a indicação do pacote
logo na primeira linha. Isto não é novidade, pois já utilizamos pacotes antes. Por causa
dessa organização em pacotes o primeiro import que temos é o da nossa classe Dado,
importada do pacote Classes. Note que estamos organizando nosso código em pacotes, do
mesmo jeito que o Java faz.
No método padrão start() da classe Application, que nós sobrescrevemos, é que vai todo
o nosso código de interface gráfica. Começamos com uma classe que ainda não tínhamos
utilizado. Na verdade, duas: Image e ImageView. A classe Image nos permite referenciar
uma imagem e fizemos esta referência à nossa imagem dados.png que está dentro do
pacote Img. Depois utilizamos a classe ImageView, que é um node Java FX. Mantivemos
a proporção com o método setPreserveRatio() e definimos um tamanho de 80 pixels de
largura (a altura se ajusta proporcionalmente por causa do ratio).

149
Depois utilizamos classes já conhecidas por nós: Label, Button, GridPane e jogamos tudo
dentro de Scene e Scene dentro de Stage. Como já estamos acostumados a fazer.
Por fim, tratamos o evento do tipo ActionEvent de btnJogarDados, quer dizer,
escrevemos código para ser executado quando o botão for pressionado. Notou que o
btnJogarDados, logo depois de criado foi definido como botão padrão com o método
setDefaultButton()? Isso quer dizer que ao se pressionar a tecla <ENTER> nesta tela, o
botão é automaticamente pressionado, mesmo que haja outros botões na mesma tela.
No código do handle() do botão nós instanciamos duas classes Dado e executamos para
gerar dois números aleatórios entre 1 e 6. Como os resultados do método getResultado
são do tipo inteiro, foi necessário converter de int para String, o que foi feito por meio
do método valueOf() da classe String.
Na comparação do resultado, que é a soma dos dois dados, com o valor informado pelo
usuário, foi necessário fazer outra conversão. Como resultado é int e o valor dentro do
FieldText é String, foi necessário converter de String para int por meio do método
parseInt() da classe Integer. O resto foi fácil. Se o número informado pelo usuário for
igual ao resultado, a aplicação informa que ele acertou. Se não, informa que ele errou.
Tem mais uma coisa. Notou que logo no início da nossa classe JogaDados nós criamos
dois atributos? Criamos o tentativas e o acertos, ambos do tipo int. Então, toda vez que o
botão btnJogarDados é pressionado o tentativas é incrementado de 1. E toda vez que o
usuário acerta o acertos é incrementado de um. Assim, é possível mostrar a proporção
entre tentativas e acertos do usuário.
E o resultado final de nossa aplicação pode ser vista na Figura 76. Ficou muito legal, não
acha? Agora é só chamar um amigo e cada um jogar uma vez. Cuidado para não ficar
viciado ☺.

Figura 76. Tela do Jodo de Dados funcionando.

150
Conclusão

Este capítulo foi muito importante. Nele aprendemos a escrever código para responder
aos eventos de nossas aplicações. Sem isso, pra falar a verdade, a gente praticamente não
tem uma aplicação, só uma telinha com botões, labels e caixas de texto.
Depois que a gente aprendeu a colocar um motor de verdade em nossas aplicações
conseguimos colocar para funcionar duas aplicações que já tínhamos começado antes: o
cálculo de IMC e o jogo de dados. Legal, né? Mas dá para fazer praticamente qualquer
coisa a partir de agora. Você já é programador Java! Parabéns!

Desafio

Como você se tornou um especialista na aplicação do jogo de dados, fica um desafio:


transforme o jogo em multiplayer, de modo que ele peça dois valores, um para o jogador
1 e outro para o jogador 2. Depois, quando o botão btnJogarDados for pressionado a
aplicação deve dizer qual dos dois ganhou. Se os dois perderam, a aplicação deve informar
isso.
Depois que você vencer o primeiro desafio, fica o segundo: coloque uma mensagem de
proporção de acertos e tentativas para cada um dos dois jogadores.

151
16. Interface gráfica – Uma mão na roda

Falamos bastante sobre interface gráfica com o usuário nos capítulos anteriores. Não
assumimos o desafio de falar tudo o que existe sobre Java FX, até porque há livros
inteiros, vários, abordando o tema. Mas o objetivo deste livro é te deixar em condições de
desenvolver aplicações em Java. Por isso nos preocupamos em incluir neste trabalho todo
o essencial para o desenvolvimento de aplicações em Java, viabilizando sua estrada para a
evolução nesta plataforma tecnológica. Dentro deste conjunto de conhecimentos
essenciais há o Scene Builder. Quer conhecer? Sugerimos fortemente que o conheça.

Scene Builder

Já construímos algumas pequenas aplicações com interface gráfica e você deve ter
percebido como pode ser trabalhoso ficar ajustando no código, executando para ver como
ficou, depois ajustando de novo e assim por diante. O Scene Builder é uma ferramenta
de software para a construção de interfaces gráficas utilizando o arrastar e soltar, ou seja,
possibilitando escolher um componente visual e colocá-lo na tela.

O Scene Builder facilita o desenvolvimento, permitindo que o


programador otimize seu tempo de construção das
interfaces gráficas.

Naturalmente, outras plataformas já proporcionam esta abordagem há muito tempo e


talvez até de maneira mais eficiente. Podemos citar, por exemplo, a linguagem C# com a
IDE Visual Studio, que implementa este modelo de trabalho de maneira muito
adequada.
No entanto, no caso do Java FX há outros aspectos que precisam ser considerados. Por
exemplo, o Java FX foi elaborado para ser multiplataforma, rodando em Windows, Mac
OS e Linux. As interfaces também são pensadas para serem auto ajustáveis aos diferentes
tamanhos de tela e também ao redimensionamento destas telas pelos usuários. Para que
tudo isso seja possível, uma estrutura um pouco mais complexa se torna necessária. Talvez
um pouco mais complexa por um lado, porém mais flexível por outro. Um aspecto a
destacar é que a plataforma Java FX suporta CSS para definições visuais de suas interfaces
gráficas. Isso quer dizer que algo que antes pertencia apenas ao mundo Web agora pode
ser aplicado também às aplicações desktop.

Instalando o Scene Builder

O Scene Builder foi disponibilizado pela própria Oracle como um produto de apoio à
plataforma Java FX. No entanto, a partir da versão 8.40 do Java a empresa cessou o
fornecimento deste produto. O projeto passou a ser cuidado pela Open JDK Community
(http://openjdk.java.net/), responsável pela implementação do conceito open-source para

152
as tecnologias Java e fortemente apoiada pela Oracle, a atual detentora destas tecnologias.
Você pode acompanhar o projeto Java FX mantido por esta comunidade em
https://wiki.openjdk.java.net/display/OpenJFX/Main.
A partir deste ponto a Gluon (http://gluonhq.com/) passou a fornecer o produto,
gratuitamente, ao mercado. E é exatamente da página deles que vamos baixar esta
importante ferramenta para nos apoiar na construção de interfaces gráficas. Uma
verdadeira mão na roda.
A primeira coisa a fazer é acessar o link http://gluonhq.com/products/scene-builder/ e
baixar o produto, conforme Figura 77. Escolha a versão mais apropriada para o seu
sistema operacional, escolhendo uma das opções apresentadas na Figura 78.

Figura 77. Página para baixar o Scene Builder.

Figura 78. Versões do Scene Builder para cada sistema operacional.

153
Depois disso é só fazer a instalação normalmente. O Scene Builder é um software
independente, que auxilia na construção visual de arquivos do tipo FXML (já falaremos
sobre isso). Por isso, você poderá carregar e utilizar o produto diretamente a partir do seu
sistema operacional. No caso do Windows, como pode ser visto na Figura 79, ele aparece
na lista de produtos instalados em seu computador.

Figura 79. Scene Builder instalado no sistema operacinal Windows.

Integrando o Scene Builder ao Eclipse

Você já tem o Scene Builder e provavelmente já entrou na aplicação e andou fuçando


algumas de suas funcionalidades. Agora queremos preparar o nosso editor de aplicações,
o Eclipse, para trabalhar em conjunto com o Scene Builder. Se você preferir usar outro
editor como o NetBeans ou o InteliJ, nenhum problema. Basta configurar a integração
deste editor com o Scene Builder.
No caso do Eclipse nós precisaremos instalar um plug-in para que essa integração
aconteça. Existe uma infinidade de plug-ins para o Eclipse e a operação de instalação é
muito simples. Então, não fique preocupado achando que se trata de algo complexo, pois
não é.
Outra dica: despois de saber disso é possível que você se sinta tentado a pesquisar os
diversos plug-ins que existem para o Eclipse e sair instalando um monte deles. Não faça
isso, pois poderá acabar comprometendo seu ambiente de desenvolvimento. Utilize
estritamente os plug-ins que sejam essenciais para o seu trabalho.
Para iniciar a instalação deu um plug-in no Eclipse nós utilizamos a opção Install New
Software da opção Help no menu, conforme a Figura 80.

154
Figura 80. Integrando o Scene Builder ao Eclipse: passo 1.

Agora é preciso instalar o plug-in e(fx)clipse a partir da fonte adequada. Utilizaremos a


eclipse.fx.ide (http://www.eclipse.org/efxclipse/index.html). Se ela não estiver disponível
na lista em Work with, simplemente adicione utilizando o botão Add e a url indicada na
Figura 81. Selecione a opção e(fx)clipse – install e pressione o botão Finish.

Figura 81. Integrando o Scene Builder ao Eclipse: passo 2.

Pronto, e(fx)clipse instalado. Logo o utilizaremos. Mas antes, é importante entendermos


bem a estrutura de uma aplicação Java FX com interfaces FXML.

155
Estrutura de uma aplicação com Java FX Scene Builder

Nós já falamos da estrutura conceitual de uma aplicação Java FX em capítulos anteriores e


inclusive construímos algumas aplicações utilizando esta estrutura. Tudo o que você
aprendeu até aqui permanece, mas agora vamos adicionar um ou dois pontos para
facilitar a nossa vida. Sabe o Pane onde você colocava tudo dentro? Vai ficar em um
arquivo separado. Sabe o código de handling que ficava junto com os nodes? Vai ficar em
um arquivo separado. E vamos usar um padrão simples conhecido como MVC. Dê uma
boa olhada na Figura 82.

Figura 82. Java FX usando modelo MVC.

156
O padrão de programação chamado MVC se refere a
separar as coisas dentro do código para ficar mais simples e
mais organizado.

Em um arquivo fica o Model, que é a nossa classe onde ficam os atributos e os métodos.
Um exemplo de Model que nós já construímos é a classe Dados. Ou seja, nada de novo
até aqui.
Em outro arquivo fica a View, que se refere a todos os controles visuais. Só que agora nós
vamos ter a ajuda do Scene Builder para construir nosso Pane e colocar os outros nodes
dentro, tipo Label, TextField e Button. A gente vai fazer isso de maneira visual, muito
mais simples.
E por fim, os códigos ativados por eventos deste nosso View ficarão em outro arquivo,
que chamaremos de Controller. Além de ficar mais organizado, você verá que ficou ainda
mais fácil escrever os handling dentro deste arquivo separado.
Assim nós temos o modelo Model-View-Controller, ou MVC. Achamos que um nome
mais legal seria VCM porque essa é a ordem que melhor explica o modelo. A View não
fala com o Model. Toda a conversa precisa ser intermediada pelo Controller. O
Controller fica no meio dos dois. Então, View fala com Controller, que fala com Model.
Por isso da ordem: V C M. Mas calma aí. Isso é invenção nossa só pra te ajudar a
entender. Não vá chamar o padrão de VCM. O nome certo é MVC mesmo.
Vamos construir uma aplicação com esses conceitos só para deixar tudo claro. Você vai
ver que na prática tudo é muito mais fácil do que parece.

Uma aplicação do Java FX Scene Builder

Vamos voltar à nossa Figura 82. Precisamos de uma classe inicial, que estenda
Application, receba a classe Stage, instancie uma classe Scene, jogue todos os nodes
dentro dela, por fim adicionando Scene dentro da Stage.

Figura 83. Criando um projeto JavaFX.

157
Vamos criar um novo projeto no Eclipse, mas desta vez não será um projeto Java e sim
um projeto JavaFX. Use o atalho CTRL+N e coloque no filtro JavaFX. Escolha a opção
JavaFX Project como na Figura 83. Depois disso é só informar o nome do projeto e clicar
em Finish.
Lembra que instalamos o Scene Builder e o integramos ao Eclipse? Então vamos utilizá-lo.
O Scene Builder constrói arquivos do tipo FXML. Esses arquivos conterão toda a parte
visual de nossa aplicação. Em outras palavras, cada tela que construirmos produzirá um
arquivo FXML. Para fazer isso a partir do Eclipse vamos mais uma vez utilizar o atalho
CTRL+N e colocar no filtro FXML, como na Figura 84.

Figura 84. Criando uma interface gráfica JavaFX.

O passo seguinte é definir o nome da tela que estamos criando e o padrão de layout que
será utilizado, ou o Root Element. Vamos escolher o GridPane, como em Figura 85.

Figura 85. Criando a classe inicial.

158
Ao fazermos isso o Eclipse cria um arquivo chamado Calcula.fxml, dentro do qual
estarão todos os nossos nodes (componentes visuais), tais como o Pane para organizar
tudo, os botões, rótulos, caixas de texto e etc. Agora, vamos carregar o Scene Builder para
construirmos a tela de maneira visual. Para isso, clique com o botão direto sobre o
arquivo Calcula.fxml e escolha a opção Open with SceneBuilder, como mostra a Figura
86.

Figura 86. Carregando o Scene Builder a partir do Eclipse.

O Scene Builder é carregado e nos permite construir nossa tela. Brinque um pouco com
ele até se habituar. No começo pode ser um pouco difícil, mas rapidamente você se
acostuma. Arraste os controles da janela à esquerda chamada Controls e vá montando a
tela. Monte a tela conforme apresentada na Figura 87. Lembre-se de que estamos usando
um GridPane, que já utilizamos antes. As regras de uso são as mesmas. A única diferença
é que agora estamos fazendo tudo visualmente e não programaticamente.

Figura 87. Tela do Scene Builder.

159
Sempre que quiser poderá salvar as alterações realizadas, no menu File/Save. O Eclipse
continuará aberto e as alterações que você fizer no Builder Scene serão refletidas no
arquivo FXML no Eclipse. Faça um Refresh no Eclipse, se necessário, pressionando F5.
O Código gerado pelo Scene Builder pode ser visto em Código 59.
Código 59. Código FXML de Calcula gerado pelo Scene Builder.

<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.TextField?>
<?import javafx.scene.layout.BorderPane?>
<?import javafx.scene.layout.ColumnConstraints?>
<?import javafx.scene.layout.GridPane?>
<?import javafx.scene.layout.RowConstraints?>

<BorderPane xmlns="http://javafx.com/javafx/9.0.1" xmlns:fx="http://javafx.com/fxml/1"


fx:controller="CalculaController">
<top>
<Label id="lblTitulo" style="-fx-font-size: large;" text="SISTEMA DE CÁLCULO DE
MÉDIA" BorderPane.alignment="CENTER">
<padding>
<Insets bottom="20.0" left="20.0" right="20.0" top="20.0" />
</padding></Label>
</top>
<bottom>
<Label style="-fx-font-size: 12px;" text="Desenvolvido por Campos Corporation. Copyright
2018." BorderPane.alignment="CENTER">
<padding>
<Insets bottom="20.0" left="20.0" right="20.0" top="20.0" />
</padding></Label>
</bottom>
<center>
<GridPane hgap="10.0" vgap="10.0" BorderPane.alignment="CENTER">
<columnConstraints>
<ColumnConstraints halignment="RIGHT" hgrow="SOMETIMES" minWidth="10.0"
prefWidth="100.0" />
<ColumnConstraints halignment="LEFT" hgrow="SOMETIMES" minWidth="10.0"
prefWidth="100.0" />
</columnConstraints>
<rowConstraints>
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
</rowConstraints>
<opaqueInsets>
<Insets />
</opaqueInsets>
<children>
<Label text="Primeir nota:" GridPane.rowIndex="1" />
<Label text="Segunda nota:" GridPane.rowIndex="2" />
<Label text="Terceira nota:" GridPane.rowIndex="3" />
<Label text="Quarta nota:" GridPane.rowIndex="4" />
<TextField fx:id="txtNota1" maxWidth="70.0" GridPane.columnIndex="1"
GridPane.rowIndex="1" />
<TextField id="txtNota2" fx:id="txtNota2" maxWidth="70.0" GridPane.columnIndex="1"
GridPane.rowIndex="2" />
<TextField id="txtNota3" fx:id="txtNota3" maxWidth="70.0" GridPane.columnIndex="1"
GridPane.rowIndex="3" />
<TextField id="txtNota4" fx:id="txtNota4" maxWidth="70.0" GridPane.columnIndex="1"
GridPane.rowIndex="4" />

160
<Button id="btnCalcular" fx:id="btnCalcular" defaultButton="true"
mnemonicParsing="false" onAction="#handleCalcula" text="Calcular" GridPane.columnSpan="2"
GridPane.halignment="CENTER" GridPane.rowIndex="5" />
<Label text="RESULTADO" GridPane.columnSpan="2" GridPane.halignment="CENTER"
GridPane.rowIndex="6" />
<Label id="lblResultado" fx:id="lblResultado" text=" "
GridPane.columnSpan="2" GridPane.halignment="CENTER" GridPane.rowIndex="7" />
</children>
</GridPane>
</center>
</BorderPane>

Não sei se você já viu código XML antes, mas o código FXML é construído baseado no
padrão XML. Ele possui tags de marcação, como também acontece com o código HTML.
Você não precisa aprender a escrever código FXML porque o Scene Builder vai fazer isso
para você. De qualquer forma é bom saber que é um código bem fácil de entender e até
de escrever se for preciso.
Bem, mas o que temos dentro deste código todo? Temos um Pane, temos rótulos, caixas
de texto e um botão. Tudo isso é a parte View (lembra do MVC?). Mas se rodar a
aplicação agora, nada vai acontecer. Por que? Porque para que tudo isso apareça
precisamos colocar dentro de uma Scene, colocar a Scene dentro de uma Stage e escrever
nosso método launch(), como já fizemos antes.
Vamos criar uma nova classe em nosso projeto, chamada CalculaMedia. Você já sabe
fazer isso no Eclipse. Agora veja o código dessa classe em Código 60.
Código 60. Classe CalculaMedia, que carrega o FXML.

import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;

/*
* CLASSE INIICAL
* Está é a classe que estende Application e carrega o View do MVC.
*/

public class CalculaMedia extends Application {

@Override
public void start(Stage primaryStage) {

Parent root = null;

try {
root = FXMLLoader.load(getClass().getResource("Calcula.fxml"));
} catch (Exception e) {
System.out.println("Aconteceu um erro: "+e.getMessage());
}

Scene scene= new Scene(root, 350,450);


primaryStage.setTitle("Média");
primaryStage.setScene(scene);

161
primaryStage.show();

public static void main(String[] args) {


Application.launch(args);
}
}

Este código não é tão diferente daquele que já construímos antes. Mas podemos perceber
que ele está menor e mais simples. Há também uma novidade nele: um tratamento de
erro foi adicionado. Caso algum tipo de erro aconteça durante a execução o nosso catch
captura o erro e envia uma mensagem mais amigável para o usuário.
A principal diferença deste código para o que construímos antes é que neste nós
carregamos o arquivo Calcula.fxml para dentro de root. E nós sabemos que todos os
nossos controles visuais estão dentro de Calcula.fxml. O restante é igual, com Scene
carregando root e Stage, em primaryStyage, recebendo Scene. Por fim, em main a gente
executa o método launch de Application e nossa aplicação já começa a funcionar.
Agora que já temos a parte do View do MVC implementada, precisamos de um Model,
ou seja, de uma classe que conterá os atributos e métodos que precisamos. Crie uma
classe nova no projeto chamada Media e adicione o seguinte código:
Código 61. Classe Media, que é o Model do nosso MVC.

import java.util.ArrayList;
import java.util.List;

/*
* MVC: ESTE É O MODEL
* O Model tem a representação dos dados e as regras de negócio
* para estes dados. Ele conversa com o Controller.
*/

public class Media {

private List<Double> fatores = new ArrayList<Double>();


private double media;

public void addMedia(double n) {


fatores.add(n);
}

private void calculaMedia() {


double total=0;

for (Double valor : fatores) {


total+=valor;
}
media = total/fatores.size();
}

public double getMedia() {

162
calculaMedia();
return media;
}

public String getMediaStr() {


calculaMedia();
return Double.toString(media);
}

Essa classe contém 2 atributos privados: fatores, que é uma ArrayList que receberá cada
elemento numérico que comporá a média e media, e um double onde será armazenada a
média resultante.
Ela possui 3 métodos: addMedia(), calculaMedia(), getMedia() e getMediaStr(). O
método addMedia() recebe um double e adiciona na ArrayList. Os métodos getMedia() e
getMediaStr() retornam o resultado, sendo que a primeira no tipo double e a segunda no
tipo String.
Há ainda o método calculaMedia(), que é do tipo privado e por isso é acessado apenas
por outros métodos da própria classe. No caso, os métodos getMedia() e getMediaStr(). O
calculaMedia() percorre todo o ArrayList fatores somando seus valores e finalizando com
a divisão do valor total dessa soma pelo número de elementos do ArrayList. Isto produz a
média dos valores, não importa quantos valores tenham sido adicionados.
Perfeito, agora temos um View funcionando e um Model fazendo tudo o que a gente
precisa. Mas nosso View não fala com nosso Model, lembra? Precisamos de um
intermediário, que é o Controller. Então vamos criar uma classe para ser Controller. Cria
uma classe nova chamada CalculaController e adicione o seguinte código:
Código 62. Classe CalculaController, Controller de nossa aplicação.

import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;

/*
* MVC: ESTE É O CONTROLLER
* O Controller é o intermediário entre o View e Model. É onde
* são colocadas as funções invocadas pelo View que acessam o Model.
*/

public class CalculaController {

@FXML private TextField txtNota1;


@FXML private TextField txtNota2;
@FXML private TextField txtNota3;
@FXML private TextField txtNota4;
@FXML private Label lblResultado;
@FXML private Button btnCalcular;

163
@FXML private void handleCalcula(ActionEvent event) {

try {
Media m=new Media();
m.addMedia(Double.parseDouble(txtNota1.getText()));
m.addMedia(Double.parseDouble(txtNota2.getText()));
m.addMedia(Double.parseDouble(txtNota3.getText()));
m.addMedia(Double.parseDouble(txtNota4.getText()));

lblResultado.setText(m.getMediaStr());
}
catch (NumberFormatException e) {
lblResultado.setText("Informe valores numéricos para todas as notas");
}
catch (Exception e) {
lblResultado.setText("Erro: "+e.getMessage());
}

A primeira coisa que podemos notar é que esta classe fala com o nosso View, porque faz
referência aos controles que estão lá, como txtNota1, lblResultado e btnCalcula. Mas
também fala com nosso Model porque instancia nossa classe Media, que é o nosso
Model. Perceber a ligação com o Model é fácil por causa da instância, mas como é feita a
ligação com o View? Essa ligação precisa ser explicitamente indicada por nós.

Figura 88. Indicando dentro da View a classe Controller.

164
Conforme apresentado na Figura 88, no canto inferior esquerdo do Scene Builder nós
podemos indicar qual é a classe que será a Controller da nossa tela, ou seja, da nossa
View. Ao fazermos essa ligação a nossa classe Controller, que no caso é a
CalculaController, pode acessar os elementos da Calcula.fxml através das declarações
@FXML.
Veja que em nosso Código 62 referenciamos os controles txtNota1, txtNota2, txtNota2,
txtNota4, lblResultado e btnCalcular, permitindo acessar esses controles no código, para
pegar valores, para usar setText() do Label e para fazer o handling do botão.
Aliás, o handling ficou ainda mais fácil de escrever e de compreender. Ele é feito em duas
partes. No CalculaController a gente cria o handler e dá um nome para ele. Neste caso
nós escolhemos chamar de handleCalcula, tratando o evento ActionEvent. Escrevemos o
código que será executado quando o evento ocorrer e pronto. Fácil, fácil. Mas como ligar
este evento ao botão que está lá no Calcula.fxml?

Figura 89. Ligando o método de handle ao botão.

Isso também é bastante simples. Basta ir lá no Scene Builder, selecionar o botão, escolher
Code na janela da direita e em On Action informar que este evento será respondido pelo
método handleCalcula, que a gente já escreveu na classe CalculaController.
Se quisermos criar métodos para serem chamados no caso da ocorrência de outros
eventos, há uma enorme lista de eventos possíveis para a classe Button que aparecem no
Scene Builder, tais como Drag Done, On Mouse Pressed e muitos outros.
Depois de tudo isso, é só executar a aplicação e realizar vários testes. Veja a tela da
aplicação rodando na Figura 90.

165
Figura 90. Aplicação JavaFX para cálculo da média de 4 notas.

Conclusão

Neste capítulo vimos como construir aplicações visuais de maneira mais prática e
organizada com a ajuda do Scene Builder. Conhecemos o Scene Builder e aprendemos a
instalá-lo e integrá-lo com o Eclipse. Compreendemos o que é o padrão Model-View-
Controller e construímos uma aplicação de cálculo da média de 4 notas utilizando este
conceito.

Desafio

Como você já sabe usar o Scene Builder, precisa se acostumar com ele e melhorar suas
habilidades no uso dele. Construa uma aplicação com uma tela de cadastro, com código,
nome, endereço (rua, bairro, cidade, CEP), telefone fixo, telefone celular e uma área de
texto para descrever a pessoa cadastrada. Coloque também um título bonito e botões,
uma para salvar e outro para cancelar. Divirta-se!

166
17. Strings

Nós temos utilizado Strings desde o início deste livro, literalmente. Não acredita? Uma
das primeiras coisas que fizemos foi criar um método main(), que tem a seguinte
assinatura:
public static void main (String args[])

Lembrou? Pois é, o método main() recebe por parâmetro um Array de Strings. Então,
sim, estamos utilizando Strings há muito tempo. Mas agora, finalmente, vamos conhecer
melhor este personagem tão popular da nossa vida de programador Java.

Classe ou tipo de dados?

Neste ponto é possível que você já tenha se perguntado: String é uma classe ou um tipo
de dado? É uma pergunta razoável. Já reparou que para declarar uma variável string a
gente coloca o S maiúsculo em String? É assim:
String s1 = “Aqui temos valores alfanuméricos”;

Isto acontece por causa de um padrão simples do Java: todos os nomes de classes iniciam
com letra maiúscula. Opa, então é isso mesmo, String é uma classe. Mas sempre que a
gente cria uma classe, a gente pode tipar uma variável com esta classe, certo? Se temos
uma classe Dado, podemos tipar uma variável com ela, assim:
Dado d;
d = new Dado();

Então, de certa forma, podemos dizer que as classes também são tipos de dados. Apenas
não são tipos primitivos, como char, byte, short, int, double e float. Olhando por este
ângulo, String, que é uma classe, é também um tipo de dado não primitivo.
Então, isso quer dizer que podemos instanciar a classe String com o new? Claro que sim.
Veja este exemplo:
String s1 = new String(“Aqui temos valores
alfanuméricos”);

Se é assim, por que temos duas formas de instanciar uma classe String? Quer dizer, por
temos o = e o new para instanciar uma classe String? Pode parecer confuso, mas não é. E
o motivo é simples: otimização e performance.

167
String pool

É fácil perceber que utilizamos muitas strings em nosso código. Provavelmente é o tipo de
dado mais utilizado em qualquer sistema. Além do mais, é um tipo de dado que consome
muito espaço na memória. Por exemplo, o maior tipo numérico, o float, consome 4 bytes
na memória. E só a variável s1 de nosso exemplo já vai consumir, no mínimo, 32 bytes.
Por isso, os idealizadores do Java pensaram em uma forma de economizar memória e
também aumentar a velocidade (ou performance) durante o uso de dados string. Mas,
para você entender bem a questão e também poder verificar o que estamos dizendo, teste
o seguinte código:
Código 63. Comparando Strings.

public class StringIgualdade {

public static void main(String args[]) {

String s1 = "Gosto de JAVA";


String s2 = "Gosto de JAVA";
String s3 = new String("Gosto de JAVA");
String s4 = new String("Gosto de JAVA");

System.out.println("Comparando s1 e s2: "+(s1==s2));


System.out.println("Comparando s3 e s4: "+(s3==s4));
System.out.println("Comparando s1 e s3: "+(s1==s3));

Quando nós utilizamos o == ele compara dois elementos e retorna true ou false. Por
exemplo, o código abaixo retorna true para a comparação.
System.out.println(“Comparando 1 com 1: ”+(1==1);

O mesmo acontece com as comparações apresentadas no Código 63. O lógico a esperar


daquele código é que ele retorne true para todas as comparações. Mas não é isso o que
acontece. Teste e veja o que acontece.
Se você testou notará o seguinte resultado no console do Ecplise:
Comparando s1 e s2: true
Comparando s3 e s4: false
Comparando s1 e s3: false

Por que isso acontece? Bem, nós sabemos que quando instanciamos uma classe ela vira
um objeto. Este objeto fica alocado na memória. E o endereço inicial deste bloco de
memória fica guardado na variável. Isso acontece sempre que instanciamos uma classe
utilizando o new. Isto vale para qualquer tipo de classe, incluindo a classe String.

168
Por causa disso, quando instanciamos s3 e s4 no Código 63, utilizando o new, este
processo foi realizado. Um bloco de memória foi reservado para cada um dos dois
objetos, o endereço inicial do primeiro bloco de memória foi armazenado em s3 e o
endereço inicial do segundo bloco de memória foi armazenado em s4. Quando a gente faz
a comparação com == ele olha para estes endereços armazenados em cada variável e
percebe que são diferentes. Por isso a comparação retorna false, pois não são o mesmo
endereço.

Gosto de JAVA
s3

s4
Gosto de JAVA

* 4832 e 3567 são os endereços hipotéticos do primeiro e


segundo bloco de memória, respectivamente.

Figura 91. Instâncias da classe String.

Este conceito pode ser melhor observado na Figura 91. Note que os endereços
(hipotéticos, pois vai depender de cada JVM e cada sistema operacional) armazenados em
s3 e em s4 são absolutamente diferentes. Então, se você pergunta ao JAVA com o == se
eles são iguais ele responde imediatamente que NÃO.
Mas espere aí um pouquinho. Então, por que a comparação de s1 e s2 retornou true? Isso
não faz o menor sentido, não acha? Faz todo o sentido do mundo se você entende como
s1 e s2 são armazenados. A primeira coisa que precisamos reparar é que s1 e s2 são
instanciados de maneira diferente de s3 e s4. Os dois primeiros são instanciados com o =
(operador sobrecarregado, mas não precisa se preocupar com isso) e os dois últimos com
o new.
O operador = foi preparado para instanciar a classe String. E é exatamente ao utilizar o =
ao invés do new que o JAVA viabiliza toda aquela otimização de que falamos. Neste caso,
o JAVA utiliza um mecanismo chamado de Pool de Strings, ou String pool. Mas, o que é
isso?
Observe a Figura 92, que representa o String pool montado pela JVM durante a execução
de uma aplicação. Neste caso, o Java, quando recebe uma string através do =, primeiro
procura dentro do String pool para ver se uma string exatamente igual já foi criada antes.
Se foi, ele pega o endereço desta string anterior e atribui à variável de referência ao
objeto. Se não, ele aloca o espaço de memória, pega o endereço inicial e atribui à variável
de referência.

169
String pool

Gosto de JAVA
s1 4832

s2
Outro texto
3567

Mais outro
5232

* 4832, 3567 e 5232 são os endereços hipotéticos dos blocos de


memória dentro do String pool.

Figura 92. Funcionamento do String pool.

Este mecanismo reduz o uso de memória em uma aplicação Java. O mesmo bloco de
memória com a mesma string pode ser referenciado por mais de uma variável. É
exatamente o que acontece em nosso exemplo no Código 63. Ao executar a atribuição
para s1 a JVM aloca espaço para a string “Gosto de JAVA”, pega o endereço de início
desse bloco e atribui à variável s1. Quando chega a hora de executar a atribuição da
mesma string para s2 a JVM percebe que já existe uma string igual armazenada no String
pool. Então ela só pega o mesmo endereço e atribui à s2. É por isso que se compararmos
s1 com s2 a resposta será true. Afinal, é o mesmo endereço que está armazenado nas duas
variáveis.
Mas lembre-se: toda esta otimização só acontece se você utilizar o operador = para
instanciar as classes String. Se preferir utilizar new então a JVM vai criar uma instância
em um novo bloco de memória para cada string, mesmo que sejam 1000 strings
exatamente iguais. Ou seja, nenhuma otimização será feita.

Métodos da classe String

Como qualquer classe a String também possui um conjunto de métodos. Esses métodos
nos serão muito úteis. No entanto, um ponto a destacar é que os métodos não alteram o
conteúdo da String, eles utilizam o conteúdo como entrada, processam esse conteúdo e
retornam um outro valor, deixando o conteúdo original intacto. Logo veremos o motivo
pelo qual o conteúdo da String nunca é alterado por seus métodos.

170
length
O primeiro método que veremos, amplamente utilizado, é aquele que retorna o número
de caracteres em uma String. Como sabemos que uma String é uma Array de char, outra
forma de dizer isso é que length() retorna o tamanho desta Array. Veja o código a seguir:
String s1 = " -Eu gosto muito de Java- ";
System.out.println("Tamanho da minha String.....: "+s1.length());

O resultado será:
Tamanho da minha String.....: 30

Note que a String possui um conjunto de espaços em branco no início, antes do traço e
no fim, depois do traço. O método length conta todos os caracteres, incluindo espaços
em branco.

indexOf
O método indexOf() recebe uma String e retorna a primeira posição desta String que
estiver contida na String original. Ele é utilizado para achar partes dentro do texto. Por
exemplo, se queremos saber se existe a palavra “muito” dentro de nossa String e em que
posição esta palavra começa, utilizamos o método indexOf. Caso a palavra não exista, ele
retorna -1. Veja o seguinte exemplo:
String s1 = " -Eu gosto muito de Java- ";
System.out.println("Posição inicial de 'muito'..: " +
s1.indexOf("muito"));

System.out.println("Posição inicial de 'demais'.: " +


s1.indexOf("demais"));

Este exemplo retorna 13 para a palavra muito, porque a letra m desta palavra está na
posição 13 da String, sempre começando a contar de zero.
Posição inicial de 'muito'..: 13
Posição inicial de 'demais'.: -1

charAt
Este método pega um caractere em uma posição específica da String e retorna como tipo
char. O tipo char é muito interessante. Ele representa um caractere, mas cada caractere
pode ser representado por um símbolo (A, B, C, D, etc.) ou por seu número
correspondente (65, 66, 67, 68, etc.).
Como o computador é um equipamento eletrônico baseado na representação numérica
ele não “sabe” o que é um A, mas sabe o que é um 65. Cada caractere é representado por
um número binário, que também pode ser visto como um número decimal. Por exemplo,
a letra A é entendido pelo computador como 65. A letra B como 66 e assim por diante.
Já um a (minúsculo) é representado por 97, um b por 98 e assim por diante. Estes

171
números foram padronizados em uma tabela internacionalmente aceita, chamada de
tabela ASCII. Que tal pesquisar um pouco sobre isso na Internet?
Voltando ao nosso método, charAt retorna um char. Isso significa que podemos utilizar
alguns operadores que foram sobrecarregados para trabalhar com este tipo de dado, como
o ++ (que incrementa de um), o -- (que decrementa de um) e o += e o -= (que somam e
subtraem valores específicos). Veja o código seguir.
String s1 = " -Eu gosto muito de Java- ";
System.out.println("13o. caracter...............: " +
s1.charAt(13));

char c=s1.charAt(13);
c++;
System.out.println("13o. somado de 1............: " + c);

Neste código nós pegamos o 13º caractere de nossa String, que já sabemos que será um
m. Depois, nós pegamos novamente este mesmo caractere e o colocamos em uma variável
do tipo char, viabilizando a realização de uma operação com ++ (que aliás acabou sendo o
nome de uma outra linguagem de que gosto muito). Veja o resultado:
A String original...........: -Eu gosto muito de
Java-
Tamanho da minha String.....: 30
Posição inicial de 'muito'..: 13
Posição inicial de 'demais'.: -1
13o. caracter...............: m
13o. somado de 1............: n

Note que ao somar o char c de 1 ele deixou de representar a letra m e passou a


representar a letra n.

substring
O método substring() retorna um pedaço da String original, considerando uma posição
de início e uma posição de fim. É interessante notar que a posição de início é inclusiva,
mas a posição de fim não é inclusiva, ou seja, a própria posição final não fará parte do
retorno. Outra possibilidade é informar apenas o início e receber como retorno um
pedaço a partir deste início até o fim da String. Veja este exemplo:
String s1 = " -Eu gosto muito de Java- ";

System.out.println("Pedaço da String............: " +


s1.substring(13,18));

System.out.println("Resto da String.............: " +


s1.substring(13));

No primeiro caso o método s1.substring(13,18) retorna um pedaço da String original que


começa na posição 13, inclusive, e vai até a posição 17, pois a posição 18 não é incluída,

172
como já mencionamos. No segundo caso, o método retorna a partir da posição 13,
inclusive, até o final da String, já que não foi informado o valor para posição final. Veja o
resultado:
Pedaço da String............: muito
Resto da String.............: muito de Java-

trim
O método trim() é bem simples. Ele retorna a String original mas sem os eventuais
espaços à esquerda e à direita. Veja este exemplo:
String s1 = " -Eu gosto muito de Java- ";
System.out.println("A String original...........: " + s1);
System.out.println("Sem os espaços..............: " +
s1.trim());

Como pode ver abaixo no resultado do console, o método trim() retira os espaços da
direita e esquerda, se houver. Espaços dentro da String não são retirados.
A String original...........: -Eu gosto muito de Java-
Sem os espaços..............: -Eu gosto muito de Java-

replace
O método replace() retorna a String original com partes substituídas por outras. Por
exemplo, no código a seguir a palavra muito será substituída pela palavra demais.
String s1 = " -Eu gosto muito de Java- ";
System.out.println("Trocando partes.............: " +
s1.replace("muito", "demais"));

Note que ao invés de ser apresentada a frase –Eu gosto muito de Java- foi
apresentada a frase –Eu gosto demais de Java-. Veja o resultado no console:

Trocando partes.............: -Eu gosto demais de Java-

Eu nasci assim, assim vou ficar

Pensando um pouco em como a JVM otimiza o uso de Strings, uma pergunta surge: se eu
posso ter várias referências (variáveis) apontando para uma mesma String em um mesmo
endereço de memória, quando eu altero uma dessas referências, o que acontece com as
demais? Por exemplo, se temos String s1 = “Gosto de Java” e String s2 =
“Gosto de Java”, sabemos que a JVM terá apenas uma String “Gosto de Java” em seu
String pool e atribuirá o mesmo endereço de memória para s1 e para s2. O que
aconteceria se mais tarde eu tivesse uma linha s2 = “Gosto muito de Java”?
Quando eu exibisse o conteúdo de s1 seria “Gosto de Java” ou “Gosto muito de Java”?
A resposta a esta pergunta depende de entendermos um conceito fundamental a respeito
das Strings: elas são imutáveis. Isso mesmo, uma vez criada, seja no String pool seja
diretamente na memória por um new, ela não pode mais ser mudada. As Strings são, na

173
verdade, Arrays de char. Você já viu que um Array, uma vez definido o seu número de
elementos, não pode mais ser alterado. Por isso, uma String não pode aumentar ou
diminuir de tamanho depois de criada. E quanto ao conteúdo? O conteúdo de um Array
pode ser alterado. Sim, mas a classe String não permite que o conteúdo seja alterado. É
uma regra de negócio implementada através de encapsulamento. Em suma, uma vez
criada a String não pode mais ser alterada.
Opa, mas que história é essa? Eu estou cansado de alterar Strings! É o que você deve estar
pensando neste momento. Sim, você já alterou o valor String para o qual uma variável
aponta, isso é verdade. Mas cada vez que você altera uma String a JVM cria uma nova
String, coloca em uma nova área de memória, pega este novo endereço e coloca na sua
variável como referência. Mas isso vai consumir muita memória, talvez pense. Isso é
verdade e a JVM possui alguns mecanismos para otimizar isso.

Criptógrafo

Agora que você conhece quase tudo de Strings chegou a hora de colocar a mão na massa
e construir um criptógrafo. Esta aplicação será capaz de criptografar uma mensagem e
também de descriptografá-la. Em outras palavras, você poderá escrever uma mensagem,
cifrá-la, enviar a um amigo e seu amigo poderá decifrar a mensagem com esta mesma
aplicação (envie a aplicação para ele).
Mas, qual seria a utilidade disso? Como deve saber as mensagens de email viajam abertas
pela Internet. Pessoas que operam determinados servidores de email, softwares maliciosos
e até alguém que abra o seu correio por acidente, poderão ler sua mensagem secreta. Só
que não, porque ela estará criptografada.
Vamos criar um projeto JavaFX chamada MsgCodificada. Lembra que uma aplicação
JavaFX é estruturada no modelo MVC? Será ótimo fazermos mais uma aplicação neste
modelo para consolidar os conhecimentos adquiridos.

application
Main.java

view controller model


Cripto.fxml CritpoController.java Criptografia.java

Figura 93. Estrutura da aplicação de criptografia.

174
Então, para ficar bem claro, vamos organizar nossa aplicação em 4 pacotes: 1) application,
2) View, 3) Controller e 4) model. Esta estrutura de pacotes pode ser vista na Figura 93.
O pacote application já é criado por padrão em projetos JavaFX. Sabe como criar os
outros pacotes? No Eclipse pressione CTRL+N e selecione package no filtro. Pressione
Next e dê o nome ao pacote. Por convenção os nomes de pacotes são sempre em
minúsculas.
E quanto às classes e demais recursos? A classe Main é aquela que vai montar nosso Stage,
carregando dentro de Scene o FXML que será nossa tela. Como pode ver em Código 64,
nada muito diferente do que já fizemos antes.
Código 64. Classe Main da aplicação MsgCodifica.

package application;

import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.stage.Stage;
import javafx.scene.Parent;
import javafx.scene.Scene;

public class Main extends Application {


@Override
public void start(Stage primaryStage) {

Parent root = null;

try {
root =
FXMLLoader.load(getClass().getResource("../view/Cripto.fxml"));
} catch(Exception e) {
System.out.println(e.getMessage());
e.printStackTrace();
}

Scene cena = new Scene(root);


primaryStage.setScene(cena);
primaryStage.show();
}

public static void main(String[] args) {


Application.launch(args);
}
}

Notou no Código 64 que informamos o nome de um arquivo FXML? Sim, é a nossa tela
e colocamos o caminho do pacote, ou seja, ../view. Mas, onde está este FXML? Vamos
criá-lo. Selecione o pacote view e pressione CTRL+N. No filtro digite FXML. Selecione a
opção New FXML Document e pressione Next. Informe o nome, que será Cripto e
pressione Finish.
Pronto, temos nosso Cripto.fxml dentro do pacote view. Agora é só clicar com o botão
direito sobre este arquivo Cripto.fxml e escolher a opção Open with SceneBuilder. O

175
Scene Builder será aberto e você poderá criar a tela para nossa aplicação. Pode deixar o
pane padrão, o AnchorPane.
Precisaremos de 3 Button, 2 TextArea e 4 Label para esta aplicação. Veja a Figura 94
para começar a entender o objetivo destes controles.

Figura 94. Janela Documento do Scene Builer para a tela Cripto.

Agora vamos entender como estes controles são utilizados e que nomes foram atribuídos
a eles. Precisaremos destes nomes para acessar os controles em nossos métodos de
handling. Veja a Figura 95 ver a disposição de todos os controles e seus nomes. Os nomes
dos controles foram adicionados à tela para facilitar a compreensão. Estes nomes estão
definidos nas propriedades fx:id no Cripto.fxml.

176
Figura 95. Organização da tela do criptógrafo.

O código xml desta tela pode ser visto em Código 65. Vale a pena lembrar que se trata de
um código de simples leitura, onde os controles e seus atributos podem ser facilmente
encontrados e inclusive alterados diretamente. O que o Scene Builder faz é nos ajudar a
construir este código visualmente, o que, como já vimos, é uma mão na roda.
Código 65. Código do Cripto.fxml.

<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.TextArea?>
<?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.text.Font?>

<AnchorPane prefHeight="395.0" prefWidth="700.0"


xmlns="http://javafx.com/javafx/9.0.1" xmlns:fx="http://javafx.com/fxml/1"
fx:controller="controller.CriptoController">
<children>
<Button fx:id="btnCripto" layoutX="312.0" layoutY="159.0"
mnemonicParsing="false" onAction="#handleCripto" text="CRIPTO &gt;&gt;" />
<Button fx:id="btnDecripto" layoutX="302.0" layoutY="265.0"
mnemonicParsing="false" onAction="#handleDecripto" text="&lt;&lt; DESCRITPO"
/>
<TextArea fx:id="txtaOriginal" layoutX="25.0" layoutY="129.0"
prefHeight="200.0" prefWidth="250.0" scrollLeft="1.0" wrapText="true" />
<TextArea fx:id="txtaFinal" layoutX="425.0" layoutY="129.0"
prefHeight="200.0" prefWidth="250.0" wrapText="true" />
<Label layoutX="239.0" layoutY="38.0" text="CRIPTÓGRAFO 1.0"
textFill="#437cd2">

177
<font>
<Font name="Arial Rounded MT Bold" size="24.0" />
</font>
</Label>
<Label layoutX="25.0" layoutY="105.0" text="Mensagem aberta" />
<Label layoutX="425.0" layoutY="105.0" text="Mensagem criptografada" />
<Button fx:id="btnFechar" layoutX="634.0" layoutY="355.0"
mnemonicParsing="false" onAction="#handleClose" text="Fechar" />
<Label fx:id="lblMensagem" layoutX="32.0" layoutY="355.0"
text="Mensagem:" textFill="#fc5208" />
</children>
</AnchorPane>

Como você já sabe a camada de view, parte visual da aplicação, fala com a camada
controller. É nesta camada que fazemos o handling dos nossos controles visuais, ou seja,
é onde colocamos os códigos que serão chamados quando os nossos controles forem
acionados. Então vamos dar uma boa olhada no Código 66.
Já vimos em nosso código anterior essas tags @FXML. Mas, o que é isso mesmo? Essas
tags são chamadas de annotations e são informações que permitem ao compilador
entender determinadas coisas. O próprio compilador entende essas annotations porque o
arquivo javafx.fxml.FXML foi importado logo no começo de nosso código.
Código 66. Camada controller do Criptógrafo.

package controller;

import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.TextArea;
import model.Criptografia;

public class CriptoController {

@FXML private TextArea txtaOriginal;


@FXML private TextArea txtaFinal;
@FXML private Button btnCripto;
@FXML private Button btnFechar;
@FXML private Button btnDecripto;
@FXML private Label lblMensagem;

@FXML private void handleCripto(ActionEvent event) throws Exception {

try {
if (txtaOriginal.getLength()>0) {
lblMensagem.setText("Mensagem:");
Criptografia cripto=new Criptografia();
String sOriginal=txtaOriginal.getText();
sOriginal=cripto.criptografa(sOriginal);
txtaFinal.setText(sOriginal);
txtaOriginal.setText("");
}
else

178
{
lblMensagem.setText("Mensagem: Nenhuma mensagem aberta foi
informada.");
}
}
catch (Exception e) {
System.out.println(e.getMessage());
e.printStackTrace();
}
}

@FXML private void handleDecripto(ActionEvent event) throws Exception {

try {
if (txtaFinal.getLength()>0) {
lblMensagem.setText("Mensagem:");
Criptografia cripto=new Criptografia();
String sFinal=txtaFinal.getText();
sFinal=cripto.decriptografa(sFinal);
txtaOriginal.setText(sFinal);
txtaFinal.setText("");
}
else
{
lblMensagem.setText("Mensagem: Nenhuma mensagem criptografada foi
informada");
}
}
catch (Exception e) {
System.out.println(e.getMessage());
e.printStackTrace();
}
}

@FXML private void handleClose(ActionEvent event) throws Exception {


System.exit(0);
}

As annotations servem para ligar o que está em nosso arquivo de código


CriptoController.java com o que está em nosso arquivo Cripto.fxml. Por exemplo, ele
consegue saber que este trecho de código em CriptoController.java:
@FXML private TextArea txtaOriginal;

Está ligado com o trecho abaixo, que está em Cripto.fxml:


<TextArea fx:id="txtaOriginal" layoutX="25.0"
layoutY="129.0" prefHeight="200.0" prefWidth="250.0"
scrollLeft="1.0" wrapText="true" />

179
E como ele consegue juntar os dois elementos? Além do @FXML é necessário usar
também o mesmo nome, ou seja, txtaOriginal. Assim, na camada controller podemos
usar txtaOriginal para acessar o controle txtaOriginal que está na tela que criamos na
camada view, tanto para ler quanto para alterar o conteúdo de seus atributos. O mesmo
vale para os demais controles.
Achou um pouquinho complicado? Não é complicado. As vezes algo novo pode parecer
complicado. Se for o caso, faça uma nova leitura desse trecho e compare os arquivos do
projeto MsgCodificada.
Você notou que os métodos handleCripto e handleDecripto utilizam uma classe chamada
Criptografia? É a classe que, de fato, é o coração da nossa aplicação, que trata do objetivo
real da aplicação. Esta classe está em nossa camada model. A camada model cuida das
informações ou dados importantes de nossa aplicação e também das regras de negócio
(regras que devem ser obedecidas por nossa aplicação). Então, vamos dar uma olhada em
nossa camada model no Código 67.
Código 67. Camada de model da aplicação.

package model;

public class Criptografia {

public String criptografa(String aberta) {

String sRet="";

for (int n=aberta.length()-1; n>=0; n--) {


char c=aberta.charAt(n);
c+=5;
sRet+=c;
}

return sRet;
}

public String decriptografa(String criptografada) {

String sRet="";

for (int n=criptografada.length()-1; n>=0; n--) {


char c=criptografada.charAt(n);
c-=5;
sRet+=c;
}

return sRet;
}

Temos a classe Criptografia que possui dois métodos, um para criptografar e um para
descriptografar. Considerando o que vimos neste capítulo sobre Strings, o que estes

180
métodos fazem é percorrer a String com um for, colocar cada caractere da string em uma
variável do tipo char, adicionar este caractere de 5 para criptografar ou subtrair de 5 para
descriptografar, ou seja, retornar ao estado original. Bastante simples, porém eficiente.

Conclusão

Vimos neste capítulo a classe String em mais detalhes. Aliás, você sabia que String era
uma classe? Se não, descobriu neste capítulo. Descobriu também que o Java otimiza a
utilização de Strings através do String pool e que as Strings são imutáveis. Aprendemos
ainda alguns dos mais utilizados métodos da classe String, tais como length, charAt,
substring, indexOf, trim e replace. Por fim, colocamos boa parte deste conhecimento em
prática ao construirmos nosso Criptógrafo, o que ajudou a fazer uma boa revisão do
modelo MVC.
Estude cuidadosamente os códigos deste capítulo para ter certeza de que entendeu tudo.
Não há nada complicado, mas há muita informação. Vale a pena uma revisão.

Desafio

Agora que você domina as Strings vai um desafio. Nosso Criptógrafo ficou legal, mas ele
trabalha com criptografia fixa. Isso quer dizer que se alguém tiver a mensagem original e a
mensagem criptografada ele poderá, por tentativa e erro, acabar descobrindo que a gente
soma ou subtrai 5 para cifrar a mensagem. Isso não aconteceria se a mensagem fosse
cifrada a partir de uma palavra-chave.
Por exemplo, se eu utilizasse os caracteres da palavra SEGREDO para cifrar uma
mensagem, usando o S para cifrar a primeira letra da mensagem, o E para a segunda, o G
para a terceira e assim por diante até retornar ao S e continuar seguindo esta ordem,
ficaria quase impossível descobrir a criptografia.
Imagine então se cada mensagem fosse cifrada com uma palavra-chave diferente! Então o
desafio é: adicione um TextField para pedir a palavra-chave e use esta palavra para
criptografar a mensagem. Assim, você poderá enviar a mensagem para um amigo e passar
a palavra-chave para ele por telefone ou mensagem de texto. Ou poderá deixar combinada
uma palavra-chave. Assim, ninguém vai descobrir o conteúdo de suas mensagens.

181
18. Classes – Visita final

Como deve ter notado ao longo deste livro, compreender o conceito de orientação a
objetos e saber aplicá-lo em seu código é fundamental para o bom programador Java. Por
isso, neste capítulo voltaremos mais uma vez ao tema para finalizar alguns pontos que
podem ter ficado em aberto.

Este capítulo é muito importante para consolidar os


conceitos já adquiridos e para aprender alguns novos.

Pacotes

Em capítulos anteriores nós trabalhamos com o conceito de pacotes para organizar


melhor o nosso código. Mas, o que são pacotes? Como eles podem ser úteis? E como
devemos trabalhar com eles?
Além de nos permitir organizar nosso código os pacotes servem para criar ambientes de
privacidade e controlar os acessos às classes que estão dentro de cada pacote. Podemos
imaginar os pacotes como se fossem a nossa casa e as classes como se fossem os cômodos
da casa. Nossa casa nos proporciona determinado grau de privacidade, de controle sobre
quem pode acessar os cômodos da casa. Isso quer dizer que as classes dentro de um
mesmo pacote podem acessar umas as outras livremente.

É importante lembrar que todas as classes de uma aplicação


estão sempre dentro de um pacote.

Mas e quando a gente cria uma aplicação e não coloca dentro de nenhum pacote?
Quando a gente não cria, a JVM cria um pacote chamado default package e todas as
classes da aplicação ficam dentro deste pacote.
Para compreendermos bem o assunto, vamos imaginar uma aplicação com a seguinte
estrutura de pacotes:
• application
• equipe
• setores
Dentro do pacote equipe nós temos duas classes:
• Pessoa
• Funcionario
A classe Funcionário estende a classe Pessoa. Veja o
Código 68 e o Código 69.

182
Código 68. Classe Pessoa.

package equipe;

class Pessoa {

// Atributos
private String nome;
private int idade;

// Getters e Setters
public String getNome() {
return nome;
}
public void setNome(String nome) {
this.nome = nome;
}
public int getIdade() {
return idade;
}
public void setIdade(int idade) {
this.idade = idade;
}

Código 69. Classe Funcionario.

package equipe;

public class Funcionario extends Pessoa {

Até agora, nada de muito notável. A classe Pessoa tem dois atributos e os devidos getters
e setters para estes atributos. E a classe Funcionario estende a classe Pessoa sem adicionar
nenhum outro método ou sobrescrever os existentes.
Foi fácil estender a classe Pessoa para Funcionario. Então vamos fazer o seguinte: criar
uma classe Gerente dentro do pacote Setores e estender a classe Pessoa para Gerente.
Deve ser fácil. O código seria o seguinte:
package setores;

import equipe.Pessoa;

public class Gerente extends Pessoa {

183
Tentou? Viu que deu errado? Por que deu errado? O problema já começa no import. Ele
não consegue enxergar a classe Pessoa porque ela está em outro pacote. É como se fosse o
cômodo de outra casa. Não é possível acessar a classe Pessoa do pacote equipe a partir do
pacote setores. O pacote controla o acesso às suas classes, não permitindo que outros
pacotes as acessem.
Entendeu o motivo pelo qual a classe Funcionario conseguiu entender a classe Pessoa
sem problemas? Porque ambas estão no mesmo pacote. É como se fossem cômodos
diferentes de uma mesma casa. Passe livre.
Agora vamos tentar algo diferente: ao invés de estender a classe Pessoa vamos estender a
classe Funcionario. O código ficaria assim:
package setores;

import equipe.Funcionario;

public class Gerente extends Funcionario {

Se você testou já viu que deu certo. Mas, espere um momento. A classe Funcionario está
em um pacote e a classe Gerente que estende Funcionario está em outro pacote. Como
pode ter funcionado? É simples. Note que no Código 69, antes do nome da classe há o
modificador de acesso public. Por isso esta classe do pacote equipe está acessível a todos
os demais pacotes da aplicação. Em outras palavras, o public é um modificador de acesso
que permite o acesso à classe mesmo que ela esteja em outro pacote. Bem, agora pode
apagar a classe Gerente. Nós a criamos apenas para apresentar o conceito do modificador
de acesso public.
Em geral, para classes e interfaces utilizamos apenas o modificador public, que foi
utilizado na classe Funcionario e o modificador default, que foi utilizado na classe Pessoa
(o default é atribuído quando nenhum modificador é explicitamente apresentado). Mas
há outros dois modificadores de acesso que são úteis para aplicação em atributos e
métodos, como veremos a seguir.

Modificadores de acesso

Para falar de modificadores de acesso, vamos lembrar da estrutura de uma classe com
praticamente tudo que ela pode ter:
package <nome do pacote>;

import <nome da classe>;

/* Comentários de
múltiplas linhas
*/

// Comentários de uma linha (ou de fim de linha)

<modificador de acesso> <modificador de comportamento> Classe <nome da classe> {

Atributos;

184
Comentários;

Construtores;

Métodos de getter e setter;

Demais métodos;

Como já vimos antes, além destes itens principais a classe pode ter ainda um extends para
herança e/ou um implements para implementação de uma interface. E acabamos de
considerar na declaração de duas classes dois modificadores de acesso public e o default.
Mas o que são mesmo esses modificadores de acesso?
Como o próprio nome indica os modificadores de acesso aumentam ou diminuem o
acesso a interfaces, classes, atributos e métodos. Existem quatro modificadores de acesso,
que podem ser vistos na Figura 96.

Figura 96. Modificadores de acesso em Java.

Quando nenhum modificador de acesso é atribuído a uma interface, classe, atributo ou


método, o modificador default é atribuído automaticamente, que é menos restritivo do
que private e mais restritivo do que protected. Para compreender melhor a utilização dos
modificadores em atributos e métodos ampliaremos a estrutura do nosso projeto
ExplicaPacotes, conforme Figura 97.
Considerando que a visibilidade dos atributos e dos métodos são idênticas não nos
preocuparemos de colocar um exemplo de cada modificador para método e depois de
cada modificador para atributo.

185
Figura 97. Estrutura do projeto ExplicaPacotes.

A primeira modificação que faremos será na classe Funcionario que está dentro do
pacote equipe. Vamos adicionar alguns atributos e métodos getter e setter, como pode ser
visto no Código 70.
Código 70. Classe Funcionario com atributos e métodos.

package equipe;

public class Funcionario extends Pessoa {

public String setor; // Visível em todos os pacotes e classes

protected String matricula; // Visível no pacote e em subclasses

int anoInicio; // Visível apenas no pacote

private String senha; // Visível apenas nesta classe

public String getSenha() {


return senha;
}

public void setSenha(String senha) {


this.senha = senha;
}

Note que criamos novos atributos e métodos para a classe Funcionario, além dos já
herdados de Pessoa. Tomamos o cuidado de definir cada atributo com um modificador
diferente, mas deixamos os métodos como public. O objetivo é podermos testar essas
visibilidades.

186
Existem duas formas fundamentais de relação entre duas classes: herança ou uso (falando
de maneira bem simples e sem entrar no mérito dos tipos de associação). Então
testaremos as duas. O primeiro teste que faremos será herdar a classe funcionário dentro
do próprio pacote e ver que atributos da superclasse são visíveis pela subclasse. Dentro do
pacote equipe vamos criar a classe HerdaFuncionarioDentroPacote. Veja o Código 71.
Código 71. Testando visibilidade por herança dentro do pacote.

package equipe;

public class HerdaFuncionarioDentroPacote extends Funcionario {

public void setValores() {

super.setor="Tecnologia"; // Definida como public

super.matricula="0455675"; // Definida como protected

super.anoInicio=2018; // Definida como default

super.senha="xyz"; // Definida como private

super.setSenha("xyz"); // Método definido como public

String senha=super.getSenha(); // Método definido como public

Esta classe estende Funcionario e ela está dentro do mesmo pacote, ou seja, equipe. Ao
tentar compilar este código teremos um erro apenas na linha super.senha = “xyz”.
Por que isso acontece? Bem, o atributo setor é public e, portanto, visível para toda e
qualquer classe. O atributo matrícula é protected, quer dizer, pode ser visto porque está
dentro do mesmo pacote. O mesmo acontece com anoInicio que é default e pode ser
visto porque está dentro do mesmo pacote. Mas o atributo senha foi definido como
private e como vimos o private só pode ser visto dentro da mesma classe, não importa em
que pacote esteja. Por isso, não compila.
Agora vamos herdar a classe funcionário de fora do pacote e ver o que acontece. Vamos
criar a classe HerdaFuncionarioForaPacote dentro do pacote application, ou seja, fora
do pacote de origem da classe Funcionario. Veja o Código 72.

Código 72. Testando visibilidade por herança fora do pacote.

package application;

import equipe.Funcionario;

public class HerdaFuncionarioForaPacote extends Funcionario {

public void setValores() {

187
super.setor="Tecnologia"; // Definida como public

super.matricula="0455675"; // Definida como protected

super.anoInicio=2018; // Definida como default

super.senha="xyz"; // Definida como private

super.setSenha("xyz"); // Método definido como public

String senha=super.getSenha(); // Método definido como public

Se você tentou compilar este código já percebeu que ele também não funciona. Mas, o
motivo muda. Além da senha, que já sabíamos que é visível apenas dentro da própria
classe, também anoInicio não é visível aqui, fora de seu pacote de origem. Isso nos mostra
que default é mais restritivo do que protected, porque default só é visível por herança
dentro do próprio pacote, ao passo que protected é visível por herança inclusive fora de
seu pacote de origem.
Mas e se for por uso, ou seja, pela instanciação da classe? Será que o comportamento de
visibilidade se mantém? Vamos ao nosso experimento. Primeiro vamos criar uma
instância de Funcionario em uma classe dentro do próprio pacote onde está esta classe.
Para isso, criaremos a classe UsaFuncionarioDentroPacote dentro do pacote equipe,
como definido no Código 73.
Código 73. Testando visibilidade por uso de dentro do pacote.

package equipe;

public class UsaFuncionarioDentroPacote {

public static void main (String args[]) {

Funcionario fnc = new Funcionario();

fnc.setor="Tecnologia"; // Definida como public

fnc.matricula="0455675"; // Definida como protected

fnc.anoInicio=2018; // Definida como default

fnc.senha="xyz"; // Definida como private

fnc.setSenha("xyz"); // Método definido como public

String senha=fnc.getSenha(); // Método definido como public

188
Tente compilar e verá que não funciona pelo mesmo motivo que não funcionou por
herança. Neste caso estamos instanciando a classe Funcionario na variável de referência
fnc. A partir desta variável tentamos acessar os atributos e não conseguimos acessar
apenas o senha, que é private e só pode ser acessado de dentro da própria classe.
Agora vamos fazer o último teste de nosso experimento: instanciar a classe fora de seu
pacote de origem. Veja o Código 74.

Código 74. Testando visibilidade por uso de fora do pacote.

package application;

import equipe.Funcionario;

public class UsaFuncionarioForaPacote {

public static void main (String args[]) {

Funcionario fnc = new Funcionario();

fnc.setor="Tecnologia"; // Definida comp public

fnc.matricula="0455675"; // Definida como protected

fnc.anoInicio=2018; // Definida como default

fnc.senha="xyz"; // Definida como private

fnc.setSenha("xyz"); // Método definido como public

String senha=fnc.getSenha(); // Método definido como public

Bem, aqui as coisas mudaram significativamente. Os atributos matricula, anoInicio e


senha não são visíveis pela classe UsaFuncionarioForaPacote. O atributo senha a gente já
sabia que não ia ser visível mesmo, mas agora o atributo protected e o atributo default
também não são vistos. Ou seja, métodos e atributos definidos como protected ou
default são vistos apenas dentro de seus pacotes. Para entender melhor a questão observe
a Tabela 7.

189
Tabela 7. Visibilidade dos modificadores de acesso.

Herança dentro do Herança fora do Instância dentro Instância fora do


pacote pacote do pacote pacote

public Visível Visível Visível Visível

protected Visível Visível Visível Não visível

default Visível Não visível Visível Não visível

private Não visível Não visível Não visível Não visível

Modificadores de comportamento

Os modificadores de comportamento são chamados também de non-acces modifiers, ou


seja, modificadores que não são de acesso.

Esses modificadores, como fica claro pelo nome em inglês,


não alteram a acessibilidade, mas sim a forma como uma
classe, interface, método ou até atributo funcionam em
nosso código.

Para entendermos bem os conceitos envolvidos na utilização deste tipo de modificador,


como sempre, faremos uma pequena aplicação para experimentar estes conceitos. A
Figura 98 já apresenta um panorama geral desta aplicação através de um diagrama de
classes. Não se assuste, é muito mais simples do que parece.
Outra coisa: se você não conhece este tipo de diagrama isto não será nenhum problema
para entender bem a aplicação. Preferimos colocar o diagrama aqui apenas para dar uma
ideia geral do que faremos, o que facilita o aprendizado.

190
Figura 98. Diagrama de classes do projeto Comportamento.

Já temos trabalhado com pacotes (package) já por vários capítulos. Este nosso projeto se
chamará Comportamento (por favor, crie no Eclipse) e terá três pacotes: application,
model e biblioteca. Então, todos os métodos e atributos que aparecem sublinhados no
diagrama são estáticos.
O pacote application conterá apenas o nosso já conhecido método main(). Note que no
diagrama ele está sublinhado. Isto acontece porque este é um método estático e falaremos
exatamente sobre o modificador static nesta seção.
O pacote biblioteca é onde colocaremos nossas classes de uso geral, aquelas que possuem
métodos genéricos que podem ser utilizados a qualquer momento por outras classes.
Lembra que o Java nos oferece uma biblioteca de classes com métodos genéricos? Então,
estamos criando a nossa biblioteca própria, em adição àquela oferecida pelo Java.
Por fim, temos o nosso pacote model, que é onde estão os nossos “dados” e as nossas
regras de negócio. É o coração da nossa aplicação. Dentro deste pacote temos a classe
Pessoa, que é do tipo abstrato. Isso é indicado pelo nome da classe que aparece em itálico.
Esta classe possui um método que também está em itálico, o getUltimoNome(),
indicando que ele é um método abstrato. Ainda nesta classe há atributos do tipo final,
que estão em maiúsculas e já com valores definidos. Temos ainda no diagrama a classe
Usuario e a interface TestaCPF.

191
Estamos falando de coisas tais como estático, abstrato e final. Mas o que é tudo isso? Esses
são alguns dos modificadores de comportamento que veremos em mais detalhes a partir
de agora.

Abstract
Vamos começar falando em abstract. Esse modificador pode ser aplicado a classes e a
métodos, mas não a atributos.

Não existem atributos abstratos.

Também pode ser aplicado a interfaces, o que seria um desperdício porque as interfaces já
são abstratas por padrão. Mas o que é uma classe ou um método abstrato?
Podemos dizer que algo abstrato é o oposto de algo concreto. Classes abstratas, por
exemplo, não podem ser instanciadas. Falando de outra forma: classes abstratas não
podem virar algo de verdade, implementado, concreto. A mesma coisa acontece com os
métodos abstratos. Eles não podem ter corpo, apenas a assinatura. Exatamente como
acontece em métodos em uma interface. Mas, por que alguém construiria classes e
métodos abstratos se a classe não pode ser instanciada e os métodos não podem ter
corpo? Vamos entender a partir da classe abstrata Pessoa, apresentada no Código 75.

Código 75. Classe abstrata Pessoa.

package model;

public abstract class Pessoa {

// Atributos
protected String CPF;
private String nome;
private int idade;
private final int MENOR_IDADE=14;
private final int MAIOR_IDADE=110;

// Getters e Setters
public String getCPF() {
return CPF;
}

public void setCPF(String cPF) {


CPF = cPF;
}

public String getNome() {


return nome;
}

public void setNome(String nome) {


this.nome = nome;
}

192
public int getIdade() {
return idade;
}

public boolean setIdade(int idade) {


if (idade <MENOR_IDADE || idade > MAIOR_IDADE)
return false;
this.idade = idade;
return true;
}

// Métodos abstratos (Impõe a implementação pela subclasse)


public abstract String getUltimoNome();

Apenas para nos situarmos é importante lembrar que a classe Pessoa está dentro do
pacote model. Agora dê uma boa olhada nesta classe. Ela começa com um public
abstract, indicando que ela pode ser vista em qualquer lugar da aplicação e que ela é do
tipo abstract, ou seja, não pode ser diretamente instanciada. Pode tentar e vai ver que
nem compila.
As classes abstratas podem ser herdadas por outras classes, mas não instanciadas
diretamente. Este é o objetivo de uma classe abstrata: ser uma classe padrão para ser
herdada por outras classes. Parece um pouco com o conceito de interface. No entanto, a
interface não contém métodos concretos, ao passo que a classe abstrata pode contê-los. É
mais ou menos assim: na classe abstrata você determina métodos que quer que as
subclasses implementem elas mesmas e também pode criar alguns métodos concretos que
essas mesmas classes poderão simplesmente usar, sem ter que implementar.
Os atributos desta classe merecem destaque. Há private e protected, além do modificador
final, que explicaremos daqui a pouco. Mas fique atento a esses modificadores de acesso e
de comportamento.
Em seguida temos alguns métodos concretos que poderão ser utilizados pela subclasse
(classe que herdar Pessoa), basicamente um conjunto de getters e setters. Isso ajuda muito
porque significa que cada subclasse que herdar pessoa não precisará repetir todo esse
código de getter e setter. Já está pronto.

Por fim temos o seguinte trecho de código:


public abstract String getUltimoNome();

Esta é uma definição de método abstrato. Lembre do que dissemos: não pode conter
corpo. Ou seja, não pode ter a implementação. Então, onde estará a implementação? Na
subclasse. Isto significa que cada subclasse poderá escolher como implementar este
método. Este tipo de construção é útil quando temos um método que sabemos ser
importante e queremos exigir que todas as subclasses o tenham, mas a implementação

193
para cada subclasse é particular, individual, específica. Claro que este método apresentado
aqui é apenas um exemplo do conceito.

final
O modificador final pode ser utilizado com uma classe, atributo (também variável local)
ou método, mas não com uma interface.

Este modificador coloca um ponto final nas coisas.

Por exemplo, se for aplicado a uma classe então ela não poderá ser mais estendida.
Quando isso é útil? Quando você quiser criar uma classe concreta que não deve ser
estendida e modificada ou ampliada. Naturalmente, não é possível criar uma classe
abstrata e final ao mesmo tempo, pois não haveria nenhum sentido, já que classes
abstratas são criadas para serem estendidas.
De maneira similar, um método quando modificado para final não pode ser sobrescrito
na subclasse. Ele deve continuar do jeito que foi originalmente criado.
E quanto a atributos e variáveis? Esses também podem ser modificados para final.
Quando isso acontece a variável só pode receber uma atribuição de valor. Uma vez
atribuído o valor ele não poderá mais ser alterado. Mas, será que isso tem sentido? Sim,
tem muito sentido. Usamos atributos final para constantes, valores de referência que não
queremos que mudem ao longo da aplicação. E se algum dia precisar mudar esses valores
é possível mudar apenas na atribuição original de valor e isto se refletirá por todo o
código. No Código 75 temos o seguinte trecho:
private final int MENOR_IDADE=14;
private final int MAIOR_IDADE=110;

A partir desta atribuição de valor eles não podem mais ser usados. Todo mundo que
estender a classe Pessoa poderá contar com esses parâmetros de menor e maior idade
aceitáveis.

static
Agora vamos falar do modificador static. Este modificador pode ser aplicado a interfaces,
classes, atributos e métodos. Não faremos todos os usos possíveis de static, mas o
utilizaremos em um atributo e em um método para ilustrar seu objetivo e
comportamento. Para nos ajudar com isso utilizaremos como referência a classe Usuário,
apresentada no Código 76.
Código 76. Classe Usuario.

package model;

import biblioteca.Alfanum;
import biblioteca.Criptografia;

public class Usuario extends Pessoa implements TestaCPF {

194
// Construtores
public Usuario() {
usuariosLogados++;
}

// Atributos
private String senha;
private static int usuariosLogados=0;

// Getters e Setters
public void setSenha(String senha) {
this.senha = Criptografia.criptografa(senha);
}

// Demais métodos da classe


public boolean validaSenha(String senha) {

boolean bRet=false;
if(this.senha.equals(Criptografia.criptografa(senha)))
bRet=true;
return bRet;
}

public static int getUsuariosLogados() {


return usuariosLogados;
}

// Imposição feita pela classe Pessoa


@Override
public String getUltimoNome() {
return Alfanum.ultimaParte(this.getNome());
}

// Imposição da interface TestaCPF


@Override
public boolean testaCPF() {
if (this.CPF.length() != 11)
return false;
else
return true;
}

Vamos começar conhecendo nossa classe. Ela está no mesmo pacote de Pessoa, ou seja, o
pacote model. Como Pessoa é abstrato e não pode ser diretamente instanciada fizemos
uma herança de Pessoa em Usuario, o que pode ser visto logo na assinatura da classe:
public class Usuario extends Pessoa implements TestaCPF

A classe Usuario estende a classe Pessoa e implementa a interface TestaCPF. Mas, como
é esta interface? Ela está no mesmo pacote model e tem o seguinte código:

195
Código 77. Interface TestaCPF.

package model;

public interface TestaCPF {

// Impõe a impementação do método


public boolean testaCPF();

A única exigência contratual desta interface é que exista um método chamado testaCPF()
e que este método retorne true ou false. Naturalmente, isso terá que ser implementado
em Usuario.
Se você voltar ao código da classe abstrata Pessoa, lembrará que lá existe um método
abstrato chamado getUltimoNome(). Este método também deverá ser implementado na
classe Usuário, pois métodos abstratos funcionam de forma similar aos métodos
definidos em uma interface; eles obrigam a implementação do método.
public abstract String getUltimoNome();

Mas alguns trechos da nossa classe Usuario nos chama à atenção. Observe os trechos de
código abaixo e repare como eles estão todos relacionados entre si. Vamos entender
direitinho o que acontece aqui.
public Usuario() {
usuariosLogados++;
}
.
.
.
private static int usuariosLogados=0;
.
.
.
public static int getUsuariosLogados() {
return usuariosLogados;
}

Primeiro é importante atentar ao fato de que o atributo privado usuariosLogados foi


definido como inteiro e modificado para static. O que isso quer dizer? Quer dizer que
esse atributo é da classe e não do objeto. Opa, que história é essa? É simples e vamos
entender com a ajuda da Figura 99.

196
Figura 99. Atributos estáticos.

Temos representados nesta figura a classe Usuario e duas instâncias dessa classe, a
instância u1 e a instância u2. O atributo usuariosLogados é estático e por isso ele
pertence à classe e não à instância. Isto quer dizer que as instâncias da classe apenas fazem
referência para onde a variável está mesmo, que é na classe (por isso estão tracejadas).
Traduzindo: quando as instâncias alteram o valor de usuariosLogados a alteração é feita
em um ponto único, na classe e não na própria instância.
E a cada vez que criamos uma nova instância da classe Usuario o seu construtor é
chamado. Ele incrementa de 1 o valor de usuariosLogados, de modo que para a primeira
instância o valor será 1 (já que foi inicializado com zero), para a segunda instância será 2,
para a terceira será 3 e assim por diante. É como se o atributo fosse compartilhado por
todas as instâncias. Como se fosse não, é isso mesmo o que acontece.
No entanto, para acessar atributos estáticos é preciso ser ou o construtor ou um método
também definido como estático. Métodos concretos não acessam atributos estáticos e
métodos estáticos não acessam atributos de instância. Cada um no seu quadrado. Por isso
é que criamos o método estático getUsuariosLogados(), que retorna o número
armazenado em usuariosLogados.
Vamos colocar tudo isso em prática e ver o que acontece? Faremos isso instanciando e
utilizando a classe Uusario. Veja o Código 78.

197
Código 78. Testando a classe Usuario.

package application;

import model.Usuario;

public class TestaComportamento {

public static void main(String[] args) {

// Usuario 1
Usuario u1 = new Usuario();
u1.setNome("Artur Campos");
u1.setCPF("12345678912");
u1.setSenha("senha");

System.out.println("Usuario: " + u1.getNome());


System.out.println("Usuario: " + u1.getUltimoNome());
System.out.println("A senha confere? " + u1.validaSenha("senha"));
System.out.println("O CPF é válido? " + u1.testaCPF());
System.out.println("Instâcias: " + Usuario.getUsuariosLogados());

System.out.println("-");

// Usuario 2
Usuario u2 = new Usuario();
u2.setNome("Andre Campos");
u2.setCPF("2345678912");
u2.setSenha("senha2");

System.out.println("Usuario: " + u2.getNome());


System.out.println("Usuario: " + u1.getUltimoNome());
System.out.println("A senha confere? " + u2.validaSenha("senha"));
System.out.println("O CPF é válido? " + u2.testaCPF());
System.out.println("Instâncias: " + Usuario.getUsuariosLogados());

A classe TestaComportamento que está no pacote application importa a classe Usuario


do pacote model. Note que fazemos duas instâncias da classe para u1 e u2 e utilizamos
seus métodos. Os métodos testaCPF() (exigido pela interface TestaCPF) e
getUltimoNome() (exigido pela classe Pessoa como método abstrato) foram
implementados de maneira muito simples, apenas para completar o exemplo.
Usamos também o método estático getUsuariosLogados(), que retorna o valor do
atributo estático usuariosLogados da classe e é compartilhado por todas as instâncias.
Veja o resultado deste código:

198
Usuario: Artur Campos
Usuario: Campos
A senha confere? true
O CPF é válido? true
Instâcias: 1
-
Usuario: Andre Campos
Usuario: Campos
A senha confere? false
O CPF é válido? false
Instâncias: 2

Embora não sejam o objeto do nosso estudo no momento, é importante que conheça as
classes de apoio que deixamos no pacote biblioteca, no Código 79 e no Código 80.
Código 79. Classe Alfanum.

package biblioteca;

public class Alfanum {

// Métodos estáticos

public static String ultimaParte(String original) {


String sRet="";

int o;
while ((o=original.indexOf(" ")) != -1) {

original=original.substring(o+1);
sRet=original;
}

return sRet;
}

Código 80. Classe Criptografia.

package biblioteca;

public class Criptografia {

// Métodos estáticos

public static String criptografa(String aberta) {

String sRet="";

for (int n=aberta.length()-1; n>=0; n--) {


char c=aberta.charAt(n);
c+=5;
sRet+=c;

199
}

return sRet;
}

public static String decriptografa(String criptografada) {

String sRet="";

for (int n=criptografada.length()-1; n>=0; n--) {


char c=criptografada.charAt(n);
c-=5;
sRet+=c;
}

return sRet;
}

Conclusão

Este capítulo fez uma última visita ao tema mais recorrente de Java: classe. É natural, já
que a linguagem é inteiramente orientada a objetos. Revimos o conceito de pacotes e
como eles podem ser usados para controlar o acesso a classes, atributos e métodos. Para
isso são utilizados os modificadores de acesso public, protected, default e private.
Conhecemos também os modificadores que não são de acesso, que chamamos de
modificadores de comportamento, ou seja, o abstract, final e static. Para exemplificar
tudo construímos um projeto simples, mas muito bem estruturado. Pronto, agora você é
mestre em classes.

Desafio

Não temos um desafio específico para você sobre este capítulo. O verdadeiro desafio é
aplicar estes conceitos na vida real, em projetos reais. Se não tem um projeto real ainda
para tocar, invente um e continue praticando o que aprende e aprendendo novas coisas.
Obs.: Um pequeno desafio que não está exatamente relacionado com o assunto deste
capítulo. Notou que na classe Pessoa o atributo CPF é protected? Entendeu o motivo
disso? Conseguiria colocá-lo como private e fazer os ajustes nas classes para que tudo
continuasse funcionando? Agora é com você.

200
19. Partiu?

Que viagem foi essa! Começamos do zerinho e fomos evoluindo devagar e sempre ao
longo do livro. Conhecemos um pouquinho da história do Java, instalamos o JDK, demos
uma passada rápida por tipos de dados, introduzimos classes, conhecemos o Eclipse,
vimos os comandos de decisão e repetição, caímos em Arrays e depois em Collections,
passamos por interfaces, exceções, demos um bom mergulho em Java FX e Scene Builder,
visitamos Strings e finalizamos com um aprofundamento em classes. Ufa, cansamos só de
lembrar tudo.
Mas foi muito divertido. Conhecer o novo pode ser uma experiência fantástica,
dependendo muito da nossa postura, da nossa atitude diante do desconhecido. Torcemos
para que você sempre se desafie, que busque o novo e que, acima de tudo, não tenha
medo de errar. Não é errando que se aprende? Só que quanto mais a gente aprende
menos a gente erra. Então a gente erra para aprender a não errar mais!
Para nós, os humildes autores, escrever esse livro foi uma experiência excepcional. Nos
tirou da zona de conforto muitas vezes e passamos o tempo todo pensando: ‘Como a
gente explica este conceito complexo de maneira simples?’ Não queríamos escrever mais
um livro de Java com um monte de conceitos e infindáveis textos chatos e exemplos mais
do que repetidos. Queríamos escrever algo que fugisse ao comum, algo que te atraísse de
alguma forma, que te encantasse. Mas tinha que ser simples acima de tudo. Só você pode
dizer se conseguimos. Mas, acredite, tentamos pra valer.
Por falar nisso, é claro que a gente também erra. Se você encontrar um erro no livro ou
uma sugestão que ache legal para a próxima edição, fale com a gente. Entre no site da
Sistema Moderno (www.sistemamoderno.com.br) e envie uma mensagem pela página de
Contato. Fique tranquilo, porque você se enviar vai chegar pra gente.
Agora queremos falar um pouco sobre você e seu futuro. A área de computação como um
todo é muito interessante e oferece um grau de empregabilidade alto. Isto também
acontece com a subárea de desenvolvimento de software. Isso é uma boa notícia. Mas
queremos compartilhar com você o nosso entusiasmo pela programação. Para além da
empregabilidade, programar é bom demais. Por favor, divirta-se programando. Aprenda
coisas novas, técnicas novas, frameworks novos, linguagens novas. É um mundo, um
universo mutante de tecnologias revolucionárias. Revolucione-se também.
Queremos que você adore programar e não que seja uma daquelas pessoas que se arrasta
ao longo do mês esperando apenas pelo dia do pagamento. Ganhe dinheiro, claro, isso é
sempre bom. Mas, acima de tudo, seja um desenvolvedor de software, um engenheiro de
software. O caminho pode ser um pouco árido no começo e por isso escrevemos esse
livro. Para te ajudar exatamente no começo. Depois tudo vai ficando cada vez mais fácil.
E então? Partiu? O começo foi aqui. O destino é você que determina.

201
Índice remissivo

Abstract, 192 getter, 39


AnchorPane, 132 GridPane, 136
Array, 64 handle, 141
ArrayList, 89 HBox, 126
Arrays, 52 Herança, 77
ASCII, 16 IDE, 32
atributos, 24 if, 43
Atributos, 24 indexOf, 171
boolean, 14 int, 15
BorderPane, 129 Interface gráfica, 109, 152
branch, 115 Interfaces, 83
break, 66 Java Project, 35
byte, 15 JAVA_HOME, 6
catch, 108 javac, 12
char, 15 JavaFX, 109
charAt, 171 JDK, 6
classe, 22 JVM, 3
Classes, 18, 70, 182 launch, 117
Classes de exceção, 100 length, 171
coleções, 93 List, 89
Coleções, 87 long, 15
construtor, 95 main, 21
Criptógrafo, 174 Modificadores, 184, 190
default, 185 nodes, 115
do, 60 Oracle, 1
do/while, 60 package, 27
double, 15 Pacotes, 26, 182
Eclipse, 32, 34 Pane, 123
else, 43, 44 Partiu, 201
Encapsulamento, 72 Polimorfismo, 75
Eventos, 139 private, 189
exceções, 99 public, 185
Exception, 100 Repetições, 59
extends, 79 replace, 173
final, 194 Scene, 111
float, 15 Scene Builder, 152
FlowPane, 115, 125 setOnAction, 141
for, 62, 64 setter, 40
foreach, 95 short, 15
FXML, 160 StackPane, 135
getChildren, 114 Stage, 113
static, 194 throws, 108
String, 15, 17 TilePane, 133
String pool, 168 trim, 173
Strings, 167 try, 108
substring, 172 VBox, 126
SUN, 1 visibilidade, 30
switch, 48 while, 59
Switch, 48 WORA, 2
telas, 120 Workspace, 34
this, 71

Você também pode gostar