Book: Desbravando Java e Orientacao a Objetos


© Casa do Código
Todos os direitos reservados e protegidos pela Lei nº9.610, de
10/02/1998.
Nenhuma parte deste livro poderá ser reproduzida, nem transmitida, sem
autorização prévia por escrito da editora, sejam quais forem os meios:
fotográficos, eletrônicos, mecânicos, gravação ou quaisquer outros.
Casa do Código
Livros para o programador
Rua Vergueiro, 3185 - 8º andar
04101-300 – Vila Mariana – São Paulo – SP – Brasil
Casa do Código
“À minha esposa Jordana e ao maior presente que um dia sonhei receber, nossa filha Katherine.”
– Rodrigo Turini
i
Casa do Código
Agradecimentos
Não há outra maneira de começar este livro que não seja agradecendo a to-
dos que incentivaram e contribuíram direta ou indiretamente em sua edição.
Victor Harada, Maurício Aniche e Guilherme Silveira foram alguns deles.
Fica um agradecimento especial ao Paulo Silveira, não só pela sua detal-
hada revisão e suas diversas sugestões e melhorias para esse livro, mas tam-
bém pela influência direta que tem em meu dia a dia profissional.
Gostaria também de estender o agradecimento para toda equipe da
Caelum. São profissionais exemplares que me incentivam a aprender e en-
sinar diariamente.
iii
Casa do Código
Sumário
Sumário
1
Java
1
1.1
Nosso primeiro código Java . . . . . . . . . . . . . . . . . . .
1
1.2
Algumas regras e convenções . . . . . . . . . . . . . . . . . .
3
1.3
Entendendo o método main . . . . . . . . . . . . . . . . . . .
4
1.4
Trabalhando com uma IDE . . . . . . . . . . . . . . . . . . . .
6
1.5
Acesse o código desse livro e entre em contato conosco . . .
9
2
Variáveis e tipos primitivos
11
2.1
Nosso projeto . . . . . . . . . . . . . . . . . . . . . . . . . . . .
11
2.2
Declaração e atribuição de variáveis . . . . . . . . . . . . . . .
11
2.3
Tipos primitivos . . . . . . . . . . . . . . . . . . . . . . . . . .
14
2.4
Casting de valores . . . . . . . . . . . . . . . . . . . . . . . . .
16
2.5
Adicionando condicionais . . . . . . . . . . . . . . . . . . . .
18
2.6
Loopings e mais loopings . . . . . . . . . . . . . . . . . . . . .
21
3
Orientação a objetos
29
3.1
Criando um molde de livros . . . . . . . . . . . . . . . . . . .
30
3.2
Criando um novo método . . . . . . . . . . . . . . . . . . . .
34
3.3
Objetos para todos os lados! . . . . . . . . . . . . . . . . . . .
38
3.4
Entendendo a construção de um objeto . . . . . . . . . . . . .
51
3.5
Vantagens da orientação a objetos . . . . . . . . . . . . . . . .
53
v
Sumário
Casa do Código
4
Encapsulamento
55
4.1
Limitando desconto do Livro . . . . . . . . . . . . . . . . . . .
55
4.2
Isolando comportamentos . . . . . . . . . . . . . . . . . . . .
59
4.3
Código encapsulado . . . . . . . . . . . . . . . . . . . . . . . .
61
4.4
Getters e Setters . . . . . . . . . . . . . . . . . . . . . . . . . .
62
4.5
Definindo dependências pelo construtor . . . . . . . . . . . .
67
5
Herança e polimorfismo
75
5.1
Trabalhando com livros digitais . . . . . . . . . . . . . . . . .
75
5.2
Reescrevendo métodos da superclasse . . . . . . . . . . . . .
80
5.3
Regras próprias de um LivroFisico . . . . . . . . . . . . . . .
85
5.4
Vendendo diferentes tipos de Livro . . . . . . . . . . . . . . .
87
5.5
Acumulando total de compras . . . . . . . . . . . . . . . . . .
91
5.6
Herança ou composição? . . . . . . . . . . . . . . . . . . . . .
94
6
Classe abstrata
95
6.1
Qual o tipo de cada Livro? . . . . . . . . . . . . . . . . . . . .
95
6.2
Minilivro não tem desconto! . . . . . . . . . . . . . . . . . . .
98
6.3
Método abstrato . . . . . . . . . . . . . . . . . . . . . . . . . .
102
6.4
Relembrando algumas regras . . . . . . . . . . . . . . . . . . .
104
7
Interface
105
7.1
O contrato Produto . . . . . . . . . . . . . . . . . . . . . . . .
108
7.2
Diminuindo acoplamento com Interfaces . . . . . . . . . . .
112
7.3
Novas regras da interface no Java 8 . . . . . . . . . . . . . . .
114
8
Pacotes
119
8.1
Organizando nossas classes . . . . . . . . . . . . . . . . . . . .
119
8.2
Modificadores de acesso . . . . . . . . . . . . . . . . . . . . .
126
9
Arrays e exception
129
9.1
Trabalhando com multiplicidade . . . . . . . . . . . . . . . .
129
9.2
As diferentes exceções e como lidar com elas . . . . . . . . .
136
9.3
Muitas e muitas Exception . . . . . . . . . . . . . . . . . . . .
142
9.4
Também podemos lançar exceções! . . . . . . . . . . . . . . .
147
vi
Casa do Código
Sumário
10 Conhecendo a API
151
10.1 Todo objeto tem um tipo em comum . . . . . . . . . . . . . .
151
10.2 Wrappers dos tipos primitivos . . . . . . . . . . . . . . . . . .
161
10.3 O pacote java.lang . . . . . . . . . . . . . . . . . . . . . . . . .
163
11 Collection Framework
169
11.1
O trabalho de manipular arrays . . . . . . . . . . . . . . . . .
169
11.2 Ordenando nossa List de produtos . . . . . . . . . . . . . . .
178
11.3
Gerenciando cupons de desconto . . . . . . . . . . . . . . . .
182
11.4 java.util.Map . . . . . . . . . . . . . . . . . . . . . . . . . . . .
188
12 Streams e novidades do Java 8
193
12.1 Ordenando com Java 8 . . . . . . . . . . . . . . . . . . . . . .
193
12.2 forEach do Java 8 . . . . . . . . . . . . . . . . . . . . . . . . . .
198
12.3 Filtrando livros pelo autor . . . . . . . . . . . . . . . . . . . .
198
13 Um pouco da história do Java
203
13.1
Origem da linguagem . . . . . . . . . . . . . . . . . . . . . . .
203
13.2 Escreva uma vez, rode em qualquer lugar! . . . . . . . . . . . 204
13.3 Linha do tempo . . . . . . . . . . . . . . . . . . . . . . . . . .
205
14 Continuando seus estudos
211
14.1 Entre em contato conosco . . . . . . . . . . . . . . . . . . . .
212
vii
Capítulo 1
Java
1.1
Nosso primeiro código Java
No lugar de começar com conceitos e teorias, vamos logo partir para uma boa
dose de prática! Abra seu editor de texto preferido e escreva o seguinte código Java:
class MeuPrimeiroPrograma {
public static void main(String[] args) {
System.out.println("O primeiro de muitos!");
}
}
Esse programa imprime um texto simples. Confuso? Não se preocupe,
em breve cada uma dessas palavras terá seu sentido bem claro.
1.1. Nosso primeiro código Java
Casa do Código
Salve o arquivo em algum diretório de sua preferência, mas é importante
que ele se chame MeuPrimeiroPrograma.java.
Nosso próximo passo será compilar esse código fonte. Faremos isso man-
ualmente utilizando o javac, compilador padrão da Oracle.
Abra seu terminal e digite javac MeuPrimeiroPrograma.java.
Note que para isso você precisa estar na mesma pasta do arquivo, ou passar o
caminho completo para ele.
Navegando pelas pastas e listando arquivos
Você pode utilizar o comando cd ( change directory) para navegar
pelas suas pastas via terminal. Um exemplo em Windows seria:
D:\> cd Desktop\livro
D:\Desktop\livro>
O mesmo comando pode ser utilizado em um ambiente Unix (Linux
ou Mac OS). Para listar, há uma diferença. Em Windows utilizamos o
comando dir:
D:\Desktop\livro> dir
MeuPrimeiroPrograma.java
MeuPrimeiroPrograma.class
Porém, nos outros sistemas que foram citados o comando será ls.
Repare:
turini ~ $ cd Desktop/livro
turini/Desktop/livro ~ $ ls
MeuPrimeiroPrograma.java
MeuPrimeiroPrograma.class
Note que, se nenhum erro de compilação ocorrer, no mesmo diretório
agora existirá um novo arquivo com o mesmo nome de nossa classe Java
(neste caso, MeuPrimeiroPrograma), porém a sua extensão será .class.
Este novo arquivo é o bytecode gerado pelo compilador.
2
Casa do Código
Capítulo 1. Java
Para executá-lo, ainda pelo terminal, digite o comando
java
MeuPrimeiroPrograma. O comando java é o responsável por executar
a JVM (máquina virtual, ou Java Virtual Machine) que irá interpretar o bytecode de seu programa. Repare que neste comando não passamos a extensão do arquivo:
turini/Desktop/livro ~ $ java MeuPrimeiroPrograma
O primeiro de muitos!
E pronto! Você já compilou e executou o seu primeiro programa em Java.
Não se preocupe, a JVM, bytecode e outros conceitos e siglas importantes serão melhor detalhados mais à frente.
Instalação do Java
Para executar os códigos deste livro, você precisará ter o JDK ( Java
Development Kit) instalado. Se precisar, você pode seguir as instruções do link a seguir para instalar de acordo com seu sistema operacional:
http://www.caelum.com.br/apostila-java-orientacao-objetos/
apendice-instalacao-do-java/
Precisa de ajuda? Não deixe de nos mandar um e-mail no grupo:
https://groups.google.com/d/forum/livro-java-oo
1.2
Algumas regras e convenções
Você pode ter reparado que seguimos algumas regras e convenções até agora.
Vamos entendê-las um pouco melhor.
Podemos
começar
pelo
nome
de
nosso
arquivo,
MeuPrimeiroPrograma.
Em Java, o nome de uma classe sempre se
inicia com letra maiúscula e, quando necessário, as palavras seguintes
também têm seu case alterado. Dessa forma “esse nome de classe
vira “EsseNomeDeClasse
. Essa abordagem é bastante conhecida como CamelCase.
Um arquivo .java possui a definição de uma classe. É uma prática
recomendada nomear a classe e o arquivo da mesma forma, caso contrário
3
1.3. Entendendo o método main
Casa do Código
poderá existir alguma confusão no momento de executá-la. Por exemplo,
considere que temos um arquivo meu-primeiro-programa.java con-
tendo a classe que chamamos de MeuPrimeiroPrograma. Para compilá-lo
faremos:
turini/Desktop/livro ~ $ javac meu-primeiro-programa.java
Porém,
o arquivo compilado (com o bytecode) terá o nome
MeuPrimeiroPrograma.class.
Como executar esse programa?
A
resposta será com o nome da classe, e não do arquivo. Neste caso:
turini/Desktop/livro ~ $ java MeuPrimeiroPrograma
Note também que há um par de chaves {} definindo o inicio e final (es-
copo) de sua classe e as instruções sempre terminam com ponto e vírgula.
Por fim, é fundamental perceber que esta é uma linguagem case sensitive, ou seja, leva em consideração o case (caixa) em que as instruções são escritas.
Escrever System com letra minúscula, por exemplo, resultaria em um erro
de compilação.
Você pode ler mais a respeito dessas e de outras convenções da linguagem
no documento oficial Code Conventions for the Java Programming Language, disponível no site da Oracle:
http://www.oracle.com/technetwork/java/index-135089.html
1.3
Entendendo o método main
Ao ser executada, a máquina virtual (JVM) procura pelo bloco main
declarado em sua classe. Esse é um bloco especial (ou método, como pas-
saremos a chamar a partir de agora) e se parece com:
public static void main(String[] args) {
// seu código aqui
}
Suas aplicações Java, em geral, vão possuir apenas um método main,
um único ponto de partida. Quando rodamos o comando java passando o
4
Casa do Código
Capítulo 1. Java
nome de nossa classe, dissemos para a JVM executar todo o conteúdo que es-
tiver dentro do corpo (das chaves) desse método. Em nosso exemplo, foi uma
simples instrução de impressão, o System.out.println("o primeiro
de muitos!").
Há ainda como passar argumentos para o método main ao executar um
programa Java. O parâmetro String[] args que o método recebe den-
tro de seus parênteses será o responsável por armazenar esses argumentos
para que possam ser acessados em nosso código. Mas, afinal, o que é esse
String[]? Um array de String em Java, porém, por enquanto não esta-
mos interessados em entender um array e todos os seus detalhes, tudo o que precisamos saber é que ele vai armazenar uma multiplicidade de Strings.
Um exemplo dessa passagem de parâmetro seria:
turini/Desktop/livro ~ $ java MeuPrimeiroPrograma Java Rodrigo
Para isso funcionar, modificamos nosso código para exibir o conteúdo
guardado nas posições 0 e 1 ( args[0], args[1]) desse conjunto de argu-
mentos:
class MeuPrimeiroPrograma {
public static void main(String[] args) {
System.out.println("O primeiro de muitos códigos
escritos em " +args[0]+ " pelo " +args[1]+ "!");
}
}
Execute para conferir o resultado! Não se esqueça que, como modifi-
camos nosso código, precisaremos compilar novamente:
turini/Desktop/livro ~ $ javac MeuPrimeiroPrograma.java
turini/Desktop/livro ~ $ java MeuPrimeiroPrograma Java Rodrigo
Nesse exemplo, o resultado impresso será:
O primeiro de muitos códigos escritos em Java pelo Rodrigo!
5
1.4. Trabalhando com uma IDE
Casa do Código
Erro de compilação?
Um erro de digitação, a falta de um ponto e vírgula ou uma difer-
ença de case em seu código são alguns dos muitos motivos que podem
resultar em um erro de compilação. Conhecer e entender esses erros é
fundamental, talvez você queira inclusive provocar algum deles para ver
como seu código se comparta.
Qual será a mensagem caso eu esqueça de escrever um ponto e vír-
gula? Escreva um teste simples pra descobrir! Um exemplo:
System.out.println("sem ponto-e-virgula")
A mensagem de erro será:
Syntax error, insert ";" to complete BlockStatements
Lembre-se que você pode e deve tirar todas as suas dúvidas no GUJ,
em http://guj.com.br.
1.4
Trabalhando com uma IDE
Escrever, compilar e executar seu código Java em um bloco de notas junto
com terminal é bastante trabalhoso. É fundamental conhecer esse processo,
mas em seu dia a dia você provavelmente vai preferir utilizar alguma das mais
diversas ferramentas de desenvolvimento conhecidas para ajudá-lo nesse tra-
balho.
Essas ferramentas são chamadas de IDE ( Integrated Development Envi-
ronment) e podem tornar seu desenvolvimento muito mais produtivo e in-
teressante, oferecendo-lhe recursos como syntax highlight e auto complete das instruções de seu código.
No decorrer deste livro, vamos utilizar o Eclipse, você pode fazer o download de sua versão mais recente em:
https://www.eclipse.org/downloads/
Essa é uma IDE gratuita e open source, sem dúvida uma das preferidas do
mercado e instituições de ensino. Seus diversos atalhos e templates prontos
6
Casa do Código
Capítulo 1. Java
são grandes diferenciais.
Existem diversas outras boas opções no mercado, como por exemplo o
NetBeans da Oracle, ou o IntelliJ IDEA, sendo este último pago.
Criando seu primeiro projeto no Eclipse
Depois de fazer o download, você não precisará instalar a IDE. Esta é
uma executável inteiramente escrita em Java, basta executá-la e escolher a
pasta onde será a sua área de trabalho ( workspace). Escolha um local de sua preferência.
Vamos criar nosso projeto! Para isso, você pode por meio do menu escol-
her as opções File > New > Java Project. Vamos chamar o projeto
que desenvolveremos durante o curso de livraria.
Repare que, depois de concluir, esse projeto será representando em seu
sistema operacional como uma pasta, dentro do workspace que você escolheu.
Por enquanto, dentro desse diretório você encontrará a pasta src, onde ficará
todo o seu código .java e também a pasta bin, onde ficará o bytecode
compilado.
Produtividade extrema
Agora que já criamos o projeto, vamos criar nossa primeira classe
pelo Eclipse. Você pode fazer isso pelo menu File > New > Class.
Para conhecer um pouco da IDE, vamos criar novamente a classe
MeuPrimeiroPrograma.
Depois de preencher o nome, selecione a opção finish e veja o resultado: public class MeuPrimeiroPrograma {
}
A estrutura de sua classe já está pronta, vamos agora escrever seu método
main. Para fazer isso, escreva a palavra main dentro de sua classe e pressione
o atalho Control + Espaço.
Esse é o atalho de code completion da IDE. Se tudo correu bem, seu código ficou assim:
7
1.4. Trabalhando com uma IDE
Casa do Código
public class MeuPrimeiroPrograma {
public static void main(String[] args) {
}
}
Interessante, não acha? Além de termos mais produtividade ao escrever,
evitamos que erros de digitação aconteçam.
Vamos além, agora dentro do método main, digite syso e pressione
Control + Espaço para fazer code completion novamente.
Você pode e deve usar e abusar desse recurso!
public class MeuPrimeiroPrograma {
public static void main(String[] args) {
System.out.println("O primeiro de muitos!");
}
}
Agora, com o código pronto, as próximas etapas seriam compilar e
executá-lo, mas a compilação já está pronta!
Isso mesmo, conforme você vai escrevendo seu código, a IDE já cuida de
compilá-lo. Repare que se você apagar o ponto e vírgula, por exemplo, essa
linha ficará sublinhada em vermelho. Essa é uma indicação visual de que seu
código não está compilando.
Para executar o código, você pode clicar com o botão direito do mouse
em sua classe e selecionar as opções Run As > Java Application . Ou
por atalho, pressionando Control + F11.
Repare que a saída de seu código vai aparecer na aba Console.
O primeiro de muitos!
8
Casa do Código
Capítulo 1. Java
Atalhos do Eclipse
Conhecer os atalhos da IDE vai torná-lo um programador muito mais
produtivo. Você pode ler mais a respeito e conhecer mais atalhos pela
documentação do Eclipse em:
https://www.eclipse.org/users/
E também no post:
http://blog.caelum.com.br/as-tres-principais-teclas-de-atalho-do-eclipse/
Se esse for o seu primeiro contato com a linguagem, recomendamos que
você pratique bastante a sintaxe antes de partir para os próximos capítu-
los. Sinta-se confortável com o processo de escrever, compilar e executar seu
código Java. Tenho certeza de que em breve essa será sua rotina.
1.5
Acesse o código desse livro e entre em con-
tato conosco
Todos os exemplos deste livro podem ser encontrados no repositório:
https://github.com/Turini/livro-oo
Mas claro, não deixe de escrever todo o código que vimos para praticar a
sintaxe e se adaptar com os detalhes da linguagem. Além disso, sempre que
possível faça novos testes além dos aqui sugeridos.
Ficou com alguma dúvida? Não deixe de me mandar um e-mail. A
seguinte lista foi criada exclusivamente para facilitar o seu contato conosco
e com os demais leitores:
https://groups.google.com/d/forum/livro-java-oo
Suas sugestões, criticas e melhorias serão muito mais do que bem-vindas!
Outro recurso que você pode usar para esclarecer suas dúvidas e partic-
ipar ativamente na comunidade Java é o fórum do GUJ, espero encontrá-lo
por lá.
http://www.guj.com.br/
9
Capítulo 2
Variáveis e tipos primitivos
2.1
Nosso projeto
Durante o livro, vamos criar e evoluir a aplicação Java de uma livraria como a
Casa do Código. Esse contexto vai nos possibilitar colocar em prática todos os principais recursos e conceitos da linguagem, desde o mais simples ao mais
avançado, além de conhecer e trabalhar com as API s e features de sua mais nova versão.
2.2
Declaração e atribuição de variáveis
Vamos criar uma nova classe Java cujo objetivo será calcular o valor
total de livros do nosso estoque na livraria.
Podemos chamá-la de
CalculadoraDeEstoque.
2.2. Declaração e atribuição de variáveis
Casa do Código
public class CalculadoraDeEstoque {
public static void main(String[] args) {
}
}
Precisamos agora armazenar dentro de seu método main o valor de nos-
sos livros. Uma forma simples de fazer isso seria:
public class CalculadoraDeEstoque {
public static void main(String[] args) {
double livroJava8;
double livroTDD;
}
}
Dessa forma, estamos pedindo ao Java que reserve regiões da memória
para futuramente armazenar esses valores. Essa instrução que criamos é con-
hecida como uma variável.
Repare que essas duas variáveis declaradas possuem um tipo double
(número com ponto flutuante). Logo, conheceremos os outros tipos exis-
tentes, mas é importante reconhecer desde já que toda variável em Java pre-
cisara ter um.
Utilizamos o sinal = (igual) para atribuir um valor para estas variáveis
que representam alguns de nossos livros:
double livroJava8;
double livroTDD;
livroJava8 = 59.90;
livroTDD = 59.90;
Mas, neste caso, como já sabemos o valor que será atribuído no momento
de sua declaração, podemos fazer a atribuição de forma direta no momento
em que cada variável é declarada:
12
Casa do Código
Capítulo 2. Variáveis e tipos primitivos
double livroJava8 = 59.90;
double livroTDD = 59.90;
Agora que já temos alguns valores de livros, vamos calcular a sua soma e
acumular em uma nova variável. Isso pode ser feito da seguinte forma:
double soma = livroJava8 + livroTDD;
Pronto, isso é tudo que precisamos por enquanto para completar a nossa
CalculadoraDeEstoque. Após somar esses valores, vamos imprimir o
valor do resultado no console:
public class CalculadoraDeEstoque {
public static void main(String[] args) {
double livroJava8 = 59.90;
double livroTDD = 59.90;
double soma = livroJava8 + livroTDD;
System.out.println("O total em estoque é "+ soma);
}
}
Escreva e execute esse código para praticar! Neste exemplo, o resultado
será:
O total em estoque é 119.8
Além do operador de soma ( +), você também pode usar os seguintes
operadores em seu código Java:
13

2.3. Tipos primitivos
Casa do Código
2.3
Tipos primitivos
Vimos que é possível representar um número com ponto flutuante utilizando
o tipo double, mas existem diversos outros tipos para representar os difer-
entes valores com que trabalhamos no dia a dia.
Para representar um número inteiro, por exemplo, podemos utilizar os
tipos byte, short, int ou long. Isso mesmo, o código a seguir compila
com qualquer um desses tipos:
byte inteiro1 = 10;
short inteiro2 = 10;
int inteiro3 = 10;
long inteiro4 = 10;
Mas, afinal, quando eu devo utilizar cada um desses? A grande diferença
está no tamanho de cada um desses tipos. Por exemplo, um short suporta
até 2 bytes, enquanto um long suporta 8 bytes.
Muito diferente de antigamente, hoje não há uma preocupação tão grande
em economizar bytes na declaração de suas variáveis. Você raramente verá
um programador utilizando um short para guardar um número pequeno,
14

Casa do Código
Capítulo 2. Variáveis e tipos primitivos
no lugar de usar logo um int. Mas claro, conhecer os diferentes tipos e a
capacidade de cada um deles é essencial para um bom programador.
Esses são os possíveis tipos primitivos da linguagem Java:
Um detalhe simples, porém muito importante, sobre os tipos primitivos
é que, quando você atribui um valor para eles (utilizando o operador =),
este valor será copiado para a sua variável. Por exemplo, repare no seguinte código:
int numero = 4;
int outroNumeroIgual = numero;
numero = numero + 5;
System.out.println(numero);
System.out.println(outroNumeroIgual);
15
2.4. Casting de valores
Casa do Código
Neste caso, com certeza o valor da variável numero no momento em que
for impressa será 9, mas e quanto ao outroNumeroIgual? Repare que ela
recebe numero e seu valor foi alterado logo em seguida.
Execute esse código e você perceberá que o outroNumeroIgual con-
tinuará com o valor 4, pois este era o valor da variável numero no momento
da atribuição. Ou seja, a variável outroNumeroIgual guardou uma cópia
do valor, e não uma referência a ela ou algo do tipo.
Tipos não primitivos
Logo conheceremos outros tipos (não primitivos) da linguagem Java,
mas desde já repare que um texto, assim como qualquer outro valor, tam-
bém tem um tipo! Esse tipo é conhecido por String.
Você poderia, no lugar de fazer:
System.out.println("Eu sou uma String");
Declarar da seguinte forma:
String texto = "Eu sou uma String";
System.out.println(texto);
2.4
Casting de valores
Como você já deve ter percebido, nem todos os valores são com-
patíveis. Por exemplo, se eu tentar declarar o valor de nossos livros na
CalculadoraDeEstoque como um int, um erro de compilação aconte-
cerá.
public class CalculadoraDeEstoque {
public static void main(String[] args) {
int livroJava8 = 59.90;
int livroTDD = 59.90;
16
Casa do Código
Capítulo 2. Variáveis e tipos primitivos
int soma = livroJava8 + livroTDD;
System.out.println("O total em estoque é "+ soma);
}
}
Repare no Eclipse (ou qualquer outra IDE ou editor de texto que estiver
utilizando em seus testes) que o seguinte erro será exibido:
Type mismatch: cannot convert from double to int
Ou seja, um int não pode guardar um número com ponto flutuante (
double). Faz sentido.
Mas, sim, o contrário é totalmente possível. Repare no código:
public class CalculadoraDeEstoque {
public static void main(String[] args) {
double livroJava8 = 60;
double livroTDD = 60;
double soma = livroJava8 + livroTDD;
System.out.println("O total em estoque é "+ soma);
}
}
Mesmo arredondando o valor dos livros para um número inteiro (neste
caso, 60), um double pode sim guardar esse valor. Portanto, eu posso
atribuir valores menores em variáveis com uma capacidade maior; o que eu
não posso é o contrário. Uma analogia dev que gosto muito e se encaixa bem
aqui é: “Você pode colocar uma formiga na casa de um cavalo, o contrário não daria certo”.
Mas repare que nem mesmo o seguinte código compila:
double livroJava8 = 60;
int numeroInteiro = livroJava8;
17
2.5. Adicionando condicionais
Casa do Código
Da mesma forma que antes, estamos tentando atribuir um double (que é
um tipo de tamanho maior) em um int. Não importa se o valor que atribuí-
mos a este double é um valor inteiro válido (um bom candidato para o tipo
int). O compilador vai se preocupar com o tipo de suas variáveis, e não
com seus valores, até porque, como o próprio nome indica, esses valores são
mutáveis, portanto essa atribuição seria perigosa.
Em algum momento, você pode precisar forçar essa conversão de tipos,
dizendo ao compilador: “tudo bem, eu garanto que o valor deste double pode
ser moldado para um numero inteiro
. Para isso, utilizamos um recurso conhecido como casting.
int numeroInteiro = (int) livroJava8;
Neste caso, seu código vai compilar sem nenhum problema e a variável
numeroInteiro terá uma copia do valor 60.
Se o valor do livroJava8 fosse 59.90, ou seja, um número com ponto
flutuante e você fizesse esse casting haveria uma perda de precisão. A var-
iável numeroInteiro teria apenas o valor 59 copiado.
2.5
Adicionando condicionais
Nossa CalculadoraDeEstoque precisa de uma nova funcionalidade. Se o
valor total de livros for menor que 150 reais, precisamos ser alertados de que
nosso estoque está baixíssimo, caso contrário, devemos mostrar uma men-
sagem indicando de que está tudo sob controle!
Em Java, podemos fazer essa condicional de uma forma bem comum, uti-
lizando um if e else. Observe:
public class CalculadoraDeEstoque {
public static void main(String[] args) {
double livroJava8 = 59.90;
double livroTDD = 59.90;
double soma = livroJava8 + livroTDD;
18
Casa do Código
Capítulo 2. Variáveis e tipos primitivos
System.out.println("O total em estoque é "+ soma);
if (soma < 150) {
System.out.println("Seu estoque está muito baixo!");
} else {
System.out.println("Seu estoque está bom");
}
}
}
Como já é esperado, esse código só vai imprimir a mensagem de estoque
baixo se o valor da soma for menor ( <) que 150. Caso contrário, irá executar o conteúdo de dentro do bloco else.
Passamos uma condição como argumento fazendo uma comparação en-
tre o valor da variável soma com o valor 150. Essa condição vai resultar em
um valor true quando verdadeira, ou false caso contrário. Esse tipo de
condição é conhecido como expressão booleana. Seu resultado sempre será
do tipo boolean:
boolean resultado = soma < 150;
Você pode usar qualquer um dos seguintes operadores relacionais pra
construir uma expressão booleana: > (maior), < (menor), >= (maior ou
igual), <= (menor ou igual), == (igual sim, são dois iguais! Lembre-se que
um único igual significa atribuição) e, por fim, != (diferente).
Há ainda a alternativa de encadear mais condições em nosso if. Por
exemplo, para receber uma mensagem indicando que o estoque está muito
alto, podemos adicionar a seguinte condição:
public class CalculadoraDeEstoque {
public static void main(String[] args) {
double livroJava8 = 59.90;
double livroTDD = 59.90;
double soma = livroJava8 + livroTDD;
19
2.5. Adicionando condicionais
Casa do Código
System.out.println("O total em estoque é "+ soma);
if (soma < 150) {
System.out.println("Seu estoque está muito baixo!");
} else if (soma >= 2000) {
System.out.println("Seu estoque está muito alto!");
} else {
System.out.println("Seu estoque está bom");
}
}
}
Operador ternário
É muito comum escrevermos condicionais como a seguinte, que, de
acordo com alguma condição booleana, retorna um valor diferente. Re-
pare:
double valor = 0;
if (v1 > v2) {
valor = 100;
} else {
valor = 0;
}
Uma alternativa bastante conhecida e utilizada para estes casos é o
operador ternário ?: . Com ele, você pode fazer o mesmo da seguinte
forma:
double valor = v1 > v2 ? 100 : 0;
Sem dúvida, fica bem mais enxuto, não acha? Performaticamente, não
há nenhuma vantagem significante em usar uma ou outra estratégia, mas
é sempre interessante levar em consideração a legibilidade do código. Em
alguns casos, usar o operador ternário pode custar um pouco da legibil-
idade e deixar o código mais complexo.
20
Casa do Código
Capítulo 2. Variáveis e tipos primitivos
2.6
Loopings e mais loopings
Considerando que todos os livros têm o preço 59.90, seria muito trabalhoso
criar tantas variáveis para que o valor da soma ultrapasse 2000 reais. Para nos ajudar na construção desses livros podemos criar uma estrutura de repetição
(um looping). Uma das formas de se fazer isso seria utilizando o while:
double soma = 0;
int contador = 0;
while (contador < 35) {
double valorDoLivro = 59.90;
soma = soma + valorDoLivro;
contador = contador + 1 ;
}
O while é bastante parecido com o if. A grande diferença é que, en-
quanto sua expressão booleana for true, seu código continuará sendo exe-
cutado. Neste exemplo estamos criando uma variável soma para acumular
o valor total dos livros e também uma variável contador para controlar a
quantidade de vezes que queremos iterar.
Note que estamos adicionando 35 livros, já que a condição é contador
< 35 e que a cada iteração incrementamos 1 ao valor do contador.
Nosso código completo fica da seguinte forma:
public class CalculadoraDeEstoque {
public static void main(String[] args) {
double soma = 0;
int contador = 0;
while (contador < 35) {
double valorDoLivro = 59.90;
soma = soma + valorDoLivro;
contador = contador + 1 ;
}
21
2.6. Loopings e mais loopings
Casa do Código
System.out.println("O total em estoque é "+ soma);
if (soma < 150) {
System.out.println("Seu estoque está muito baixo!");
} else if (soma >= 2000) {
System.out.println("Seu estoque está muito alto!");
} else {
System.out.println("Seu estoque está bom");
}
}
}
Ao executá-lo a saída será:
O total em estoque é 2096.5000000000014
Seu estoque está muito alto!
Looping infinito
É muito comum esquecermos de modificar o valor que está sendo
comparado dentro do while ou nas diferentes formas que veremos para
se fazer um looping. Isso vai resultar no que chamamos de looping infinito, uma vez que a condição de looping sempre será true. Se quiser testar,
adicione um comentário na linha que incrementa o valor do contador
e execute o código:
while (contador < 35) {
double valorDoLivro = 59.90;
soma = soma + valorDoLivro;
// contador = contador + 1;
}
Podemos deixar nosso código ainda mais enxuto utilizando o operador
de atribuição +=. Basta, no lugar de fazer:
soma = soma + valorDoLivro;
22

Casa do Código
Capítulo 2. Variáveis e tipos primitivos
Mudarmos o código para:
soma += valorDoLivro;
Essa é uma forma equivalente de se incrementar o valor de uma variável.
O mesmo poderia ser feito com nosso contador, repare:
contador += 1;
Podemos utilizar essa técnica com os seguintes operadores:
Mas para esse caso há uma forma ainda mais enxuta, utilizando o oper-
ador unário ++:
contador ++;
O código do while agora fica da seguinte forma:
while (contador < 35) {
double valorDoLivro = 59.90;
soma += valorDoLivro;
contador ++;
}
23
2.6. Loopings e mais loopings
Casa do Código
Nosso código pode ficar ainda mais enxuto utilizando uma outra forma
de looping extremamente conhecida e utilizada, o for.
Observe que a estrutura que utilizamos no while foi parecida com:
// inicialização do contador
while (condição) {
// atualização do contador
}
Com o for, podemos fazer isso de uma forma ainda mais direta:
for(inicialização; condição; atualização) {
}
Isso mesmo, além da condição booleana ele também reserva um local para inicialização de variáveis e atualização de seus valores. Com o for nosso
código pode ser escrito assim:
double soma = 0;
for (int contador = 0; contador < 35; contador ++) {
soma += 59.90;
}
Sem dúvida, é uma forma mais direta. Você muitas vezes verá essa variável
contador que criamos ser chamada de i ( index, ou índice). Seguindo esse padrão, nosso código completo fica assim:
public class CalculadoraDeEstoque {
public static void main(String[] args) {
double soma = 0;
for (double i = 0; i < 35; i + + ) {
soma += 59.90;
}
24
Casa do Código
Capítulo 2. Variáveis e tipos primitivos
System.out.println("O total em estoque é "+ soma);
if (soma < 150) {
System.out.println("Seu estoque está muito baixo!");
} else if (soma >= 2000) {
System.out.println("Seu estoque está muito alto!");
} else {
System.out.println("Seu estoque está bom");
}
}
}
25
2.6. Loopings e mais loopings
Casa do Código
Trabalhando com continue e break
Você pode utilizar a palavra-chave continue para pular uma iter-
ação de seu looping e forçar a execução do próximo laço. O código a
seguir vai imprimir todos os números de 0 a 10, mas vai pular o número
7:
for(int i = 0; i <= 10; i++) {
if (i == 7) {
continue;
}
System.out.println(i);
}
Outra possibilidade comum é parar a execução de um looping
dada uma determinada condição. Para isso, utilizamos a palavra-chave
break:
for(int i = 0; i <= 10; i++) {
if (i == 7) {
break;
}
System.out.println(i);
}
Neste caso, apenas os números de 0 a 6 serão impressos.
26
Casa do Código
Capítulo 2. Variáveis e tipos primitivos
Operadores lógicos
Você pode e deve usar operadores lógicos em suas expressões
booleanas sempre que necessário. Por exemplo, repare no código a
seguir:
if (v1 > v2) {
if (v2 < v3) {
// é > que v1 e < que v3
}
}
Esse encadeamento de ifs prejudica um pouco a legibilidade, não
acha? Que tal fazer:
if (v1 > v2 && v2 < v3) {
// é > que v1 e < que v3
}
O código terá o mesmo efeito com um único if, agora usando o
operador and ( &&). Outras opções são o or ( ||) e a negativa ( !). Para negar uma condição booleana tudo o que você precisa fazer é adicionar
o sinal ! antes de sua declaração, veja:
boolean condicao = 1 > 0;
if (!condicao) {
// não vai entrar aqui
}
27
Capítulo 3
Orientação a objetos
A linguagem Java tem como forte característica ter como paradigma a orien-
tação a objetos, que estudaremos profundamente no decorrer do livro. Esse paradigma existe desde a década de 70, mas foi depois do surgimento do Java
que ficou bastante famoso e que passou a ser levado mais a sério.
Repare que nossa CalculadoraDeEstoque está fazendo todo o tra-
balho dentro de seu método main, ainda de forma muito procedural. A ori-
entação a objetos propõe uma maneira diferente de fazer isso, você passa a
trabalhar de um jeito mais próximo à realidade humana. Para cada necessi-
dade importante teremos objetos que interagem entre si e que são compostos
por estado (atributos) e comportamento (métodos). Quer um exemplo? Ob-
serve como estamos representando o preço de nossos livros:
double soma = 0;
3.1. Criando um molde de livros
Casa do Código
for (double i = 0; i < 35; i ++) {
soma += 59.90;
}
O valor 59.90 está fazendo isso. Ele representa o valor do livro; mas, e
quanto ao seu nome, descrição e demais informações? Todas essas infor-
mações representam o que um livro tem e são extremamente importantes
para nosso sistema. O grande problema do paradigma procedural é que não
existe uma forma simples de conectar todos esses elementos, já na orientação
a objetos podemos fazer isso de um jeito muito simples! Assim como no con-
texto real, podemos criar um objeto para representar tudo o que um livro tem
e o que ele faz.
Uma nova forma de pensar
Se você já está acostumado com algum outro paradigma, esse é o
momento de abrir a sua mente. Repare que esse é um paradigma to-
talmente diferente, você precisará pensar de maneira diferente e escrever
seu código de outra forma.
3.1
Criando um molde de livros
Vamos criar uma nova classe Java chamada Livro. Essa classe será um
molde, que representará o que um livro deve ter e como ele deve se comportar
em nosso sistema.
public class Livro {
}
Para deixar essa classe mais interessante, vamos adicionar alguns campos
para representar o que um livro tem. Esses são os atributos de nossa classe: public class Livro {
String nome;
30
Casa do Código
Capítulo 3. Orientação a objetos
String descricao;
double valor;
String isbn;
}
Repare que esses atributos são muito parecidos com variáveis que pode-
mos criar dentro de nosso método main, por exemplo. Mas eles não estão
dentro de um bloco e, sim, dentro do escopo da classe, por isso recebem o
nome diferenciado de atributo.
Nosso molde já está pronto para uso. Por enquanto, um livro terá um
nome, descrição, valor e ISBN (um número de identificação, International
Standard Book Number).
Esses campos não serão populados na classe Livro, ela é apenas o molde!
O que precisamos é criar um objeto a partir desse molde. Para fazer isso,
utilizamos a palavra-chave new.
Livro livro = new Livro();
Observe que a variável livro tem um tipo, assim como qualquer variável
em Java, mas diferente de um int, double ou String como já estamos
acostumados, agora seu tipo é a própria classe Livro.
Ao criar um objeto de Livro e atribuir a uma variável livro, estamos
estabelecendo uma forma de nos referenciar a esse objeto que até então não
tem nenhum valor populado.
Populando os atributos do livro
Agora que fizemos isso, a partir da variável livro, podemos acessar o
objeto que foi criado em memória e popular os seus atributos. Um exemplo
seria:
Livro livro = new Livro();
livro.nome = "Java 8 Prático";
livro.descricao = "Novos recursos da linguagem";
livro.valor = 59.90;
livro.isbn = "978-85-66250-46-6";
31
3.1. Criando um molde de livros
Casa do Código
Repare que utilizamos um . (ponto) para acessar os atributos desse objeto
em específico. Você também pode recuperar as informações adicionadas nos
seus objetos acessando seus atributos da seguinte forma:
System.out.println("O nome do livro é " + livro.nome);
Neste caso, a saída será:
O nome do livro é Java 8 Prático
Vamos criar uma nova classe chamada CadastroDeLivros em nosso
projeto. Ela deve ter um método main que cria um novo livro, preenche
alguns de seus atributos e depois imprime os seus valores. Algo como:
public class CadastroDeLivros {
public static void main(String[] args) {
Livro livro = new Livro();
livro.nome = "Java 8 Prático";
livro.descricao = "Novos recursos da linguagem";
livro.valor = 59.90;
livro.isbn = "978-85-66250-46-6";
System.out.println(livro.nome);
System.out.println(livro.descricao);
System.out.println(livro.valor);
System.out.println(livro.isbn);
}
}
Execute a classe para ver a saída! Deverá ser:
Java 8 Prático
Novos recursos da linguagem
59.9
978-85-66250-46-6
32
Casa do Código
Capítulo 3. Orientação a objetos
Classe x Objeto
Uma classe é apenas um molde. Uma especificação que define para a
máquina virtual o que um objeto desse tipo deverá ter e como ele deve
se comportar. Nossa livraria poderá ter milhares de livros (objetos), mas
existirá apenas uma classe Livro (molde). Cada objeto que criarmos
do tipo Livro terá seus próprios valores, ou seja, cada livro terá o seu
próprio nome, sua descrição, um valor e um número de ISBN.
Nosso CadastroDeLivros está muito simples, com um único livro.
Vamos criar mais um, já com seus atributos preenchidos:
public class CadastroDeLivros {
public static void main(String[] args) {
Livro livro = new Livro();
livro.nome = "Java 8 Prático";
livro.descricao = "Novos recursos da linguagem";
livro.valor = 59.90;
livro.isbn = "978-85-66250-46-6";
System.out.println(livro.nome);
System.out.println(livro.descricao);
System.out.println(livro.valor);
System.out.println(livro.isbn);
Livro outroLivro = new Livro();
outroLivro.nome = "Lógica de Programação";
outroLivro.descricao = "Crie seus primeiros programas";
outroLivro.valor = 59.90;
outroLivro.isbn = "978-85-66250-22-0";
System.out.println(outroLivro.nome);
System.out.println(outroLivro.descricao);
System.out.println(outroLivro.valor);
System.out.println(outroLivro.isbn);
33
3.2. Criando um novo método
Casa do Código
}
}
Porém, observe que até agora nossa classe Livro só tem atributos, ou
seja, só guarda valores. Vimos que uma classe vai além, ela também pode ter
comportamentos (métodos).
3.2
Criando um novo método
A todo momento que criamos um novo objeto do tipo Livro, estamos im-
primindo seus valores. Essa é uma necessidade comum entre todos os livros
de nosso sistema, mas da forma como estamos fazendo, toda hora repetimos
as mesmas 4 linhas de código:
System.out.println(livro.nome);
System.out.println(livro.descricao);
System.out.println(livro.valor);
System.out.println(livro.isbn);
A única coisa que muda é o nome da variável, de
livro para
outroLivro.
Pense na seguinte situação, em breve nosso cadastro terá cerca de 100
livros. Para cada livro, teremos 4 linhas de código imprimindo os seus 4 atrib-
utos. Ou seja, teremos 400 linhas de código muito parecidas, praticamente
repetidas.
Essa repetição de código sempre tem um efeito colateral de-
sagradável, que é a dificuldade de manutenção.
Quer ver?
O que
acontece se adicionarmos um novo atributo no livro, por exemplo a
data de seu lançamento?
Para imprimir a data toda vez que criar um
livro, teremos que mudar 100 partes de nosso código, adicionando o
System.out.println(livro.dataDeLancamento).
No lugar de deixar essa lógica de impressão dos dados do livro toda espal-
hada, podemos isolar esse comportamento comum entre os livros na classe
Livro! Para isso, criamos um método. Uma forma seria:
34
Casa do Código
Capítulo 3. Orientação a objetos
void mostrarDetalhes() {
System.out.println(nome);
System.out.println(descricao);
System.out.println(valor);
System.out.println(isbn);
}
Esse método define um comportamento para classe Livro. Repare que
a sintaxe de um método é um pouco diferente do que estamos acostumados,
sua estrutura é:
tipoDeRetorno nomeDoComportamento() {
// código que será executado
}
Nesse caso, não estamos retornando nada e, sim, apenas executando in-
struções dentro do método, portanto, seu tipo de retorno é void. Essa
palavra reservada indica que um método não tem retorno.
Um método também pode ter variáveis declaradas, como por exemplo:
void mostrarDetalhes() {
String mensagem = "Mostrando detalhes do livro ";
System.out.println(mensagem);
System.out.println(nome);
System.out.println(descricao);
System.out.println(valor);
System.out.println(isbn);
}
A variável mensagem foi declarada dentro do método, logo esse será
seu escopo, ou seja, ela só existirá e poderá ser utilizada dentro do método
mostrarDetalhes.
Nossa classe Livro ficou desta forma:
public class Livro {
String nome;
String descricao;
35
3.2. Criando um novo método
Casa do Código
double valor;
String isbn;
void mostrarDetalhes() {
String mensagem = "Mostrando detalhes do livro ";
System.out.println(mensagem);
System.out.println(nome);
System.out.println(descricao);
System.out.println(valor);
System.out.println(isbn);
}
}
Agora que cada livro possui o comportamento de exibir os seus detalhes,
podemos remover as linhas que faziam esse trabalho no método main e pas-
sar a invocar esse novo método. Repare:
public class CadastroDeLivros {
public static void main(String[] args) {
Livro livro = new Livro();
livro.nome = "Java 8 Prático";
livro.descricao = "Novos recursos da linguagem";
livro.valor = 59.90;
livro.isbn = "978-85-66250-46-6";
livro.mostrarDetalhes();
Livro outroLivro = new Livro();
outroLivro.nome = "Lógica de Programação";
outroLivro.descricao = "Crie seus primeiros programas";
outroLivro.valor = 59.90;
outroLivro.isbn = "978-85-66250-22-0";
outroLivro.mostrarDetalhes();
}
}
36
Casa do Código
Capítulo 3. Orientação a objetos
Ao isolar esse comportamento dentro da classe Livro já tivemos um
ganho evidente. Tivemos que escrever menos linhas em nosso main, o
código ficou com menos repetições pois teve maior reaproveitamento. Mas
essa não é a única vantagem em isolar um comportamento: mesmo que eu
tenha 100 livros cadastrados e use o método mostrarDetalhes 100 vezes,
em quantas partes do meu código eu terei que mexer para modificar a forma
como os livros são exibidos? A resposta é: uma!
Agora que o comportamento está isolado, só precisaremos alterar o
método, um único ponto do nosso código, para refletir a mudança em to-
dos os lugares que o usam. Vamos colocar isso em prática, basta mudar o
método mostrarDetalhes para:
void mostrarDetalhes() {
System.out.println("Mostrando detalhes do livro ");
System.out.println("Nome: " + nome);
System.out.println("Descrição: " + descricao);
System.out.println("Valor: " + valor);
System.out.println("ISBN: " + isbn);
System.out.println("--");
}
Execute agora o main da classe CadastroDeLivros. Sem precisar mu-
dar nenhuma linha de seu código, o resultado será:
Mostrando detalhes do livro
Nome: Java 8 Prático
Descrição: Novos recursos da linguagem
Valor: 59.9
ISBN: 978-85-66250-46-6
--
Mostrando detalhes do livro
Nome: Lógica de Programação
Descrição: Crie seus primeiros programas
Valor: 59.9
ISBN: 978-85-66250-22-0
--
Nosso código agora tem uma manutenabilidade muito maior! Sempre
devemos criar métodos de forma genérica e reaproveitável, assim será muito 37
3.3. Objetos para todos os lados!
Casa do Código
mais fácil e produtivo evoluir o código no futuro.
3.3
Objetos para todos os lados!
Falando em evoluir código, precisamos saber mais informações sobre nos-
sos livros. Por exemplo, quem escreveu o livro? Qual o e-mail do autor? E
quando foi a sua data de publicação? Todas essas informações são relevantes
para nossa livraria e também para nossos clientes. Podemos adicionar essas e
outras informações na classe Livro:
public class Livro {
String nome;
String descricao;
double valor;
String isbn;
String nomeDoAutor;
String emailDoAutor;
String cpfDoAutor;
void mostrarDetalhes() {
System.out.println("Mostrando detalhes do livro ");
System.out.println("Nome: " + nome);
System.out.println("Descrição: " + descricao);
System.out.println("Valor: " + valor);
System.out.println("ISBN: " + isbn);
System.out.println("--");
}
}
Mas repare que todas essas novas informações pertencem ao autor do
livro e não necessariamente ao livro. Se autor é um elemento importante
para nosso sistema, ele pode e deve ser representado como um objeto! Vamos
fazer essa alteração, basta criar a classe Autor e declarar seus atributos:
public class Autor {
String nome;
38
Casa do Código
Capítulo 3. Orientação a objetos
String email;
String cpf;
}
Portanto, podemos adicionar na classe Livro um atributo do tipo
Autor, que acabamos de criar. Uma classe pode ter outra classe como atrib-
uto, esse é um processo natural conhecido como composição. Nosso código
fica assim:
public class Livro {
String nome;
String descricao;
double valor;
String isbn;
Autor autor;
void mostrarDetalhes() {
System.out.println("Mostrando detalhes do livro ");
System.out.println("Nome: " + nome);
System.out.println("Descrição: " + descricao);
System.out.println("Valor: " + valor);
System.out.println("ISBN: " + isbn);
System.out.println("--");
}
}
Vamos agora criar alguns autores no CadastroDeLivros e associar ao
seu devido livro. Uma forma de fazer isso seria:
Autor autor = new Autor();
autor.nome = "Rodrigo Turini";
autor.email = "[email protected]";
autor.cpf = "123.456.789.10";
Nesse código, apenas criamos o autor e populamos os seus atributos.
Agora precisamos associar esse objeto ao seu livro. Podemos simplesmente
fazer:
livro.autor = autor;
39
3.3. Objetos para todos os lados!
Casa do Código
Vamos fazer isso com os dois livros, nosso código completo deve ficar
parecido com:
public class CadastroDeLivros {
public static void main(String[] args) {
Autor autor = new Autor();
autor.nome = "Rodrigo Turini";
autor.email = "[email protected]";
autor.cpf = "123.456.789.10";
Livro livro = new Livro();
livro.nome = "Java 8 Prático";
livro.descricao = "Novos recursos da linguagem";
livro.valor = 59.90;
livro.isbn = "978-85-66250-46-6";
livro.autor = autor;
livro.mostrarDetalhes();
Autor outroAutor = new Autor();
outroAutor.nome = "Paulo Silveira";
outroAutor.email = "[email protected]";
outroAutor.cpf = "123.456.789.10";
Livro outroLivro = new Livro();
outroLivro.nome = "Lógica de Programação";
outroLivro.descricao = "Crie seus primeiros programas";
outroLivro.valor = 59.90;
outroLivro.isbn = "978-85-66250-22-0";
outroLivro.autor = outroAutor;
outroLivro.mostrarDetalhes();
}
}
40
Casa do Código
Capítulo 3. Orientação a objetos
Referência a objetos
É fundamental perceber que, quando instanciamos um novo objeto com a
palavra reservada new, um Autor por exemplo, guardamos em sua variável
uma referência para esse objeto, e não seus valores. Ou seja, a variável autor
não guarda o valor de um nome, email e outros atributos da classe Autor,
mas sim uma forma de acessar esses atributos do autor em memória. Muito
diferente de quando trabalhamos com tipos primitivos que guardam uma
cópia do valor.
Ainda não ficou claro? Observe o seguinte exemplo:
public class ComparandoReferencias {
public static void main(String[] args) {
Autor autor = new Autor();
autor.nome = "Rodrigo Turini";
autor.email = "[email protected]";
autor.cpf = "123.456.789.10";
Autor autor2 = new Autor();
autor2.nome = "Rodrigo Turini";
autor2.email = "[email protected]";
autor2.cpf = "123.456.789.10";
}
}
Aqui estamos criando dois autores com as mesmas informações, mas o
que acontece se eu compará-los da seguinte forma?
if (autor == autor2) {
System.out.println("Iguais, mesmo autor!");
} else {
System.out.println("hein!? Por que diferentes?");
}
Rode o código para conferir o resultado! A saída será:
hein!? Por que diferentes?
41

3.3. Objetos para todos os lados!
Casa do Código
A resposta é simples, quando comparamos utilizando o == estamos com-
parando o valor da variável, que sempre será a referência (endereço) para
onde encontrar o objeto na memória. E ,claro, cada objeto que criamos fica em um endereço diferente na memória.
A imagem a seguir representa os objetos em memória e cada variável de
referência:
Que ir além? Observe esse código:
Autor autor = new Autor();
autor.nome = "Rodrigo Turini";
Livro livro = new Livro();
livro.autor = autor;
livro.autor.nome = "Guilherme Silveira";
System.out.println(autor.nome);
Estamos criando um autor e adicionando o nome Rodrigo Turini.
Depois disso, criamos um livro e o associamos a esse autor. Mas re-
pare que em seguida mudamos livro.autor.nome para Guilherme
42

Casa do Código
Capítulo 3. Orientação a objetos
Silveira. Qual será a saída ao imprimir o nome do autor original (
autor.nome)?
Execute o código para ver a saída. Se você estava esperando Rodrigo
Turini, não fique desapontado.
Acontece que, depois de associar a referência de autor ao livro, es-
tamos atribuindo ao atributo livro.autor o mesmo endereço (referência)
de memória que tem o autor. Ou seja, das duas formas a seguir estamos
acessando o mesmo objeto:
autor.nome = "Rodrigo Turini";
livro.autor.nome = "Guilherme Silveira";
Achou complicado? Não se preocupe, durante a leitura faremos muitas
atribuições e relacionamentos entre objetos. Afinal, com certeza isso fará
parte de seu dia a dia.
Mais e mais métodos
Nosso código evoluiu, agora todo Livro pode ter um Autor. Podemos
evoluir nosso método mostrarDetalhes para que ele passe a mostrar as
informações do autor de cada livro. Uma forma de fazer isso seria:
43
3.3. Objetos para todos os lados!
Casa do Código
void mostrarDetalhes() {
System.out.println("Mostrando detalhes do livro ");
System.out.println("Nome: " + nome);
System.out.println("Descrição: " + descricao);
System.out.println("Valor: " + valor);
System.out.println("ISBN: " + isbn);
System.out.println("Mostrando detalhes do autor ");
System.out.println("Nome: " + autor.nome);
System.out.println("Email: " +autor.email);
System.out.println("CPF: " + autor.cpf);
System.out.println("--");
}
Isso funcionaria bem, mas ainda não é a solução ideal. O problema desse
código é que a classe Livro está fazendo mais do que é sua responsabili-
dade. Mostrar as informações de autor deveria ser um comportamento da
classe Autor! E, além disso, o que aconteceria caso nosso próximo objetivo
fosse mostrar apenas os dados de um autor, sem as informações de seu livro?
Teríamos que repetir todas essas ultimas linhas de código! Veja:
System.out.println("Mostrando detalhes do autor ");
System.out.println("Nome: " + autor.nome);
System.out.println("Email: " +autor.email);
System.out.println("CPF: " + autor.cpf);
Para evitar isso, podemos isolar esse comportamento na classe Autor!
Basta criar um método mostrarDetalhes em seu modelo também:
public class Autor {
String nome;
String email;
String cpf;
void mostrarDetalhes() {
System.out.println("Mostrando detalhes do autor ");
System.out.println("Nome: " + nome);
System.out.println("Email: " + email);
44
Casa do Código
Capítulo 3. Orientação a objetos
System.out.println("CPF: " + cpf);
}
}
Pronto! Mais uma vez evitamos repetição de código usando bem a orien-
tação a objetos. Toda classe pode e deve, além de seus atributos, ter compor-
tamentos bem definidos, isolando suas regras de negócio. Podemos agora
remover essas linhas repetidas do método mostrarDetalhes da classe
Livro e apenas delegar a chamada desse novo método:
void mostrarDetalhes() {
System.out.println("Mostrando detalhes do livro ");
System.out.println("Nome: " + nome);
System.out.println("Descrição: " + descricao);
System.out.println("Valor: " + valor);
System.out.println("ISBN: " + isbn);
autor.mostrarDetalhes();
System.out.println("--");
}
Depois de fazer essas mudanças, vamos executar mais uma vez a classe
CadastroDeLivros. A saída será:
Mostrando detalhes do livro
Nome: Java 8 Prático
Descrição: Novos recursos da linguagem
Valor: 59.9
ISBN: 978-85-66250-46-6
Mostrando detalhes do autor
Nome: Rodrigo Turini
Email: [email protected]
CPF: 123.456.789.10
--
Mostrando detalhes do livro
Nome: Lógica de Programação
Descrição: Crie seus primeiros programas
Valor: 59.9
ISBN: 978-85-66250-22-0
Mostrando detalhes do autor
45
3.3. Objetos para todos os lados!
Casa do Código
Nome: Paulo Silveira
Email: [email protected]
CPF: 123.456.789.10
--
Métodos com parâmetro
Depois de instanciado, um objeto do tipo Livro pode precisar ter seu
valor ajustado. É comum aplicar um desconto no preço de um livro, por ex-
emplo. Podemos escrever o seguinte código para aplicar 10% de desconto no
valor de um livro:
public static void main(String[] args) {
Livro livro = new Livro();
livro.valor = 59.90;
System.out.println("Valor atual: " + livro.valor);
livro.valor -= livro.valor * 0.1;
System.out.println("Valor com desconto: " + livro.valor);
}
Note que a linha abaixo está calculando a porcentagem de desconto e já
subtraindo do valor do livro.
livro.valor -= livro.valor * 0.1;
Executando o código, teremos a saída esperada, que é:
Valor atual: 59.9
Valor com desconto: 53.91
Mas esse código não está nem um pouco orientado a objetos, estamos
escrevendo um comportamento do livro direto no método main! Já vimos
qual o problema em fazer isso, imagine que existam 100 livros e eu aplique
desconto em todos eles. A nossa lógica de aplicar desconto vai ficar repetida
e espalhada por todo o código.
46
Casa do Código
Capítulo 3. Orientação a objetos
Como evitar isso? Se aplicar um desconto em seu valor é um comporta-
mento do livro, podemos isolar esse comportamento em um novo método!
No lugar de fazer:
livro.valor -= livro.valor * 0.1;
Podemos criar um método aplicaDescontoDe que recebe um valor
como parâmetro. Repare:
livro.aplicaDescontoDe(0.1);
Sim, um método pode e deve receber parâmetros sempre que for preciso.
Veja como fica a sua implementação:
public void aplicaDescontoDe(double porcentagem) {
valor -= valor * porcentagem;
}
Note que todo parâmetro de método também precisa ter um nome e
tipo definido. A porcentagem receberá o valor 0.1 ( double) que foi pas-
sado como argumento no momento em que o método foi invocado em nosso
método main.
47
3.3. Objetos para todos os lados!
Casa do Código
Nomes ambíguos e o T H I S
O que aconteceria se o nome do parâmetro do método fosse igual ao
nome do atributo da classe? Veja como ficaria o nosso código:
public void aplicaDescontoDe(double valor) {
valor -= valor * valor;
}
No lugar de uma porcentagem, chamamos o parâmetro de valor,
assim como o nome do atributo da classe Livro. Como o Java vai saber
qual valor queremos atualizar? A resposta é: ele não vai. Nosso código
vai subtrair e multiplicar o valor do parâmetro por ele mesmo, já que este
tem um escopo menor que o atributo da classe. Ou seja, mesmo com a
ambiguidade neste caso o código vai compilar, mas o valor considerado
será o que possui o menor escopo.
Para evitar esse problema, podemos utilizar a palavra reservada this
para mostrar que esse é um atributo da classe. Ainda que seja op-
cional, é sempre uma boa prática usar o this em atributos para evitar
futuros problemas de ambiguidade e também para deixar claro que este
é um atributo da classe, e não uma simples variável.
Com isso, o código de nosso método aplicaDescontoDe fica assim:
public void aplicaDescontoDe(double porcentagem) {
this.valor -= this.valor * porcentagem;
}
E a classe Livro completa:
public class Livro {
String nome;
String descricao;
double valor;
String isbn;
Autor autor;
48
Casa do Código
Capítulo 3. Orientação a objetos
void mostrarDetalhes() {
System.out.println("Mostrando detalhes do livro ");
System.out.println("Nome: " + nome);
System.out.println("Descrição: " + descricao);
System.out.println("Valor: " + valor);
System.out.println("ISBN: " + isbn);
autor.mostrarDetalhes();
System.out.println("--");
}
public void aplicaDescontoDe(double porcentagem) {
this.valor -= this.valor * porcentagem;
}
}
Métodos com retorno
Nossa classe Livro já possui dois métodos, mas os dois são void. Vimos
que o void representa a ausência de um retorno, mas há situações em que
precisaremos retornar algo. Por exemplo, como saber se um livro tem ou
não um autor? Um alternativa seria criar um método temAutor, tendo um
boolean como retorno. Observe um exemplo de uso:
if(livro.temAutor()) {
System.out.print("O nome do autor desse livro é ");
System.out.println(livro.autor.nome);
}
A implementação desse método é bem simples, ele não receberá nenhum
parâmetro e terá o tipo de retorno boolean:
boolean temAutor(){
// o que fazer aqui?
}
Mas como saber se o autor do livro existe ou não? Na verdade, é bem
simples: quando um objeto não foi instanciado, ele não tem nenhuma refer-
ência, portanto seu valor será null. Sabendo disso, tudo o que precisamos
49
3.3. Objetos para todos os lados!
Casa do Código
fazer no corpo do método é retornar um boolean, informando se o atrib-
uto autor é diferente de null. Para retornar um valor, utilizamos a palavra
reservada return:
boolean temAutor(){
boolean naoEhNull = this.autor != null;
return naoEhNull;
}
Podemos fazer isso de uma forma ainda mais simples, retornando direta-
mente a expressão booleana sem a criação da variável naoEhNull:
boolean temAutor(){
return this.autor != null;
}
Agora que o Livro ganhou esse novo comportamento, vamos tirar
proveito dele dentro do método mostrarDetalhes da própria classe. Pode-
mos mostrar os detalhes do autor apenas quando ele existir. Uma forma de
fazer isso seria:
void mostrarDetalhes() {
System.out.println("Mostrando detalhes do livro ");
System.out.println("Nome: " + nome);
System.out.println("Descrição: " + descricao);
System.out.println("Valor: " + valor);
System.out.println("ISBN: " + isbn);
if (this.temAutor()) {
autor.mostrarDetalhes();
}
System.out.println("--");
}
Pronto! Ao imprimir os detalhes de um livro que não tem autor, apenas
os dados do livro serão exibidos.
50
Casa do Código
Capítulo 3. Orientação a objetos
3.4
Entendendo a construção de um objeto
Agora que já conhecemos um pouco sobre objetos, podemos entender melhor
como funciona seu processo de construção. Repare na sintaxe utilizada para
criar um novo Livro:
Livro livro = new Livro();
Quando escrevemos a instrução Livro() seguida da palavra reservada
new, estamos pedindo para a JVM procurar a classe Livro e invocar o seu
construtor, que se parece com:
public Livro(){
}
Um construtor é bastante parecido com um método comum, mas ele
não é um. Diferente dos métodos, um construtor tem o mesmo nome da
classe e não tem um retorno declarado.
Mas, se nunca escrevemos esse construtor, quem o fez? Sempre que você
não criar um construtor para suas classes, o compilador fará isso para você.
Quer uma prova? Vamos utilizar o programa javap que já vem no JDK
para ver o código compilado. Acesse pelo terminal do seu sistema operacional
a pasta bin de seu projeto e rode o comando javap Livro. A saída deverá
se parecer com:
Turini@ ~/Documents/workspace/livro-oo > javap Livro
Compiled from "Livro.java"
public class Livro {
java.lang.String nome;
java.lang.String descricao;
double valor;
java.lang.String isbn;
public Livro();
void mostrarDetalhes();
public void aplicaDescontoDe(double);
}
Note que, mesmo sem criar o construtor vazio, ele está presente em nosso
código compilado:
51
3.4. Entendendo a construção de um objeto
Casa do Código
public Livro();
E, sim, todo código que estiver declarado dentro do construtor será ex-
ecutado quando um objeto desse tipo for criado. Por exemplo, o código a
seguir imprime uma mensagem sempre que criarmos um novo Livro:
public class Livro {
String nome;
String descricao;
double valor;
String isbn;
Autor autor;
public Livro() {
System.out.println("novo livro criado");
}
// demais métodos da classe
}
Para testar, podemos criar alguns livros dentro de um método main e
executar esse código:
public static void main(String[] args) {
Livro livro1 = new Livro();
Livro livro2 = new Livro();
Livro livro3 = new Livro();
Livro livro4 = new Livro();
}
Como já é esperado, a saída será:
novo livro criado
novo livro criado
novo livro criado
novo livro criado
Agora que já temos um construtor, o compilador não vai criar mais nen-
hum. Ele só faz isso quando a sua classe não tem nenhum construtor definido.
52
Casa do Código
Capítulo 3. Orientação a objetos
Veremos mais adiante que construtores também podem receber parâmetros
e inicializar os atributos de suas classes.
3.5
Vantagens da orientação a objetos
Vimos neste capitulo apenas algumas das muitas vantagens de se trabalhar
com orientação a objetos. Observe com atenção o código que criamos. Você
vai conseguir perceber que uma das grandes diferenças da OO é que temos
uma forma forte de criar conexão entre informações e funcionalidades. Além
disso, nosso código fica muito mais organizado e evitamos muita repetição.
O que ganhamos? Um código mais flexível e, com isso, mais fácil de evoluir
e manter.
Esse é só o começo, no decorrer do livro veremos vários conceitos e recur-
sos como encapsulamento, herança, polimorfismo e muitos outros que tornam o paradigma ainda mais interessante.
53
Capítulo 4
Encapsulamento
4.1
Limitando desconto do Livro
Quando criamos o método aplicaDescontoDe na classe Livro, não de-
terminamos a porcentagem total de desconto que um livro pode ter, mas isso é
muito importante para nossa regra de negócio. Um livro pode ter no máximo
30% de desconto.
Uma forma simples de evoluir nosso método seria:
public boolean aplicaDescontoDe(double porcentagem) {
if (porcentagem > 0.3) {
return false;
}
this.valor -= this.valor * porcentagem;
return true;
}
4.1. Limitando desconto do Livro
Casa do Código
Repare que agora estamos retornando um boolean para indicar ao
usuário se o desconto foi aplicado ou não. No momento de usá-lo podemos
fazer algo como:
if (!livro.aplicaDescontoDe(0.1)){
System.out.println("Desconto não pode ser maior do que 30%");
}
Código flexível e reaproveitável
O método aplicaDescontoDe poderia, sim, no lugar de retornar
um boolean já imprimir a mensagem de validação do limite de de-
sconto, como por exemplo:
public void aplicaDescontoDe(double porcentagem) {
if (porcentagem > 0.3) {
System.out.println("Desconto não pode ser
maior do que 30%");
}
this.valor -= this.valor * porcentagem;
}
Com isso, não seria necessário escrever o if em nosso método main,
mas repare que estamos perdendo a flexibilidade de customizar nos-
sas mensagens. Será que todos que forem utilizar esse método querem
mostrar a mensagem Desconto não pode ser maior do que 30%? E se eu
quiser mudar esse texto dependendo do usuário ou simplesmente não
mostrar a mensagem de validação em algum momento?
Lembre-se sempre de evitar deixar informações tão específicas em
seus moldes, além de não sobrecarregá-los com comportamentos que
não deveriam ser de sua responsabilidade.
Pronto! Nossa lógica já está bem definida, agora um livro não pode ter
um desconto maior do que 30%. Mas vamos testá-la para ter certeza. Para
isso, basta criar a classe RegrasDeDesconto com o seguinte cenário:
public class RegrasDeDesconto {
56
Casa do Código
Capítulo 4. Encapsulamento
public static void main(String[] args) {
Livro livro = new Livro();
livro.valor = 59.90;
System.out.println("Valor atual: " + livro.valor);
if (!livro.aplicaDescontoDe(0.1)){
System.out.println("Desconto não pode ser
maior do que 30%");
} else {
System.out.println("Valor com desconto: "
+ livro.valor);
}
}
}
Ao executá-la, teremos o resultado:
Valor atual: 59.9
Valor com desconto: 53.91
E, mudando o valor do desconto para 0.4, a saída será:
Valor atual: 59.9
Desconto não pode ser maior do que 30%
Excelente! É exatamente o comportamento que estamos esperando. Mas
há um problema grave. Nada obriga o desenvolvedor a utilizar o método
aplicaDescontoDe, ele poderia perfeitamente escrever o código desta
forma:
public class RegrasDeDesconto {
public static void main(String[] args) {
Livro livro = new Livro();
livro.valor = 59.90;
System.out.println("Valor atual: " + livro.valor);
57
4.1. Limitando desconto do Livro
Casa do Código
livro.valor -= livro.valor * 0.4;
System.out.println("Valor com desconto: " + livro.valor);
}
}
Ele consegue aplicar o desconto de 40% ou mais, independente de nossa
regra de negócio.
O problema é que o atributo valor da classe Livro pode ser acessado
diretamente e modificado pelas outras lógicas do nosso sistema. Isso é muito
ruim! Como vimos, ao expor nossos dados, estamos abrindo caminho para
que os objetos possam ser modificados independente das condições verifi-
cadas em seus métodos.
Apesar de ser um problema muito sério, a solução é bastante simples.
Basta modificar a visibilidade do atributo valor, que até então é default
(padrão). Podemos restringir o acesso para esse atributo para que fique
acessível apenas pela própria classe Livro. Repare:
public class Livro {
private double valor;
// outros atributos e métodos
}
Como estamos utilizando o modificador de visibilidade
private,
ninguém mais além da própria classe Livro conseguirá acessar e modificar
esse valor. Portanto, a seguinte linha da classe RegrasDeDesconto não irá
compilar.
livro.valor -= livro.valor * 0.4;
O erro será The field Livro.valor is not visible. Ótimo,
isso fará com que a única forma de se aplicar um desconto em um livro
seja passando pela nossa regra de negócio, que está isolada no método
aplicaDescontoDe.
58
Casa do Código
Capítulo 4. Encapsulamento
Modificadores de visibilidade
Além do private e default (padrão), existem outros modifi-
cadores de visibilidade. São eles: public e protected. No momento
certo entenderemos a fundo cada um deles, mas tenha em mente desde
já que todos eles são diferentes e possuem regras bem específicas.
4.2
Isolando comportamentos
Ao alterar a visibilidade do atributo valor para private todos os lugares
que acessavam esse atributo passaram a não compilar, já que apenas a própria
classe Livro pode fazer isso. Por exemplo, observe como estávamos pas-
sando o valor do livro:
livro.valor = 59.90;
Como esse acesso não pode mais ser feito, precisaremos criar um com-
portamento (método) na classe Livro para fazer a atribuição desse valor.
Poderíamos chamá-lo de adicionaValor.
livro.adicionaValor(59.90);
Sua implementação será bem simples, observe:
void adicionaValor(double valor) {
this.valor = valor;
}
Há outro erro de compilação que precisaremos resolver. Repare na forma
como estamos imprimindo o valor do Livro:
System.out.println("Valor atual:" + livro.valor);
Bem, você já pode ter imaginado que a solução será parecida. Preciso
criar um método retornaValor que não receberá nenhum argumento e
retornará o atributo double valor. Algo como:
59
4.2. Isolando comportamentos
Casa do Código
double retornaValor() {
return this.valor;
}
E como esse método não recebe parâmetros, o uso do this será opcional.
Agora podemos imprimir dessa forma:
System.out.println("Valor atual: " + livro.retornaValor());
Quando usar o private?
Isso resolveu o problema do valor do livro, mas e quanto aos demais
atributos? Quando devemos deixar um atributo de classe com visibilidade
private? Você deve estar pensando: “quando não quero que ninguém acesse
esse atributo diretamente
, e é verdade. Mas a questão é: quando eu quero que alguém acesse algum
atributo de forma direta?
O nome do livro está com visibilidade default, portanto estamos adi-
cionando da seguinte forma:
livro.nome = "Java 8 Prático";
Considere que depois de alguns meses apareça a necessidade de validar
que a String passada possui ao menos duas letras, caso contrário não será
um nome válido. Ou seja, nosso código precisará fazer algo como:
livro.nome = "Java 8 Prático";
if(livro.nome.lenght() < 2) {
System.out.println("Nome inválido");
}
Repare que o método lenght foi usado para retornar o tamanho de uma
String. Além desse há diversos outros métodos úteis definidos na classe
String, que conheceremos melhor adiante. O importante agora é perce-
ber que, se acessamos o atributo nome diretamente em mil partes de nosso
código, precisaremos replicar essa mudança em mil lugares. Nada fácil.
Já vimos que todo comportamento da classe Livro deveria ser isolado
em um método, assim evitamos repetição de código possibilitando o reúso
60
Casa do Código
Capítulo 4. Encapsulamento
desse método por toda a aplicação. O ideal seria desde o começo ter criado
um método adicionaNome com esse comportamento.
Mas apenas criar o método adicionaNome não teria resolvido o prob-
lema, percebe? Se a visibilidade do atributo ainda fosse default, haveria
duas formas de se adicionar um nome ao livro e nossa lógica de adicionar
nome ainda poderia estar espalhada pela aplicação.
Como garantir que isso nunca aconteça? Usando private sempre! Todo
atributo de classe deve ser privado, assim garantimos que ninguém os acesse
diretamente e viole as nossas regras de negócio.
4.3
Código encapsulado
Essa mudança que iniciamos em nossa classe faz parte de um conceito muito
importante da OO conhecido como encapsulamento. A ideia é simplesmente
esconder todos os atributos de suas classes (deixando-os private) e encap-
sular seus comportamentos em métodos. Além disso, encapsular é esconder como funcionam suas regras de negócio, os seus métodos.
Um bom termômetro para descobrir se o seu código está bem encapsu-
lado é fazendo as duas perguntas:
• O que esse código faz?
• Como esse código faz?
Veja por exemplo quando aplicamos o desconto acessando o atributo di-
retamente:
livro.valor -= livro.valor * 0.4;
Olhando para esse código, nós conseguimos responder as duas perguntas:
o que e como é feito, mas em um código bem encapsulado você só deveria
conseguir responder a primeira.
Observe este segundo exemplo:
livro.aplicaDescontoDe(0.1);
61
4.4. Getters e Setters
Casa do Código
Repare que fica claro o que o método aplicaDescontoDe está fazendo,
mas apenas olhando para ele não conseguimos responder como isto será feito.
Se em algum momento no futuro resolvermos mandar um e-mail sempre que
algum desconto for aplicado, não precisaremos mudar milhares de partes de
nosso código, mas sim esse único método que está bem encapsulado. Por-
tanto, o encapsulamento deixa nosso código muito mais passível de mu-
danças.
Interface da classe
Os métodos públicos existentes em sua classe são comumente chama-
dos de interface da classe, afinal em um código encapsulado eles são a
única maneira de você interagir com os objetos dessa classe.
Pensar em algum contexto do mundo real pode ajudar a entender en-
capsulamento e interface da classe. Por exemplo, quando você vai escr-
ever um texto em um computador, o que importa é o teclado (a interface
que você usa para fazer isso). Pouco importa como ele funciona interna-
mente. Quando você mudar de computador ou simplesmente de tipo de
teclado, não precisará reaprender a usar um teclado.
Por mais diferente que eles funcionem internamente, isso não fará
diferença para um usuário final. Ninguém precisa desmontar a lateral
de um computador e manualmente mudar os circuitos para que o com-
putador possa interpretar os sinais enviados e mostrar os dados na tela,
afinal esse comportamento está muito bem encapsulado na interface do
teclado.
4.4
Getters e Setters
Se todos os atributos de nossas classes forem private, precisaremos criar
um método sempre que quisermos que alguém consiga adicionar um valor
ao atributo e o mesmo quando quisermos que alguém consiga ler e ex-
ibir este valor. Assim como fizemos com os métodos adicionaValor e
retornaValor.
A convenção de nome para esses dois métodos é utilizar o prefixo get
62
Casa do Código
Capítulo 4. Encapsulamento
(pegar) e set (atribuir). Por exemplo, no lugar de chamar os métodos de
adicionaValor e retornaValor, podemos chamá-los de setValor e
getValor, respectivamente.
Nosso código fica assim:
public class Livro {
private double valor;
// demais atributos e métodos
public double getValor() {
return valor;
}
public void setValor(double valor) {
this.valor = valor;
}
}
Repare que o método get não recebe nenhum parâmetro e apenas re-
torna o atributo, enquanto o set sempre recebe um parâmetro com o mesmo
tipo do que o atributo que será atualizado.
Eclipse: GGAS e Autocomplete
Criar getters e setters pode parecer um processo trabalhoso,
mas, por ser um padrão de código muito comum, as principais IDE s do
mercado possuem atalhos e templates que nos ajudam a fazer isso.
No eclipse, você pode fazer isso de forma muito produtiva utilizando
o atalho Control + 3 e depois digitando ggas. Repare que essas
são as iniciais do comando Generate Getters and Setters. Se-
lecionando esta opção você poderá escolher para quais atributos criar os
métodos e clicar em Finish.
Tudo pronto, o Eclipse gerou o código todo pra você!
63
4.4. Getters e Setters
Casa do Código
Os getters e setters possibilitam o acesso dos atributos de forma
controlada. Mas é muito importante perceber que nem todo atributo deve ter
um getter e setter, você só deve criar esses métodos quando houver uma
real necessidade. Há um post do Paulo Silveira muito interessante ilustrando
essa situação:
http://blog.caelum.com.br/2006/09/14/nao-aprender-oo-getters-e-setters/
Depois de criar os getters e setters necessários, nossa classe Livro
deve ficar assim:
public class Livro {
private String nome;
private String descricao;
private double valor;
private String isbn;
private Autor autor;
// demais métodos omitidos
public double getValor() {
return valor;
}
public void setValor(double valor) {
this.valor = valor;
}
public String getNome() {
return nome;
}
public void setNome(String nome) {
this.nome = nome;
}
public String getDescricao() {
return descricao;
}
64
Casa do Código
Capítulo 4. Encapsulamento
public void setDescricao(String descricao) {
this.descricao = descricao;
}
public String getIsbn() {
return isbn;
}
public void setIsbn(String isbn) {
this.isbn = isbn;
}
public Autor getAutor() {
return autor;
}
public void setAutor(Autor autor) {
this.autor = autor;
}
}
Precisamos fazer o mesmo para nossa classe Autor:
public class Autor {
private String nome;
private String email;
private String cpf;
// demais métodos omitidos
public String getNome() {
return nome;
}
public void setNome(String nome) {
this.nome = nome;
}
65
4.4. Getters e Setters
Casa do Código
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getCpf() {
return cpf;
}
public void setCpf(String cpf) {
this.cpf = cpf;
}
}
E, por fim, será necessário modificar a classe CadastroDeLivros para
que deixe de acessar os atributos diretamente. Seu código final deve ficar parecido com:
public class CadastroDeLivros {
public static void main(String[] args) {
Autor autor = new Autor();
autor.setNome("Rodrigo Turini");
autor.setEmail("[email protected]");
autor.setCpf("123.456.789.10");
Livro livro = new Livro();
livro.setNome("Java 8 Prático");
livro.setDescricao("Novos recursos da linguagem");
livro.setValor(59.90);
livro.setIsbn("978-85-66250-46-6");
livro.setAutor(autor);
66
Casa do Código
Capítulo 4. Encapsulamento
livro.mostrarDetalhes();
Autor outroAutor = new Autor();
outroAutor.setNome("Paulo Silveira");
outroAutor.setEmail("[email protected]");
outroAutor.setCpf("123.456.789.10");
Livro outroLivro = new Livro();
outroLivro.setNome("Lógica de Programação");
outroLivro.setDescricao("Crie seus primeiros programas");
outroLivro.setValor(59.90);
outroLivro.setIsbn("978-85-66250-22-0");
outroLivro.setAutor(outroAutor);
outroLivro.mostrarDetalhes();
}
}
4.5
Definindo dependências pelo construtor
Autor é um atributo obrigatório
Agora que nosso código já está bem encapsulado, precisamos atender outra
necessidade de nossa livraria. Todo Livro precisa ter um Autor. Isso é bem
simples: assim como já está sendo feito, logo após criar um livro, podemos
associá-lo ao seu autor pelo método setAutor:
Autor autor = new Autor();
autor.setNome("Rodrigo Turini");
// set dos outros atributos
Livro livro = new Livro();
// set dos outros atributos
livro.setAutor(autor);
Isso já resolve o problema, mas há uma questão. Dessa forma, por alguns
instantes o Livro existe em memória sem ter um Autor relacionado. E
67
4.5. Definindo dependências pelo construtor
Casa do Código
esse nem é o pior dos problemas, o que acontecerá se esquecermos de chamar
o método setAutor? A resposta é: nada. Ou seja, da forma como nosso
código está escrito, um livro pode existir sem seu autor.
Como resolver isso?
Bem, como já vimos, toda classe tem um
construtor. Ainda que eu não tenha declarado, o compilador fará isso para
mim. O que ainda não vimos é que construtores podem receber parâmetros.
Com isso, podemos obrigar a passagem de alguns valores logo no momento
de criação de nossos objetos.
Vamos criar um construtor na classe Livro que deve receber um Autor
como parâmetro. Seu código deve ficar assim:
public Livro(Autor autor) {
this.autor = autor;
}
Repare que ele ficou com a responsabilidade do método setAutor, que
é atribuir o autor do parâmetro ao atributo da classe.
A partir do momento em que criamos esse construtor com parâmetro
na classe Livro, o compilador não criará mais o construtor default (vazio).
Portanto, o seguinte código não compila:
Livro livro = new Livro();
Se o único construtor existente na classe recebe um Autor, a única forma
de fazer esse código compilar e criar um Livro será passando um Autor
como argumento. Veja:
Autor autor = new Autor();
autor.setNome("Rodrigo Turini");
// set dos outros atributos
Livro livro = new Livro(autor);
// set dos outros atributos
Claro, há situações em que queremos deixar as duas formas válidas, ou
seja, queremos criar um Livro passando ou não um Autor. Para que isso
funcionasse, precisaríamos adicionar na classe Livro um novo construtor
que não recebe nenhum argumento. Seu código ficaria assim:
68
Casa do Código
Capítulo 4. Encapsulamento
public class Livro {
private String nome;
private String descricao;
private double valor;
private String isbn;
private Autor autor;
public Livro(Autor autor) {
this.autor = autor;
}
public Livro() {
}
// setters, getters e outros métodos
}
Uma classe pode, sim, ter mais de um construtor, isso é conhecido
como uma sobrecarga ( overloaded) de construtor. Mas, assim como nos de-
mais métodos, isso funcionará contanto que os construtores não tenham a
mesma quantidade de parâmetro. Por exemplo, se eu tentar fazer:
public Livro() {
}
public Livro() {
System.out.println("novo livro criado");
}
Esse código não compilará, afinal, quando alguém fizer new Livro()
qual dos construtores seria chamado?
Como nossa regra de negócio exige um Autor para cada Livro, vamos
manter apenas o construtor com parâmetro. Para isso, precisaremos mudar
as nossas classes que criam um novo Livro para atender essa condição. A
classe CadastroDeLivros deve ficar assim:
public class CadastroDeLivros {
69
4.5. Definindo dependências pelo construtor
Casa do Código
public static void main(String[] args) {
Autor autor = new Autor();
autor.setNome("Rodrigo Turini");
autor.setEmail("[email protected]");
autor.setCpf("123.456.789.10");
Livro livro = new Livro(autor);
livro.setNome("Java 8 Prático");
livro.setDescricao("Novos recursos da linguagem");
livro.setValor(59.90);
livro.setIsbn("978-85-66250-46-6");
livro.mostrarDetalhes();
Autor outroAutor = new Autor();
outroAutor.setNome("Paulo Silveira");
outroAutor.setEmail("[email protected]");
outroAutor.setCpf("123.456.789.10");
Livro outroLivro = new Livro(outroAutor);
outroLivro.setNome("Lógica de Programação");
outroLivro.setDescricao("Crie seus primeiros programas");
outroLivro.setValor(59.90);
outroLivro.setIsbn("978-85-66250-22-0");
outroLivro.mostrarDetalhes();
}
}
70
Casa do Código
Capítulo 4. Encapsulamento
Mantemos ou não o método setAutor?
Se o construtor já atribuiu o Autor do livro, precisamos manter um
setter para esse atributo? Isso vai depender de nossa necessidade, claro.
Pode ser que faça bastante sentido para nossa livraria modificar o autor
de um livro depois que ele foi criado. Mas fique atento! Você só deve
manter o setter se ele realmente for necessário, lembre-se da importância de manter suas rotinas encapsuladas.
Inicializando atributos da classe
Por fim, outro ponto que precisamos resolver em nossa livraria
é relacionado ao número de ISBN de nossos livros.
Em nosso
CadastroDeLivros, sempre que criamos um
Livro, já atribuímos
seu número de ISBN, mas normalmente esse identificador pode demorar
alguns dias para ficar pronto.
Repare qual será o resultado do método mostrarDetalhes quando o
ISBN não estiver preenchido:
Mostrando detalhes do livro
Nome: Java 8 Prático
Descrição: Novos recursos da linguagem
Valor: 59.9
ISBN: null
Mostrando detalhes do autor
Nome: Rodrigo Turini
Email: [email protected]
CPF: 123.456.789.10
O valor null será impresso. Isso acontece pois, diferente dos tipos
primitivos, que sempre possuem um valor padrão ( default), objetos como a String não possuem, portanto, antes de serem inicializadas terão sua refer-
ência igual a null.
Esse é o valor default de cada tipo primitivo:
71

4.5. Definindo dependências pelo construtor
Casa do Código
Outro uso bem comum de um construtor é para inicialização de atrib-
utos, como o caso do ISBN. Para fornecer um valor padrão para este atributo, poderíamos simplesmente inicializá-lo no construtor da classe:
public Livro(Autor autor) {
this.autor = autor;
this.isbn = "000-00-00000-00-0";
}
Nesse caso, ao mostrarDetalhes de um Livro sem ISBN, teremos a
saída:
Mostrando detalhes do livro
Nome:Java 8 Prático
Descrição:Novos recursos da linguagem
Valor:59.9
72
Casa do Código
Capítulo 4. Encapsulamento
ISBN: 000-00-00000-00-0
Mostrando detalhes do autor
Nome: Rodrigo Turini
Email: [email protected]
CPF: 123.456.789.10
Delegando para outros construtores
Repare que, se a classe Livro também tivesse um construtor sem
argumentos, como a seguir:
public Livro(Autor autor) {
this.autor = autor;
this.isbn = "000-00-00000-00-0";
}
public Livro() {
}
O valor de ISBN só seria inicializado quando o construtor com um
Autor fosse chamado. Para resolver isso, você pode encadear a chamada
dos construtores utilizando a palavra reservada this, como a seguir:
public Livro(Autor autor) {
this();
this.autor = autor;
}
public Livro() {
this.isbn = "000-00-00000-00-0";
}
73
Capítulo 5
Herança e polimorfismo
5.1
Trabalhando com livros digitais
Por causa da sua praticidade e fácil portabilidade, muitos leitores preferem
livros digitais ( e-books) aos livros físicos. Nossa livraria não pode ficar para trás, portanto, passaremos a trabalhar com esse novo tipo de livro.
Mas, afinal, como podemos diferenciar um Livro impresso de um
Ebook? Uma forma simples de fazer isso seria adicionando um atributo que
define o tipo de livro. Poderia simplesmente ser um boolean, marcando se
um livro é impresso ou não. Repare:
public class Livro {
private String nome;
private String descricao;
private double valor;
5.1. Trabalhando com livros digitais
Casa do Código
private String isbn;
private Autor autor;
private boolean impresso;
public Livro(Autor autor) {
this.autor = autor;
this.isbn = "000-00-00000-00-0";
this.impresso = true;
}
// outros métodos da classe
}
Note que em nosso construtor já definimos um valor padrão para o atrib-
uto impresso. Todo Livro que não tenha o valor de impresso definido
será considerado um livro impresso.
Essa é uma forma simples de resolver o problema e talvez até possa aten-
der bem as nossas necessidades, mas o problema dessa abordagem é que um
Ebook tem alguns comportamentos bastante diferentes do que um livro im-
presso. Por exemplo, o método aplicaDescontoDe da classe Livro at-
ualmente limita a porcentagem de desconto em 30%.
public boolean aplicaDescontoDe(double porcentagem) {
if (porcentagem > 0.3) {
return false;
}
this.valor -= this.valor * porcentagem;
return true;
}
Mas quando se trata de um Ebook, a regra é um pouco diferente: pode-
mos aplicar no máximo 15% de desconto. Podemos resolver esse problema
adicionando mais uma condição ao método:
public boolean aplicaDescontoDe(double porcentagem) {
if (porcentagem > 0.3) {
return false;
} else if (!this.impresso && porcentagem > 0.15) {
return false;
76
Casa do Código
Capítulo 5. Herança e polimorfismo
}
this.valor -= this.valor * porcentagem;
return true;
}
Agora nosso código funciona como esperado, mas está um pouco mais
verboso e difícil de entender. O grande problema aqui é que, a cada método
cuja regra seja diferente, teremos que colocar um novo if parecido com esse,
isso sem contar que novos tipos de Livro podem aparecer e tornar nosso
código ainda mais complicado e cheio de condicionais. Devemos sempre es-
crever nosso código pensando em como será sua evolução no futuro.
Além disso, existem alguns comportamentos e atributos que só servem
para um Ebook. Um deles é o watermark (marca d’água). Essa é a forma
de identificar discretamente o nome e e-mail do dono daquele livro digital,
normalmente no rodapé das páginas.
Se Ebook é um elemento importante, possui comportamentos e atributos
específicos, ele deveria ser representado como um Objeto! Podemos criar
uma classe Ebook definindo os atributos e comportamentos específicos desse
novo tipo.
public class Ebook {
private String nome;
private String descricao;
private double valor;
private String isbn;
private Autor autor;
private String waterMark;
public void setWaterMark(String waterMark) {
this.waterMark = waterMark;
}
public String getWaterMark() {
return waterMark;
}
77
5.1. Trabalhando com livros digitais
Casa do Código
// getters, setters e outros métodos
}
Nosso código já está um pouco mais interessante, afinal não estamos mais
sobrecarregando a classe Livro com atributos e métodos que serão uti-
lizados apenas quando o tipo do livro for um ebook. Mas há muito código repetido aqui: além dos comportamentos do Ebook, temos todos os atributos e métodos já escritos na classe Livro.
Para evitar toda essa repetição de código, podemos ensinar ao compilador
que o Ebook é um tipo de Livro, ou seja, além de seus próprios atributos e
métodos, essa classe possui tudo o que um Livro tem. Para fazer isto, basta
Ebook dizer na declaração da classe que ela é um Livro, que é uma extensão dessa classe:
public class Ebook extends Livro {
private String waterMark;
public Ebook(Autor autor) {
super(autor);
}
public void setWaterMark(String waterMark) {
this.waterMark = waterMark;
}
public String getWaterMark() {
return waterMark;
}
}
78
Casa do Código
Capítulo 5. Herança e polimorfismo
delegate constructor
Como a classe Livro tinha um construtor obrigando a passagem de
um Autor como parâmetro, ao herdar de um Livro, a classe Ebook
também herdou essa responsabilidade. Repare que utilizamos a palavra
super para delegar a responsabilidade para a superclasse que já tem esse comportamento bem definido.
public Ebook(Autor autor) {
super(autor);
}
Ao utilizar a palavra reservada extends, estamos dizendo que um
Ebook ( subclasse) herda tudo o que a classe Livro ( superclasse) tem. Portanto, mesmo sem ter nenhum desses métodos declarados diretamente na
classe Ebook, podemos executar o seguinte código sem nenhum problema:
Ebook ebook = new Ebook();
ebok.setNome("Java 8 Prático");
Como a classe Livro tem os setters para o atributo nome declarados,
um Ebook também terá.
Herança múltipla
Uma regra importante da herança em Java é que nossas classes só
podem herdar diretamente de uma classe pai. Ou seja, não há herança
múltipla como na linguagem C++. Mas sim, uma classe pode herdar de
uma classe que herda de outra e assim por diante. Você pode encadear
a herança de suas classes, no entanto, veremos mais à frente que essa es-
tratégia não é muito interessante por aumentar de mais o acoplamento
entre suas classes.
79
5.2. Reescrevendo métodos da superclasse
Casa do Código
5.2
Reescrevendo métodos da superclasse
Agora que temos uma forma forte de se representar um Ebook, podemos
criar um método aplicaDescontoDe com o desconto máximo de 15%. As-
sim, evitamos aquele if que verificava se um Livro era impresso ou não.
Na classe Livro o método deve ficar assim:
public boolean aplicaDescontoDe(double porcentagem) {
if (porcentagem > 0.3) {
return false;
}
this.valor -= this.valor * porcentagem;
return true;
}
Mas no lugar de a classe Ebook herdar esse comportamento (que deve
ser diferente para ela), podemos reescrevê-lo como a seguir:
public class Ebook extends Livro {
private String waterMark;
public Ebook(Autor autor) {
super(autor);
}
public boolean aplicaDescontoDe(double porcentagem) {
if (porcentagem > 0.15) {
return false;
}
this.valor -= this.valor * porcentagem;
return true;
}
// get e set do watermark
}
Por mais que um Ebook seja um Livro, quando alguém chamar o
método aplicaDescontoDe, o desconto será de 15%, e não de 30% com
o está definido na classe pai.
80

Casa do Código
Capítulo 5. Herança e polimorfismo
A JVM sempre vai procurar o método primeiro no objeto que foi instanci-
ado e apenas quando não encontrar procurará em sua superclasse. Você pode ver um exemplo aqui:
81
5.2. Reescrevendo métodos da superclasse
Casa do Código
@Override
Opcionalmente podemos marcar os métodos reescritos com a ano-
tação @Overrride. Uma anotação é apenas uma marcação, como um
post-it com uma observação importante sobre o local anotado. As annotation s surgiram no Java 1.5, e têm um papel bastante importante na
maior parte das bibliotecas mais atuais da linguagem. Por questões de
compatibilidade, o uso do @Override é facultativo, mas é bastante in-
teressante anotar os seus métodos dessa forma, já que, com isso, o com-
pilador nos ajudará a validar que esse método é realmente uma reescrita.
@Override
public boolean aplicaDescontoDe(double porcentagem) {
if (porcentagem > 0.15) {
return false;
}
this.valor -= this.valor * porcentagem;
return true;
}
Ao anotar nosso método com @Override, o código não compilará
caso esse método não exista na classe pai ( superclasse).
Antes de testar essa mudança, precisamos fazer uma ultima alteração para
que esse método compile.
Como a visibilidade do atributo valor da classe Livro é private,
a linha que o acessa diretamente do método aplicaDescontoDe da classe
Ebook não vai funcionar. Afinal, um atributo private só pode ser acessado
pela própria classe, nem mesmo as classes filhas ( subclasses) podem violar essa regra.
Para que o código funcione, precisamos aumentar essa visibilidade, mas
já conhecemos o problema de deixar os atributos public. Uma alternativa
é modificar a visibilidade dos atributos da classe Livro para protected,
que é um meio termo entre public e private.
82
Casa do Código
Capítulo 5. Herança e polimorfismo
A visibilidade protected deixará os atributos visíveis também para as
classes filhas, mas normalmente evitamos utilizar esse modificador de visibil-
idade porque ele acaba tendo mais algumas exceções que afrouxam o nosso
encapsulamento. Conheceremos mais sobre o protected adiante, mas
desde já vamos evitar essa alternativa.
Uma forma bem interessante e simples de resolver o problema é utilizando
a interface da classe pai, seus próprios métodos. Por exemplo, no lugar de acessar o atributo diretamente, poderíamos utilizar os métodos getValor e
setValor como a seguir:
@Override
public boolean aplicaDescontoDe(double porcentagem) {
if (porcentagem > 0.15) {
return false;
}
double desconto = this.getValor() * porcentagem;
this.setValor(this.getValor() - desconto);
return true;
}
Dessa forma, não perderemos nem um pouco do encapsulamento da
classe Livro! Quanto mais você estudar e se habituar com a orientação a
objetos, mais você perceberá que garantir o encapsulamento das classes é
fundamental. Ainda que um pouco, quando liberamos acesso dos atributos
da superclasse para suas classes filhas, estamos violando o encapsulamento dessa classe.
83
5.2. Reescrevendo métodos da superclasse
Casa do Código
this ou super?
No método aplicaDescontoDe da classe Ebook, chamamos o
getter e setter do atributo valor utilizando a palavra reservada
this. Uma outra forma de fazer isso seria utilizando o super. Assim,
estamos deixando claro que o método invocado é da classe pai. Existem
situações em que é muito interessante utilizar super, como neste caso:
@Override
public boolean aplicaDescontoDe(double porcentagem) {
if (porcentagem > 0.15) {
return false;
}
return super.aplicaDescontoDe(porcentagem);
}
Repare que, logo após fazer a nossa condição, estamos delegando para
a lógica do método aplicaDescontoDe da classe pai. Com isso, evita-
mos repetir a lógica que já está definida na superclasse. Mas o que aconteceria se no lugar de super tivéssemos utilizado o this?
Nesse caso, o método aplicaDescontoDe da classe Ebook estaria
chamando ele mesmo! Entraríamos em um looping infinito.
Pronto, chegou a hora de testar esse código para garantir que tudo está
funcionando como esperado. Abra (ou crie, caso já não exista) a classe
RegrasDeDesconto e escreva o seguinte código:
public class RegrasDeDesconto {
public static void main(String[] args) {
Autor autor = new Autor();
autor.setNome("Rodrigo Turini");
Livro livro = new Livro(autor);
livro.setValor(59.90);
84
Casa do Código
Capítulo 5. Herança e polimorfismo
if (!livro.aplicaDescontoDe(0.3)){
System.out.println("Desconto no livro não pode ser
maior do que 30%");
} else {
System.out.println("Valor do livro com desconto: "
+ livro.getValor());
}
Ebook ebook = new Ebook(autor);
ebook.setValor(29.90);
if (!ebook.aplicaDescontoDe(0.3)){
System.out.println("Desconto no ebook não pode ser
maior do que 15%");
} else {
System.out.println("Valor do ebook com desconto: "
+ ebook.getValor());
}
}
}
A saída será:
Valor do livro com desconto: 41.93
Desconto no ebook não pode ser maior do que 15%
Mudando o valor de desconto passado para o e-book de 0.3 para 0.15, o
resultado será:
Valor do livro com desconto: 41.93
Valor do ebook com desconto: 25.415
Ótimo, cada tipo de livro teve a sua regra de desconto devidamente apli-
cada.
5.3
Regras próprias de um LivroFisico
Deixamos os comportamentos específicos de um
Ebook
bem
encapsulados em sua classe, mas repare que há um problema em
85
5.3. Regras próprias de um LivroFisico
Casa do Código
nossa herança. Em que lugar devemos colocar os métodos que fazem sentido
apenas para um livro físico? Por exemplo, todo livro físico possui uma taxa
de impressão. Podemos criar um getTaxaImpressao para nos retornar
esse valor, como a seguir:
public double getTaxaImpressao() {
return this.getValor() * 0.05;
}
Mas onde adicionamos esse método? Se adicionarmos na classe Livro,
o Ebook herdará esse comportamento que não deveria existir em seu tipo.
Mais uma vez entramos na regra: se um elemento é importante e tem
regras específicas, ele deve ser representado como um objeto. Podemos
criar uma nova classe em nosso projeto, para representar tudo o que um
LivroFisico tem e como ele se comporta. Observe:
public class LivroFisico extends Livro {
public LivroFisico(Autor autor) {
super(autor);
}
public double getTaxaImpressao() {
return this.getValor() * 0.05;
}
}
Dessa maneira, também temos uma forma forte de representar um
LivroFisico, bem encapsulada e que não causa nenhum efeito colateral
nos demais filhos de Livro, como o Ebook.
86
Casa do Código
Capítulo 5. Herança e polimorfismo
Uma nova classe a cada tipo de livro?
Uma reação bem comum de quem está aprendendo orientação a ob-
jetos é perguntar: então eu devo criar uma nova classe pra cada tipo de
livro? . A resposta é sim. E você logo perceberá que isso não é um problema.
É muito mais interessante trabalhar com classes menores, que rep-
resentam bem os seus tipos e possuem suas regras específicas bem en-
capsuladas, do que trabalhar com uma única classe carregando toda essa
responsabilidade.
Poderíamos sim ter uma única classe Livro que tivesse todos os
comportamentos de um LivroFisico, um Ebook e dos demais tipos
que podem aparecer. Mas imagine o tamanho dessa classe, a quantidade
de métodos e ifs encadeados para lidar com as diferentes regras de cada
tipo de Livro. A classe seria enorme e bem difícil de manter.
Lembre-se sempre que Java é uma linguagem fortemente tipada, você
pode e deve criar objetos para representar cada tipo de elemento da sua
aplicação.
5.4
Vendendo diferentes tipos de Livro
Para dar mais um passo em nossa aplicação, criaremos agora uma classe
RegistroDeVendas, por enquanto com um método main e o seguinte con-
teúdo:
public class RegistroDeVendas {
public static void main(String[] args) {
Autor autor = new Autor();
autor.setNome("Mauricio Aniche");
LivroFisico fisico = new LivroFisico(autor);
fisico.setNome("Test-Driven Development");
87
5.4. Vendendo diferentes tipos de Livro
Casa do Código
Ebook ebook = new Ebook(autor);
ebook.setNome("Test-Driven Development");
}
}
Repare que criamos um LivroFisico e um Ebook com o mesmo
Autor. Nosso próximo passo será adicionar esses dois elementos em um
CarrinhoDeCompras. O código deve ficar parecido com este:
CarrinhoDeCompras carrinho = new CarrinhoDeCompras();
carrinho.adiciona(fisico);
carrinho.adiciona(ebook);
Vamos agora criar a classe
CarrinhoDeCompras e seu método
adiciona. Como vamos adicionar LivroFisicos e também Ebooks, uma
forma de fazer isso seria criando dois métodos adiciona, um para cada tipo
de Livro (uma sobrecarga):
public class CarrinhoDeCompras {
public void adiciona(LivroFisico livro) {
System.out.println("Adicionando: " + livro);
}
public void adiciona(Ebook livro) {
System.out.println("Adicionando: " + livro);
}
}
Esse código funcionaria sem nenhum problema, mas repare que está
muito repetido. Além disso, a cada novo tipo de Livro, precisaremos criar
um novo método adiciona que receba esse tipo como parâmetro, o que
seria um tanto trabalhoso e difícil de manter.
Para evitar isso, podemos utilizar um recurso da linguagem bastante útil
e poderoso. Como existe uma herança envolvida, podemos dizer que tanto
um LivroFisico como um Ebook são filhos ( extensões) da classe Livro.
Poderíamos criar um único método adiciona, que recebe um Livro ( su-
perclasse) como parâmetro:
88

5.4. Vendendo diferentes tipos de Livro
Casa do Código
Não estamos transformando objetos
Lembre-se que uma variável guarda uma referência para um objeto,
e não um objeto em si. Até agora, estávamos sempre declarando um
Ebook com o tipo Ebook. Repare:
Ebook ebook = new Ebook();
Mas, como vimos, também podemos dizer que um Ebook é do tipo
Livro, afinal ele herda (é um) Livro.
Livro ebook = new Ebook();
Mas é fundamental perceber que não estamos transformando esse ob-
jeto. Se criamos um Ebook, ele será um Ebook e ponto. Estamos apenas
nos referenciando a ele como um Livro, uma abstração.
Perceba que, como estamos referenciando o parâmetro passado para o
método adiciona da classe CarrinhoDeCompras como um Livro, ape-
nas os métodos presentes na classe Livro poderão ser invocados sem que
um erro de compilação ocorra.
Para ficar mais claro, ainda que passando um objeto do tipo Ebook para
o adiciona, ao tentar invocar seu método getWaterMark, o seguinte erro
de compilação ocorrerá:
The method getWaterMark() is undefined for the type Livro
Faz sentido. Para que isso funcione, precisaríamos fazer um casting
moldando o parâmetro livro para o tipo Ebook:
public void adiciona(Livro livro) {
Ebook ebook = (Ebook) livro;
ebook.getWaterMark();
// restante do código omitido
}
Isso vai funcionar muito bem, contanto que o valor passado de fato seja
do tipo Ebook. Isso é um tanto perigoso, pois podemos muito bem passar
90
Casa do Código
Capítulo 5. Herança e polimorfismo
um LivroFisico como argumento. Neste caso até seria possível fazer um
if para validar que o objeto passado é uma instância de Ebook, mas deixaria
de ser uma vantagem usar polimorfismo aqui.
5.5
Acumulando total de compras
O CarrinhoDeCompras precisa acumular o valor total que está sendo com-
prado. Podemos fazer isso de uma forma bem simples, adicionando um atrib-
uto total e incrementando com o valor do Livro a cada adição.
public void adiciona(Livro livro) {
System.out.println("Adicionando: " + livro);
total += livro.getValor();
}
Além disso, antes de acumular o total da compra, o método adiciona
aplicará um desconto de 5% para cada livro adicionado:
public void adiciona(Livro livro) {
System.out.println("Adicionando: " + livro);
livro.aplicaDescontoDe(0.05);
total += livro.getValor();
}
Ao final, nossa classe CarrinhoDeCompras deve ficar assim:
public class CarrinhoDeCompras {
private double total;
public void adiciona(Livro livro) {
System.out.println("Adicionando: " + livro);
livro.aplicaDescontoDe(0.05);
total += livro.getValor();
}
public double getTotal() {
return total;
}
}
91


5.6. Herança ou composição?
Casa do Código
Como esperado, apenas o desconto do LivroFisico foi aplicado, pois
o método aplicaDescontoDe da classe Ebook limita o desconto a 15%.
Portanto, não importa a forma como nós nos referenciamos a nossos ob-
jetos, no fim o método executado sempre será o dele.
5.6
Herança ou composição?
Entenderemos melhor o problema mais à frente, mas sempre que possível
procure favorecer o uso da composição entre classes no lugar de utilizar her-ança. Veremos que o uso da herança aumenta bastante o acoplamento de suas classes e de alguma forma sempre acaba comprometendo o encapsulamento.
Tudo isso será melhor aprofundado nos próximos capítulos, mas desde já
você pode se interessar em ler o seguinte artigo no qual James Gosling, considerado pai do Java, fala sobre o assunto:
http://www.artima.com/intv/gosling3P.html
94
Capítulo 6
Classe abstrata
6.1
Qual o tipo de cada Livro?
Agora que temos uma forma forte de representar os diferentes tipos de
Livro, podemos nos expressar bem ao fazer uma venda:
Ebook ebook = new Ebook(autor);
ebook.setNome("CDI");
carrinho.adiciona(ebook);
Além de Ebook, também temos um LivroFisico, que já provou ser
um objeto diferente por ter comportamentos diferentes. Mas o que estamos
vendendo quando fazemos new em um Livro?
Livro livro = new Livro(autor);
livro.setNome("CDI");
6.1. Qual o tipo de cada Livro?
Casa do Código
carrinho.adiciona(livro);
Afinal, o que é um Livro agora? Um Ebook ou um LivroFisico?
Na verdade, nenhum dos dois. Um Livro é apenas uma abstração de tudo
que os diferentes tipos de livro devem ter ( herdar) em nossa livraria.
Ao fazer uma venda, queremos saber ao certo o tipo de livro que está
sendo vendido, nunca deveríamos permitir a venda de um Livro, mas sim
de suas subclasses.
Para nossa sorte, há uma forma bem simples de impedir que a classe
Livro seja instanciada e utilizada dessa forma: podemos simplesmente adi-
cionar em sua declaração o modificador abstract. Repare:
public abstract class Livro {
// continuação do código
}
A partir do momento em que tornamos nossa classe abstrata, o compi-
lador vai impedir que qualquer código tente instanciar um Livro. Por ex-
emplo, em nosso CadastroDeLivros:
Livro livro = new Livro(autor);
livro.setNome("Java 8 Prático");
livro.setDescricao("Novos recursos da linguagem");
livro.setValor(59.90);
livro.setIsbn("978-85-66250-46-6");
O erro de compilação apresentado será:
Cannot instantiate the type Livro
Bem claro, não acha? Não se pode mais criar um Livro e ponto final.
96
Casa do Código
Capítulo 6. Classe abstrata
Por que precisamos da classe LI V R O?
Afinal, para que serve a classe
Livro, se não podemos mais
instanciá-la? Lembre-se que essa classe idealiza tudo o que um Livro
tem, ela ainda está sendo muito útil isolando todos os atributos e compor-
tamentos que são um padrão entre os diferentes tipos de livro. A classe
passa a servir exclusivamente para herança e polimorfismo, que são recursos bastante úteis e poderosos.
Todo livro criado agora precisa ter seu tipo bem definido, portanto pre-
cisaremos mudar todas as partes de nosso código que criava um Livro para
usar uma de suas classes filhas. A classe CadastroDeLivros, por exemplo,
ficará assim:
public class CadastroDeLivros {
public static void main(String[] args) {
Autor autor = new Autor();
autor.setNome("Rodrigo Turini");
autor.setEmail("[email protected]");
autor.setCpf("123.456.789.10");
Livro livro = new LivroFisico(autor);
livro.setNome("Java 8 Prático");
livro.setDescricao("Novos recursos da linguagem");
livro.setValor(59.90);
livro.setIsbn("978-85-66250-46-6");
livro.mostrarDetalhes();
// outros livros cadastrados
}
}
Apesar de não poder instanciar uma classe abstrata, você ainda pode (e
muitas vezes deve) usá-la como referência. Como no método adiciona do
97
6.2. Minilivro não tem desconto!
Casa do Código
CarrinhoDeCompras:
public void adiciona(Livro livro) {
System.out.println("Adicionando: " + livro);
livro.aplicaDescontoDe(0.16);
total += livro.getValor();
}
Repare que ainda estamos recebendo um Livro como parâmetro para
continuar usando o polimorfismo. Isso é perfeitamente possível, afinal um Ebook e um LivroFisico são filhos de Livro, portanto herdam o seu
tipo.
Quando uma classe deve ser abstrata?
Quando você for planejar a hierarquia e herança de suas classes, você
verá que algumas classes são bastante específicas e que jamais deveriam
ser instanciadas. A classe Animal pode ser vista como um exemplo. O
que exatamente é um Animal? Poderia ser um Leao, um Pinguim ou
qualquer outro Animal do planeta. Essa classe pode definir tudo o que
todos os animais têm em comum, mas cada tipo de Animal tem suas
particularidades e deve ser representado de uma forma própria.
6.2
Minilivro não tem desconto!
Agora que Livro é abstrato, podemos adicionar um novo tipo de Livro
em nossa livraria. Será um MiniLivro, representando um livro mais enx-
uto e com algumas particularidades que logo conheceremos. Vamos começar
criando essa nova classe:
public class MiniLivro extends Livro {
public MiniLivro(Autor autor) {
super(autor);
}
}
98
Casa do Código
Capítulo 6. Classe abstrata
O que acontecerá quando modificarmos o código da classe
RegrasDeDesconto criando um
MiniLivro onde anteriormente
criávamos um Livro? Vamos fazer essa alteração e executá-la para ver o
resultado. A classe deve ficar assim:
public class RegrasDeDesconto {
public static void main(String[] args) {
Autor autor = new Autor();
autor.setNome("Rodrigo Turini");
Livro livro = new MiniLivro(autor);
livro.setValor(39.90);
if (!livro.aplicaDescontoDe(0.3)){
System.out.println("Desconto no livro não pode
ser maior do que 30%");
} else {
System.out.println("Valor do livro com desconto: "
+ livro.getValor());
}
// outros descontos omitidos
}
}
Ao executar esse código o retorno será:
aplicando desconto no Livro
Valor do livro com desconto: 27.93
Observe que foi aplicado um desconto de 30% no valor do livro, mas um
MiniLivro não pode ter desconto! Ele já é um livro com preço promocional,
não podemos permitir que nenhum outro valor de desconto seja aplicado.
Ao criar um MiniLivro, mesmo sem definirmos isso, ele já poderia ter
um desconto de até 30% pois herdou esse comportamento da classe Livro.
Há uma forma simples de resolver o problema: poderíamos reescrever o
método aplicaDescontoDe da classe MiniLivro para sempre retornar
false. Seu código ficaria assim:
99
6.2. Minilivro não tem desconto!
Casa do Código
public class MiniLivro extends Livro {
public MiniLivro(Autor autor) {
super(autor);
}
@Override
public boolean aplicaDescontoDe(double porcentagem) {
return false;
}
}
Essa solução resolveria o problema, mas é um tanto sensível. Será que
qualquer pessoa que criasse esse novo tipo de Livro se lembraria de modi-
ficar o desconto padrão? Provavelmente não.
É bastante improvável que um novo desenvolvedor, ou mesmo quem
tenha criado a regra, lembre-se deste detalhe sempre que um novo tipo de
livro for criado. Depender da memória humana nunca é uma boa estratégia.
No lugar de precisar sobrescrever o método sempre que houver desconto,
poderíamos inverter a situação deixando o comportamento padrão do Livro
abstrato retornar false. Dessa forma, por padrão nenhum livro terá de-
sconto. Sobrescrevemos essa regra apenas quando for necessário.
Nosso código ficaria assim:
public abstract class Livro {
private String nome;
private String descricao;
private double valor;
private String isbn;
private Autor autor;
public Livro(Autor autor) {
this.autor = autor;
this.isbn = "000-00-00000-00-0";
}
public boolean aplicaDescontoDe(double porcentagem) {
100
Casa do Código
Capítulo 6. Classe abstrata
return false;
}
// outros métodos, getters e setters
}
Agora que o método aplicaDescontoDe da classe pai sempre retorna
false, precisaremos mudar a classe LivroFisico para permitir até 30%
de desconto:
public class LivroFisico extends Livro {
public LivroFisico(Autor autor) {
super(autor);
}
public double getTaxaImpressao() {
return this.getValor() * 0.05;
}
public boolean aplicaDescontoDe(double porcentagem) {
if (porcentagem > 0.3) {
return false;
}
double desconto = getValor() * porcentagem;
setValor(getValor() - desconto);
System.out.println("aplicando desconto no LivroFisico");
return true;
}
}
Nossa classe MiniLivro não precisará mais sobrescrever o método. Seu
código pode ficar assim:
public class MiniLivro extends Livro {
public MiniLivro(Autor autor) {
super(autor);
}
}
101
6.3. Método abstrato
Casa do Código
Essa estratégia é um pouco mais interessante, afinal não permitiremos
mais desconto do que uma classe deveria ter. O problema é que precisaremos
lembrar de reescrever esse comportamento em todo novo tipo de Livro que
tenha desconto. Ainda corremos o risco de esquecer e, desta vez, não permitir
desconto para um Livro que tenha direito.
6.3
Método abstrato
Se cada novo Livro terá uma estratégia de desconto diferente, ou seja, se
não há um desconto padrão entre todos os tipos de livro, poderíamos simples-
mente apagar esse método dessa classe abstrata e escrevê-lo apenas nas classes
que devem ter desconto. O problema disso é que perderíamos o polimorfismo, afinal, se o método não estiver presente na tipo Livro, o seguinte código não
funcionará:
public void adiciona(Livro livro) {
System.out.println("Adicionando: " + livro);
livro.aplicaDescontoDe(0.16);
total += livro.getValor();
}
Nem todo Livro terá o método aplicaDescontoDe, não há nenhuma
garantia disso.
Podemos resolver o problema de uma forma mais efetiva. Toda classe
abstrata, como é o caso da nossa classe Livro, pode ter métodos abstratos.
Toda classe filha ( subclasse) concreta (não abstrata) é obrigada a escrever os métodos abstratos da classe pai ( superclasse), caso contrário seu código não compilará.
Para tornar o método aplicaDescontoDe abstrato na classe Livro,
basta adicionar o modificador abstract em sua declaração e remover todo
o corpo, colocando apenas um ponto e vírgula. Repare:
public abstract class Livro {
private String nome;
private String descricao;
private double valor;
102
Casa do Código
Capítulo 6. Classe abstrata
private String isbn;
private Autor autor;
public Livro(Autor autor) {
this.autor = autor;
this.isbn = "000-00-00000-00-0";
}
public abstract boolean aplicaDescontoDe(double porcentagem);
// outros métodos, getters e setters
}
Apenas classes abstratas podem ter métodos abstratos. Afinal, se al-
guém invocasse o método aplicaDescontoDe da classe Livro, qual seria
o resultado? Não há nenhuma implementação nele, portanto ele não poderia
ser executado.
A partir do momento em que tornamos o método aplicaDescontoDe
abstrato, todas as classes filhas precisam escrevê-lo. Por esse motivo, a classe MiniLivro vai parar de compilar. O erro será:
The type MiniLivro must implement the inherited abstract method
Livro.aplicaDescontoDe(double)
Para que tudo funcione a classe precisará ficar assim:
public class MiniLivro extends Livro {
public MiniLivro(Autor autor) {
super(autor);
}
@Override
public boolean aplicaDescontoDe(double porcentagem) {
return false;
}
}
Ainda não é uma solução perfeita, estamos sendo obrigados a escrever
esse método na classe MiniLivro mesmo que ela não possua essa regra de
103
6.4. Relembrando algumas regras
Casa do Código
desconto. Mas a grande vantagem é que eliminamos totalmente a possibili-
dade de esquecer de definir esse comportamento em novas implementações
da classe Livro. A partir de agora, todo filho de Livro logo quando criado
já precisará definir qual a sua regra de desconto, caso contrário o código não
compila.
6.4
Relembrando algumas regras
Não é nem um pouco complicado trabalhar com classes abstratas, mas exis-
tem algumas regras que precisam ser respeitadas. Apenas para relembrar, são
elas:
• Uma classe pode ser abstrata sem ter nenhum método abstrato. A par-
tir do momento em que ela se tornar abstrata, nenhum código poderá
mais instanciá-la.
• Se você declarar um método abstrato, precisará tornar a classe abstrata
também! Você não pode ter métodos abstratos em uma classe que não
é abstrata.
• Uma classe abstrata pode ter métodos abstratos e não abstratos (con-
cretos).
• Toda classe filha ( subclasse) precisa implementar os métodos abstratos da classe pai ( superclasse). A não ser que ela também seja abstrata.
104
Capítulo 7
Interface
Além dos mais diversos tipos de Livros, nossa livraria também trabalhará
com Revistas e futuramente outros produtos. Podemos criar uma nova
classe para representá-la, como a seguir:
public class Revista {
private String nome;
private String descricao;
private double valor;
private Editora editora;
// getters e setters
public boolean aplicaDescontoDe(double porcentagem) {
if (porcentagem > 0.1) {
Casa do Código
return false;
}
double desconto = getValor() * porcentagem;
setValor(getValor() - desconto);
return true;
}
}
Repare que, além de um nome, descricao e valor, uma revista tam-
bém possui uma regra de desconto e é composta pela classe Editora. Essa
é uma outra classe bastante simples:
public class Editora {
private String nomeFantasia;
private String razaoSocial;
private String cnpj;
// getters e setters
}
Precisamos agora evoluir nosso CarrinhoDeCompras para que seja
possível, além de Livros, adicionar Revistas. Uma solução seria duplicar
seu método adiciona:
public class CarrinhoDeCompras {
private double total;
public void adiciona(Livro livro) {
System.out.println("Adicionando: " + livro);
livro.aplicaDescontoDe(0.05);
total += livro.getValor();
}
public void adiciona(Revista revista) {
System.out.println("Adicionando: " + revista);
revista.aplicaDescontoDe(0.05);
total += revista.getValor();
106

Casa do Código
Capítulo 7. Interface
}
public double getTotal() {
return total;
}
}
Mas note como os dois métodos ficaram bastante parecidos, há muita
repetição de código! Além disso, para cada novo produto precisaríamos criar
um novo método, o que tornaria trabalhosa a evolução dessa classe.
Poderíamos fazer a classe Revista herdar de Livro, de modo que o
polimorfismo seria aplicável e teríamos um único método adiciona. Mas ao fazer isso toda Revista teria um Autor, ISBN além dos demais atributos e
comportamentos que só se aplicam para os Livros, uma verdadeira bagunça.
Sim, podemos criar um nível a mais na hierarquia de nossas classes
adicionando a classe Produto, assim Livro e Revista herdariam de
Produto e poderíamos utilizar esse tipo no polimorfismo. A hierarquia de nossas classes seria:
107
7.1. O contrato Produto
Casa do Código
O grande problema dessa abordagem é que aumentaria demais o acopla-
mento entre as classes, ou seja, o quanto uma classe depende da outra. A herança cria uma relação muito forte entre as classes. Ao mudar a classe
Produto, por exemplo, todas as subclasses (e também subclasses das subclasses) seriam influenciadas.
E se eu quiser aplicar a mudança apenas para algumas classes? Teríamos
que sobrescrever esse novo comportamento de uma forma desnecessária em
todas as outras, assim como fizemos com a classe MiniLivro. Mesmo sem
existir desconto para essa classe, ela foi obrigada a sobrescrever o método
aplicaDescontoDe para apenas retornar false.
Com o passar do tempo, o uso da herança faz com que nossas classes
fiquem cada vez mais acopladas e isso dificulta sua evolução.
7.1
O contrato Produto
Em Java, há uma outra forma para se tirar proveito de todos os benefícios do
polimorfismo sem ter que acoplar tanto as suas classes com vários níveis de herança. Você pode estabelecer um fator em comum entre as classes, criando
uma espécie de contrato.
Para esse contrato, não importa a forma como será implementado, a única
coisa que importa é que seus métodos ( cláusula s) sejam implementados de alguma forma. Isso lembra algo? Sim, é bastante parecido com um método
abstrato cujo corpo você só define na superclasse para que todas as subclasses herdem a obrigação de implementá-lo.
Esse tipo de contrato Java é conhecido como Interface.
Não se trata de uma interface gráfica
É muito comum confundir no inicio, mas não estamos falando de
uma interface gráfica de usuário ( GUI). Também não estamos falando da
interface da classe, seus métodos públicos. Você perceberá no decorrer
do capítulo que se trata de um recurso diferente, um contrato Java.
Podemos criar uma interface Produto, que por enquanto terá um
único método abstrato estabelecendo que todo produto deve ter o método
108
Casa do Código
Capítulo 7. Interface
getValor. Uma interface se parece bastante com uma classe abstrata que
tenha apenas métodos abstratos, mas no lugar de declará-la como uma classe,
utilizamos a palavra reservada interface:
public interface Produto {
public abstract double getValor();
}
Como todo método sem corpo de uma interface é abstrato, o uso do mod-
ificador abstract é opcional. Não precisamos também adicionar o modi-
ficador public, pois seus métodos também são públicos por padrão. Pode-
mos simplificar a escrita da interface Produto deixando apenas:
public interface Produto {
double getValor();
}
Uma interface não pode ter atributos e, até a versão 1.7 da linguagem, tam-
bém não pode ter nenhum método concreto, ou seja, com implementação.
Veremos que, a partir do Java 1.8, isso mudou um pouco.
Podemos agora fazer com que todas as classes que idealizam produto s de nossa livraria assinem o contrato Produto. Para fazer isso, basta adicionar
a palavra-chave implements seguida do nome da interface que deve ser
implementada na declaração das classes, veja:
public abstract class Livro implements Produto {
// atributos e métodos omitidos
}
public class Revista implements Produto {
// atributos e métodos omitidos
}
Como todas essas classes já possuem o método getValor declarado,
você não perceberá nenhuma diferença. Nosso código passa a funcionar
como esperado. Mas é importante perceber que se apagarmos o método
109

7.1. O contrato Produto
Casa do Código
getValor a classe deixará de compilar, afinal toda classe que implementa
a interface Produto é obrigada a implementar seus métodos abstratos.
Veja que com a interface o diagrama de nossas classes fica assim:
Como todos que implementam uma interface podem ser referenci-
ados por este tipo, podemos usar polimorfismo com interface s. Por exemplo, no método adiciona do CarrinhoDeCompras podemos receber um
Produto como parâmetro:
public void adiciona(Produto produto) {
System.out.println("Adicionando: " + produto);
produto.aplicaDescontoDe(0.16);
total += produto.getValor();
}
O polimorfismo funcionará, mas o problema desse código é que nem todo
Produto tem o método aplicaDescontoDe, mas apenas os filhos da classe
110
Casa do Código
Capítulo 7. Interface
Livro. Sim, poderíamos mover o método abstrato aplicaDescontoDe
para a interface Produto, mas se nem todos os produtos têm um desconto,
devemos evitar isso. Por enquanto, removeremos esse desconto, mas logo
veremos uma forma mais interessante de resolver o problema. Nosso método
deve ficar assim:
public void adiciona(Produto produto) {
System.out.println("Adicionando: " + produto);
total += produto.getValor();
}
Rode a classe RegistroDeVendas para ver que tudo está funcionando
como esperado. A classe continua assim:
public class RegistroDeVendas {
public static void main(String[] args) {
Autor autor = new Autor();
autor.setNome("Mauricio Aniche");
LivroFisico fisico = new LivroFisico(autor);
fisico.setNome("Test-Driven Development");
fisico.setValor(59.90);
Ebook ebook = new Ebook(autor);
ebook.setNome("Test-Driven Development");
ebook.setValor(29.90);
CarrinhoDeCompras carrinho = new CarrinhoDeCompras();
carrinho.adiciona(fisico);
carrinho.adiciona(ebook);
System.out.println("Total " + carrinho.getTotal());
}
}
Ao executá-la, o resultado será:
111
7.2. Diminuindo acoplamento com Interfaces
Casa do Código
Adicionado: LivroFisico@4f23c55
Adicionado: Ebook@21a33b44
Total 89.8
7.2
Diminuindo acoplamento com Interfaces
O uso da interface já nos ajudou a resolver o problema de polimorfismo de nossos produtos. Agora há uma forma simples e flexível para representar qual-
quer Produto de nossa livraria. Mas ainda precisamos resolver o problema
do método aplicaDescontoDe.
Ao adicionar esse método abstrato na classe Livro, obrigamos todas as
suas subclasses a implementá-lo, mas não é bem isso que precisamos. Essa solução deixa de ser interessante quando nem todos os Livros tenham esse
comportamento, como é o caso do MiniLivro, que não pode ter um de-
sconto.
Outra questão é que apenas os filhos da classe
Livros têm essa
obrigação, sendo que uma
Revista também deve possuir o método
aplicaDescontoDe.
Podemos diminuir esse acoplamento de uma
forma bem simples, criando uma nova interface!
Vamos chamá-la de
Promocional:
public interface Promocional {
boolean aplicaDescontoDe(double porcentagem);
}
Agora podemos remover o método aplicaDescontoDe abstrato da
classe Livro e dizer que apenas as classes promocionais, que possuem de-
sconto, implementam essa nova interface:
public class LivroFisico extends Livro implements Promocional {
// atributos e métodos omitidos
}
public class Ebook extends Livro implements Promocional {
// atributos e métodos omitidos
}
112

Casa do Código
Capítulo 7. Interface
public class Revista implements Produto, Promocional {
// atributos e métodos omitidos
}
Assim como podemos assinar diversos contratos ao longo de nossas vidas,
uma classe também pode implementar diversas interfaces. Repare que a classe
Revista agora implementa duas interfaces, utilizando uma vírgula em sua
declaração.
A grande vantagem de trabalhar com interfaces é que apenas as classes
que a implementam são obrigadas a implementar seus métodos, portanto,
se eu não quero que MiniLivro tenha desconto, basta não implementar a
interface Promocional.
Observe como a estrutura de nossas classes está mais flexível. Qualquer
nova classe pode passar a ser promocional, sem herdar nenhuma outra obri-
gação. O mesmo ocorre com o Produto: tudo que uma classe precisa fazer
para ter o tipo Produto é assinar esse contrato e implementar seu método
getValor, sem nenhum efeito colateral indesejado.
113

Casa do Código
Capítulo 7. Interface
Adicionado: Ebook@21a33b44
Total 83.81
Mais à frente veremos que diversos default methods foram adicionados
nas interfaces da API do Java. Foi possível evoluir seu comportamento sem quebrar compatibilidade com suas implementações já existentes e bastante
utilizadas.
Interface funcional
Uma interface não precisa ter um único método abstrato, mas essa é uma
estrutura bem comum. Normalmente, trabalhar com interfaces menores é
uma estratégia interessante, afinal temos mais flexibilidade. Se alguém imple-
mentar aquela interface é porque realmente precisa do comportamento que
ela estabelece.
A partir do Java 8, as interfaces que obedecem essa regra de ter um único método abstrato podem ser chamadas de interface funcional. Mesmo
sem ter esse propósito, nossas interfaces Produto e Promocional possuem
essa estrutura e se enquadram no perfil de uma interface funcional.
Logo veremos que a interface funcional é a chave para outros novos recursos da linguagem, que são as expressões lambda e method reference s.
A anotação @FunctionalInterface
Podemos marcar uma interface como funcional explicitamente, para que
o fato de ela ser uma interface funcional não seja pela simples coincidência de ter um único método abstrato. Para fazer isso, usamos a anotação
@FuncionalInterface:
@FunctionalInterface
public interface Promocional {
boolean aplicaDescontoDe(double porcentagem);
default boolean aplicaDescontoDe10Porcento() {
return aplicaDescontoDe(0.1);
}
}
115
7.3. Novas regras da interface no Java 8
Casa do Código
Ao fazer essa alteração, note que nada mudou.
Ela continua com-
pilando e, executando nossas classes de teste, o resultado será o mesmo.
Mas diferente do caso de não termos anotado nossa interface com
@FunctionalInterface, tente alterá-la da seguinte forma, adicionando
um novo método abstrato:
@FunctionalInterface
public interface Promocional {
boolean aplicaDescontoDe(double porcentagem);
boolean naoSouMaisUmaInterfaceFuncional();
default boolean aplicaDescontoDe10Porcento() {
return aplicaDescontoDe(0.1);
}
}
O código deixará de compilar, com a seguinte mensagem:
Invalid '@FunctionalInterface' annotation;
Promocional is not a functional interface
E ao tentar compilar fora do Eclipse, com o javac como fizemos no
primeiro capítulo, a mensagem seria ainda mais explicativa:
java: Unexpected @FunctionalInterface annotation
Promocional is not a functional interface
multiple non-overriding abstract methods found
in interface Promocional
Outro detalhe que você já deve ter percebido é que podemos ter um ou
mais default method s declarados em nossa interface e isso não influencia o fato de ela ser ou não uma interface funcional, apenas métodos abstratos
são considerados.
Herança múltipla no Java 8?
Métodos defaults foram adicionados para permitir que interfaces
evoluam sem quebrar código existente. Essa frase foi bastante repetida na 116
Casa do Código
Capítulo 7. Interface
lista de discussão da especificação dessa nova versão da linguagem. Eles não
foram criados para permitir alguma variação de herança múltipla ou de mix-
ins. Vale lembrar que há uma série de restrições para esses métodos. Em especial, eles não podem acessar atributos de instância, até porque isso não
existe em interfaces! Em outras palavras, não há herança múltipla ou com-
partilhamento de estado.
117
Capítulo 8
Pacotes
8.1
Organizando nossas classes
Por enquanto nossas classes estão todas em um mesmo arquivo, dentro da
pasta src. Conforme o projeto vai evoluindo, mais e mais classes são cri-
adas e fica cada vez mais difícil manter a organização de nosso projeto. Mas
organização não será o único problema aqui.
Com o passar do tempo, trabalharemos com classes de terceiros ( bibliote-
cas) e classes da própria API da linguagem, o que torna ainda maior o risco de criarmos uma classe com o nome igual a outra existente em alguma dessas
bibliotecas.
Quer um exemplo? Poderíamos criar uma classe que se chama Date:
class Date {
private int dia;
8.1. Organizando nossas classes
Casa do Código
private int mes;
private int ano;
// getters e setters
}
Em um método main qualquer de nossa aplicação, poderíamos instan-
ciar essa classe fazendo:
public static void main(String[] args) {
Date date = new Date();
}
Mas na API do Java, já existe uma classe chamada Date. Como a
JVM sabe diferenciar quando precisamos instanciar a nossa classe Date ou quando deve ser o Date do Java? A resposta é bem simples: em Java classes
são agrupadas por pacotes ( packages).
O pacote da classe Date da API do Java com certeza será diferente do
pacote de nossa classe, portanto, para tirar a ambiguidade podemos instanciar
a classe pelo seu nome completo, como por exemplo:
public static void main(String[] args) {
java.util.Date date = new java.util.Date();
}
Estranho? Não se preocupe, vamos melhorar a legibilidade desse código
logo. Mas repare que o nome completo da classe, ou fully qualified name como é comumente chamado, é composto por nome do pacote . (ponto) nome
da classe. Neste caso, o nome do pacote onde se encontra a classe Date é java.util.
120
Casa do Código
Capítulo 8. Pacotes
Pacotes da API do Java
Além do java.util, existem diversos pacotes com classes essen-
ciais para nosso dia a dia trabalhando com Java. Algumas classes como
a String e System não precisam ser escritas com o nome completo,
pois fazem parte do pacote java.lang, que é o pacote padrão do Java.
No próximo capítulo estudaremos a fundo o pacote java.lang e, no
decorrer do livro, veremos alguns dos demais principais pacotes da API.
Observando o código a seguir você já deve ter sentido uma perda de leg-
ibilidade:
java.util.Date date = new java.util.Date();
Podemos simplificar esse código adicionando um import com o nome
completo da classe no início de nosso arquivo. Repare:
import java.util.Date;
class Teste {
public static void main(String[] args) {
Date date = new Date();
}
}
Só será necessário escrever o nome completo da classe sem um import se
por alguma razão você precisar usar duas classes com o mesmo nome dentro
do mesmo arquivo. Nesse caso, você poderá fazer o import de uma delas,
mas utilizar o nome completo quando for se referir à outra.
A essa altura você já deve ter se perguntado: Qual o nome completo da
nossa classe Date? Como nós criamos a classe diretamente no diretório src,
ela não tem um pacote definido. Dizemos que ela está no default package.
Isso não é nada bom, toda classe deve ser agrupada em pacotes. Isso,
além de ajudar na organização de nossos projetos, ajudará quando houver
uma ambiguidade de nomes.
121
8.1. Organizando nossas classes
Casa do Código
Nomenclatura padrão dos pacotes Java
Por padrão, um pacote em Java sempre:
• é escrito em letra minúscula ( lowercase);
• deve ser um nome de domínio, iniciado com com, edu, gov etc.
É muito natural que o pacote seja o seu domínio (ou da
empresa), como
br.com.casadocodigo,
br.com.alura ou
br.com.caelum. O link a seguir fala um pouco mais sobre as con-
venções de nomenclaturas de pacotes do Java:
http://www.oracle.com/technetwork/java/codeconventions-135099.
html
Agora que já sabemos disso, podemos criar alguns pacotes para melhor
organizar o nosso projeto. No Eclipse isso pode ser feito pelo menu File
> New > Package, ou utilizando o atalho Control + N e selecionando a
opção package.
A princípio, criaremos os pacotes:
• br.com.casadocodigo.livraria para as classes
Autor e
Editora;
• br.com.casadocodigo.livraria.produtos para as interfaces
Promocional, Produto e suas implementações;
• br.com.casadocodigo.livraria.teste para nossas classes ex-
ecutáveis, ou seja, que possuem o método main.
Antes de mover as classes para seus devidos pacotes, é importante con-
hecer algumas regras. A primeira delas é que nossas classes devem ser públi-
cas para que fiquem visíveis entre os diferentes pacotes. Logo falaremos mais sobre os modificadores de visibilidade e entenderemos a fundo as suas
regras, mas desde já mantenha a regra em mente.
122
Casa do Código
Capítulo 8. Pacotes
Outro detalhe importante é que, para o caso das classes públicas, o ar-
quivo .java obrigatoriamente tem que ter o nome da classe. Por exemplo,
a classe Livro tem que ser salva no arquivo Livro.java. Como vimos no
início deste livro, o ideal é você sempre nomear suas classes dessa forma. Essa é considerada uma boa prática em Java.
Agora sim, mãos à massa! Você pode arrastar as classes com o mouse para
mover entre pacotes, mas uma alternativa interessante é o atalho Control
+ Alt + V. Esse atalho é equivalente ao menu Refactor > Move....
Vamos selecionar as classes Autor e Editora e mover para o pa-
cote br.com.casadocodigo.livraria. Nesse momento, alguns erros
de compilação devem aparecer, mas devemos terminar de mover as classes
antes de corrigir qualquer um deles.
Promocional,
Produto,
Livro,
LivroFisico,
MiniLivro,
Ebook,
Revista e qualquer outra implementação de
Produto
que
você
tenha
criado
podem
ser
movidos
para
o
pacote
br.com.casadocodigo.livraria.produtos.
Para terminar, vamos mover as demais classes, que têm um método main,
para o pacote br.com.casadocodigo.livraria.testes. Lembre-se,
em um projeto real não criaríamos várias classes com método main, só esta-
mos fazendo isso para testar os nossos exemplos.
package, import e possíveis erros
Se você moveu as classes utilizando o Eclipse ou alguma IDE equivalente, repare que foi adicionado um import para toda classe que referencia alguma
presente em outro pacote. Caso você não tenha movido pela IDE ou por algum motivo o import não tenha sido adicionado, você precisará fazer isso
manualmente como no exemplo:
package br.com.casadocodigo.livraria.testes;
import br.com.casadocodigo.livraria.Autor;
// outros imports
public class CadastroDeLivros {
123
8.1. Organizando nossas classes
Casa do Código
public static void main(String[] args) {
Autor autor = new Autor();
autor.setNome("Rodrigo Turini");
// continuação da classe
}
Como a classe Autor está em um pacote ( package) diferente da classe
CadastroDeLivros, o import é necessário. Ele sempre é necessário
quando queremos utilizar classes de outros pacotes. E não se esqueça que,
para que uma classe seja visível para outro pacote, ela deve ser pública.
Um outro detalhe é que agora todas as classes têm o
package
a que pertencem declarado.
Neste exemplo, o
package da classe
CadastroDeLivros é:
package br.com.casadocodigo.livraria.testes;
A ordem das instruções de seus arquivos .java agora será: primeiro o
package ao qual ela pertence, seguido do(s) import(s) quando necessário
e, por último, a declaração da classe.
124
Casa do Código
Capítulo 8. Pacotes
Importando todas as classes de um package
No lugar de fazer os seguintes imports:
import br.com.casadocodigo.livraria.Autor;
import br.com.casadocodigo.livraria.Editora;
Você também pode utilizar o
* para importar todas as classes de
um package, neste caso o import seria:
import br.com.casadocodigo.livraria.*;
É comum ouvirmos que isso prejudica a performance, mas na reali-
dade não afetará o tempo de execução. O problema dessa abordagem é
que não previne a ambiguidade de nomes. Por isso, é sempre recomen-
dado que você importe classe a classe, pois, além de evitar problemas
com classes de mesmo nome, tornará a legibilidade dos imports mais
clara.
Ainda existe um erro de compilação em nosso projeto (ou talvez mais,
se o seu código não estiver idêntico ao do livro). Em nosso caso, o prob-
lema é na própria classe CadastroDeLivros, quando ela invoca o método
mostrarDetalhes do LivroFisico. O erro de compilação é o seguinte:
The method mostrarDetalhes() from the type Livro is not visible
Isso acontece porque, assim como as classes, para que um método seja
visível em outro pacote ele precisa ser public. Podemos acessar a classe
Livro e adicionar o modificador de visibilidade public no método ou fazer
isso pelo atalho Control + 1 do Eclipse ( quickfix), selecionando a opção Change visibility of method mostrarDetalhes() to public. No final, o método deve ficar assim:
public void mostrarDetalhes() {
System.out.println("Mostrando detalhes do livro ");
System.out.println("Nome: " + nome);
System.out.println("Descrição: " + descricao);
125
8.2. Modificadores de acesso
Casa do Código
System.out.println("Valor: " + valor);
System.out.println("ISBN: " + isbn);
if (this.temAutor()) {
autor.mostrarDetalhes();
}
System.out.println("--");
}
Estrutura de pastas
Note que um pacote é representado como uma estrutura de pastas no
seu sistema operacional. Por exemplo, a classe Livro que está local-
izada no pacote br.com.casadocodigo.livraria está dentro do
diretório br/com/casadocodigo/livraria, que se encontra den-
tro da pasta src de seu projeto.
8.2
Modificadores de acesso
Agora que nossas classes estão organizadas em pacotes, podemos entender
um pouco melhor os diferentes modificadores de acesso. Já vimos que, para
uma classe ou método ser acessado de outro pacote, eles precisam ter visi-
bilidade public. A regra é clara: uma classe pública pode ser acessada por
qualquer outra classe presente no mesmo projeto. O mesmo vale para atrib-
utos, métodos e construtores.
Também já conhecemos o private. Esse modificador de acesso torna
classes, atributos, métodos ou construtores visíveis apenas para a própria
classe. Por esse motivo, uma classe não deve ser anotada com private,
quem poderá acessá-la? Mas vimos que faz bastante sentido, para manter o
encapsulamento, sempre deixar seus atributos private.
Há ainda a visibilidade default (quando não há modificador algum).
A essa altura, você já pode ter percebido que neste caso apenas classes do
mesmo pacote podem ter acesso aos atributos, construtores, métodos ou
classes com a ausência de um modificador de acesso.
126

Casa do Código
Capítulo 8. Pacotes
O último modificador é o protected, que tentamos evitar no capítulo
de herança. Todo elemento que for protected ficará visível para a própria
classe, para suas classes filhas e também para quem estiver no mesmo pa-
cote. Classes também não podem utilizar esse modificador de acesso.
A imagem a seguir representa cada modificador de acesso e sua visibili-
dade:
127
Capítulo 9
Arrays e exception
9.1
Trabalhando com multiplicidade
Nosso CarrinhoDeCompras só está fazendo uma parte de seu trabalho, que
é acumular o valor total dos produtos adicionados. Também precisaremos ter
um atributo nessa classe para guardar uma referência a todos esses Produtos
adicionados.
Poderíamos adicionar um número limitado de atributos do tipo
Produto, como:
public class CarrinhoDeCompras {
private double total;
private Produto produto1;
private Produto produto2;
private Produto produto3;
9.1. Trabalhando com multiplicidade
Casa do Código
private Produto produto4;
// ...
// demais métodos da classe
}
Deixando esses atributos com o tipo Produto, no lugar de um Livro
ou Revista por exemplo, o polimorfismo nos daria a flexibilidade de
adicionar qualquer classe que implemente essa interface Produto. Mas,
fora isso, nosso código não está nem um pouco flexível! Além de termos
um número limitado de produtos, no método adiciona precisaríamos
fazer várias condições para verificar qual atributo está disponível antes de adicionar:
public class CarrinhoDeCompras {
private double total;
private Produto produto1;
private Produto produto2;
private Produto produto3;
private Produto produto4;
//..
public void adiciona(Produto produto) {
System.out.println("Adicionando: " + produto);
if (this.produto1 != null) {
this.produto1 = produto;
}
else if (this.produto2 != null) {
this.produto2 = produto;
}
else if (this.produto3 != null) {
this.produto3 = produto;
}
else if (this.produto4 != null) {
this.produto4 = produto;
}
// ...
130
Casa do Código
Capítulo 9. Arrays e exception
else {
System.out.println("Não tem mais espaços");
return;
}
total += produto.getValor();
}
}
Imagine como seria para mostrar os detalhes dos produtos adicionados,
novamente precisaríamos repetir esses ifs todos com as condicionais. Esse
código seria bem difícil de manter, quase inviável. Além disso, quantos atrib-
utos do tipo Produto um CarrinhoDeCompras teria? 10? 20? Isso vai
depender muito do cliente, ele pode estar comprando um único livro ou uma
coleção completa e diversos outros produtos.
Criando um array de Produto
Evitamos até agora trabalhar com esse recurso, mas, como vimos
no primeiro capítulo, podemos resolver esse problema trabalhando com
arrays! Não lembra o que é isso? Dê uma boa olhada novamente na
declaração do método main de qualquer uma de suas classes:
public static void main(String[] args) {
// seu código aqui
}
Repare que ele recebe um array de Strings como parâmetro! O
String[] args.
Podemos
utilizar
essa
mesma
estratégia
em
nosso
CarrinhoDeCompras, basta remover todos estes atributos do tipo
Produto e declarar um único array. Como fazer isso? É bem simples,
basta adicionar [] depois do tipo e pronto. Nosso CarrinhoDeCompras
vai ficar assim:
public class CarrinhoDeCompras {
private double total;
private Produto[] produtos;
131
9.1. Trabalhando com multiplicidade
Casa do Código
public void adiciona(Produto produto) {
// precisamos adicionar no array
}
}
Um array é um tipo, um objeto. Isso significa que, antes de fazer qual-
quer operação com o atributo produtos, precisaremos instanciá-lo. A sin-
taxe é um pouco diferente, mas é bem simples:
public class CarrinhoDeCompras {
private double total;
private Produto[] produtos = new Produto[10];
//..
}
Note que ao criar o array fomos obrigados a informar o seu tamanho!
Neste caso, criamos um array de 10 posições. Poderíamos sim receber
a quantidade de Produtos no construtor da classe ou criar um método
setProdutos, isso a deixaria um pouco mais flexível. Mas a princípio,
deixaremos o CarrinhoDeCompras assim, limitado a 10 produtos.
Agora que estamos trabalhando com um array, precisamos mudar o
método adiciona. Podemos fazer algo como:
public void adiciona(Produto produto) {
System.out.println("Adicionando: " + produto);
this.produtos[1] = produto;
this.total += produto.getValor();
}
Note que estamos adicionando todos os produtos na mesma posição do
array, na posição 1. Um detalhe muito importante é que a posição 1 é a
segunda posição do array, e não a primeira. Isso acontece pois seus índices
vão de 0 (zero) até seu tamanho -1, ou seja, como criamos um array de 10
posições, ele vai de 0 a 9.
Uma alternativa seria mudar a assinatura do método adiciona para
receber um int indice como parâmetro, mas deixar esse controle na mão
132
Casa do Código
Capítulo 9. Arrays e exception
de quem vai chamar o método pode não ser muito interessante, isso abriria
caminho para chamar o método duas vezes com o mesmo indice e um
elemento sobrescrever o outro, por exemplo.
No lugar disso podemos criar um atributo contador que é incremen-
tado a cada nova adição, veja:
public class CarrinhoDeCompras {
private double total;
private Produto[] produtos = new Produto[10];
private int contador = 0;
public void adiciona(Produto produto) {
System.out.println("Adicionando: " + produto);
this.produtos[contador] = produto;
contador ++;
this.total += produto.getValor();
}
public double getTotal() {
return total;
}
}
Problema resolvido! Sim, ainda estamos deixando passar algumas situ-
ações, como por exemplo tentar adicionar mais produtos do que cabe den-
tro de nosso array. E se eu tentar adicionar 11 produtos? Precisamos
tratar isso de alguma forma. Por enquanto, o código ficará assim, mas
em breve voltaremos para refatorar e cuidar desse tipo de detalhe da classe
CarrinhoDeCompras.
133


9.2. As diferentes exceções e como lidar com elas
Casa do Código
O enhanced-for do Java 5
Desde o Java 1.5, é possível iterar em um array (ou qualquer outro
Iterable, que veremos nos próximos capítulos) com uma sintaxe um
pouco diferente. No lugar de:
for (int i = 0; i < produtos.length; i++) {
Produto produto = produtos[i];
if (produto != null) {
System.out.println(produto.getValor());
}
}
Podemos fazer o mesmo da seguinte forma, com o enhanced-for:
for (Produto produto : produtos) {
if (produto != null) {
System.out.println(produto.getValor());
}
}
Repare que nesse caso não temos um
indice e não pre-
cisamos conhecer o tamanho ( lenght) do
array para con-
seguir percorrê-lo, isso evitaria a confusão com o <= que causou o
ArrayIndexOutOfBoundsException.
9.2
As diferentes exceções e como lidar com
elas
Conhecendo a Stacktrace
Pode parecer um pouco assustador no começo, mas a stacktrace normal-
mente nos passa as informações necessárias e fundamentais para a compreen-
são e resolução dos problemas. Vamos analisá-la com mais atenção:
Exception in thread "main"
java.lang.ArrayIndexOutOfBoundsException: 10
136
Casa do Código
Capítulo 9. Arrays e exception
at br.com.casadocodigo.livraria.testes
.RegistroDeVendas.main(RegistroDeVendas.java:38)
Essa é uma stacktrace bem pequena, você pode perceber o quanto é
fácil de entender. A primeira linha está nos informando qual o nome do prob-
lema (ou exception, como passaremos a chamá-lo) que ocorreu no código
e nos diz em qual posição do array isso aconteceu ( 10). Na segunda e última
linha temos o fully qualified name da classe em que a exception
aconteceu, que ainda nos diz em qual linha, neste caso 38!
Investigando problemas
É muito comum enviarmos a stacktrace quando estamos procu-
rando por ajuda em fóruns (como o http://guj.com.br) ou listas de dis-
cussões. Isso facilita bastante na investigação do problema.
Tratando exceptions
Evitar essa exception seria bem simples, bastaria corrigir a comparação
com <= ou utilizar o enhanced-for. Mas há uma forma bastante conhecida
e já utilizada em diversas linguagens para tentar executar um bloco de código de risco e capturar caso ocorra uma exception neste bloco, evitando que a stacktrace seja exibida para o usuário final e que a exceção pare a execução
de nosso código. Esse recurso é conhecido como try/catch.
Para testá-lo, coloque um sysout logo após o for que está gerando o
problema no RegistroDeVendas, algo como:
for (int i = 0; i <= produtos.length; i++) {
Produto produto = produtos[i];
if (produto != null) {
System.out.println(produto.getValor());
}
}
System.out.println("Fui executado!");
137
9.2. As diferentes exceções e como lidar com elas
Casa do Código
Rode novamente e repare na saída, a mensagem "Fui executado!"
não foi impressa. Vamos agora adicionar o try/catch e executar o código
novamente:
for (int i = 0; i <= produtos.length; i++) {
try {
Produto produto = produtos[i];
if (produto != null) {
System.out.println(produto.getValor());
}
} catch (ArrayIndexOutOfBoundsException e) {
System.out.println("deu exception no indice: "+ i);
}
}
System.out.println("Fui executado!");
Agora a saída será:
Total 83.81
53.91
29.9
deu exception no indice: 10
Fui executado!
Como a exception foi devidamente tratada, a execução do código não
foi interrompida.
138
Casa do Código
Capítulo 9. Arrays e exception
Entendendo a sintaxe do T R Y/C A T C H
Pode parece um pouco estranho ou até mesmo assustador no começo,
mas a sintaxe do try/catch não tem nada de tão complicado. Veja:
try {
// algum código de risco
} catch (ArrayIndexOutOfBoundsException e) {
// o que fazer aqui?
}
Nesse caso, dentro do catch podemos colocar o código que quere-
mos executar apenas quando uma exception do tipo declarado acon-
tecer. Ou seja, nele colocamos o código que queremos executar apenas
caso um ArrayIndexOutOfBoundsException ocorra.
Toda exception é filha de Exception
Em
nosso
try/catchm
estamos
capturando
apenas
o
ArrayIndexOutOfBoundsException,
mas
o
que
aconte-
ceria se o
array produtos fosse
null?
Mesmo que com o
try/catch declarado, receberíamos um outro tipo de exception, a
java.lang.NullPointerException.
Isso
acontece
porque
estamos
deixando
claro
no
parâmetro
de
nosso
catch
que
queremos
capturar
um
ArrayIndexOutOfBoundsException,
e
não
um
NullPointerException. Poderíamos fazer algo mais genérico como:
for (int i = 0; i <= produtos.length; i++) {
try {
Produto produto = produtos[i];
if (produto != null) {
System.out.println(produto.getValor());
}
} catch (Exception e) {
System.out.println("deu exception no indice: "+ i);
139

Casa do Código
Capítulo 9. Arrays e exception
Capturar Exception nem sempre é muito interessante, afinal se trata de
um tipo muito genérico! Muitas vezes queremos dar uma tratativa diferente
para cada tipo de exceção do código. Uma alternativa para isso seria capturar
mais de uma exception em um mesmo bloco try:
try {
Produto produto = produtos[i];
if (produto != null) {
System.out.println(produto.getValor());
}
} catch (ArrayIndexOutOfBoundsException e) {
System.out.println("deu exception no indice: "+ i);
} catch (NullPointerException e) {
System.out.println("Aarray não foi instanciado!");
}
Agora estamos mostrando uma mensagem diferente para cada situação.
M U L T I C A T C H do Java 7
Caso você queira executar a mesma ação para dois tipos de
Exception diferentes, desde o Java 7 você pode utilizar uma sintaxe
um pouco diferente:
try {
Produto produto = produtos[i];
if (produto != null) {
System.out.println(produto.getValor());
}
} catch
(ArrayIndexOutOfBoundsException | NullPointerException e) {
System.out.println("foi uma das duas");
}
A
mensagem
"foi uma das duas"
será
exibida
caso
uma
ArrayIndexOutOfBoundsException
ou
um
NullPointerException tenha ocorrido.
141
9.3. Muitas e muitas Exception
Casa do Código
Executando uma ação com ou sem exception
Existem momentos em que precisamos executar alguma ação após um
try ou catch, independente se houve ou não uma exception envolvida.
Nesses casos, podemos utilizar uma terceira cláusula chamada finally.
Observe sua sintaxe:
try {
// código de risco
} catch (NullPointerException e) {
// tratando NPE
} catch (Exception e) {
// tratando Exception
} finally {
// alguma ação importante
}
As instruções do bloco finally serão executadas sempre, independente
de tudo ter ido bem ou de alguma exception ter acontecido.
Esse recurso é muito comum quando estamos trabalhando com conexão
do banco de dados ou leitura/escrita de arquivos. Independente de qualquer
coisa, depois de usar uma conexão, por exemplo, sempre queremos fechá-la.
9.3
Muitas e muitas Exception
As RuntimeExceptions
Claro, além de um for tentando acessar mais índices do que deveria e a
tentativa de executarmos ações em referências nulas, existem diversas outras
situações de risco em que a JVM lançará uma exception. Cada situação
dessa tem uma forma (um tipo) forte de ser representada; conhecê-las pode
ser muito importante para nosso dia a dia.
Veremos diversas dessas situações e diferentes tipos de Exception no
decorrer deste capítulo e dos demais do livro, mas desde já é interessante en-
tendermos que todos estes casos que vimos até agora poderiam facilmente ser
evitados de forma simples pelo programador.
142

Casa do Código
Capítulo 9. Arrays e exception
Conhecemos estes tipos de Exceptions como unchecked, pois o com-
pilador não verifica (checa) se as estamos tratando. Em outras palavras, nosso
código compilará com ou sem os try/catchs declarados.
As
unchecked exceptions não herdam diretamente da classe
Exception, mas sim de sua filha RuntimeException. Os casos mais co-
muns são:
Checked Exceptions
Diferente disso, quando estamos fazendo a leitura de um arquivo ou qual-
quer outra operação de risco que não pode ser facilmente evitada (o ar-
quivo pode não existir, por exemplo) o compilador nos obriga a tratar esse
código! Esse é um outro tipo de exception da linguagem, conhecida como
checked exception.
Para testar, vamos escrever o seguinte código que realiza a leitura de um
143
9.3. Muitas e muitas Exception
Casa do Código
arquivo que não existe. Não se preocupe com o código em si, entenderemos
a API de IO (leitura e escrita) mais à frente. O ponto aqui é perceber que esse código não compila:
new java.io.FileInputStream("arquivo.txt");
O erro será “Unhandled exception type FileNotFoundException
. Note que o compilador checou que ela não está sendo tratada. Para que
o código compile poderíamos fazer algo como:
try {
new java.io.FileInputStream("arquivo.txt");
} catch (FileNotFoundException e1) {
System.out.println("Não consegui abrir o arquivo");
}
Delegando a tratativa com throws
Uma alternativa para o try/catch nesse caso seria adicionando um
throws FileNotFoundException. Isso significa que estamos delegando
a tratativa para quem chamar este método:
public void abreArquivo() throws FileNotFoundException {
new java.io.FileInputStream("arquivo.txt");
}
Dessa forma, o throws está indicando ao compilador que quem chamar
o método abreArquivo deverá tratar o FileNotFoundException. Em
outras palavras, isso diz: “Ei, esse é um código de risco, ele pode lançar uma FileNotFoundException
.
Chamando-o a partir de um método main poderíamos tratar o erro:
public static void main(String[] args) {
try {
abreArquivo();
} catch (FileNotFoundException e1) {
System.out.println("Não consegui abrir o arquivo");
}
}
144
Casa do Código
Capítulo 9. Arrays e exception
A saída será:
Não consegui abrir o arquivo
Ou ainda, usar o throws novamente, no próprio método main:
public static void main(String[] args)
throws FileNotFoundException {
abreArquivo();
}
Dessa forma, ninguém tratará a exception, estamos deixando passar
para a JVM. A saída será:
Exception in thread "main" java.io.FileNotFoundException:
arquivo.txt (No such file or directory)
at java.io.FileInputStream.open(Native Method)
at java.io.FileInputStream.<init>(FileInputStream.java:131)
at java.io.FileInputStream.<init>(FileInputStream.java:87)
at br.com.casadocodigo.livraria.testes
.RegistroDeVendas.abreArquivo(RegistroDeVendas.java:62)
at br.com.casadocodigo.livraria.testes
.RegistroDeVendas.main(RegistroDeVendas.java:17)
T H R O W ou T H R O W S?
É muito comum confundir no início, mas lembre-se que usamos
o throw no imperativo quando estamos efetivamente lançando uma
exeption. Quando escrito no indicativo, o throws apenas informa
ao compilador e a quem mais interessar que determinado método lança
uma exception.
145

9.3. Muitas e muitas Exception
Casa do Código
A familia Throwable
Vimos que existem diversas Exceptions, sejam elas checked ou
unchecked, porém, além delas existem os tão temidos Errors. Estes são
como um tipo de exception, mas normalmente representam problemas
na JVM (como o OutOfMemoryError). Apesar de em alguns casos con-
seguirmos capturar/tratar estes erros, muitas vezes o melhor a fazer é deixar
a JVM encerrar sua execução. E apesar de ser possível, não devemos lançar
Error em nosso código. Isso não é considerado uma boa prática.
O Error e Exception são irmãos, pois ambos herdam de Throwable!
A imagem a seguir mostra um pouco da imensa família Throwable:
146
Casa do Código
Capítulo 9. Arrays e exception
9.4
Também podemos lançar exceções!
Nosso código também pode lançar uma exception caso algo inesperado
aconteça. E não estou falando de lançar uma exception acidentalmente.
Assim como um array dispara o ArrayIndexOutOfBoundsException
quando fazemos algo inesperado (tentar acessar mais valores do que o array
suporta), podemos fazer isso com nossas regras.
Um exemplo simples é na própria classe Livro, onde desde o início
definimos que seria obrigatório passar um Autor como argumento. A re-
gra é clara: não se pode ter um Livro sem Autor e, para garantir isso,
criamos o construtor que obrigava a passagem desse parâmetro:
public Livro(Autor autor) {
this.autor = autor;
this.isbn = "000-00-00000-00-0";
}
Isso já é bem interessante, mas o grande problema é que ainda podemos
passar uma referência nula de Autor no momento em que criamos o livro,
como:
Livro livro = new LivroFisico(null);
Até poderíamos validar o valor passado para o construtor:
public Livro(Autor autor) {
if (autor == null) {
// o que fazer aqui?
}
this.autor = autor;
this.isbn = "000-00-00000-00-0";
}
Mas o que poderia ser feito se o autor fosse null? Assim como a API do
Java, podemos lançar exceptions para casos como esse. Essa é uma forma
mais robusta do que validar e retornar um boolean ou uma mensagem que
pode simplesmente ser ignorada.
Veja como fica nosso código:
147
9.4. Também podemos lançar exceções!
Casa do Código
public Livro(Autor autor) {
if (autor == null) {
throw new RuntimeException();
}
this.autor = autor;
this.isbn = "000-00-00000-00-0";
}
Repare que a palavra reservada throw precede a exception que está
sendo disparada, neste caso uma RuntimeException. Há ainda a possibil-
idade de passar uma mensagem via construtor, comportamento presente na
superclasse Exception:
public Livro(Autor autor) {
if (autor == null) {
throw new RuntimeException(
"O Autor do Livro não pode ser nulo");
}
this.autor = autor;
this.isbn = "000-00-00000-00-0";
}
A saída será:
Exception in thread "main" java.lang.RuntimeException:
O Autor do Livro não pode ser nulo
at br.com.casadocodigo.
livraria.produtos.Livro.<init>(Livro.java:16)
at br.com.casadocodigo.
livraria.produtos.LivroFisico.<init>(LivroFisico.java:9)
at br.com.casadocodigo.
livraria.testes.RegistroDeVendas.main(RegistroDeVendas.java:16)
Criando sua própria Exception
No lugar de lançar uma RuntimeException, podemos criar uma
exception bem específica para esse comportamento inesperado. Vamos
criar a AutorNuloException!
148
Casa do Código
Capítulo 9. Arrays e exception
Como fazer isso? Na verdade, é bem simples: assim como qualquer outra
Exception essa será uma classe normal que herda de Exception ou de
uma de suas filhas. Para que ela seja evitável ( uncheked), vamos herdar de
RuntimeException:
package br.com.casadocodigo.livraria.exception;
public class AutorNuloException extends RuntimeException {
public AutorNuloException(String mensagem) {
super(mensagem);
}
}
Note que criamos um construtor que delega a mensagem para o constru-
tor da superclasse, dessa forma nosso código poderá ficar assim:
public Livro(Autor autor) {
if (autor == null) {
throw new AutorNuloException(
"O Autor do Livro não pode ser nulo");
}
this.autor = autor;
this.isbn = "000-00-00000-00-0";
}
Rode novamente o código problemático e veja que a saída será:
Exception in thread "main" br.com.casadocodigo.livraria.exception
.AutorNuloException: O Autor do Livro não pode ser nulo
at br.com.casadocodigo.livraria.produtos
.Livro.<init>(Livro.java:17)
at br.com.casadocodigo.livraria.produtos
.LivroFisico.<init>(LivroFisico.java:9)
at br.com.casadocodigo.livraria.testes
.RegistroDeVendas.main(RegistroDeVendas.java:16)
Você pode ler mais sobre Exceptions em sua documentação:
149
9.4. Também podemos lançar exceções!
Casa do Código
http://docs.oracle.com/javase/8/docs/api/java/lang/Exception.html
Repare que logo no inicio deste arquivo temos suas subclasses diretas
( Direct Known Subclasses). Achou que são muitas? E essas são apenas as filhas diretas, ainda temos as filhas de RuntimeException e de suas diversas
outras subclasses.
Por isso, sempre antes de criar uma Exception própria, tente conferir
se já não há uma implementação existente que represente bem a situação in-
esperada.
150
Capítulo 10
Conhecendo a API
10.1
Todo objeto tem um tipo em comum
Criamos a interface Produto para o CarrinhoDeCompras suportar a
adição de Revistas e Livros. Essa interface estabelece um tipo em comum
entre essas duas classes, portanto o CarrinhoDeCompras ficou assim:
public class CarrinhoDeCompras {
private double total;
private Produto[] produtos = new Produto[10];
private int contador = 0;
public void adiciona(Produto produto) {
System.out.println("Adicionando: " + produto);
this.produtos[contador] = produto;
10.1. Todo objeto tem um tipo em comum
Casa do Código
contador ++;
this.total += produto.getValor();
}
// getters para o total e array de produtos
}
O polimorfismo é a chave para que isso tudo funcione, nosso método
adiciona recebe um Produto como parâmetro e o acumula no array de
Produtos.
Essa foi uma boa estratégia, mas as duas classes não precisariam im-
plementar nenhuma interface para ter um tipo em comum, pois sem-
pre que criamos uma nova classe em Java ela obrigatoriamente terá uma
superclasse.
Até a classe Revista tem uma superclasse, mas repare em sua assi-
natura:
public class Revista implements Produto, Promocional {
// código omitido
}
Não há nenhum extends declarado nessa classe, como ela pode es-
tar herdando de alguém? Essa é uma situação parecida com a do constru-
tor default, lembra? Na ausência de um construtor, o compilador adi-
cionará um construtor vazio para você. Assim é com a herança, na ausência
de um extends, o Java dirá que sua classe extends um tipo conhecido
como Object.
Com isso, podemos concluir que toda classe em Java é um Object, sem
exceção. Pode não ser diretamente, mas ainda que indiretamente, ela será,
pois herdará de alguém que herda diretamente ou indiretamente de Object.
A grande vantagem desse tipo em comum é que toda classe herda alguns
métodos declarados na classe Object e ainda podem ser referenciadas como
tal. Logo veremos alguns desses métodos e como tirar proveito deles em nosso
dia a dia.
152
Casa do Código
Capítulo 10. Conhecendo a API
Castings e mais castings
Se toda classe já tem esse tipo em comum, por que não mudamos
nosso CarrinhoDeCompras para receber e acumular Object, no lugar
de Produto, como estamos fazendo? Nosso código ficaria assim:
public class CarrinhoDeCompras {
private double total;
private object[] objects = new object[10];
private int contador = 0;
public void adiciona(object object) {
System.out.println("Adicionando: " + object);
this.objects[contador] = object;
contador ++;
this.total += object.getValor();
}
// getters para o total e arrayde objects
}
Tente mudar seu código, que logo você perceberá o primeiro problema.
A seguinte linha não compila:
this.total += object.getValor();
Já descobriu por quê? Object não tem o método getValor, não há
garantia para o compilador de que qualquer coisa que passarmos como argu-
mento para esse método terá esse método.
Como resolver? Bem, poderíamos fazer um casting como a seguir,
moldando a referência do Object para um Produto:
Produto moldado = (Produto) object;
this.total += moldado.getValor();
Mas, além de muito feio, esse código é muito perigoso, afinal e se o
argumento passado (que agora pode ser qualquer coisa) não implementar
Produto? O resultado seria um ClassCastException.
153
10.1. Todo objeto tem um tipo em comum
Casa do Código
E o problema iria além. Por exemplo, para imprimir o valor dos produtos
na classe RegistroDeVendas, estamos fazendo o seguinte código:
Produto[] produtos = carrinho.getProdutos();
for (int i = 0; i <= produtos.length; i++) {
try {
Produto produto = produtos[i];
if (produto != null) {
System.out.println(produto.getValor());
}
} catch (ArrayIndexOutOfBoundsException e) {
System.out.println("deu exception no indice: "+ i);
e.printStackTrace();
}
}
Mas agora que já vimos que essa não é a melhor forma de iterar em um
array, podemos utilizar o enhanced-for do Java 7. Nosso código ficaria
assim:
Produto[] produtos = carrinho.getProdutos();
for (Produto produto : produtos) {
System.out.println(produto.getValor());
}
Muito mais simples, não acha? Só que se produtos fosse um array de
Object, esse código não funcionaria como esperamos. Repare:
Object[] produtos = carrinho.getProdutos();
for (Object object : produtos) {
System.out.println(object.getValor());
}
Novamente: Object não tem o método getValor, portanto, o código
não compila. Poderíamos fazer o casting, claro, mas veja como ficaria
nosso código:
154
Casa do Código
Capítulo 10. Conhecendo a API
Object[] produtos = carrinho.getProdutos();
for (Object object : produtos) {
try {
Produto moldado = (Produto) object;
System.out.println(moldado.getValor());
} catch(ClassCastException e) {
System.out.println("O objeto passado não implementa Produto");
}
}
Difícil de ler, manter e nem um pouco flexível. Você já deve ter perce-
bido que receber um Object como parâmetro nem sempre é uma boa al-
ternativa, pois sempre precisaremos de castings e mais castings, não
há muito como fugir. Manter o tipo do parâmetro e array da classe
RegistroDeVendas como Produto neste caso será uma estratégia bem
mais interessante.
C A S T I N G em uma só linha
Também podemos fazer nossos castings em uma única linha,
como no exemplo:
double valor = ((Produto)object).getValor();
O efeito do código seria o mesmo, mas talvez com uma sintaxe um
pouco mais carregada custando legibilidade.
Alguns dos métodos da classe Object
Vimos que o custo de fazer polimorfismo com Object pode nem
sempre valer a pena, mas isso não invalida o quanto ela é importante para a
linguagem Java. Nela, estão presentes todos os métodos que toda classe em
Java deve ter!
Quer um exemplo? Alguma vez você já deve ter escrito o seguinte código:
System.out.println(ebook);
155

Casa do Código
Capítulo 10. Conhecendo a API
contrário, o código não compilaria. Vamos testar, basta imprimir um ebook
novamente:
System.out.println(ebook);
Agora a saída será:
Eu sou um Ebook
É sempre interessante sobrescrever esse método, porque assim con-
seguimos estabelecer como representar nossas classes como um texto de uma
forma mais elegante do que o padrão do Object. Já estávamos fazendo isso
com o método mostrarDetalhes, lembra? Podemos modificar sua assi-
natura para que agora seja o toString das classes Livro e Autor, um
exemplo:
public abstract class Livro implements Produto {
// código omitido
@Override
public void toString() {
System.out.println("Nome: " + nome);
System.out.println("Descrição: " + descricao);
System.out.println("Valor: " + valor);
System.out.println("ISBN: " + isbn);
if (this.temAutor()) {
autor.toString();
}
System.out.println("--");
}
}
Comparando objetos com equals
Outra situação muito comum em nosso dia a dia é a necessidade de com-
parar objetos. Vimos nos primeiros capítulos do livro que == sempre com-
para o valor dos atributos, que em caso de objetos será sua referência. Isso
significa que o resultado da seguinte comparação será false:
157
10.1. Todo objeto tem um tipo em comum
Casa do Código
Autor autor = new Autor();
autor.setNome("Rodrigo Turini");
Autor autor2 = new Autor();
autor2.setNome("Rodrigo Turini");
if (autor == autor2) {
System.out.println("Igual");
} else {
System.out.println("Diferente");
}
Execute o código para comprovar: será impressa a palavra Diferente.
Faz sentido, já era de se esperar. Mas e se estivermos interessados em com-
parar os valores dos atributos? Podemos utilizar outro método muitíssimo
interessante da classe Object, o equals. Mas o Java por si só não vai saber
como queremos comparar nossos Autores, se eu apenas mudar o código
para utilizar o equals da forma a seguir, o resultado ainda será false:
if (autor.equals(autor2)) {
System.out.println("Igual");
} else {
System.out.println("Diferente");
}
O resultado ainda será false, pois o método equals da classe Object
faz uma comparação com ==, assim como já estávamos fazendo. Mas, afinal,
qual a vantagem de usar o equals? Podemos sobrescrevê-lo ensinando quais
são os critérios de comparação. Um exemplo:
public class Autor {
// atributos e métodos omitidos
@Override
public boolean equals(Object obj) {
Autor outro = (Autor) obj;
return this.nome.equals(outro.nome);
158
Casa do Código
Capítulo 10. Conhecendo a API
}
}
Estamos definindo que, sempre que um Autor tiver um nome igual ao
outro, eles são iguais; em outras palavras, o equals deve retornar true.
Vamos entender melhor essa sobrescrita, a começar pala linha:
Autor outro = (Autor) obj;
Note que o equals recebe Object como argumento, portanto a
primeira coisa que fizemos ao implementá-lo foi moldar esse parâmetro para
o tipo Autor. Sim, há um risco bem grande de um ClassCastException
caso o parâmetro passado não seja realmente um Autor, depois veremos
algumas alterativas para lidar com isso.
Veja que logo em seguida fazemos o seguinte retorno:
return this.nome.equals(outro.nome);
Qual seria o problema de fazer o seguinte?
return this.nome == outro.nome;
Já percebeu? Claro, String é um objeto, logo estaríamos comparando
sua referência. Adiante falaremos mais sobre o problema da comparação
de Strings, mas o importante agora é perceber que estamos delegando a
comparação do nome ( String) para o método equals da própria classe
String. Ele foi sobrescrito para fazer a comparação pelo texto passado, e não
pela referência de memória.
Lidando com o ClassCastException
Vimos brevemente que uma ClassCastException pode ser lançada
caso o parâmetro passado para o equals não seja do mesmo tipo moldado,
como no seguinte exemplo:
if(autor.equals("Rodrigo")) {
System.out.println("iguais");
}
159
10.1. Todo objeto tem um tipo em comum
Casa do Código
Esse
código
compilará
sem
nenhum
problema,
afinal
String é um
Object.
Mas ao executá-lo recebemos uma
java.lang.ClassCastException: java.lang.String cannot
be cast to br.com.casadocodigo.livraria.Autor.
Como re-
solver? Uma maneira seria tratando com um try/catch, mas veja como o
código fica verboso e difícil de ler:
@Override
public boolean equals(Object obj) {
Autor outro;
try {
outro = (Autor) obj;
} catch (ClassCastException e) {
return false;
}
return this.nome.equals(outro.nome);
}
Uma forma mais interessante seria utilizando o instanceof, como a
seguir:
@Override
public boolean equals(Object obj) {
if (!(obj instanceof Autor)) return false;
Autor outro = (Autor) obj;
return this.nome.equals(outro.nome);
}
Dessa maneira, se o parâmetro passado não foi uma instância do tipo
Autor, retornamos false indicando que não são objetos iguais.
Existem outros métodos interessantes na classe Object, alguns deles são
o getClass e hashcode. Falaremos deste último no próximo capítulo, mas
saiba desde já que ele sempre acompanha o equals. Um exemplo simples
de uso do getClass seria para mostrar o nome simples da classe, sem seu
pacote ( fully qualified name). Repare:
System.out.println(autor.getClass().getSimpleName());
160
Casa do Código
Capítulo 10. Conhecendo a API
10.2
Wrappers dos tipos primitivos
Já não é nenhuma surpresa que o seguinte código compile:
Object objeto = new Autor("Rodrigo");
Nem mesmo se for apenas uma String, pois todo objeto em Java herda
direta ou indiretamente de Object.
Object objeto = "Uma String";
Mas o que você acha do código a seguir?
Object objeto = 10;
Se você estiver utilizando uma versão maior que Java 1.4, ele compilará.
Isso não significa que os tipos primitivos como o int herdam de Object ou
são do mesmo tipo que uma referência, esse é apenas um truque do compi-
lador adicionado no Java 1.5 para simplificar um pouco a sintaxe. Esse recurso
é conhecido como autoboxing.
Sem isso, para que o código compile precisaríamos fazer:
Object objeto = new Integer(10);
Note que a classe Integer é usada para embrulhar ( wrapping) o tipo
primitivo int, de forma que podemos tratá-la como uma referência, um
Object. O mesmo se aplica aos demais tipos primitivos. A imagem a seguir
mostra cada um deles e sua classe wrapper (box).
161

10.2. Wrappers dos tipos primitivos
Casa do Código
Também podemos desembrulhar os valores primitivos das classes
wrappers:
Integer integer = new Integer(10);
int valor = integer.intValue();
Mas essa não é a única utilidade das classes wrappers, elas estão repletas
de métodos estáticos que podem ser bastante úteis para manipular os tipos
primitivos.
Conversão de valores
Como transformar um boolean em uma String? E o contrário? Como
transformar um texto em um double válido? Todas essas conversões po-
dem ser feitas de forma bem simples a partir das classes que embrulham esses
162
Casa do Código
Capítulo 10. Conhecendo a API
tipos primitivos. Por exemplo, o método parseBoolean da classe Boolean
transforma uma String em um booleano. Veja:
boolean resultado = Boolean.parseBoolean("false");
O mesmo pode ser feito para os demais tipos:
byte parseByte = Byte.parseByte("1");
short parseShort = Short.parseShort("10");
int parseInt = Integer.parseInt("10");
long parseLong = Long.parseLong("10");
float parseFloat = Float.parseFloat("10.0");
double parseDouble = Double.parseDouble("10.0");
Para transformar qualquer um desses valores em uma String, podemos
utilizar seu método estático valueOf:
String numeroEmTexto = String.valueOf(10);
Claro, precisamos tomar bastante cuidado com o valor passado, pois adi-
vinhe qual será o resultado do código a seguir?
int parseInt = Integer.parseInt("ABC");
Uma Exception! Neste caso: java.lang.NumberFormatException:
For input string: "ABC". Faz bastante sentido, afinal, como transfor-
mar ABC em um número?
Mais métodos úteis
Aproveite o momento para explorar os diversos métodos existentes
em Object e nas classes wrapper s, como a Interger. Eles com certeza
serão bem úteis em seu dia a dia codando em Java!
10.3
O pacote java.lang
Você já deve ter percebido que até agora não precisamos fazer import de
nenhuma das novas classes que vimos neste capítulo. Outro detalhe é que em
163
10.3. O pacote java.lang
Casa do Código
nenhum momento anterior precisamos fazer import da classe String ou
mesmo da classe System, não é?
Isso porque Object, String, System, classes wrappers dos tipos
primitivos entre diversas outras estão todas presentes no pacote padrão do
Java, o java.lang. Este pacote é automaticamente incluído em todas as
suas classes, diferente de todos os demais da linguagem.
Nele você encontrará classes como a Math, que tem um arsenal completo
de métodos que o auxiliam em operações numéricas como arredondamento
( round), mínimo ( min), máximo ( max), seno ( sin), entre muitos outros.
Alguns exemplos de uso:
long round = Math.round(3.99);
long max = Math.max(100, 10);
int min = Math.min(100, 10);
int abs = Math.abs(-5);
double sqrt = Math.sqrt(4);
Os valores das variáveis, respectivamente, serão:
4
100
10
5
2.0
Você pode ver a documentação completa dessa classe em:
http://docs.oracle.com/javase/8/docs/api/java/lang/Math.html
Outra classe muito útil em nosso dia a dia é a Random, veja como é fácil
imprimir um número aleatório de 0 a 10:
Random random = new Random();
System.out.println(random.nextInt(10));
Da mesma forma como o nextInt, também existe um método next
para cada tipo primitivo, como o nextBoolean ou nextDouble.
164
Casa do Código
Capítulo 10. Conhecendo a API
java.lang.String
Para fechar nosso passeio pelo pacote padrão do Java, vamos entender um
pouco melhor a classe String. É muito comum confundi-la com um tipo
primitivo, mas Strings guardam referências a objetos. A prova disso é que
podemos instanciá-la da seguinte forma:
String java = new String("Java");
Assim como qualquer outra variável de referência, não devemos
compará-la utilizando o operador ==, mas sim o equals que foi reescrito na
classe String para comparar cada caractere do texto. Portanto, o resultado
do código a seguir será true:
String java = "java";
String java2 = "java";
System.out.println(java.equals(java2));
Mas algo bastante inesperado acontecerá ao compararmos duas Strings
desta forma:
String java = "java";
String java2 = "java";
System.out.println(java == java2);
Aqui, o resultado também será true! Por quê? Isso acontece pois, por
otimização, o Java cria um String pool, um tipo de cache de Strings.
Sempre antes de adicionar em memória, a JVM consulta esse pool para ver-
ificar se não há uma String igual que possa ser reutilizada.
Para que isso funcione bem, o Java não poderia permitir que a mudança
no valor de uma String afetasse outras Strings que tivessem seu mesmo
valor. Por isso, toda String é imutável.
Quer uma prova? Tente substituir o valor de uma String com seu
método replace. É bem simples, o primeiro argumento será o valor atual e
o segundo o valor que deverá tomar o seu lugar, por exemplo:
165
10.3. O pacote java.lang
Casa do Código
java.replace("v", "c");
System.out.println(java);
É natural esperar que o resultado impresso seja “jaca”, uma vez que sub-
stituímos o caractere “v” por “c”, mas rode o código para ver o resultado:
java
É isso mesmo, o valor da variável java continuou o mesmo, já que toda
String é imutável. Para obter o resultado esperado, podemos resgatar o re-
torno do método replace, que será uma nova String. Repare:
String novaString = java.replace("v", "c");
System.out.println(novaString);
O mesmo vale para todo método que aplica transformações da String,
eles sempre retornam uma referência nova para o valor transformado.
Agora que já conhecemos essa característica fundamental, podemos usar
os seus mais de 60 métodos. Alguns exemplos são:
String replace = java.replace("v", "c");
String upperCase = java.toUpperCase();
String lowerCase = "JAVA".toLowerCase();
char charAt = java.charAt(0);
boolean endsWith = java.endsWith("a");
boolean startsWith = java.startsWith("s");
boolean equals = java.equalsIgnoreCase("JAVA");
Os valores das variáveis, respectivamente, serão:
jaca
JAVA
java
j
true
false
true
Dominar o pacote padrão do Java é um grande diferencial. Muitas vezes
já há algo implementado para nos ajudar com situações do dia a dia. Em
166
Casa do Código
Capítulo 10. Conhecendo a API
outras palavras, o pacote java.lang com certeza fará parte de sua rotina.
Aproveite para estudar seus diversos outros métodos e classes. Você pode
encontrar a documentação completa em:
http://docs.oracle.com/javase/8/docs/api/java/lang/package-summary.
html
167
Capítulo 11
Collection Framework
11.1
O trabalho de manipular arrays
A princípio, deixamos nosso CarrinhoDeCompras limitando o total de
Produtos em 10 itens. Um número fixo e nem um pouco real. Uma forma
simples de flexibilizar a quantidade de Produtos seria forçando a passagem
do array de Produtos pelo construtor:
public class CarrinhoDeCompras {
private double total;
private Produto[] produtos;
private int contador = 0;
public CarrinhoDeCompras(Produto[] produtos) {
this.produtos = produtos;
11.1. O trabalho de manipular arrays
Casa do Código
}
// outros métodos da classe
}
Isso torna seu uso um pouco mais interessante. No momento de criar um
CarrinhoDeCompras, passamos a quantidade de Produtos que poderá ser
adicionada, como:
CarrinhoDeCompras carrinho =
new CarrinhoDeCompras(new Produto[ 10 ]);
Mas como saber exatamente se teremos 2, 10, ou 100 Produtos neste
carrinho? Não há um valor fixo, afinal o cliente poderá comprar quantos pro-
dutos ele bem entender. O ponto é que a necessidade de passar um número
fixo no momento de instanciar um array não ajuda muito neste momento.
E o tamanho de um array nunca muda; depois de criado, ele terá aquela
capacidade e ponto, nunca poderá ser redimensionado.
Poderíamos, sim, no momento de adicionar novos produtos verificar se
há espaço livre no array, e caso não haja, criar um novo com mais espaço
e mover os objetos do antigo para ele. Mas isso seria muito trabalhoso, não
acha? E não é o único trabalho que teríamos ao utilizar um array.
Outra necessidade seria remover produtos do CarrinhoDeCompras.
Uma implementação simples do método remove seria:
public void remove(int posicao) {
this.produtos[posicao] = null;
}
Ao atribuir null para determinada posição, ganhamos uma série de
problemas. Repare como ficará nosso array após removermos o elemento
de posição 2:
170

Casa do Código
Capítulo 11. Collection Framework
E agora, como faremos ao adicionar novos itens? Estávamos inserindo
sequencialmente, mas precisaremos considerar as possíveis posições vazias
no meio do array.
Uma possibilidade seria criar um outro array, guardando as posições
livres, e consultá-lo antes de inserir um novo Produto. Outra possibilidade
seria sempre reordenar o array depois de remover um item. Mas repare
bem, todas elas tornariam nosso código mais complexo e difícil de manter.
Manipular um array é bastante trabalhoso. Essas foram apenas algu-
mas das muitas questões que apareceriam em nosso código. Por esse e outros
motivos, o Java2 1.2 introduziu um conjunto de classes e interfaces bastante
conhecido como Collections Framework. Essa robusta API traz estruturas bem mais interessantes para lidar com os mais diferentes tipos de dados.
ArrayList versus array
No lugar de trabalhar diretamente com arrays, podemos utilizar uma
ArrayList para representar a multiplicidade de Produtos e nos auxiliar
nas operações necessárias em nosso CarrinhoDeCompras. Essa classe é
uma das introduções da API de Collections, veja a seguir como seu uso
é simples.
ArrayList lista = new ArrayList();
String valor = "conhecendo uma ArrayList";
lista.add(valor);
System.out.println(lista.contains(valor));
lista.remove(valor);
171
11.1. O trabalho de manipular arrays
Casa do Código
System.out.println(lista.contains(valor));
A saída será:
true
false
O ArrayList tem um conjunto de métodos que, na maior parte do
tempo, representam bem as nossas necessidades. O método remove pode
agora receber o próprio valor a ser removido, mas também tem uma sobre-
carga que recebe a posição que deve ser removida. Você pode escolher qual
opção lhe atender melhor.
Um ponto muito interessante é que o ArrayList, assim como as de-
mais classes da API de Collection, trabalham de forma genérica. Caso
contrário, haveria uma ArrayList para cada tipo de dado que precisásse-
mos adicionar, como uma String, um int, ou mesmo uma data. Todos os
seus métodos trabalham com Object.
Portanto, o seguinte código compila:
ArrayList lista = new ArrayList();
lista.add(10);
lista.add("uma string");
lista.add(new Revista());
Mas já vimos o problema de trabalhar com Object dessa forma: será
necessário um casting sempre que precisarmos recuperar um valor dessa
lista. Por exemplo:
int valor = (int) lista.get(0);
E se o valor do índice zero não fosse um int? Como saber exatamente
o tipo de cada elemento em cada posição da lista? Trabalhar dessa forma não
seria nem um pouco simples, não acha? E na maior parte do tempo, queremos
ter uma lista de um único tipo, e não vários como fizemos agora. Por exemplo,
nosso CarrinhoDeCompras deverá ter uma lista de Produtos.
Para auxiliar nesse trabalho, desde o Java 5 podemos restringir o tipo
de objetos de uma determinada lista utilizando um recurso conhecido como
Generics. Repare:
172
Casa do Código
Capítulo 11. Collection Framework
ArrayList<Produto> produtos = new ArrayList<Produto>();
A sintaxe pode parecer estranha no começo, mas note que a única mu-
dança é que agora estamos indicando ao compilador que essa lista deve tra-
balhar com Produtos. Há dois grandes ganhos aqui, o primeiro será pela
não necessidade de um casting ao recuperar valores desta lista. Veja:
Produto buscado = produtos.get(0);
O código fica bem mais limpo sem os casting s e evitamos as recorrentes ClassCastExceptions. O outro ganho será a segurança de que não con-seguiremos adicionar nada que não seja do tipo Produto nessa lista. O
seguinte código não compila:
produtos.add("Eu não sou um produto");
173
11.1. O trabalho de manipular arrays
Casa do Código
diamond operator do Java 7
Desde o Java 7, no lugar de passar o tipo genérico duas vezes como
fizemos ao instanciar a lista de Produtos, podemos utilizar um re-
curso conhecido como diamond operator (operador diamante), deixando
o código assim:
ArrayList<Produto> produtos = new ArrayList<>();
Esse recurso era bastante limitado, funcionava basicamente se a
declaração fosse feita na mesma linha. No Java 8, a inferência de tipos
foi bastante melhorada e, como um efeito colateral, agora podemos fazer
códigos como este:
public class CarrinhoDeCompras {
private ArrayList<Produto> produtos;
public CarrinhoDeCompras() {
this.produtos = new ArrayList<>();
}
}
Note que só estamos instanciando o ArrayList no construtor
da classe, mas como o compilador sabe inferir bem qual o tipo do
this.produtos, conseguimos utilizar o diamante sem nenhum prob-
lema.
Veja como fica nossa classe CarrinhoDeCompras utilizando uma lista:
public class CarrinhoDeCompras {
private double total;
private ArrayList<Produto> produtos;
public CarrinhoDeCompras() {
this.produtos = new ArrayList<>();
174
Casa do Código
Capítulo 11. Collection Framework
}
public void adiciona(Produto produto) {
this.produtos.add(produto);
}
public void remove(int posicao) {
this.produtos.remove(posicao);
}
public double getTotal() {
return total;
}
public ArrayList<Produto> getProdutos() {
return produtos;
}
}
Muito mais simples, não acha?
Não precisamos de um atributo
contador, não temos que limitar o tamanho do array, entre outras desvan-
tagens comentadas.
É interessante reconhecer que um ArrayList não é um array! Essa
confusão é bastante comum, mas na verdade ArrayList usa um array
internamente para armazenar os dados, mas isso está bem encapsulado. Em nenhum momento precisaremos recuperar seu array interno para fazer operações mais complexas, não temos acesso a ele e nem deveríamos.
O ArrayList resolve todos os problemas que comentamos do array.
Utilizá-lo, com certeza, será uma melhor opção. Mas existem outros tipos de
lista que não utilizam necessariamente um array internamente. Um exem-
plo é a LinkedList (lista ligada); enquanto a ArrayList é mais rápida
para fazer pesquisas (iterar), a LinkedList é mais rápida para inserir e re-
mover elementos de suas pontas.
Cada estrutura tem suas vantagens, vale a pena conhecer as diferenças
entre elas para tomar uma boa decisão no momento de utilizar uma lista.
O que todas elas têm em comum é a interface
List, do pacote
java.util. Para não deixar nosso código dependendo de um tipo de lista
175
11.1. O trabalho de manipular arrays
Casa do Código
específico (acoplamento), já que podemos a qualquer momento mudá-las, é
uma boa prática programar voltado para a interface. Repare como fica nossa
classe CarrinhoDeCompras:
public class CarrinhoDeCompras {
private double total;
private List<Produto> produtos;
public CarrinhoDeCompras() {
this.produtos = new ArrayList<Produto>();
}
public void adiciona(Produto produto) {
this.produtos.add(produto);
}
public void remove(int posicao) {
this.produtos.remove(posicao);
}
public double getTotal() {
return total;
}
public List<Produto> getProdutos() {
return produtos;
}
}
Note que, agora que o tipo ArrayList aparece apenas no momento
de instanciar a lista, em todos os demais pontos nos referimos a ela como
uma List<Produto>. Isso torna nosso código muito mais flexível para
mudanças. Para utilizar um LinkedList, bastaria mudar o tipo em que es-
tamos dando new sem causar nenhum efeito colateral indesejado ao restante
do código.
A imagem a seguir mostra a interface List e algumas de suas implemen-
tações:
176

Casa do Código
Capítulo 11. Collection Framework
Para finalizar nossa migração de array para List, precisamos corrigir
o erro de compilação da classe RegistroDeVendas, no momento em que
estamos iterando pelos itens do carrinho:
Produto[] produtos = carrinho.getProdutos();
for (Produto produto : produtos) {
System.out.println(produto);
}
177
11.2. Ordenando nossa List de produtos
Casa do Código
A única mudança necessária será no tipo de retorno do método
getProdutos. Basta mudar de Produto[] para List<Produto> e todo
o restante continuará igual.
List<Produto> produtos = carrinho.getProdutos();
for (Produto produto : produtos) {
System.out.println(produto);
}
Isso significa que o enhanced-for também funciona com qualquer
tipo de List (na verdade, com qualquer Iterable, como veremos mais
à frente).
11.2
Ordenando nossa List de produtos
Ao imprimir os valores dessa lista, você perceberá que serão exibidos na
mesma ordem em que foram inseridos. Mas é fato que, sempre que estamos
trabalhando com listas, entre outros tipos de coleções, é natural a necessidade de exibir seus dados de forma ordenada seguindo algum outro critério.
Até o Java 7, podíamos utilizar o método estático sort, presente na classe
java.util.Collections:
List<String> nomes = new ArrayList<>();
nomes.add("Rodrigo Turini");
nomes.add("Adriano Almeida");
nomes.add("Paulo Silveira");
Collections.sort(nomes);
System.out.println(nomes);
Executando esse código, o resultado será:
[Adriano Almeida, Paulo Silveira, Rodrigo Turini]
É importante perceber que o método sort efetivamente modificou a es-
trutura interna da lista, neste caso deixando-a em ordem alfabética.
178
Casa do Código
Capítulo 11. Collection Framework
A classe Collections possui diversos outros métodos que podem ser
muito úteis quando você está trabalhando com coleções, principalmente com
Java 7 ou menor. Logo veremos que o Java 8 introduziu diversos recursos para
tornar seu trabalho com coleções ainda mais prático e funcional.
Alguns outros exemplos de métodos da classe Collections são: o
reverse, para inverter a ordem dos elementos da lista; o copy, para copiar
os elementos de uma lista para outra; e também o emptyList, que retornará
uma lista vazia. Você pode ver os diversos métodos dessa classe em sua de-
talhada documentação:
http://docs.oracle.com/javase/8/docs/api/java/util/Collections.html
O sort em nossa List<Strings> utilizou como critério de ordenação
a ordem alfabética. Mas qual seria o resultado do seguinte código?
List<Produto> produtos = new ArrayList<>();
// populando a lista com alguns produtos
Collections.sort(produtos);
Esse código não compila! Observe a saída:
The method sort(List<T>) in the type Collections
is not applicable for the arguments (List<Produto>)
Faz sentido, afinal qual seria o critério de ordenação? Precisamos ensinar
ao método sort qual elemento deve vir antes de qual, ou seja, como ordenar
elementos do tipo Produto.
O método sort precisa receber uma lista de elementos comparáveis,
que possuam um método específico ensinando-o como deve comparar tais
elementos. Para garantir que os elementos passados tenham esse método,
mais uma vez a API do Java fez uso de uma interface! Todo elemento or-
denável deve implementar a interface java.lang.Comparable, que pos-
sui o método compareTo.
Para que nosso código compile, podemos dizer que todo Produto im-
plementa Comparable, mas repare no código a seguir:
179
11.2. Ordenando nossa List de produtos
Casa do Código
public interface Produto extends Comparable<Produto> {
double getValor();
}
Pode parecer um pouco estranho na primeira vez que você olhar, afinal
estamos utilizando extends, e não o implements. Já percebeu por quê?
Como Produto é uma interface, ela apenas herdará as obrigações da inter-
face Comparable. Já as classes que a implementarem agora além da obri-
gação de escrever seu método getValor terão também que implementar
compareTo, o que logo veremos.
Apenas um último detalhe antes de partirmos para a implementação do
método: você notou que o Comparable também faz uso do generics do Java 5? Se não tinha notado, dê uma boa olhada na assinatura da interface, que
tem um Comparable<Produto>. Com isso, eliminamos a necessidade de
fazer o casting de Object, que é o tipo do parâmetro de seu método int
compareTo(Object).
Pronto, finalmente podemos partir para a implementação, que poderá
ser feita na classe abstrata Produto e, portanto, replicada a suas subclasses
LivroFisico, Ebook e MiniLivro. O critério de comparação será seu
valor:
public abstract class Livro implements Produto {
// código omitido
@Override
public int compareTo(Produto outro) {
if (this.getValor() < outro.getValor()){
return -1;
}
if (this.getValor() > outro.getValor()){
return 1;
}
return 0;
}
}
180
Casa do Código
Capítulo 11. Collection Framework
O mesmo pode ser feito na classe Revista, que não é filha de Livro
mas também é um Produto que deve ser ordenado por valor. O código
pareceu confuso? Não se preocupe, vamos entendê-lo um pouco melhor.
Note que o método compareTo retorna um int. Devemos retornar
0 (zero) se o objeto comparado (parâmetro outro) for igual a este objeto (
this), um número negativo se este objeto for menor (deva vir antes quando
ordenado) que o objeto passado, ou um número positivo caso seja maior
(deva vir depois).
Execute o seguinte código para ver o resultado em prática:
public class OrdenandoComJava {
public static void main(String[] args) {
Autor autor = new Autor();
autor.setNome("Rodrigo Turini");
LivroFisico fisico = new LivroFisico(autor);
fisico.setNome("Java 8 Prático");
fisico.setValor(59.90);
Ebook ebook = new Ebook(autor);
ebook.setNome("Java 8 Prático");
ebook.setValor(29.90);
List<Produto> produtos = Arrays.asList(fisico, ebook);
Collections.sort(produtos);
for (Produto produto : produtos) {
System.out.println(produto.getValor());
}
}
}
A saída será:
29.9
59.9
181
11.3. Gerenciando cupons de desconto
Casa do Código
Outras implementações
Como estamos comparando apenas o valor do
Produto,
poderíamos simplificar nossa implementação retornando apenas a
subtração de um valor pelo outro. Repare como nosso código fica mais
simples:
public abstract class Livro implements Produto {
// código omitido
@Override
public int compareTo(Produto outro) {
return this.getValor() - outro.getValor();
}
}
Mais simples, não acha? Alternativamente, também poderíamos uti-
lizar o método Integer.compare passando os dois valores como
parâmetro. Experimente utilizar alguma dessas estratégias para deixar
seu código mais simples, essa é uma prática bastante comum.
11.3
Gerenciando cupons de desconto
Agora que nossa classe CarrinhoDeCompras já está bem resolvida, pode-
mos seguir para uma outra necessidade de nosso projeto. Precisamos agora
gerenciar cupons de desconto promocionais.
Como
ponto
de
partida,
podemos
criar
a
classe
GerenciadorDeCupons que possui internamente uma lista com al-
guns cupons já cadastrados. Repare:
public class GerenciadorDeCupons {
private List<String> cupons;
public GerenciadorDeCupons() {
182
Casa do Código
Capítulo 11. Collection Framework
this.cupons = Arrays.asList("CUP74", "CUP158",
"CUP14", "CUP52", "CUP21", "CUP221", "CUP91",
"CUP327", "CUP410", "CUP275", "CUP484", "CUP207",
"CUP96", "CUP119", "CUP174", "CUP291", "CUP1",
"CUP115", "CUP222", "CUP272");
}
}
Note
que
setamos
o
método
estático
asList
da
classe
java.util.Arrays para criar nossa lista.
Poderíamos criar um
ArrayList e chamar seu método add para cada elemento, mas é co-
mum utilizar esse novo método para simplificar esse processo. O método
asList é uma fábrica ( factory) de List e, com certeza, será muito útil em seu dia a dia.
Nosso próximo passo será criar um método para validar os cupons passa-
dos pelo cliente. Ele retornará um boolean indicando se o cupom passado
está ou não presente em nossa List<String> cupons. Vamos chamá-lo
de validaCupom:
public boolean validaCupom(String cupom) {
return this.cupons.contains(cupom);
}
Note que utilizamos o já conhecido método contains para fazer essa
consulta. Bem simples, não acha?
Já estamos prontos para testar esse código. Vamos criar uma nova classe
chamada ConsultaDeDescontos, que deve utilizar esse novo método, o
validaCupom. Repare:
public class ConsultaDeDescontos {
public static void main(String[] args) {
GerenciadorDeCupons gerenciador =
new GerenciadorDeCupons();
if(gerenciador.validaCupom("CUP1234")){
System.out.println("Cupom de desconto valido.");
183
11.3. Gerenciando cupons de desconto
Casa do Código
// aplica o desconto desse cupom
} else {
System.out.println("Esse cupom não existe.");
}
}
}
Rode esse código para ver o resultado. Como o cupom não existe em
nossa lista, a saída será:
Esse cupom não existe.
Trabalhar com uma List aqui resolve o problema, mas ela não é a es-
trutura ideal para fazer esse trabalho. Normalmente, trabalhamos com uma
lista quando seus elementos internos podem repetir e sua ordem tem alguma
importância, mas note que não é esse o caso de nossos cupons. Um cupom
é único, não há repetições e, além disso, a ordem deles não importa, afinal
estamos utilizando esses dados apenas para consulta.
Em casos como este, podemos utilizar uma outra estrutura de dados bas-
tante interessante, um conjunto ( java.util.Set).
java.util.Set
Um conjunto (ou Set em Java) funciona de forma parecida com os con-
juntos da matemática. Ela é uma coleção, assim como a List, mas em que
não há repetição de seus dados internos. Além disso, sua ordem não é nec-
essariamente a ordem em que os valores foram inseridos, isso pode variar
bastante de cada implementação.
Assim como a List, o Set é apenas uma interface. Para utilizar um Set,
precisamos instanciar alguma de suas implementações, como a HashSet,
que é uma das implementações mais usadas. Observe como fica nosso código
da classe GerenciadorDeCupons:
public class GerenciadorDeCupons {
private Set<String> cupons;
public GerenciadorDeCupons() {
184
Casa do Código
Capítulo 11. Collection Framework
this.cupons = new HashSet<String>();
cupons.addAll(Arrays.asList("CUP74", "CUP158",
"CUP14", "CUP52", "CUP21", "CUP221", "CUP91",
"CUP327", "CUP410", "CUP275", "CUP484", "CUP207",
"CUP96", "CUP119", "CUP174", "CUP291", "CUP1",
"CUP115", "CUP222", "CUP272"));
}
public boolean validaCupom(String cupom) {
return this.cupons.contains(cupom);
}
}
Note que utilizamos o método addAll para facilitar essa mudança. Mas
é natural adicionar elementos no Set chamando seu método add, assim
como na List. Outro detalhe importante é que continuamos programando
voltados para a interface, isto é, apesar de dar new em HasSet, o tipo do
atributo cupons é um Set. Essa é e sempre será uma excelente prática, posto
que diminui o acoplamento de nossa classe e facilita evoluções no futuro.
Nosso código continua funcionando assim como antes. Rode a classe
ConsultaDeDescontos para confirmar! Aparentemente, tudo está igual,
mas na verdade algumas coisas mudaram.
O primeiro ponto importante a se perceber é que, mesmo que você adi-
cione mais 500 mil cupons iguais nesse conjunto, ao mandar imprimir o seu
tamanho ( size) ele ainda será 20. Você pode rodar o código a seguir para
confirmar isso:
HashSet<String> set = new HashSet<String>();
set.add("Não há repeticão em Conjuntos");
set.add("Não há repeticão em Conjuntos");
set.add("Não há repeticão em Conjuntos");
set.add("Não há repeticão em Conjuntos");
set.add("Não há repeticão em Conjuntos");
System.out.println(set.size());
Qual foi o valor da saída? Exatamente 1, ele ignorou as repetições. Para
ajudar a saber quando você está inserindo uma repetição em um Set, seu
185



11.4. java.util.Map
Casa do Código
11.4
java.util.Map
Nosso GerenciadorDeCupons já está quase completo. O próximo passo
será aplicar o desconto específico para determinado cupom que foi validado.
Mas como vincular um cupom único com o seu valor de desconto?
Essa é uma necessidade muito comum: vincular um objeto a uma chave
única, para conseguir buscar esse valor rapidamente. Poderíamos resolver
isso com uma lista, mas já vimos que essa não é a melhor estrutura quando
queremos fazer buscas.
188



Capítulo 12
Streams e novidades do Java 8
A API de Collections é bastante robusta, sem dúvidas. Há muito o que explo-
rar em suas diversas estruturas e soluções. Mas o fato é que estamos falando
de uma API gigante e com mais de 15 anos, que com certeza possui algumas
limitações e características que não poderiam ser evoluidas sem que houvesse
uma sobrecarga ou ainda quebra de compatibilidade (veremos no próximo
capítulo o quanto isso é importante para o Java).
Por esse e diversos outros motivos o Java 8 introduziu diversas novidades
que nos trazem uma forma bem mais interessante e funcional de trabalhar
com nossas coleções. Veremos algumas dessas novidades durante o capítulo.
12.1
Ordenando com Java 8
Vimos que uma maneira de ordenar a List<Produto> era tornando seus el-
ementos comparávis, para isso implementando java.lang.Comparable.
12.1. Ordenando com Java 8
Casa do Código
Mas e se você tiver mais de um critério de ordenação? E se você quiser em
alguns momentos comparar um Livro pelo seu valor e em outros pelo seu
nome?
Até o Java 7 uma forma de fazer isso seria criando uma nova classe, um
comparador de Livro por nome. Essa classe precisava implementar a inter-
face java.util.Comparator, algo como:
public class ComparadorPorNome implements Comparator<Livro>{
@Override
public int compare(Livro l1, Livro l2) {
return l1.getNome().compareTo(l2.getNome());
}
}
O método sort da Collection tem uma sobrecarga que recebe um
Comparator como parâmetro, portanto agora basta fazer:
Collections.sort(livros, new ComparadorPorNome());
Vamos ver esse código na prática! Crie a classe NovidadesDoJava8 e
adicione o seguinte código para testar:
public class NovidadesDoJava8 {
public static void main(String[] args) {
Autor autor = new Autor();
autor.setNome("Rodrigo Turini");
Livro javaoo = new LivroFisico(autor);
javaoo.setNome("Java O.O.");
Livro java8 = new LivroFisico(autor);
java8.setNome("Java 8 Prático");
Livro ruby = new LivroFisico(autor);
ruby.setNome("Livro de Ruby");
194
Casa do Código
Capítulo 12. Streams e novidades do Java 8
List<Livro> livros = Arrays.asList(javaoo, java8);
Collections.sort(livros, new ComparadorPorNome());
for (Livro livro : livros) {
System.out.println(livro.getNome());
}
}
}
Agora execute o código e repare que a saída será:
Java 8 Prático
Java O.O.
Livro de Ruby
Classes anônimas e o lambda
O que pode incomodar bastante dessa solução é que para cada
novo critério de ordenação, precisaríamos criar uma nova classe como a
ComparadorPorNome. Ainda que seu código seja bem pequeno e não seja
reutilizado em nenhum outro lugar.
Uma alternativa bastante utilizada até o Java 7 eram as conhecidas classes
anônimas. Você pode sim dar new em uma interface, mas terá que implemen-
tar seus métodos ali mesmo, numa mesma instrução. Podemos fazer isso com
a interface Comparator, no lugar de criar a classe ComparadorPorNome,
podemos fazer:
Collections.sort(livros, new Comparator<Livro>() {
@Override
public int compare(Livro l1, Livro l2) {
return l1.getNome().compareTo(l2.getNome());
}
});
Isso mesmo, é o mesmo código da classe ComparadorPorNome, mas
declarado numa única instrução. Esse recurso é conhecido como classe anôn-
ima, pois afinal essa classe não tem nem mesmo um nome! Achou a sintaxe
195
12.1. Ordenando com Java 8
Casa do Código
estranha? Feia? difícil de ler? Bem... eu concordo! Isso polui bastante o nosso código.
Para simplificar esse processo o Java 8 introduziu algumas mudanças, uma
delas foi adicionando o método sort na própria interface List! Pode pare-
cer uma mudança simples, mas agora podemos fazer:
livros.sort(new Comparator<Livro>() {
@Override
public int compare(Livro l1, Livro l2) {
return l1.getNome().compareTo(l2.getNome());
}
});
Para não quebrar a compatibilidade da interface List, esse método
sort é um método concreto, com código dentro! Lembra do nome desse re-
curso? No capitulo de interface brevemente comentei sobre os tais default
methods, heis um bom exemplo:
default void sort(Comparator<? super E> c) {
Collections.sort(this, c);
}
Isso mesmo, esse método defalt da List apenas delega a chamada
para o método sort de Collections, que já fazia bem este trabalho. Mas
isso ainda não resolve todos os problemas de ordenação, não é? Então vamos
para outra novidade.
Também no capitulo de interfaces, vimos brevemente que o Java 8 intro-
duziu o conceito de interfaces funcionais. Em poucas palavras, uma interface com um único método abstrato pode ser considerada funcional, como
é o caso da interface Comparator que possui apenas o método compare.
Com isso ganhamos algumas possibilidades, como por exemplo transformar
aquela classe anônima em uma expressão lambda! Uma primeira forma de
fazer isso seria:
livros.sort((Livro l1, Livro l2) -> {
return l1.getNome().compareTo(l2.getNome());
});
196
Casa do Código
Capítulo 12. Streams e novidades do Java 8
Note que a diferença desse código para a classe anônima é que deixamos
apenas os parâmetros do método compare, seguidos do operador -> e sua
implementação. Como o método sort espera receber um Comparator, o
compilador já infere que esse é o tipo da expressão lambda.
Podemos simplificar ainda mais esse código, retirando o tipo Livro dos
parêmetros, as chaves, o return e até mesmo o ponto-e-virgula. Repare:
livros.sort(
(l1, l2) -> l1.getNome().compareTo(l2.getNome())
);
Esse é um conceito um pouco mais avançado e com certeza
pode parecer bastante estranho ou mesmo assustador no ínicio.
O
código
(l1, l2) -> l1.getNome().compareTo(l2.getNome())
resulta
em
uma
instância
de
Comparator
que
devolve
l1.getNome().compareTo(l2.getNome().
Não há necessidade
de declarar o tipo ( Livro) dos parâmteros, o próprio compilador sabe
inferir isso pra você. Não há necessidade da palavra return e nem mesmo
de chaves, já que temos uma única instrução após o operador ->.
Para tornar o código ainda mais enxuto e um pouco mais fluente, o
método default comparing foi adicionado na interface Comparator. Ele é uma fábrica ( factory) de Comparator, tudo que precisamos fazer é passar
uma expressão lambda com o critério de comparação como a seguir:
livros.sort(comparing(l -> l.getNome()));
Note que como há apenas um parâmetro nesse lambda, não precisamos
passá-lo dentro de parênteses. Rode o código para ver o resultado! O que
acha?
Simplificando ainda mais com method reference
A expressão lambda l -> l.getNome() apenas diz ao comparing
qual o método que deverá ser utilizado em sua comparação. Ele está ape-
nas referenciando um método, neste caso o getNome. Em casos como este
podemos ainda fazer:
livros.sort(comparing(Livro::getNome));
197
12.2. forEach do Java 8
Casa do Código
Esse é um outro recurso do Java 8, conhecido como
method
reference. Note que sua sintaxe é o nome da classe seguido de :>: e o
nome do método. Esse recurso pode ser utilizado sempre que temos uma ex-
pressão lambda como a l -> l.getNome(), que apenas delega a chamada
de um método.
12.2
forEach do Java 8
Outro default method que com total certeza fará parte de seu dia a dia é
o forEach, presente na interface Iterable. Note na forma como estamos
iterando pela lista de livros utilizando o enhanced-for:
for (Livro livro : livros) {
System.out.println(livro.getNome());
}
No Java 8 agora podemos fazer:
livros.forEach(l -> System.out.println(l.getNome()));
O método forEach, assim como a maior parte dos default methods,
recebe uma interface funcional como parâmetro. Portanto note que foi pos-
sivel utilizar uma expressão lambda para representar nossas intenções.
O lambda l -> System.out.println(l.getNome()) nada mas
diz do que: “para cada livro, chame o método println imprimindo seu nome”.
12.3
Filtrando livros pelo autor
Além da forma de ordenar e iterar, o Java 8 mudou bastante a forma de fazer
algumas operações rotineiras de quando estamos trabalhando com coleções.
Quer um exemplo prático?
O trabalho de filtrar uma Collection
Dada uma lista de livros, queremos filtar apenas os que tenham a palavra
Java em seu nome. Até o Java 1.7 uma das formas mais tradicionais de se
fazer isso seria criando uma nova lista para o resultado e condicionando os
elementos que deveriam ser inseridos, como a seguir:
198
Casa do Código
Capítulo 12. Streams e novidades do Java 8
List<Livro> filtrados = new ArrayList<>();
for (Livro livro : livros) {
if (livro.getNome().contains("Java")) {
filtrados.add(livro);
}
}
Para testar, adicione esse código na classe NovidadesDoJava8 e logo
depois mostre os resultados dessa lista, fazendo um simples for como a
seguir:
for (Livro livro : filtrados) {
System.out.println(livro.getNome());
}
Note que a saída será:
Java 8 Prático
Java O.O.
Excelente, resolvemos o problema! Mas de uma boa olhada em seu
código... o que achou? Note que além de ser muito verboso, ele exigiu a cri-
ação de uma lista intermediária para o resultado. Esse é sem dúvidas um
código bastante imperativo.
Operações comuns como essa, filtrar elementos de uma coleção, estão
agora presentes em uma nova API, conhecida como Stream. O Stream traz
para o Java uma forma mais funcional de trabalhar com as nossas coleções,
usando uma interface fluente! Separando as funcionalidades do Stream
da Collection, também ficou mais fácil de deixar claro que métodos são
mutáveis, evitar problema de conflito de nome de métodos, entre outros.
Stream e o filter
Como criar um Stream? Isso é bem simples, um novo método default
chamado stream foi definido na interface Collection. Com isso, basta
fazer:
Stream<Livro> stream = livros.stream();
199
12.3. Filtrando livros pelo autor
Casa do Código
A partir desse nosso Stream<Livro> conseguimos utilizar por exemplo
o método filter, como a seguir:
livros.stream().filter(l -> l.getNome().contains("Java"));
Estamos fazendo a mesma coisa que aquele for com um if dentro do
Java 7, só que agora de uma forma muito mais declarativa!
Não deixe de testar, mude o código da classe NovidadesDoJava8 e es-
creva o seguinte for para exibir a lista de livros:
for (Livro livro : livros) {
System.out.println(livro.getNome());
}
Rode o código e o resultado será:
Java 8Prático
Java O.O.
Livro de Ruby
Ops, esse Livro de Ruby não deveria estar no resultado! O filtro não
funcionou? Na verdade funcionou, mas a API de Streams é imutável, isso é,
não altera sua coleção inicial. Você pode fazer quantas transformações quiser
sem se preocupar com efeitos colaterais em sua lista.
Portanto, no lugar de iterar na lista original podemos utilizar o método
forEach também presente na API de Streams da seguinte forma:
livros.stream()
.filter(l -> l.getNome().contains("Java"))
.forEach(l -> System.out.println(l.getNome()));
Agora sim, ao executar o código teremos apenas os elementos filtrados:
Java 8Prático
Java O.O.
Claro, o filter é apenas um dos zilhares métodos presentes nessa nova
API. Você pode ver a lista completa em e alguns exemplos em:
http://docs.oracle.com/javase/8/docs/api/java/util/stream/Stream.html
200
Casa do Código
Capítulo 12. Streams e novidades do Java 8
Vimos nesse capitulo apenas um pouco do muito que o Java 8 introduziu
com seus novos recursos e APIs. O seguinte post do blog da caelum fala um
pouco mais sobre essas novidades:
http://blog.caelum.com.br/o-minimo-que-voce-deve-saber-de-java-8/
O livro Java 8 Prático
Junto com Paulo Silveira escrevi o livro Java 8 Prático: Lambdas,
Streams e os novos recursos da linguagem, que entra a fundo em cada de-
talhe e sumariza os novos recursos do Java. Se você gostou das novidades
com certeza vai gostar de seu conteúdo:
http://www.casadocodigo.com.br/products/livro-java8
201
Capítulo 13
Um pouco da história do Java
13.1
Origem da linguagem
Já colocamos em prática muitos dos conceitos da orientação a objetos e re-
cursos específicos da linguagem Java, mas para concluir essa primeira parte
do estudo é fundamental conhecermos um pouco de sua história.
Acredito que quando foi iniciado o Green Team, um projeto da Sun Mi-crosystems cujo objetivo era criar uma plataforma de computação interativa, ninguém imaginou que esse seria o início de uma linguagem que mudou o
rumo da história da programação e que atualmente possui mais de 9 milhões
de desenvolvedores. Em 1992 foi feita a primeira demonstração desse projeto,
mas apenas em 1995 que foi anunciado o lançamento oficial da plataforma
Java.
Na época o uso principal da linguagem era focado em navegadores web
13.2. Escreva uma vez, rode em qualquer lugar!
Casa do Código
para rodar pequenas aplicações (os tão conhecidos applets), tanto que foi in-serida no Netscape Navigator que era o principal navegador do momento.
Desde então a grande idéia da linguagem é você escrever uma aplicação e
executá-la nos diferentes dispositivos existentes. Ou seja, portabilidade é uma das características mais fortes e presente desde suas origens. Claro que essa
não é a única característica forte da linguagem, que também é robusta, par-
alelizável, segura e dentre diversas outras que ainda serão melhor detalhadas:
estável.
Algumas das características que tornam o Java muito bem vindo como
linguagem são sua estabilidade e retrocompatibilidade. Você pode reparar
isso pela numeração de suas versões, repare que apesar da estratégia de mar-
keting de chamá-lo de Java 8, por exemplo, a versão continua sendo 1.8. Ou
seja, o 1 sempre está presente indicando que ainda não houve uma quebra de
compatibilidade desde seu lançamento.
Podemos então em 2014, com a versão mais recente da linguagem, com-
pilar e executar uma aplicação escrita por volta de 1995 com Java 1.0.2 sem
nenhuma dificuldade. Isso nos dá uma boa segurança de que uma atualiza-
ção de versão não será um problema, por exemplo.
Há uma comunidade conhecida como JCP ( Java Community Process)
cujo objetivo é garantir esse padrão de estabilidade e compatibilidade multi-
plataforma da tecnologia Java. Você pode ler mais sobre a JCP e suas pro-
postas em:
https://www.jcp.org/en/introduction/overview
13.2
Escreva uma vez, rode em qualquer lugar!
Diferente das demais linguagens, uma aplicação Java pode ser executada em
qualquer um dos diferentes sistemas operacionais existentes, como por exem-
plo Windows, Linux ou Mac OS. Essa possibilidade abre muitos caminhos e
foi um dos principais fatores que tornaram a linguagem tão atraente pro mer-
cado. Exibindo esse benefício, o Java teve como slogan oficial o termo Write once, run anywhere (escreva uma vez, rode em qualquer lugar).
A grande chave para essa portabilidade é a máquina virtual, ou JVM ( Java
Virtual Machine). No lugar de instruções nativas para um determinado hard-204

Casa do Código
Capítulo 13. Um pouco da história do Java
ware, após compilado um código-fonte em Java é traduzido para um formato
conhecido como bytecode. Esse bytecode independe da arquitetura do sistema em que foi gerado e portanto poderá ser executado em qualquer plataforma,
contanto que ela tenha uma JVM instalada. A imagem a seguir representa
esse processo:
Inicialmente o desempenho da linguagem foi um fator preocupante,
tendo como parâmetro de comparação as demais linguagens compiladas em
instruções nativas para uma determinada plataforma. Ao ganhar em porta-
bilidade a linguagem sofreu um grande impacto por adicionar essa camada
intermediária. Em pouco tempo o desempenho deixou de ser um problema,
com introduções e tecnologias que serão melhor detalhadas a seguir nas de-
scrições das diferentes versões da plataforma.
13.3
Linha do tempo
A linguagem foi sem duvida um caso de sucesso. Em sua primeira ver-
são tinha cerca de 250 classes e agora conta com mais de 4.200, ricas APIs,
poderosos recursos e é executada por mais de 3 bilhões de dispositivos. Para
entendermos melhor todo esse sucesso, precisamos conhecer um pouco de
sua história, alguns de seus principais acontecimentos e introduções.
205
13.3. Linha do tempo
Casa do Código
Java 1
Em 1991 a linguagem foi originalmente chamada de Oak (carvalho, em
inglês) por influência da vista na janela do escritório de seu criador, James
Gosling. Somente a partir de sua primeira versão estável, que foi a 1.0.2 em
1995, passou a ser chamada de Java 1.
Existiam diversos bugs nesse momento e a performance era bastante
problemática.
Java 1.2
Com mais de 2 milhões de downloads em sua versão atual, em 1998 foi
lançado o Java 1.2. Nessa versão diversos bugs foram corrigidos e a perfor-
mance foi consideravelmente melhorada. Um dos principais fatores respon-
sáveis por essa melhora foi a introdução do compilador JIT ( Just In Time) em que a compilação é feita durante a excecução do programa (em runtime) ao invés de antes de sua execução.
Outra introdução muito importante dessa versão foi a API de Collections, que será profundamente estudada mais adiante.
Java 2
Por uma estratégia de marketing da época a linguagem passou a se
chamar Java 2 (independente de sua versão, por exemplo a versão 1.3 era
conhecida como Java(TM) 2 Platform, Standard Edition version 1.3). Ape-
nas em 2004 na versão 1.5 que o 2 deixou de fazer parte do nome da lin-
guagem.
Java 1.3
Em 2000 a mais nova versão do java foi lançada, o Java 1.3. Alavancando
ainda mais sua performace, uma JVM conhecida como HotSpot passou a ser a máquina virtual padrão da Sun. Essa JVM combinava interpretação de código
e compilação JIT (em tempo de execução).
O seu código Java ainda era inicialmente interpretado, mas durante a ex-
ecução do programa a JVM passou a identificar os pontos executados com
206
Casa do Código
Capítulo 13. Um pouco da história do Java
maior frequência (conhecidos como pontos quentes, ou HotSpot s) e quando necessário transformava esses códigos em instruções nativas da plataforma
que são executadas diretamente no hardware.
Java 1.4
Chegando a quase 3 mil classes, a API foi enriquecida com introduções
como expressões regulares ( Regular Expression), processamento de XML,
Logging e muito mais em sua versão 1.4, lançada em 2002. Essa foi a primeira versão do Java desenvolvida no âmbito da JCP ( Java Community Process).
Java 1.5
Sem duvida uma das versões mais importantes, o Java 1.5 foi lançado em
setembro de 2004. Dentre diversas outras, Generics, imports estáticos, anotações ( metadata) e boxing de tipos primitivos foram algumas de suas novidades.
Essa versão também ficou bastante conhecida por introduzir uma forma
mais sucinta de iterar em coleções, o enhanced for que será melhor detalhado adiante.
Uma nova estratégia de marketing surge. Para melhor refletir o nível de
maturidade, estabilidade, escalabilidade e segurança da plataforma, o Java 1.5
deixou se ser Java 2 e passou a ser conhecido como Java 5. Mas claro, não
estamos falando de uma quebra de compatibilidade, a versão continua sendo
1.5.
Desde então as versões da linguagem passaram a ser conhecidas pelo seu
ultimo numero.
Java 1.6
A fim de impulsionar ainda mais a adoção da tecnologia, a Sun tornou
o Java open source. Ou seja, seu código foi aberto para comunidade com a licença GNU ( General Public License), a mesma utilizada pelo sistema operacional Linux. Além disso, diversas otimizações no core da plataforma e na
JVM marcaram a versão 1.6.
207
13.3. Linha do tempo
Casa do Código
Java 1.7
Quatro anos após sua ultima versão, essa foi a primeira versão após a
aquisição da Sun pela Oracle. O Java 7 foi lançado sob uma nova perspec-
tiva, teve como novidades uma nova API de I/O, recursos como o operador
diamante ( diamond operator <>) e o try-with-resources.
Java 8
A mais atual versão da plataforma sem duvida apresentou mudanças im-
pactantes para a linguagem. Seu lançamento aconteceu em março de 2014,
depois de 3 anos de muita espera. Diversos recursos foram introduzidos nessa
versão, defalt methods, expressões lambdas e method reference, que vimos brevemente no capitulo anterior, foram algum deles.
A versão também conta com uma nova API de datas baseada na con-
hecida biblioteca Joda Time, além de uma forma mais funcional de trabalhar com suas coleções com a API de Stream.
Vimos que até então seu código que era escrito dessa forma:
List<Usuario> usuariosFiltrados = new ArrayList<>();
for(Usuario usuario : usuarios) {
if(usuario.getPontos() > 100) {
usuariosFiltrados.add(usuario);
}
}
Collections.sort(usuariosFiltrados, new Comparator<Usuario>() {
public int compare(Usuario u1, Usuario u2) {
return u1.getNome().compareTo(u2.getNome());
}
});
for(Usuario usuario : usuariosFiltrados) {
System.out.println(usuario);
}
Agora com Java 8 pode ser escrito de forma muito mais declarativa e fun-
cional:
208
Casa do Código
Capítulo 13. Um pouco da história do Java
usuarios.stream()
.filter(u -> u.getPontos() > 100)
.sorted(comparing(Usuario::getNome))
.forEach(System.out::println);
Você pode ler mais sobre a história da plataforma e ver a timeline completa em:
http://www.java.com/en/javahistory/
http://oracle.com.edgesuite.net/timeline/java/
209
Capítulo 14
Continuando seus estudos
Não deixe de praticar cada exercício proposto durante o Livro e sempre ir
além com novos testes e cenários mais complexos.
Há diversas formas de continuar seus estudos, o GUJ é uma delas. Não
apenas para postar suas dúvidas, mas também respondendo aos demais
usuários e sendo um membro ativo da comunidade Java do Brasil. Ensinar
é uma das mais efetivas formas de aprender, espero te ver ensinado e apren-
dendo por lá:
http://www.guj.com.br
Outro caminho natural para continuar seus estudos é o livro Java 8
Prático: Lambdas, Streams e os novos recursos da linguagem. Nele explicamos detalhadamente desde a sintaxe até o uso prático de cada novidade da mais
esperada versão do Java.
http://www.casadocodigo.com.br/products/livro-java8
14.1. Entre em contato conosco
Casa do Código
14.1
Entre em contato conosco
Encaminhe suas dúvidas ou crie tópicos para discussão na lista que foi criada
especialmente para esse livro:
https://groups.google.com/d/forum/livro-java-oo
Além de perguntar, você também pode contribuir com sugestões, críticas
e melhorias para nosso conteúdo. Todas serão muito mais do que bem vindas.
212