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
ISBN: 978-85-45563-00-6
CDU 004.4
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.
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.
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
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.
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.
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.
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.
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.
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).
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
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.
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.
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.
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 {
11
double boletoValor;
double valorMulta;
double valorTotal;
boletoValor=450;
valorMulta=boletoValor*0.10;
valorTotal=boletoValor+valorMulta;
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.
12
Figura 12. A lista de programas e de seus respectivos compilados em byte-code.
Erros de gravação
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;
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.
14
byte -128 a 127
char 0 a 65535
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.
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.
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;
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";
System.out.println(a);
System.out.println(b);
17
4. Classes – Primeira visita
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.
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?
É 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
}
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.
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.
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 {
}
}
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.
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.
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 {
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.
Pacotes e visibilidade
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.
package Apoio;
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[]) {
m.multiplica(5,10);
System.out.println("Resultado: "+m.resultado);
}
}
Agora vamos tentar compilar e executar nosso programa, conforme Figura 19.
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;
Como pode ser visto na Figura 20, tudo funciona perfeitamente após a implementação
do ajuste de visibilidade do atributo resultado.
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;
import Apoio.Matematica;
class QuartoPrograma {
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
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):
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.
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.
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
35
Figura 27. Janela para criação de projetos.
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.
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.
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.
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.
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.
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.
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.
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());
42
6. Adicionando Inteligência
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");
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 .
int valor1=10;
int valor2=5;
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;
if (valorDec/2==valorInt/2) System.out.println("Mesmo
resultado.");
else System.out.println("Resultados diferentes.");
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;
if (valorDec/2==valorInt/2) System.out.println("Mesmo
resultado.");
else System.out.println("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().
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().
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");
}
}
}
Switch
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().
if (valorDec/2==valorInt/2) retorno=true;
return retorno;
}
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.
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");
}
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
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>];
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.
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>
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.
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 {
// Definindo os Arrays
String[] a;
String b[];
// Criando os Arrays
a = new String[5];
b = new String[5];
54
a[1] = "Maria";
a[2] = "Marcos";
a[3] = "Clara";
a[4] = "Rogério";
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.
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.
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 {
// Definindo os Arrays
String Competicoes[][];
// Criando os Arrays
Competicoes = new String[3][4];
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
while
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.
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
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.
System.out.println("-");
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.
System.out.println("-");
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
62
Figura 46. Funcionamento da estrutura for.
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.
System.out.println("-");
63
i++;
} while (i<5);
System.out.println("-");
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]);
}
// Definindo os Arrays
String[] a;
String b[];
// Criando os Arrays
a = new String[5];
65
b = new String[5];
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.
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;
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.
System.out.println(n);
if(n==3)
break;
n++;
}
System.out.println("-");
if(i==3)
break;
i++;
} while (i<5);
System.out.println("-");
if(z==3)
break;
}
68
int distancia=500;
double tanqueGasolina=50;
double quilometrosPorLitro=8.5;
double consumoPorQuilometro=1/quilometrosPorLitro;
tanqueGasolina-=consumoPorQuilometro;
System.out.println("Quilometros rodados: "+decorrido);
if(tanqueGasolina<=0) {
System.out.println("Acabou a gasolina.");
break;
}
}
}
}
69
9. Classes - Revisita
package ClassesBase;
import java.util.Random;
// ATRIBUTOS
public int aposta;
public int resultado;
// MÉTODOS
public void executaJogo() {
this.resultado=(new Random().nextInt(6)+1);
}
if (this.resultado == this.aposta)
System.out.println("Parabens, você acertou!");
70
else System.out.println ("Errou. Mas, continue tentando...");
}
import ClassesBase.Jogo;
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...
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;
// 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;
}
if (this.resultado == this.aposta)
System.out.println("Parabéns, você acertou!");
74
Código 27. Classe TestaJogo ajustada para encapsulamento.
import ClassesBase.Jogo;
Polimorfismo
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;
}
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;
}
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;
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;
}
if (this.resultado == this.aposta)
System.out.println("Parabéns, você acertou!");
import ClassesBase.Jogo;
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;
Ó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;
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;
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;
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.
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;
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;
}
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.
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;
}
// MÉTODOS
public void executaJogo() {
Random rnd = new Random();
this.setResultado(rnd.nextInt(6)+1);
}
if (this.resultado == this.aposta)
System.out.println("Parabéns, você acertou!");
package ClassesBase;
package ClassesBase;
import java.util.Random;
81
public void executaJogo() {
Random rnd = new Random();
this.setResultado(rnd.nextInt(37));
}
import ClassesBase.Dado;
import ClassesBase.Roleta;
System.out.println("-");
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.
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();
}
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.
// 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.
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;
// 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;
}
// MÉTODOS
public void executaJogo() {
Random rnd = new Random();
this.setResultado(rnd.nextInt(6)+1);
}
if (this.resultado == this.aposta)
System.out.println("Parabéns, você acertou!");
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.
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;
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>();
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:
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);
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);
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
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;
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);
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.
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.
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;
alunos.add(a1);
alunos.add(a2);
alunos.add(a3);
alunos.add(a4);
}
}
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.
// ATRIBUTOS
private String nome;
private Double nota;
// CONSTRUTOR
public Aluno() {
// 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.
import java.util.ArrayList;
import java.util.List;
alunos.add(a1);
alunos.add(a2);
alunos.add(a3);
alunos.add(a4);
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;
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.
System.out.println(dividir(10,0));
int r=0;
r=a/b;
return r;
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.
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;
}
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.
int v1 = 10;
int v2 = 0;
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:
int r = 0;
try {
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;
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.
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.
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().
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
JavaFX
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
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.
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
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);
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);
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);
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;
@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);
// BRANCH NODE B
Label labelD=new Label("Eu sou o primeiro label do grupo B");
labelD.setLayoutX(0);
labelD.setLayoutY(0);
// 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);
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
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:
120
• TableView • TextArea • TextInputControl
• 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.
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().
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");
Organizando na tela
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;
@Override
public void start(Stage telaEmBranco) {
/* Criar o painel */
Pane painel=new BorderPane();
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);
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.
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;
@Override
public void start(Stage telaEmBranco) {
/* Criar o painel */
FlowPane painel=new FlowPane();
painel.setOrientation(Orientation.HORIZONTAL);
painel.setVgap(4);
painel.setHgap(10);
// SCENE
Scene cena = new Scene(painel, 300, 300);
125
telaEmBranco.setTitle("Exemplo de FlowPane");
telaEmBranco.setScene(cena);
telaEmBranco.sizeToScene();
telaEmBranco.show();
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;
@Override
public void start(Stage telaEmBranco) {
// PAINEL E SEUS NODES
/* Criar o painel */
HBox painel=new HBox();
painel.setSpacing(4);
// SCENE
Scene cena = new Scene(painel, 300, 300);
127
Figura 62. Resultado de exemplo de HBox com 5 botões.
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.
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.
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.
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;
@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"));
// 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);
// SCENE
Scene cena = new Scene(root, 300, 300);
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;
@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"));
// 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);
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;
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);
// SCENE
Scene cena = new Scene(root, 500, 300);
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.
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;
@Override
public void start(Stage telaEmBranco) {
// PAINEL PRINCIPAL
/* Primary Node, também chamado de Root Node */
StackPane root = new StackPane();
// SCENE
Scene cena = new Scene(root, 500, 300);
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;
@Override
public void start(Stage telaEmBranco) {
// PAINEL PRINCIPAL
/* Primary Node, também chamado de Root Node */
GridPane root = new GridPane();
root.setVgap(10);
136
root.add(new Label(" CADASTRO DE CLIENTES "), 1, 0);
// SCENE
Scene cena = new Scene(root, 400, 320);
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.
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
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.
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;
@Override
public void start(Stage telaEmBranco) {
// PAINEL PRINCIPAL
/* Primary Node, também chamado de Root Node */
GridPane root = new GridPane();
root.setVgap(10);
root.add(lblTitulo, 0, 0);
root.add(lblMensagem, 1, 3);
root.add(btnOk, 1, 5);
root.add(btnApagar, 1, 6);
btnApagar.setOnAction(new EventHandler<ActionEvent>() {
public void handle (ActionEvent ae) {
lblMensagem.setText("");
}
});
// SCENE
Scene cena = new Scene(root,300, 200);
140
telaEmBranco.sizeToScene();
telaEmBranco.show();
}
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.
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.
Mouse é movido ou um
botão do mouse é MouseEvent
pressionado.
Gesto de rotação é
realizado sobre um RotateEvent
objeto.
Gesto de swipe é
realizado sobre um SwipeEvent
objeto.
Gesto de zoom é
realizado sobre um ZoomEvent
objeto.
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;
@Override
public void start(Stage telaEmBranco) throws Exception {
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);
144
peso = Double.parseDouble(txtPeso.getText());
altura = Double.parseDouble(txtAltura.getText());
resultado = peso/(altura*altura);
lblMensagem.setText("Seu IMC é "+resultado);
}
});
}
peso = Double.parseDouble(txtPeso.getText());
altura = Double.parseDouble(txtAltura.getText());
resultado = peso/(altura*altura);
lblMensagem.setText("Seu IMC é "+resultado);
}
});
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.
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.
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.
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;
// ATRIBUTOS DA CLASSE
147
int acertos=0;
int tentativas=0;
@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);
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.");
}
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 ☺.
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
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 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.
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.
154
Figura 80. Integrando o Scene Builder ao Eclipse: passo 1.
155
Estrutura de uma aplicação com Java FX Scene Builder
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.
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.
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.
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.
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.
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.
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.
<?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?>
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.
*/
@Override
public void start(Stage primaryStage) {
try {
root = FXMLLoader.load(getClass().getResource("Calcula.fxml"));
} catch (Exception e) {
System.out.println("Aconteceu um erro: "+e.getMessage());
}
161
primaryStage.show();
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.
*/
162
calculaMedia();
return 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.
*/
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.
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?
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.
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.
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);
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
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
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.
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"));
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
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- ";
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:
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
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;
try {
root =
FXMLLoader.load(getClass().getResource("../view/Cripto.fxml"));
} catch(Exception e) {
System.out.println(e.getMessage());
e.printStackTrace();
}
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.
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.
<?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?>
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;
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();
}
}
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();
}
}
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;
String sRet="";
return sRet;
}
String sRet="";
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.
Pacotes
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;
}
package equipe;
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;
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;
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>;
/* Comentários de
múltiplas linhas
*/
Atributos;
184
Comentários;
Construtores;
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.
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;
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;
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.
package application;
import equipe.Funcionario;
187
super.setor="Tecnologia"; // Definida 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;
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.
package application;
import equipe.Funcionario;
189
Tabela 7. Visibilidade dos modificadores de acesso.
Modificadores de comportamento
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.
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.
package model;
// 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;
}
192
public int getIdade() {
return idade;
}
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.
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.
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;
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);
}
boolean bRet=false;
if(this.senha.equals(Criptografia.criptografa(senha)))
bRet=true;
return bRet;
}
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;
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;
}
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;
// Usuario 1
Usuario u1 = new Usuario();
u1.setNome("Artur Campos");
u1.setCPF("12345678912");
u1.setSenha("senha");
System.out.println("-");
// Usuario 2
Usuario u2 = new Usuario();
u2.setNome("Andre Campos");
u2.setCPF("2345678912");
u2.setSenha("senha2");
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;
// Métodos estáticos
int o;
while ((o=original.indexOf(" ")) != -1) {
original=original.substring(o+1);
sRet=original;
}
return sRet;
}
package biblioteca;
// Métodos estáticos
String sRet="";
199
}
return sRet;
}
String sRet="";
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