Notas de aula (apostila)

Propaganda
UENP - UNIVERSIDADE ESTADUAL DO NORTE DO PARANÁ
CAMPUS LUIZ MENEGHEL
Centro de Ciências Tecnológicas
Programação II
Notas de Aula
Prof. José Reinaldo Merlin
Bandeirantes – 2017
Sumário
0
1
2
3
Introdução
0.1 Ementa . . . . . . . . . . . . . . . . . . . . . . . . . . . .
0.2 Bibliografia . . . . . . . . . . . . . . . . . . . . . . . . . .
0.3 Livros recomendados . . . . . . . . . . . . . . . . . . . . .
0.4 Orientação a Objetos e Java . . . . . . . . . . . . . . . . . .
0.4.1 História da linguagem Java . . . . . . . . . . . . . .
0.4.2 Plataformas Java . . . . . . . . . . . . . . . . . . .
0.4.3 Máquina Virtual Java (JVM - Java Virtual Machine)
0.4.4 O que vamos precisar . . . . . . . . . . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
0
0
0
0
0
1
1
1
1
Orientação a Objetos
1.1 Classes e objetos . . . . . .
1.1.1 Atributos e métodos
1.1.2 Herança . . . . . . .
1.2 Identificando classes . . . .
1.3 Considerações finais . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
3
3
4
4
5
5
Classes e Objetos em Java
2.1 Estrutura de uma classe . . . . . . . . . . . . .
2.1.1 Atributos . . . . . . . . . . . . . . . .
2.1.2 Métodos . . . . . . . . . . . . . . . .
2.1.3 Métodos sobrecarregados (overloaded)
2.2 Instanciando objetos . . . . . . . . . . . . . .
2.2.1 Construtores . . . . . . . . . . . . . .
2.2.2 Sobrecarga de construtores . . . . . . .
2.3 Ciclo de vida de um objeto . . . . . . . . . . .
2.4 Classes wrappers . . . . . . . . . . . . . . . .
2.5 Exercício . . . . . . . . . . . . . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
6
6
6
7
7
8
8
9
10
11
12
Modificadores e Encapsulamento
3.1 Pacotes (Packages) . . . . . . . .
3.1.1 Instrução import . . . . .
3.2 Modificadores de acesso . . . . .
3.2.1 Modificador public . . . .
3.2.2 Modificador private . . .
3.2.3 Modificador protected . .
3.2.4 Acesso default (de pacote)
3.3 Modificadores final e static . . . .
3.3.1 Modificador final . . . . .
3.3.2 Modificador static . . . .
3.4 Encapsulamento . . . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
14
14
15
16
16
16
17
17
17
17
18
19
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
2
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
3.4.1
4
Getters e setters . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
.
.
.
.
21
21
22
23
24
5
Classes Abstratas e Interfaces
5.1 Classes abstratas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
5.2 Interfaces . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
25
25
26
6
Programação Genérica
6.1 Definindo uma classe genérica simples
6.2 Métodos genéricos . . . . . . . . . .
6.3 Limites para variáveis de tipo . . . . .
6.4 A classe ArrayList . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
28
28
29
29
30
Exceções
7.1 Um exemplo: divisão por zero . . . .
7.2 Tratamento de exceções . . . . . . . .
7.3 Princípios do tratamento de exceções .
7.3.1 Tipos de exceções . . . . . .
7.4 Criando as próprias classes de exceção
7.5 Instrução try-with-resources . . . . .
7.6 Múltiplos catch . . . . . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
32
32
32
33
34
34
35
36
7
Herança
4.1 Implementando herança . . . . . . .
4.2 Atributos e métodos sobrescritos . .
4.3 Polimorfismo e vinculação dinâmica
4.4 A classe Object . . . . . . . . . .
19
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
Referências Bibliográficas
37
A Entrada e Saída em Java
A.1 Entrada de dados via teclado . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
A.2 Saída de dados via monitor . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
38
38
38
B Arrays em Java
B.1 Arrays unidimensionais . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
B.2 Arrays multidimensionais . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
40
40
41
C Classes String e StringBuilder
C.1 Criando e manipulando Strings . .
C.1.1 Concatenação . . . . . . .
C.1.2 Imutabilidade . . . . . . .
C.1.3 Métodos da classe String .
C.2 StringBuilder . . . . . . . . . . .
42
42
42
43
43
44
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
Capítulo 0
Introdução
O objetivo desta disciplina é introduzir o estudo de programação orientada a objetos.
0.1
Ementa
Estudo do paradigma orientado a objetos e tópicos relacionados. Ambientes de Desenvolvimento.
0.2
Bibliografia
Bibliografia da disciplina:
Deitel, D. Java, Como Programar. Porto Alegre: Bookman, 2001.
Sierra, K; Bates, B. Use a cabeça!Java. Alta Books, 2005.
Deitel, D. C++ Como Programar. Porto Alegre: Bookman, 2001.
0.3
Livros recomendados
Estes são alguns dos livros úteis para a disciplina.
Gupta, Mala. OCA Java SE 7 Programmer I Certification Guide: Prepare for 1ZO-803 Exam.
Shelter Island: Manning, 2013.
Pressman, R. S. Engenharia de Software: uma abordagem profissional. Porto Alegre: McGraw Hill,
2011. (Apêndice 2: Conceitos Orientados a Objetos).
0.4
Orientação a Objetos e Java
Orientação a objetos é um paradigma de programação. Um paradigma é um “jeito de pensar” a estruturação dos programas. Existem diversas linguagens que podem ser utilizadas para programação orientada
a objetos, tais como PHP, C#, Java, Python, entre outras. Neste curso será utilizada a linguagem Java, no
entanto, os conceitos trabalhados são válidos para qualquer linguagem orientada a objetos.
0
0.4.1
História da linguagem Java
Java foi desenvolvida pela Sun Microsystems em 1991, com a finalidade de programar dispositivos eletrônicos inteligentes, tais como cafeteiras e controladores de TV a cabo. O projeto não obteve sucesso
na época, no entanto, tomou um rumo diferente. Em 1993 a web começou a se tornar popular e a Sun
resolver focar este segmento. Isto deu um impulso à linguagem tornando–a uma das mais utilizadas no
mundo. Em 2009, a Sun foi comprada pela Oracle, sendo esta a atual proprietária do Java.
0.4.2
Plataformas Java
Java pode ser executada em diversos tipos de dispositivos, desde computadores de grande porte até
celulares. Existem duas plataformas principais:
Java Plataform, Standard Edition: contém o ambiente necessário para criação de aplicações Java.
Java Plataforma, Enterprise Edition: inclui capacidades avançadas de programação, como desenvolvimento distribuído, programação para web, manipulação de banco de dados e várias outras.
Além disso, existem outras plataformas, como Java ME (celulares), Java Card (cartões com chip) , Java
TV...
0.4.3
Máquina Virtual Java (JVM - Java Virtual Machine)
Tradicionalmente, os programas devem ser escritos e compilados para um sistema operacional específico.
Um programa escrito em C para Windows, por exemplo, deve ser compilado para Windows. O binário
deste programa não pode ser executado em Linux, o código fonte teria que ser compilado novamente
para Linux e, o que é pior, possivelmente algumas bibliotecas não são compatíveis.
Java foi concebida para que os programas possam ser executados em vários sistemas operacionais
sem necessidade de mudança. Ao invés de compilar para binário, o compilador Java produz um código
intermediário chamado de bytecode. Este bytecode pode ser executado na máquina virtual específica de
cada sistema operacional, como ilustrado na Figura 1.
0.4.4
O que vamos precisar
Para acompanhar esta disciplina é necessário ter instalado:
• JDK/JRE: Java Development Kit/ Java Runtime Environment
• JEE: Java Enterprise Edition
• Apache Tomcat
• NetBenas
A forma mais fácil de ter tudo funcionando é instalar o NetBeans com tudo integrado, inclusive o
servidor Tomcat.
1
Figura 1: Máquina Virtual Java.
2
Capítulo 1
Orientação a Objetos
Orientação a objetos é uma paradigma de desenvolvimento. Bom, mas isto não explica muita coisa. O
objetivo deste capítulo é introduzir os conceitos deste paradigma, especialmente Classe e Objeto.
O conceito de orientação a objetos é bastante antigo. Simula 67 (1967) geralmente é considerada a
primeira linguagem que apresenta características deste paradigma. Já os mais puritanos consideram que
Smalltalk (1972) é a primeira linguagem genuinamente orientada a objetos. A orientação a objetos se
tornou popular durante as décadas de 1980 e 1990. Nesta disciplina será utilizada a linguagem Java, mas
lembre-se: o objetivo não é aprender a linguagem, e sim o paradigma.
1.1
Classes e objetos
Para começar a entender os conceitos de classe e objeto considere o jogo Robocode 1 (Figura 1.1), um
jogo de programação cujo objetivo é programar um tanque robótico de batalha para competir com outros
robôs em uma arena 2 . Durante o jogo, os programadores não interagem com os robôs, eles agem de
acordo com sua programação anterior que pode, inclusive, incorporar conceitos de inteligência artificial.
Figura 1.1: Tela do jogo Robocode.
No jogo, os personagens principais andam pela arena e atiram contra outros competidores. Podemos
dizer que no jogo existe a “categoria” robô, com características e comportamentos comuns a todo “ser”
deste tipo. Em orientação a objetos, chamamos esta “categoria” de classe. Na definição de Horstmann
1
2
Download em http://http://robocode.sourceforge.net/
http://robocode.sourceforge.net/docs/ReadMe.html
3
e Cornell [5] , uma classe é “o modelo ou esquema a partir do qual os objetos são criados”. Assim,
a classe Robô é uma estrutura que permite a criação de muitos objetos robôs, todos com as mesmas
características e comportamentos. A criação de um objeto é chamada de instanciação (criação de uma
instância).
Classes existem em tempo de compilação. Em tempo de execução, o que existem são objetos executando ações e trocando mensagens entre si.
Em programação orientada a objetos (POO), um programa é um conjunto de classes. O desenvolvimento de um programa é feito por meio da utilização de classes existentes e da criação de classes
específicas. A biblioteca padrão linguagem Java já fornece milhares de classes prontas para utilização.
Por exemplo, a classe GregorianCalendar, que traz um conjunto de funcionalidades para manipulação de datas. O programador não precisa saber como esta classe está implementada, basta saber como
utilizá–la (para isso, basta consultar a documentação).
1.1.1
Atributos e métodos
Em POO, classes definem atributos e métodos. Atributos são características que os objetos da classe
terão. Por exemplo, a classe Robô poderia definir um atributo numeroDeVidas. Métodos são comportamentos que os objetos poderão ter (ou operações que o objeto realiza). A classe Robô tem o método
atirar. Todos os objetos instanciados a partir da classe Robô terão o atributo numeroDeVidas e o método atirar. Um objeto possui um estado, que é o conjunto de valores que seus atributos possuem em
determinado instante. O estado do objeto é mutável e, geralmente, é alterado pelos próprios métodos do
objeto. Um objeto da classe Robô poderia ter um método morrer, que diminuiria o numeroDeVidas.
1.1.2
Herança
Um característica específica da POO é a herança. Herança é a possibilidade de se construir classes derivadas de outras já existentes, reaproveitando todos os atributos e métodos sem necessidade de reescrevê–
los. Claro que as classes derivadas adicionam ou modificam atributos e comportamentos, pois de outra
forma não haveria utilidade em se criar uma classe derivada de outra. Voltando ao nosso jogo, os robôs
não são construídos do zero, eles são derivados de uma classe base Robot. Esta classe tem os atributos e
comportamentos básicos de todo robô.
No exemplo mostrado na Figura 1.1, há dois tipos de robôs: Crazy e Fire. Esses robôs tem algumas
características comuns, por exemplo, a cor e vivo (true ou false). Também possuem comportamentos
comuns, como andar e atirar. No entanto, a forma como um Crazy se move é diferente da forma como
Fire se move. No contexto da POO, poderíamos ter uma classe Robô que teria os atributos comuns
(cor e vivo) e o método comum (atirar). A classe Fire seria herdeira de Robô e teria o método andar
básico. A classe Crazy também seria herdeira de Robô e teria o método andar implementado de
forma diferente da classe Fire.
Em POO, chamamos a classe Robô de superclasse, classe mãe ou classe base e as classes Crazy e
Fire de subclasses, classes filhas ou classes derivadas. Diz–se, também que Robô é uma generalização
de Crazy e Fire e que Crazy e Fire são especializações de Robô.
Nos próximos capítulos estes conceitos serão detalhados.
4
1.2
Identificando classes
Um método simples para identificar classes é localizar substantivos na análise do problema. Métodos
são identificados a partir de verbos. Se fôssemos descrever textualmente nosso jogo, poderíamos dizer
que ...
O jogo possui dois ou mais robôs que se movem pela arena escaneando a presença de inimigos.
Quando localiza o inimigo, atira contra ele. Quando um robô bate na parede, ele gira para caminhar
em outra direção.
Substantivos: arena, robôs, parede...
Verbos: andar, escanear, atirar, girar...
Naturalmente, o projetista deve utilizar sua experiência para decidir quais substantivos e verbos são
importantes para criar suas classes [5].
1.3
Considerações finais
Neste capítulo foram apresentados, de maneira introdutória, alguns conceitos de orientação a objetos.
Nos próximos capítulos, os principais conceitos serão vistos com mais detalhes.
5
Capítulo 2
Classes e Objetos em Java
Neste capítulo será mostrado como escrever classes em Java e instanciar objetos. Pressupõe–se que o
leitor tenha conhecimentos básicos de programação em Linguagem C, tais como sobre variáveis, tipos,
estruturas de seleção e repetição.
2.1
Estrutura de uma classe
Em Java, todo código deve estar dentro de uma classe. A estrutura básica de uma classe é:
public class Carro {
// atributos
// métodos
}
A definição de uma classe começa com a palavra reservada class seguida do nome da classe. Por
padrão, o nome da classe se inicia com letra maiúscula. (Não se preocupe ainda com o modificador
public). Observe a chave de abertura e de fechamento da classe.
Uma classe em Java deve ser salva em um arquivo com o mesmo nome da classe seguido da extensão
.java.
2.1.1
Atributos
Atributos são declarados na forma <tipo> + <nome>. Um atributo pode ser de um tipo primitivo (int,
boolean) bem como ser de tipo classe (String). Atributos geralmente são declarados logo após a chave
de abertura, mas nada impede que você declare os atributos no final da classe. Por padrão, nomes de
atributos são escritos em minúsculas.
public class Carro {
int velocidade;
boolean motorLigado;
String marca;
// métodos
}
6
Atributos também são chamados de variáveis de instância. Cada objeto possui suas próprias variáveis
de instância. Se o valor de uma variável de instância de um objeto for alterado, em nada altera o valor
das variáveis dos outros objetos.
2.1.2
Métodos
Métodos definem comportamentos dos objetos. Geralmente, atuam sobre os próprios atributos do objeto,
modificando seu estado. No exemplo a seguir são mostrados os métodos ligar( ), desligar( ), getMarca()
e setVelocidade() da classe Carro:
public class Carro {
int velocidade;
boolean motorLigado;
String marca;
void ligar(){
motorLigado = true;
}
void desligar(){
motorLigado = false;
}
String getMarca(){
return marca;
}
void setVelocidade(int velocidade){
this.velocidade = velocidade;
}
}
Métodos podem receber parâmetros e retornar valor. O método getMarca() retorna o valor do atributo
marca, enquanto que o método setVelocidade() recebe o novo valor para o atributo velocidade.
Um comentário sobre a palavra reservada this. Observe que o nome do parâmetro (velocidade) é o
mesmo do atributo. Para que não haja confusão (velocidade = velocidade ?), a palavra this identifica que
o que vem depois do operador de ponto é o atributo da classe.
2.1.3
Métodos sobrecarregados (overloaded)
Sobrecarga de método é uma facilidade oferecida pela linguagens orientadas a objetos. Consiste na
escrita de dois ou mais métodos com o mesmo nome mas com assinaturas diferentes. Em outras palavras,
a quantidade, a ordem ou o tipo dos parâmetros devem ser diferentes. Considere o exemplo a seguir,
em que nossa classe Carro tem um método acelerar() sobrecarregado. O primeiro método não recebe
parâmetros enquanto o segundo recebe um inteiro indicando quantas vezes o carro tem que acelerar. Qual
método será executado será decidido em tempo de execução. Se o método for chamado sem parâmetros,
o primeiro será executado; se for chamado com um inteiro, o segundo será executado.
7
public class Carro {
// atributos e demais métodos...
void acelerar(){
velocidade++;
}
void acelerar(int vezes){
for (int i=0; i < vezes, i++){
velocidade++;
}
}
}
2.2
Instanciando objetos
Uma classe, como já foi dito, é uma estrutura para se criar objetos. Para a execução da aplicação, objetos
devem ser criados. A criação de objetos é feita com o comando new, na forma:
Carro carro = new Carro();
Por padrão, nomes de objetos são escritos em minúsculas. Uma vez instanciado o objeto, o acesso aos
atributos e métodos é feito por meio do operador de ponto 1 :
carro.ligar();
//chama o método ligar() do objeto carro
System.out.println(carro.motorLigado); // exibe o valor do atributo
2.2.1
Construtores
Quando um objeto é instanciado por meio do operador new(), um método especial é chamado na classe.
Este método é chamado de construtor. Alguns autores dizem que o construtor não é um método propriamente, mas vamos considerá–lo um “método especial”. É ele que se encarrega de criar um novo objeto
e inicializar os campos. Existem dois tipos de construtores: default e criado pelo programador.
O construtor default é criado pelo compilador quando o programador não cria um construtor para a
classe. Se o programador escreve seu próprio construtor, o construtor default NÃO é criado. A classe
Carro do nosso exemplo não tem um construtor. Isto significa que, quando um objeto é criado, o
construtor padrão é chamado. Este construtor inicializa os tipos primitivos numéricos com 0 (zero),
os tipos booleanos com false e os tipos objetos com null. Null pode ser entendido como um ponteiro
apontado para lugar nenhum.
XLembre–se: quando aparecer a mensagem “null pointer exception” é porque o programa
tentou acessar um objeto não instanciado.
Construtores são escritos, preferencialmente, no início da classe. Informalmente falando, eles servem
para deixar o objeto no estado desejado no momento da criação. Como os construtores são chamados no
momento de criação dos objetos, o programadores podem utilizá–los para associar valores padrão para
as variáveis de instância. No exemplo da classe Carro, o programador pode desejar que os objetos sejam
1
Ver Apêndice A sobre entrada e saída em Java
8
criados com velocidade = 0, motorLigado = false e marca “em branco”. A maneira de se fazer isso é
criando um construtor da seguinte forma:
public class Carro {
int velocidade;
boolean motorLigado;
String marca;
Carro(){
velocidade = 0;
motorLigado = false;
marca = "";
}
//demais métodos
}
Construtores tem o mesmo nome da classe e NÃO tem tipo de retorno, nem mesmo void. Os construtores retornam um objeto do tipo da classe da qual fazem parte. Se você especificar um tipo para um
construtor ele deixa de ser um construtor e passa a ser um método comum.
2.2.2
Sobrecarga de construtores
Construtores, assim como os demais métodos, podem ser sobrecarregados. Um construtor sobrecarregado é um construtor com uma lista de parâmetros diferente dos demais. Exemplo:
public class Carro {
int velocidade;
boolean motorLigado;
String marca;
Carro(){
velocidade = 0;
motorLigado = false;
marca = "";
System.out.println("Chamou o construtor 1");
}
Carro(int vel, boolean lig, String marc){
velocidade = vel;
motorLigado = lig;
marca = marc;
System.out.println("Chamou o construtor 2");
}
Carro(String marc){
velocidade = 0;
motorLigado = false;
marca = marc;
System.out.println("Chamou o construtor 3");
}
}
9
Mas, ao instanciar um objeto, qual construtor será chamado? Depende dos parâmetros passados no
momento da chamada... Se a chamada for
Carro c = new Carro();
será chamado o primeiro construtor, pois não foi passado nenhum argumento. Se a chamada for
Carro c = new Carro(“Ford”);
o construtor 3 será chamado e o argumento passado será atribuído à variável de instância fabricante.
Uma classe pode ter quantos construtores forem necessários (naturalmente com uma lista de parâmetros
diferentes para cada um). Será executado o construtor que “combinar” com a chamada. Por outro lado,
a seguinte chamada ao construtor
Carro c = new Carro(20, “Bazinga”);
resultará em erro de compilação, pois não há um construtor que receba int, String.
Construtores podem chamar outros construtores da mesma classe. O código a seguir é válido:
...
Carro(){
this(0, false, "");
}
Carro(int vel, boolean lig, String marc){
velocidade = vel;
motorLigado = lig;
marca = marc;
}
...
Um detalhe a ser observado é que, quando um construtor chama outro por meio da palavra reservada
this, a chamada deve ser a primeira instrução no método. O código a seguir não compila:
...
Carro(){
System.out.println("Executou o construtor 1");
this(0, false, "");
}
...
2.3
Ciclo de vida de um objeto
Existe uma diferença entre objeto e variável de referência que muitas vezes não é percebida. A instrução
Carro carro = new Carro();
declara uma variável chamada carro. O operador new cria uma objeto e associa este objeto à variável
carro. Poderíamos, inclusive, dividir as instruções em duas:
10
Carro carro;
carro = new Carro();
A declaração de uma variável é uma operação distinta da inicialização da variável. Para ilustrar este
conceito, Gupta compara a declaração de variável ao nome de uma bebê ainda não nascido e o objeto
com o bebê real [4] mostrado na Figura 2.1. Embora seja possível criar um objeto sem associá–lo à uma
variável, isto só é feito em situações específicas que não serão tratadas por enquanto, pois o objeto ficaria
inacessível.
Figura 2.1: Diferença entre declarar uma variável e inicializar a variável. Fonte: [4].
Uma vez que o objeto foi criado, ele pode ser acessado por meio da variável de referência. Ele
permanece acessível até que fique fora de escopo ou a seja atribuído null explicitamente à variável.
Outra situação que deixa o objeto inacessível é quando outro objeto for atribuído à outra variável, como
no exemplo a seguir.
...
Carro c1 = new Carro();
Carro c2 = new Carro();
c2 = c1;
...
(1)
(2)
(3)
Em (1), o primeiro objeto foi criado e associado à variável c1. Em (2), o segundo objeto foi criado e
atribuído à variável c2. Em (3), o primeiro objeto foi associado à variável c2. Agora o primeiro objeto
não tem mais como ser acessado... (No exemplo, ao final, c1 e c2 referenciam o mesmo objeto).
2.4
Classes wrappers
Em Java, podemos dividir os tipos de dados em tipos primitivos e classes. Wrappers são classes especiais
que possuem métodos capazes de lidar com tipos primitivos. Existe uma classe para cada tipo primitivo.
Grosso modo, a classe “guarda” o tipo primitivo (wrap, em inglês, significa “envelopar”). Na Tabela 2.1
são mostradas as classes wrapper correspondente a cada tipo primitivo.
Classes wrapper são usadas quando é necessário tratar um tipo primitivo como objeto. Por exemplo, classes e métodos genéricos não aceitam métodos primitivos, no entanto, aceitam a classe wrapper
correspondente.
11
Tabela 2.1: Classes wrapper correspondentes a cada tipo primitivo.
Tipo Primitivo Classe wrapper
boolean
Boolean
byte
Byte
char
Character
short
Short
long
Long
int
Integer
float
Float
double
Double
A forma de instanciar um objeto do tipo Integer é mostrada a seguir. Há dois construtores na
classe: um que recebe um int e outro que recebe uma string.
Integer integer1 = new Integer(1);
Integer integer2 = new Integer("2");
Para se recuperar o tipo primitivo, chamamos o método intValue():
int i = integer1.intValue();
Autoboxing
Autoboxing é o processo pelo qual um tipo primitivo é automaticamente encapsulado (boxed) no seu tipo
wrapper equivalente. As duas linhas de código a seguir executam exatamente a mesma operação:
Integer integer1 = new Integer(1);
Integer integer2 = 1;
Auto-unboxing
Auto-unboxing é o processo pelo qual o valor de um objeto é automaticamente extraído do tipo wrapper.
Basta atribuir o objeto ao tipo primitivo:
int i = integer1;
Aqui foram mostradas algumas características das classes wrapper. Para saber mais, consulte a
documentação Java de cada classe.
2.5
Exercício
Considere a descrição seguinte.
A UENP possui vários veículos que são utilizados por várias pessoas. Pode ser que apareça uma
multa para determinado veículo. É necessário saber quem foi o motorista que cometeu a infração.
Carros, basicamente, possuem marca, modelo, cor e placa. Os usuários podem ser professores, agentes
ou alunos. Pretende–se que, quando um carro for entregue, sejam registrados o motorista, data e hora
12
da retirada e quilometragem atual. Quando o carro for devolvido, devem ser registradas a data e hora da
devolução bem como a quilometragem. O registro das informações é feito por funcionário administrador
mediante login e senha.
Analise a descrição e crie classes com atributos e métodos adequados para projetar um programa.
13
Capítulo 3
Modificadores e Encapsulamento
Neste capítulo serão mostrados os diferentes modificadores que podem se aplicados a atributos, métodos
e classes. Também será mostrado o conceito de encapsulamento. Inicialmente, trataremos de pacote, que
tem relação com modificadores.
3.1
Pacotes (Packages)
Todas classes Java são parte de um pacote. Se um pacote não for declarado, a classe fará parte do
pacote default (padrão). Um pacote é uma “pasta” onde são mantidas uma ou mais classes relacionadas.
Pacotes são importantes para organizarem a aplicação. Por padrão, o nome de pacote se inicia com letra
minúscula. Se uma classe definir explicitamente um pacote, a declaração do pacote deve ser a primeira
declaração no arquivo 1 .
package carros;
class Carro {
// atributos
// métodos
}
Também é possível criar pacotes dentro de pacotes. Suponha uma aplicação para gerenciamento
de uma frota de veículos. Poderíamos ter o pacote veiculos e dentro deste pacote, os pacotes carros e
caminhoes. O acesso a pacotes dentro de pacotes é feito por meio do operador de ponto. Veja o exemplo
modificado.
package veiculos.carros;
public class Carro {
// atributos
// métodos
}
1
Geralmente as organizações possuem suas regras para nomear pacotes. Exemplo: br.edu.uenp.aplicação.pacote
14
Não é possível ter duas classes com o mesmo nome dentro de um mesmo pacote. Por outro lado, se
as classes estiverem em pacotes distintos, não há problema em terem o mesmo nome.
3.1.1
Instrução import
Classes não “enxergam” outras classes que não estão no mesmo pacote. Considere as classes e respectivos pacotes apresentados na Figura 3.1.
Figura 3.1: Exemplo de classes em pacotes distintos.
Vamos imaginar que a classe Master queira instanciar um objeto da classe Carro. Como a classe
Carro está em outro pacote, é necessário “incluir” esta classe por meio do comando import.
package com;
import veiculos.carro.Carro;
// <-- import da classe Carro
public class Master {
void metodoX(){
Carro c = new Carro();
}
}
Caso queiramos utilizar mais de uma classe do pacote veiculos.carros, utilizamos o “*”:
import vaiculos.carros.*;
O comando anterior realiza o import de todas as classes do pacote carros. No entanto, o “*” não importa
subpacotes, apenas classes. A instrução:
import vaiculos.*;
não é equivalente à anterior.
É possível utilizar uma classe de outro pacote sem o comando import. Para isto, devemos utilizar o
caminho completo, ou “nome completamente qualificado”, tecnicamente falando:
15
package com;
public class Master {
void metodoX(){
veiculos.carros.Carro c = new Carro();
}
}
// <-- sem import
Esta opção é menos prática, mas pode ser necessária quando uma classe que tenha o mesmo nome
de outra classe em outro pacote.
3.2
Modificadores de acesso
Modificadores de acesso alteram a visibilidade de uma classe e seus membros dentro de um pacote ou em
pacotes separados. Há quatro modificadores de acesso em Java: public, protected, private e “nenhum”.
Este último ocorre quando o programador não especifica o modificador, então a classe ou membro assume
o acesso default, também chamado de acesso de pacote (package access).
A forma de aplicar um modificador é colocando a palavra correspondente antes da classe, método ou
variável de instância:
public class MinhaClasse { ... }
public void meuMetodo () { ... }
private String meuAtributo;
Modificadores de acesso não são aplicados a variáveis locais ou a parâmetros de método.
3.2.1
Modificador public
O modificador public, quando aplicado a uma classe, a torna acessível à qualquer classe no universo
Java [4], quer estejam no mesmo pacote ou em pacotes diferentes, quer sejam classes derivadas ou
classes sem qualquer relação.
O modificador public aplicado a métodos permite acesso ao método em qualquer classe do universo
Java. Em outras palavras, o método pode ser chamado por um objeto que esteja em qualquer lugar.
Um atributo public pode ser acessado por qualquer classe.
3.2.2
Modificador private
O modificador private pode ser aplicado à classes internas (classes definidas dentro de outras). Este
modificador torna a classe acessível apenas à classe em que estão contidas.
Um método definido como private é acessível apenas pela própria classe e não é herdado por classes
derivadas. Um método private faz sentido quando, por exemplo, queremos um método que execute uma
tarefa para outro método da própria classe. Assim, os métodos da própria classe, e apenas eles, chamam
o método private quando precisarem.
Um atributo private só pode ser acessado por métodos da própria classe. No caso de classes derivadas, os atributos private são herdados mas não são diretamente acessíveis. Eles podem ser acessados por
métodos públicos da superclasse (que são herdados).
16
3.2.3
Modificador protected
O modificador protected não é aplicado à classes.
Quando o modificador protected é aplicado a método, faz com que o método seja acessível apenas
no mesmo pacote ou por classes derivadas, mesmo que estejam em pacotes diferentes.
Atributos protected seguem a mesma regra: são acessíveis pela classe, são herdados e acessíveis pela
subclasse.
3.2.4
Acesso default (de pacote)
O acesso de pacote (default), que ocorre quando não há nenhum modificador, permite que a classe seja
acessível apenas às outras classes do mesmo pacote. Em outras palavras, estas classes não permitem
import por outras classes. Lembre–se que classes no mesmo pacote não necessitam de import para
serem utilizadas.
Um método com acesso de pacote (default) não pode ser chamado por uma classe que esteja fora do
pacote.
Os atributos com acesso default são acessíveis apenas dentro do pacote em que as classes às quais
pertencem estão. Mesmo as classes derivadas, se estiverem em pacotes diferentes, não podem acessar
estes atributos.
3.3
Modificadores final e static
Os modificadores de acesso controlam a visibilidade, enquanto os demais modificadores alteram as propriedades default das classes Java e seus membros [4]. Os principais modificadores são static, final e
abstract. Este último será tratado no Capítulo 5.
3.3.1
Modificador final
O modificador final modifica o comportamento default de classes, variáveis e métodos.
Classes
Uma classe final não pode ser estendida por outra (não pode haver herdeiras da classe). Para tornar a
classe final, acrescentamos a palavra reservada final na definição da classe:
public final class Carro {...
}
Variáveis
Uma variável final não pode ter seu valor alterado. Em outras palavras, seu valor só pode ser atribuído
uma única vez. Por padrão, escrevemos o nome das variáveis final inteiramente em maiúsculas. Observe
o seguinte exemplo :
public class Carro {
final int VEL_MAX;
17
public Carro(){
VEL_MAX = 240;
}
}
Métodos
Um método final, por sua vez, não pode ser sobrescrito por uma classe derivada. Por outro lado, uma
classe derivada pode redefinir o método (mesmo nome, assinatura diferente). Suponha que a classe Carro
tenha o método:
public void metodoX(){ ...
}
uma classe derivada CarroLuxo poderia ter o método:
public void metodoX(int y){ ...
3.3.2
}
Modificador static
O modificador static pode ser aplicado a classes, variáveis e métodos.
Variáveis
Uma variável static é uma variável de classe e não de objeto. Em outras palavras, ela é compartilhada
entre todas as instâncias de uma classe. Ela existe e pode ser acessada mesmo que nenhum objeto da
classe seja instanciado. Uma variável static pode ser acessada utilizando o nome da variável de referência
ou o nome da classe.
Os modificadores static e final podem ser usados para definir constantes. No exemplo a seguir, a
classe Carro define uma constante VEL_MAX:
public class Carro {
public static final int VEL_MAX = 240;
}
Embora seja possível definir uma constante não–estática, é uma prática comum definir as constantes
como membros estáticos. Assim, as constantes podem ser acessadas pela classe ou pelos objetos.
Métodos
Métodos static não estão associados a objetos e não podem acessar as variáveis de instância de uma
classe (a não ser que a variável seja static).
Um exemplo de método static é o método pow( ), que efetua a exponenciação e pode ser encontrado
na classe Math:
public static double pow(double a, double b)
A forma de chamar o método é por meio da classe:
double resultado = Math.pow( 5, 3);
18
Geralmente, métodos utilitários são static. Métodos utilitários são aqueles usados para manipular os
parâmetros do métodos, realizar alguma computação e retornar um resultado, sem relação com um objeto
específico.
Embora métodos static possam ser chamados pela classe ou pelo objeto, o comum é chamar por meio
da classe, uma vez que o método não pertence ao objeto.
Classes
Em Java, não é possível definir uma classe como static, a menos que ela seja uma classe interna.
3.4
Encapsulamento
Encapsulamento é um princípio da orientação a objetos segundo o qual uma classe não deve expor suas
partes internas para o mundo exterior [4]. Em outras palavras, os atributos de uma classe devem ser
privados e a classe deve fornecer métodos públicos para acessar estes atributos. Uma classe também
pode ter métodos encapsulados (private).
Tomando como exemplo a classe Carro, vamos supor que um objeto execute a seguinte instrução:
carro.velocidade = -10;
Isto deixaria nosso objeto em um estado inválido, pois não queremos uma velocidade negativa. Uma
forma de contornar isso e tornando o atributo velocidade privado e criando métodos públicos para acessálo:
public class Carro {
private int velocidade;
public void setVelocidade(int vel){
if (vel >= 0 && vel < 240){
this.velocidade = vel;
}
public int getVelocidade(){
return this.velocidade;
}
}
Desta forma, a funcionalidade do objeto é exposta utilizando métodos públicos, ao mesmo tempo em
que as informações ficam protegidas.
O conceito de encapsulamento, muitas vezes, é usado como sinônimo de ocultamento de informação
(information hiding). Encapsulamento, a princípio, é a definição de variáveis e métodos juntos em uma
classe. O ocultamento de informação é uma consequência do encapsulamento.
3.4.1
Getters e setters
Um padrão utilizados pelos desenvolvedores Java é criar um método get+atributo para acesso às propriedades privadas da classe e um método set+atributo para modificar as propriedades privadas de uma
19
classe. No código do exemplo anterior, há a propriedade velocidade (private) e os métodos públicos
getVelocidade() para obter o valor da propriedade e o método setVelocidade() para modificar este valor.
20
Capítulo 4
Herança
Herança é um conceito fundamental em orientação a objetos. O mecanismo de herança permite criar
novas classes com base nas classes existentes [5]. A classe “herdeira” reutiliza os métodos e atributos e
adiciona novas propriedades e comportamentos.
4.1
Implementando herança
Imagine uma aplicação que defina uma classe Carro, da seguinte forma:
public class Carro {
int velocidade;
boolean motorLigado;
String marca;
public
public
public
public
void
void
void
void
ligar(){...}
desligar(){...}
acelerar(){...}
frear(){...}
}
Agora considere que seja necessária a criação de outra classe, CarroLuxo, que tem o atributo
adicional arCondicionado e os métodos ligarAr( ) e desligarAr( ). A nova classe difere pouco da classe
Carro já existente. Então, por que não aproveitar o que já está pronto e testado? Obviamente, para
classes muito simples a vantagem não é muito visível.
A forma de se implementar a herança é criar a nova classe com a palavra reservada extends, como a
seguir, em que CarroLuxo herda de Carro (construtores omitidos para simplificação).
public class CarroLuxo extends Carro{
boolean arLigado;
public void ligarAr(){...}
public void desligarAr(){...}
}
21
Ao herdar de uma classe, a classe derivada, também chamada de subclasse ou classe filha, possui todos os campos e métodos da classe base, também chamada de superclasse ou classe mãe, sem a
necessidade de reescrevê–los.
Agora, vamos imaginar que queremos criar uma nova classe, CarroInvisivel, que tem todas as
características de CarroLuxo e mais a possibilidade de ficar invisível. Poderíamos fazer a nova classe
herdar de CarroLuxo, da seguinte forma:
public class CarroInvisivel extends CarroLuxo{
boolean estaInvisivel;
public void ficarInvisivel(){...}
public void ficarVisivel(){...}
}
Observe que CarroInvisivel especializa (extends) CarroLuxo, mas CarroLuxo é subclasse
de Carro, portanto, CarroInvisivel tem todos os campos e comportamentos de Carro também,
tais como ligarMotor() e acelerar().
XLembre–se: construtores não são herdados, mas eles podem ser chamados a partir da
classe derivada.
Uma regra para saber se uma classe deve ser projetada como subclasse ou uma classe independente é
perguntar “A é um B? ”. No exemplo, CarroLuxo pode ser projetada como subclasse de Carro, pois
um CarroLuxo é um Carro.
4.2
Atributos e métodos sobrescritos
Quando uma classe derivada define um atributo com o mesmo nome da classe base, somente o novo
atributo é visível na classe derivada. Quando a classe derivada define um método com o mesmo nome da
classe base, naturalmente com corpo diferente, este método é chamado de sobrescrito (overridden).
XLembre–se: sobrescrita (overridden) é diferente de sobrecarga (overloaded).
Considere que a classe Carro tenha o método desligar(), que torna o motor = false. Vamos imaginar
que o projetista da classe CarroLuxo queira, também, desligar o ar condicionado quando o motor for
desligado. Agora, o método desligar( ) herdado já não serve mais ao nosso propósito. Devemos, então,
sobrescrever o método desligar( ) da classe base na classe derivada. Nossas classes ficariam assim
(demais métodos omitidos):
22
public class Carro {
int velocidade;
boolean motorLigado;
String marca;
public class CarroLuxo{
boolean arCondicionado;
public void desligar(){
motorLigado = false;
this.desligarAr();
}
public void desligar
motorLigado = false;
}
public void desligarAr(){
arCondicionado = false;
}
}
}
Observe que a classe CarroLuxo está acessando o atributo motorLigado que é definido na classe
base. Uma alternativa é chamar o método da classe base para que ela execute a operação. Isto é especialmente necessário quando o atributo da classe base é private (ver capítulo sobre Modificadores). A
palavra reservada super faz com que o método da superclasse seja chamado. Sempre que o comando
super for usado, ele deve ser a primeira instrução do método.
public class CarroLuxo{
boolean arCondicionado;
public void desligar(){
super.desligar();
this.desligarAr();
}
//chama o método da superclasse
}
4.3
Polimorfismo e vinculação dinâmica
Polimorfismo é um conceito muitas vezes confundido com sobrecarga. Polimorfismo é o fato de uma
variável de referência poder se referir a múltiplos tipos reais. Selecionar automaticamente o método
apropriado em tempo de execução é chamado de vinculação dinâmica [5].
Vamos por partes. Antes precisamos entender que o tipo da variável de referência pode ser diferente
do tipo do objeto. A seguinte construção é válida
Carro carro = new CarroLuxo();
pois CarroLuxo é um Carro. Naturalmente, o inverso não é possível. O que temos é uma variável do
tipo Carro e um objeto do tipo CarroLuxo. Duas observações importantes:
• Não podemos chamar o método ligarAr() na variável carro.
• Se o método desligar() for chamado, será executado o método da classe filha, uma vez que o objeto
é do tipo CarroLuxo.
Considere o seguinte exemplo:
23
Carro[] frota = new Carro[3];
frota[0] = new Carro();
frota[1] = new CarroLuxo();
frota[2] = new Carro();
...
for (int i = 0; i < 3; i++){
frota[i].desligar();
}
A vinculação dinâmica ocorre quando, em tempo de execução, há mais de um método possível de
ser executado. No exemplo anterior, a chamada a frota[1].desligar() deixa 2 possibilidades: executar o
método correspondente ao tipo da variável ou ao tipo do objeto. Neste caso, vai ser executado o método
na subclasse, uma vez que ela tem o método correspondente. Se não tivesse, o método seria buscado na
superclasse.
4.4
A classe Object
Todas as classes, automaticamente, herdam da superclasse ancestral Object. Não é necessário a palavra
extends para que a classe seja uma subclasse de Object. Por isso, o código seguinte é válido
Carro carro = new Carro();
carro.toString();
Mesmo que nossa classe Carro não tenha o método toString(), ela herda de Object. Portanto, qualquer objeto, de qualquer tipo é um Object. Isto torna a construção a seguir possível, embora de discutível
utilidade.
Object[]
lista[0]
lista[1]
lista[2]
lista
= new
= new
= new
= new Object[3];
Carro();
Pessoa();
Abobora();
XLembre–se: tipos primitivos não são objetos, portanto não herdam de Object.
24
Capítulo 5
Classes Abstratas e Interfaces
Classes abstratas são classes que, por algum motivo, não podem ser instanciadas. Interfaces não são
classes, mas sim um conjunto de requisitos aos quais as classes precisam se adequar. Neste capítulo
serão mostradas as diferenças entre esses dois tipos de estruturas.
5.1
Classes abstratas
Algumas vezes as classes estão tão altas na hierarquia de herança que, na prática, não se criam instancias
delas. Outras são genéricas a ponto de não ser possível instanciar objetos. Considere o exemplo mostrado
na Figura 5.1.
Figura 5.1: Exemplo de hierarquia de herança.
O exemplo mostra uma hierarquia de herança muito simples, mas permite algumas análises. Não faz
sentido instanciar objetos da classe Veiculo, uma vez que, no contexto da aplicação, só existem objetos
do tipo Carro, Caminhao e Moto. Não faz sentido, também, implementar o método calcularIPVA()
na classe Veiculo, pois como a maneira de calcular o IPVA é diferente em cada classe, todas as classes
derivadas sobrescrevem o método. No entanto, o projetista da aplicação quer obrigar que todas as classes
implementem este método. Por outro lado, todas as classes derivadas tem o atributo anoFabricacao.
Nesta situação, pode–se criar a classe Veiculo como abstrata (abstract). Uma classe abstrata é
uma classe que não pode ser instanciada, mas serve de base para criação de classes derivadas. No nosso
exemplo, vamos definir o método calcularIPVA() como abstrato na classe Veiculo.
25
public abstract class Veiculo {
private int anoFabricacao;
public abstract double calcularIPVA();
public int getAnoFabricacao() {
return anoFabricacao;
}
public void setAnoFabricacao(int anoFabricacao) {
this.anoFabricacao = anoFabricacao;
}
}
Uma classe abstract pode ter métodos abstract e métodos concretos. Métodos abstract não tem
corpo. Uma classe que tem ao menos um método abstract é, obrigatoriamente, abstract. No entanto,
uma classe abstract não precisa ter métodos abstract, pode ter somente métodos concretos. Neste caso,
o que a torna abstract é o modificador antecedendo a palavra reservada class.
Continuando com o exemplo, a classe Carro pode estender a classe Veiculo. Ao fazer isso, ela
é obrigada a implementar todos os métodos abstract da classe base, caso contrário, Carro também se
tornará uma classe abstract.
public class Carro extends Veiculo {
@Override
public double calcularIPVA() {
//corpo do método
}
}
XLembre–se: uma variável nunca pode ser marcada como abstract.
5.2
Interfaces
Interfaces são uma maneira de especificar o que as classes devem fazer sem dizer como devem fazer [5].
Interfaces não possuem variáveis de instância, mas podem ter constantes.
Vamos a um exemplo. Suponha um jogo que tenha vários personagens (marcianos, terráqueos, cachorros, cavalos, bolas, grama). Suponha que o (objeto) jogo precise, em dados momentos, desenhar
os objetos na tela. Acontece que cada objeto tem um jeito diferente de se desenhar. A solução é criar
uma interface com o método desenhar( ) e obrigar todos as classes assumir esta interface. Desta forma,
garante–se que todos os objetos presentes no jogo saibam desenhar a si mesmas.
Para criar uma interface, ao invés da palavra class, usa–se a palavra interface. A nossa interface
poderia ser definida da seguinte maneira:
26
public interface IJogavel {
public void desenhar();
}
Agora, podemos ter classes concretas que implementam a interface IJogavel. Estas classes,
obrigatoriamente, devem implementar o método desenhar(), pois ele é abstrato na interface. Naturalmente, a classe pode ter outros métodos próprios. Uma classe Marciano, para implementar a interface
IJogavel, utiliza a palavra reservada implements.
public class Marciano implements IJogavel {
@Override
public void desenhar() {
//corpo do método
}
//métodos próprios
}
Em Java, não é possível herança múltipla (uma classe herdar de duas classes base ao mesmo tempo),
mas uma classe pode implementar mais de uma interface:
public class Marciano implements IJogavel, IDesenhavel {...}
Até a versão 7 de Java, interfaces não tinham métodos concretos. A partir da versão 8, passaram a
poder ter métodos default e static.
27
Capítulo 6
Programação Genérica
Programação genérica é uma maneira de escrever código que pode ser utilizado para objetos de muitos
tipos diferentes [5]. Programadores de aplicativos escrevem pouco código genérico. No entanto, é
importante conhecer sobre genéricos para utilizar as classes genéricas da linguagem.
6.1
Definindo uma classe genérica simples
Considere o código não genérico a seguir:
public class Caixa{
private Integer i;
public Caixa(){
i = new Integer(0);
}
//gets e sets
}
A classe possui um atributo do tipo Integer. O que acontece se for necessário utilizar a classe com
o tipo String? Teríamos que reescrever a classe... A programação genérica pode resolver este problema.
Uma classe genérica é uma classe com uma ou diversas variáveis de tipo [5].
public class Caixa <T> {
private T objeto;
public Caixa(){}
public Caixa(T objeto){
this.objeto = objeto
}
public T getObjeto() {
return objeto;
}
28
public void setObjeto(T objeto) {
this.objeto = objeto;
}
}
Pronto, agora a classe Caixa pode ser instanciada com qualquer tipo de objeto. A forma de fazer isso
é, no momento da instanciação, definir qual o tipo de objeto será utilizado.
Caixa<String> caixa1 = new Caixa<>();
Caixa<Carro> caixa2 = new Caixa<>(new Carro());
No exemplo, o objeto referenciado por caixa1 manipula um objeto do tipo String, uma vez que na
definição da variável de referência estipulamos este tipo. Já caixa2 opera com objetos do tipo Carro. Em
resumo, uma classe genérica pode ser instanciada com qualquer tipo, mas uma vez instanciada, só opera
com aquele tipo. Seria um erro tentar a seguinte atribuição:
caixa2.setObjeto("blábláblá");
6.2
Métodos genéricos
Podemos ter métodos genéricos em classes comuns. Para tornar um método genérico colocamos a variável de tipo (<T>) depois dos modificadores e antes do tipo de retorno. O método a seguir recebe um
objeto de qualquer tipo e retorna uma String com o nome da classe do parâmetro:
public static <T> String metodoGenerico(T t){
return t.getClass().getName();
}
6.3
Limites para variáveis de tipo
Pelo que vimos até aqui, classes e métodos genéricos são construídos para operar com qualquer tipo de
classe. Pode ser que, em alguns casos, seja necessário limitar o tipo de classe que nossa classe ou método
genérico aceite. Isto é feitos introduzindo o limite para tipo:
public class Coisa <T extends Serializable> { ...
}
Neste caso, T pode ser de qualquer classe, desde que esta classe implemente a interface Serializable.
Detalhe: embora interfaces sejam implementadas, a palavra utilizada é extends.
No caso de métodos, também podemos limitar os tipos aceitáveis:
public static <T extends Number> Double getDobro(T num) {
return num.doubleValue() * num.doubleValue();
}
29
O método anterior retorna o dobro do parâmetro recebido. Por razões óbvias, o método deve aceitar
somente números. Assim, limitamos o tipo para classes derivadas de Number. Todas as classes wrapper
( BigDecimal, BigInteger, Byte, Double, Float, Integer, Long, Short) são
derivadas de Number.
6.4
A classe ArrayList
A classe ArrayList é uma classe genérica muito utilizada. Ela oferece uma combinação entre arrays
e a estrutura de dados tipo lista. As operações mais comuns com listas são: adicionar itens, modificar
itens, excluir itens e percorrer a lista [4]. Ao contrário dos arrays que têm tamanho fixo, listas podem
crescer de tamanho quando adicionamos itens e diminuir quando excluímos itens.
Criando um ArrayList
Para criar um ArrayList especifica–se o tipo de objeto que ela vai conter dentro dos colchetes angulares.
Por exemplo, para criar uma lista de strings, a instrução é:
ArrayList<String> listaNomes = new ArrayList<>();
Adicionando elementos à lista
Há duas formas de se adicionar um elemento: no final ou especificando–se a posição. Chamar o método
add() faz com que o objeto seja adicionado ao final da lista. Exemplo:
listaNomes.add("Machado de Assis");
Se quisermos adicionar um objeto em uma posição específica, chamamos o método add() e passamos a
posição, além do objeto a ser inserido. Por exemplo, para inserir na posição 1 (segundo elemento, pois o
primeiro está na posição 0), a chamada é:
listaNomes.add(1, “Rigby”);
Neste caso, o elemento existente na posição e os seguintes serão “empurrados” para frente.
Acessando elementos da lista
Para percorrer uma lista podemos usar uma estrutura for (enhanced for):
for (String s : listaNomes) {
System.out.println(s);
}
Opcionalmente, podemos usar um ListIterator para percorrer a lista:
ListIterator<String> i = listaNomes.listIterator();
while (i.hasNext()){
System.out.println(i.next());
}
30
As duas maneiras percorrem os elementos, do primeiro ao último, na ordem em que foram inseridos.
A diferença é que usando ListIterator é possível remover algum elemento enquanto se percorre a
lista.
Substituindo elementos de um ArrayList
Para substituir um elemento de uma ArrayList utiliza–se o método set(). Por exemplo, para substituir
o primeiro elemento, a instrução é:
listaNomes.set(0, “Mordecai”);
Removendo elementos
Para remover um elemento de uma posição específica, a instrução é:
listaNomes.remove(pos);
onde pos é o índice do elemento a ser removido.
31
Capítulo 7
Exceções
Uma exceção é a indicação de que algum problema ocorreu durante a execução de um programa, impedindo a continuação normal. Tratar exceções é escrever algum código adicional que lide com essas
situações previsíveis que podem ser gerenciadas. Logicamente, existem situações que não podem ser
contornadas por meio de programação, como se o processador “queimar” durante a execução. Neste
capítulo veremos como tratar as exceções em um programa Java.
7.1
Um exemplo: divisão por zero
Como é sabido, não podemos ter divisão por zero em um programa. Mas o que acontece se, por exemplo,
um usuário digitar 0 em uma entrada e ela for usada em uma divisão? Neste caso, ocorre um erro em
tempo de execução e o programa termina de forma não desejada. Este é um exemplo de situação que
pode ser tratada e, ao invés de o programa terminar, uma solução alternativa poderia ser oferecida.
Considere o trecho de código a seguir, em que são inseridos dois números e efetuada a divisão do
primeiro pelo segundo 1 .
Scanner teclado = new Scanner(System.in);
int dividendo = teclado.nextInt();
int divisor = teclado.nextInt();
int resultado = dividendo/divisor;
System.out.println(resultado);
Se o usuário digitar 0 para o dividendo, o programa será encerrado com a seguinte mensagem de
erro:
Exception in thread "main"java.lang.ArithmeticException: / by zero
7.2
Tratamento de exceções
Em Java, uma exceção é um tipo de objeto “lançado” quando ocorre um erro. O objeto lançado pode ser
capturado e tratado de maneira apropriada. A forma de tratar exceções é por meio de blocos try/catch.
1
Ver Apêndice A sobre a classe Scanner.
32
O código que pode gerar a exceção é escrito dentro do bloco try. Se a exceção ocorrer, ela será tratado
dentro do bloco catch. Sintaxe:
try {
// código que pode gerar exceção
}
catch (tipo da exceção) {
// código para tratar a exceção
}
Agora vamos reescrever o exemplo anterior, da divisão de dois números de forma a tratar a exceção.
...
Scanner teclado = new Scanner(System.in);
int dividendo, divisor, resultado=0;
boolean ok = false;
while (! ok){
try{
dividendo = teclado.nextInt();
divisor = teclado.nextInt();
resultado = dividendo/divisor;
ok = true;
}
catch(Exception e){
System.out.println("Erro: divisão por zero.");
}
}
System.out.println(resultado);
Agora, caso seja digitado 0 para o valor do dividendo, o programa exibe uma mensagem e volta a
solicitar os números. Em outras palavras, não será encerrado. (Ok, você jamais fará um código assim,
mas este é apenas um exemplo para fins didáticos.) É importante ressaltar que a execução não retorna ao
ponto em que a exceção foi disparada
O tratamento de exceção funciona da seguinte maneira: quando ocorre um erro dentro do método, o
método cria um objeto do tipo Exception. Este objeto contém informações sobre o tipo de erro e o
estado do programa. A isto dá–se o nome de disparar uma exceção.
Em um programa bem projetado, o código de tratamento de erros fica separado do código “normal”.
7.3
Princípios do tratamento de exceções
O tratamento de exceções foi projetado para situações em que o método que detecta um erro é incapaz
de lidar com ele [2]. Esse método dispara uma exceção. Se houver um método projetado para tratar esta
exceção, ela será capturada e tratada.
Para tratar a exceção, o programador inclui em um bloco try o código que pode gerar a exceção. O
bloco try é seguido por um ou mais blocos catch. Cada catch especifica o tipo de exceção que pode
capturar e que contém o tratador de exceção. Depois do último bloco catch, um bloco finally opcional
33
fornece o código que sempre é executado independentemente de uma exceção ocorrer ou não. O bloco
finally é o lugar ideal para código que libera recursos. Se não houver blocos catch, o bloco finally é
obrigatório [2].
Quando uma exceção é lançada, o controle do programa deixa o bloco try e os blocos catch são
pesquisados para encontrar o bloco apropriado (aquele cujo parâmetro corresponde ao tipo de exceção
lançada). Se nenhuma exceção for disparada, os blocos catch são pulados. Se o bloco finally existir, será
executado sendo ou não disparada a exceção.
A superclasse Exception pode ser considerada a “mãe” de todas as exceções. Por isso, muitas
vezes, um parâmetro deste tipo é utilizado no último bloco catch. Considere o exemplo a seguir:
int lista[] = new int[3];
try{
lista[3] = 30;
}
catch (ArrayIndexOutOfBoundsException ae){
System.out.println("Capturada no primeiro catch");
}
catch (Exception e){
System.out.println("Capturada no segundo catch");
}
O código tenta acessar uma posição do array que não existe. Quando a exceção é disparada, ela é
capturada no primeiro bloco catch, pois o tipo lançado “bate” com o parâmetro.
7.3.1
Tipos de exceções
Em Java, as exceções se dividem em verificadas (checked) e não verificadas (unchecked). O programa
deve, obrigatoriamente, tratar as exceções verificadas. Já para as exceções não verificadas, o tratamento
é opcional, como por exemplo ArrayIndexOutOfBoundsException. Neste último caso, o código que
potencialmente gera a exceção não precisa estar dentro de um bloco try. O ideal é que este tipo de erro
seja resolvido de outra maneira.
7.4
Criando as próprias classes de exceção
Durante a construção de um programa, podemos utilizar as classes de exceção próprias da linguagem e,
se necessário, criar nossas próprias classes de exceção, como no exemplo a seguir, que trata da divisão
por zero.
public class ExcecaoDivisaoPorZero extends Exception{
public ExcecaoDivisaoPorZero(String exc){
super("Exceção: tentativa de dividir por zero");
}
}
34
Considere o caso de divisão por zero. Como vimos, não é possível realizar a divisão por um inteiro
igual a zero. No entanto, se o denominador for do tipo double, a divisão é possível. Suponha que não
queremos que isso aconteça. Podemos tratar isto no método que efetua a divisão:
public class Calculadora {
public double dividir(double a, double b)
throws ExcecaoDivisaoPorZero{
if (b == 0){
throw new ExcecaoDivisaoPorZero(" dividendo: " + b);
}
return (a/b);
}
}
Agora estamos tratando o caso do dividendo igual a zero e disparando a nossa exceção por meio
do comando throw. As exceções criadas pelo programador devem ser do tipo verificadas. Nossa classe
ExcecaoDivisaoPorZero, por herdar de Exception, automaticamente obriga que a exceção seja
do tipo verificada. Agora observe que a classe Calculadora lança uma exceção no método dividir().
Este método deve declarar no cabeçalho os tipos de exceção que pode lançar por meio da cláusula throws. A cláusula throws transfere a responsabilidade pelo tratamento da exceção para a classe cliente do
método. Sempre que alguma classe for utilizar o método, a chamada deve estar dentro de um bloco try,
como a seguir:
Calculadora c = new Calculadora();
try{
c.dividir(x, y);
}
catch (ExcecaoDivisaoPorZero exc){
System.out.println(exc.getMessage());
}
Se a chamada ao método não estiver dentro de um bloco try, isto resulta em um erro de compilação.
7.5
Instrução try-with-resources
Um recurso é um objeto que precisa ser fechado. A instrução try-with-resources assegura que
todos os recursos sejam fechados ao final da utilização. Um arquivo é um exemplo de recurso, bem como
todos os objetos que implementam a interface Closeable. No trecho de código a seguir, um arquivo é
aberto e um objeto é gravado nele. Após a gravação o arquivo é fechado (out.close()).
35
public void escrever(Cachorro cachorro){
try {
FileOutputStream fos =
new FileOutputStream("cachorros.dat");
ObjectOutput out = new ObjectOutputStream(fos );
out.writeObject(cachorro);
out.close();
// fechando o recurso
} catch (IOException ex) {
System.out.println("Falha em escrever no arquivo "+ ex);
}
}
O trecho a seguir representa a mesma operação de escrita no arquivo, sendo que a instrução que abriu
o recurso que precisa ser fechado foi colocada no try. Assim, o programa “sabe” que precisa fechar o
recurso ao final, sem a necessidade de chamar explicitamente o método close().
try (ObjectOutput out = new ObjectOutputStream
(new FileOutputStream("cachorros.dat"))) {
out.writeObject(cachorro);
} catch (IOException ex) {
System.out.println("Falha em escrever no arquivo "+ ex);
}
7.6
Múltiplos catch
Muitas vezes somos “obrigados” a tratar mais de um tipo de exceção para um mesmo try, como no
trecho de código a seguir, em que capturamos NumberFormatException e IllegalArgumentException em
dois catch separados.
try{
Integer i = Integer.parseInt(s);
}
catch(NumberFormatException e){
System.out.println("Entrada inválida para converter");
}
catch(IllegalArgumentException e){
System.out.println("Entrada inválida para converter");
}
Nos casos em que se quer dar o mesmo tratamento para mais de um tipo de exceção, pode–se usar
múltiplos catch, como no trecho a seguir. Observe o caractere “|” entre os tipos de exceção.
try{
Integer i = Integer.parseInt(s);
}
catch(NumberFormatException | IllegalArgumentException e){
System.out.println("Entrada inválida para converter");
}
36
Referências Bibliográficas
[1] Rogers Cadenhead e Laura Lemay. Sams Teach Yourself Java 6 in 21 Days. Sams Publishing,
Indianapolis, 2007.
[2] H. M. Deitel e P. J. Deitel. JAVA Como Programar. Bookman, Porto Alegre, 3 edition, 2001.
[3] Jeanne Boyarsky e Scott Selikoff. OCA: Oracle Certified Associate Java SE 8 Programmer I. Study
Guyide. Exam 1Zo-808. John Wiley & Sons, Indianapolis, 2015.
[4] Mala Gupta. OCA Java SE 7 Programmer I Certification Guide: Prepare for 1ZO-803 Exam. Shelter
Island, Manning, 2013.
[5] Cay S. Horstmann and Garry Cornell. Core Java, volume I: fundamentos. Pearson Education, São
Paulo, 2011.
37
Apêndice A
Entrada e Saída em Java
Este apêndice não tem o objetivo de apresentar streams em Java, apenas mostrar de forma prática, como
se realizam entradas e saídas de programas por meio dos dispositivos padrão (teclado e monitor).
A.1
Entrada de dados via teclado
A entrada de dados via teclado pode ser feita utilizando a classe Scanner. No trecho de código a seguir
é mostrado como ler um inteiro e uma String em um programa:
public class EntradaSaida{
1
2
3
4
5
6
7
8
9
public static void main(String[] args) {
Scanner teclado = new Scanner(System.in);
int i = teclado.nextInt();
teclado.nextLine();
String nome = teclado.nextLine();
}
}
Na linha 4 um objeto java.util.Scanner é instanciado e atribuído à variável de referência
teclado. Na linha 5, um inteiro é lido do teclado e atribuído à variável i por meio do método nextInt().
Na linha 7, uma cadeia de caracteres (String) é lida do teclado e atribuída à variável nome por meio do
método nextLine().
Observe a linha 6 do programa que, aparentemente, não faz nada. Bom, ela é necessária sempre
que um nextLine() vier depois de nextInt(). Explicando a grosso modo, pode–se dizer que toda entrada
é uma cadeia de caracteres finalizada pelo delimitador (caractere de fim de linha). Acontece que o
método nextInt() “pega” o inteiro e deixa o delimitador no buffer. Esse delimitador “deixado para trás” é
consumido na linha 7. O que é feito na linha 6, na prática, é o descarte do delimitador.
Consulte a documentação se precisar saber mais sobre a classe Scanner.
A.2
Saída de dados via monitor
A saída de dados pode ser feita por meio do método print() ou println() do objeto out da classe System:
System.out.println(30);
System.out.println(idade);
System.out.println(nome);
1
2
3
38
System.out.println("Machado de Assis");
System.out.println("Nome: "+ nome);
System.out.println("Idade: " + idade +" anos.");
4
5
6
O método println() é sobrecarregado cerca de 10 vezes. Em (1 e 4) exibe constantes, em (2), o
conteúdo de uma variável inteira, em (3) o conteúdo de um objeto String. Em (5) e (6) há dois exemplos
de concatenação. Em Java, qualquer coisa concatenada à uma cadeia de caracteres passa a ser uma cadeia
de caracteres.
39
Apêndice B
Arrays em Java
Arrays são estruturas de dados capazes de armazenar uma lista de itens do mesmo tipo, sejam eles:
• Tipos primitivos de dados.
• Objetos da mesma classe.
• Objetos que tenham classe base comum.
Java implementa arrays de uma forma um pouco diferente de outras linguagens. Em Java eles são
tratados como objetos. Para criar um array em Java, devemos [1]:
• Declarar a variável para referenciar o array.
• Criar um objeto do tipo array e atribuí–lo à variável.
• Armazenar informação no array.
B.1
Arrays unidimensionais
Para declarar uma variável que referencia um array, devemos acrescentar um par de colchetes ([ ]) após
o tipo ou após o nome da variável:
int[] numeros;
int pontos[];
String[] nomes;
String cidades[];
Para criar o array, utiliza–se o operador new:
int[] pontos = new int[10]; // array de 10 posições
Ao se criar um array com o operador new, todas as posições serão automaticamente preenchidas com
um valor inicial padrão. Este valor é 0 (zero) para arrays de números, false para arrays de booleans e
null para arrays de objetos.
Como arrays de objetos são preenchidos com uma referência nula, é preciso atribuir referências
válidas antes de fazer uso deles. Por exemplo, um array de strings seria “preenchido” da seguinte forma:
String[]
nomes[0]
nomes[1]
nomes[2]
nomes
= new
= new
= new
= new String[3];
String();
String();
String();
A chamada a new String() cria uma String vazia. Arrays também podem ser criados e inicializados
ao mesmo tempo envolvendo os elementos em chaves, separados por vírgulas:
40
String[] nomes = {"Huguinho", "Luizinho", "Zezinho"};
Criar um array desta maneira faz com que ele tenha uma quantidade de elementos igual à quantidade de
elementos declarados entre as chaves.
A seguinte declaração e inicialização também é válida:
String[] nomes = new String[] {"Huguinho", "Luizinho", "Zezinho"};
No entanto, o código seguinte não compila:
String[] nomes = new String[3]{"Huguinho", "Luizinho", "Zezinho"};
B.2
Arrays multidimensionais
A quantidade de dimensões de um array é Java é dada pela quantidade de colchetes presentes na declaração. Os comandos seguintes declaram arrays de duas dimensões:
int[] arrayInteiros[];
int[] [] arrayInteiros;
int arrayInteiros[] [];
A alocação de memória para o array multidimensional é feita por meio do operador new:
arrayInteiros = new int[2][3];
Embora seja possível criar arrays de mais de duas dimensões, o mais comum é se trabalhar com
arrays de uma ou duas dimensões.
41
Apêndice C
Classes String e StringBuilder
Dados do tipo texto, em Java, são manipulados por objetos das classes String e StringBuilder.
Essas classes possuem características específicas, sendo que algumas delas serão analisadas neste apêndice.
C.1
Criando e manipulando Strings
A classe String é fundamental em qualquer programa. Nem mesmo o método main() pode ser escrito
sem se valer desta classe [3]. Uma string é basicamente uma sequência de caracteres, como por exemplo:
String texto = “Bazinga”;
No exemplo, String é o tipo, texto a variável de referência e Bazinga o objeto associado à variável.
Certo, e o operador new necessário para se instanciar objetos? Bem, no caso da classe String, o
operador new não é necessário. As formas a seguir são válidas:
String texto = “Bazinga”;
String texto = new String(“Bazinga”);
C.1.1
Concatenação
Concatenação é a operação de unir duas ou mais strings por meio do operador “+”. Este operador é
sobrecarregado, pois 1 + 2 resulta 3, no entanto, “1” + “2” resulta “12”. O operador “+” pode ser usado
de duas formas na mesma linha [3]:
1. Se ambos operandos são numéricos, “+” significa adição;
2. Se ao menos um operador é String, “+” significa concatenação;
3. A expressão é avaliada da esquerda para direita.
Alguns exemplos:
System.out.println(1 +
System.out.println("a"
System.out.println("a"
System.out.println(1 +
System.out.println("a"
2);
+ "b");
+ "b" + 3);
2 + "a");
+ 3 + 2 + "b");
42
//
//
//
//
//
3
ab
ab3
3a
a32b
C.1.2
Imutabilidade
Uma vez que um objeto String é criado, ele se torna imutável (ele não pode ser alterado). Não podemos
nem ao menos alterar uma simples letra do conteúdo. Considere o seguinte exemplo:
String texto = "um ";
texto = texto + "dois";
System.out.println(texto);
// um dois
Criamos uma string com o valor inicial “um ” e, em seguida, concatenamos com o texto “dois”.
Fazendo isso, alteramos o conteúdo da string, certo? Errado. O que acontece é que Java trabalha com o
conceito de string pool, para evitar strings duplicadas na memória durante a execução de um programa.
Assim, quando criamos uma string na forma:
String texto = “Bazinga”;
a JVM 1 verifica se já existe alguma string “Bazinga” no pool. Se existir, ela faz a variável referenciar
esta string. Se não existir, ela cria uma string “Bazinga” e faz a variável referenciar este novo objeto.
Voltando ao exemplo, quando declaramos:
String texto = “um ”;
a JVM cria um objeto string com o conteúdo “um ” (se não existir). Em seguida, quando fazemos:
texto = texto + “dois ”;
a JVM cria um objeto string com o conteúdo “dois” (se não existir), depois cria outro objeto com o
conteúdo “um dois” (se não existir), aí sim faz texto referenciar este terceiro objeto. Os dois objetos
anteriores permanecem vivos no pool.
É importante lembrar que a instrução:
String texto = new String(“texto”);
obriga a JVM a criar um novo objeto, existindo ou não outro objeto igual no pool.
C.1.3
Métodos da classe String
A classe String possui dezenas de métodos que não serão comentados aqui. Veja a documentação da
classe para conhecê–los. Vamos analisar apenas os métodos equals() e equalsIgnoreCase().
equals() e equalsIgnoreCase()
O operador de igualdade ( == ) não pode ser usado para comparar se duas strings são iguais. Ao invés
deste operador, utiliza–se o método equals(), que retorna verdadeiro se forem iguais, ou falso, caso
contrário. Exemplo:
String s1 = "1";
String s2 = "2";
if (s1.equals(s2)){
System.out.println("São iguais");
}
O método equalsIgnoreCase() é semelhante ao anterior, apenas ignora as diferenças entre maiúsculas
e minúsculas que podem existir.
1
Java Virtual Machine
43
C.2
StringBuilder
Ao contrário de objetos da classe String, objetos da classe StringBuilder são mutáveis. Isto quer
dizer que podemos alterar um objeto (concatenar, substituir caracteres) e o objeto continuará sendo o
mesmo objeto. Isto tem influência no desempenho do programa.
A forma de instanciar um objeto StringBuilder é bastante similar à instanciação de um objeto
String:
StringBuilder sb1 = new StringBuilder();
StringBuilder sb2 = new StringBuilder("Bazinga");
Para objetos StringBuilder não se usa o operador de atribuição ( = ). Ao invés disso, utiliza–se
o método append():
StringBuilder sb = new StringBuilder();
sb.append("Big");
sb.append("Bang";
System.out.println(sb); // Big Bang
Um objeto StringBuilder pode ser convertido em String por meio do método toString():
String str = sb.toString();
A decisão sobre usar String ou StringBuilder depende do contexto do programa.
44
Download