Buscar

apostilaLP2_V8_1

Faça como milhares de estudantes: teste grátis o Passei Direto

Esse e outros conteúdos desbloqueados

16 milhões de materiais de várias disciplinas

Impressão de materiais

Agora você pode testar o

Passei Direto grátis

Você também pode ser Premium ajudando estudantes

Faça como milhares de estudantes: teste grátis o Passei Direto

Esse e outros conteúdos desbloqueados

16 milhões de materiais de várias disciplinas

Impressão de materiais

Agora você pode testar o

Passei Direto grátis

Você também pode ser Premium ajudando estudantes

Faça como milhares de estudantes: teste grátis o Passei Direto

Esse e outros conteúdos desbloqueados

16 milhões de materiais de várias disciplinas

Impressão de materiais

Agora você pode testar o

Passei Direto grátis

Você também pode ser Premium ajudando estudantes
Você viu 3, do total de 36 páginas

Faça como milhares de estudantes: teste grátis o Passei Direto

Esse e outros conteúdos desbloqueados

16 milhões de materiais de várias disciplinas

Impressão de materiais

Agora você pode testar o

Passei Direto grátis

Você também pode ser Premium ajudando estudantes

Faça como milhares de estudantes: teste grátis o Passei Direto

Esse e outros conteúdos desbloqueados

16 milhões de materiais de várias disciplinas

Impressão de materiais

Agora você pode testar o

Passei Direto grátis

Você também pode ser Premium ajudando estudantes

Faça como milhares de estudantes: teste grátis o Passei Direto

Esse e outros conteúdos desbloqueados

16 milhões de materiais de várias disciplinas

Impressão de materiais

Agora você pode testar o

Passei Direto grátis

Você também pode ser Premium ajudando estudantes
Você viu 6, do total de 36 páginas

Faça como milhares de estudantes: teste grátis o Passei Direto

Esse e outros conteúdos desbloqueados

16 milhões de materiais de várias disciplinas

Impressão de materiais

Agora você pode testar o

Passei Direto grátis

Você também pode ser Premium ajudando estudantes

Faça como milhares de estudantes: teste grátis o Passei Direto

Esse e outros conteúdos desbloqueados

16 milhões de materiais de várias disciplinas

Impressão de materiais

Agora você pode testar o

Passei Direto grátis

Você também pode ser Premium ajudando estudantes

Faça como milhares de estudantes: teste grátis o Passei Direto

Esse e outros conteúdos desbloqueados

16 milhões de materiais de várias disciplinas

Impressão de materiais

Agora você pode testar o

Passei Direto grátis

Você também pode ser Premium ajudando estudantes
Você viu 9, do total de 36 páginas

Faça como milhares de estudantes: teste grátis o Passei Direto

Esse e outros conteúdos desbloqueados

16 milhões de materiais de várias disciplinas

Impressão de materiais

Agora você pode testar o

Passei Direto grátis

Você também pode ser Premium ajudando estudantes

Prévia do material em texto

INSTITUTO FEDERAL DE MATO GROSSO DO SUL
Tecnologia em Sistemas para Internet
Disciplina: Linguagem de Programação II
Prof.º Msc. Sidney Roberto de Sousa
Apostila – Linguagem de Programação II
NOTA: Para a boa interpretação do conteúdo desta apostila é altamente recomendável que o aluno tenha concluído a
leitura da apostila da disciplina Linguagem de Programação I, assim como a resolução de seus exercícios. Desta forma,
o professor presume tais fatos sobre o aluno, ou ao menos que este tenha domínio sobre os assuntos abordados
naquela apostila.
1 – Conteúdo
• O que são classes e objetos?
• Criando uma classe
◦ Criando atributos
◦ Criando métodos
• Instanciando objetos
• Matrizes de objetos
• Construtores
• Herança
• Métodos estáticos
• Classes com construtor privado
• Tratamento de exceções
• A palavra reservada finally
• Criando exceções
• Manipulação de arquivos
• Leitura em arquivos textuais
• Gravação em arquivos textuais
• Gravação e leitura em arquivos binários
2 – O que são classes e objetos
É bem possível que neste ponto de seus estudos você já tenha ouvido falar em 
“orientação a objetos”. Mas enfim, o que vem a ser orientação a objetos?
A orientação a objetos é um paradigma de análise, projeto e programação de 
sistemas que visa a organização de componentes de software como objetos que interagem 
entre si. É incorreto dizer que a orientação a objetos é apenas um paradigma de programação, 
pois como já enfatizado anteriormente ela também pode ser aplicada nas fases de análise e 
projeto, conforme descrito abaixo:
• Análise orientada a objetos: Tudo o que existe no mundo real pode ser 
classificado. Os filósofos antigos perceberam isto e criaram modelos como as 
ontologias para a classificação de entidades do mundo real. Por exemplo, analisando
um supermercado, podemos levantar classes como Cliente, Produto, Atendente de 
Caixa, entre outras. A classe Cliente possui instâncias (os objetos), isto é, os 
clientes que frequentam o mercado. Assim, os clientes do mercado podem ser 
classificados por meio da classe Cliente.
• Projeto orientado a objetos: Classes e objetos levantados na fase de análise 
podem ser modelados como componentes de software por meio de uma linguagem 
de modelagem orientada a objetos. A linguagem mais popular para tal finalidade é a
UML (Unified Modeling Language). A UML possui diagramas para a modelagem de 
classes e objetos, além de seus comportamentos e suas interações entre si.
• Programação orientada a objetos: A programação orientada a objetos é 
realizada utilizando-se uma linguagem orientada a objetos. No nosso caso, 
utilizaremos a linguagem Java para resolver problemas utilizando o paradigma de 
orientação a objetos.
Nesta disciplina, deixaremos (não totalmente) de lado as fases de análise e projeto e 
focaremos na programação orientada a objetos. Assim, espera-se que no final deste curso você
seja capaz de criar programas que utilizem o paradigma de orientação a objetos para resolver 
problemas diversos.
3 – Criando uma classe
Na linguagem Java, uma classe pode ser criada por meio da palavra reservada class. O
código abaixo cria uma classe Java chamada Cliente, situada no pacote br.edu.ifms.lp2:
package br.edu.ifms.lp2;
public class Cliente {
}
Note que o modificador public foi utilizado para definir que a classe Cliente é pública,
isto é, pode ser utilizada por qualquer outra classe, esteja esta no mesmo pacote que a classe
Cliente ou não. Os modificadores protected e private não são permitidos na definição de
classes. Apesar disso, a omissão do modificador public na definição de uma classe a torna
protegida, isto é, acessível apenas para as classes definidas dentro de seu pacote. Talvez você
esteja se perguntando: é possível criar uma classe privada? Falaremos sobre isto mais tarde.
Além do modificador public para definir o seu acesso, uma classe pode conter também
os modificadores final ou abstract. Também falaremos sobre isto em um momento posterior!
3.1 – Criando atributos
Os atributos de uma classe definem as características referentes aos objetos de uma
classe. Por exemplo, um cliente possui um nome e um endereço. Dependendo do contexto, ele
também pode possuir uma data de nascimento. Assim, podemos definir atributos para
especificar tais características do cliente. O código abaixo modifica a classe Cliente,
adicionando os atributos nome, endereco e dataNascimento:
package br.edu.ifms.lp2;
import java.util.Date;
public class Cliente {
private String nome;
private String endereco;
private Date dataNascimento;
}
Assim como os métodos, atributos também possuem modificadores de acesso. Se um
atributo é definido como public (público), qualquer outro trecho de código – seja este de sua
classe ou não - pode utilizá-lo. Se um atributo é definido como protected (protegido), apenas
os trechos de código escritos em sua classe, nas classes que residem no mesmo pacote de sua
classe ou que herdam a sua classe podem utilizá-lo. Por fim, se um atributo é definido como
private (privado), apenas os trechos de código escritos em sua própria classe podem
invocá-lo. A tabela abaixo resume estas informações. A omissão de um modificador de acesso
torna o atributo protegido.
Permissão de acesso Característica
public Atributo visível por qualquer outra classe
protected Atributo visível apenas por sua classe, pelas
classes filhas de sua classe e pelas classes do
mesmo pacote de sua classe
private Atributo visível apenas por sua classe
 No nosso exemplo, os três atributos foram definidos como privados. Isto é uma prática
comum na programação orientada a objetos, chamada de encapsulamento. Ao definir
atributos como privados, estamos definindo que eles representam dados que dizem respeito
apenas à própria classe; assim, eles não são visíveis diretamente a outras classes.
3.2 – Criando métodos
Como dito anteriormente, o encapsulamento de atributos serve para proteger dados
privados à classe. Este tipo de encapsulamento não é obrigatório, porém é uma prática bem
aceita na comunidade de desenvolvimento de software. Porém, é extremamente comum que
classes precisem consultar valores de atributos alheios entre si. Por exemplo, digamos que uma
classe necessite acessar os dados dos atributos da classe Cliente. Devido ao fato de que tais
atributos foram definidos como privados, eles não estarão acessíveis diretamente para a outra
classe. Desta forma, precisamos criar métodos para fornecer acesso para a consulta de tais
atributos. Além disso, é possível que a outra classe necessite modificar valores dos atributos
da classe Cliente a fim de realizar alguma operação. 
Métodos que oferecem a consulta a atributos de uma classe são chamados de getters.
Métodos que permitem a modificação de atributos são chamados de setters. A seguir, a classe
Cliente com métodos getters e setters para o atributo nome:
package br.edu.ifms.lp2;
import java.util.Date;
public class Cliente {
private String nome;
private String endereco;
private Date dataNascimento;
public String getNome() {
return nome;
}
public void setNome(String nome) {
this.nome = nome;
}
}
Agora, é possível que outras classes possam acessar e modificar o valor do atributo
nome. Observe as nomenclaturas dos métodos. É uma convenção que um método getter seja
nomeado com a palavra get seguida do nome do atributo ao qual o método se refere,
utilizando o padrão Camel Case. Analogamente, é uma convenção que um método setter seja
nomeado com a palavra set seguida do nome do atributo ao qual o método se refere, também
utilizando o padrão Camel Case. 
Observe agora o método setNome. Em sua única linha de código podemos ver o uso da
palavra reservada this:
this.nome = nome;
A palavra this (em português, “isto” ou “este”) é utilizada paracriar uma auto
referência, isto é, this representa a própria classe Cliente. No caso, this.nome referencia o
atributo nome da classe Cliente, enquanto que a variável nome (do outro lado da atribuição)
é um parâmetro de entrada do método setNome.
Obviamente, uma classe pode conter outros métodos que não sejam getters ou setters.
Por exemplo, vamos adicionar um método na classe Cliente para imprimir os dados do cliente:
package br.edu.ifms.lp2;
import java.util.Date;
public class Cliente {
private String nome;
private String endereco;
private Date dataNascimento;
public String getNome() {
return nome;
}
public void setNome(String nome) {
this.nome = nome;
}
public void imprimeDados() {
System.out.println("Nome: " + nome);
}
}
Para saber mais sobre métodos e suas características, por favor, leia as seções 14.1 e
14.2 da apostila da disciplina Linguagem de Programação I.
Exercício:
a) Estenda a classe Cliente, criando os métodos getter e setter para os atributos
endereco e dataNascimento.
4 – Instanciando objetos
Uma vez que a classe exista, é possível instanciar objetos dela. Instanciar um objeto
de uma classe significa criar uma cópia de tal classe, onde o conjunto de atributos do
objeto possuirá valores específicos a alguma necessidade e seu conjunto de métodos operarão
de acordo com algum comportamento específico. 
Como exemplo, imagine que desejamos criar uma classe para preencher dados de
clientes. Para tanto, utilizaremos a classe CadastroCliente, conforme o código abaixo:
package br.edu.ifms.lp2;
import java.util.Date;
public class CadastroCliente {
private Cliente cliente;
public Cliente getCliente() {
return cliente;
}
public void setCliente(String nome, String endereco, Date dataNascimento) {
cliente = new Cliente();
cliente.setNome(nome);
cliente.setEndereco(endereco);
cliente.setDataNascimento(dataNascimento);
}
}
Note que a classe CadastroCliente possui um único atributo, o qual é um objeto da
classe Cliente. No momento em que o atributo cliente é definido na classe CadastroCliente
ele apenas foi declarado e seu valor é null. Sua instanciação só ocorre de fato no instante em
que a primeira linha do método setCliente é executada. Nesta linha, o construtor da classe
Cliente é invocado para que seja criada uma cópia da classe e então esta cópia seja associada
ao objeto cliente. Falaremos mais sobre construtores em uma seção posterior.
Dentro do método setCliente o objeto cliente é instanciado e seus atributos são
preenchidos indiretamente por meio de seus métodos setter, com os valores contidos nos
parâmetros do método setCliente. Para mostrar as mudanças de valores no objeto cliente,
utilizaremos a classe CadastroCliente para acessar e manipular seu valor. Observe o código
da classe TesteCadastroCliente abaixo:
package br.edu.ifms.lp2;
import java.util.Date;
public class TesteCadastroCliente {
public static void main(String[] args) {
CadastroCliente cadastro = new CadastroCliente();
// Imprime: null
System.out.println(cadastro.getCliente());
cadastro.setCliente("Sidney Sousa", "Rua Cinco, Vila Ycarai", new Date());
// Imprime: br.edu.ifms.lp2.Cliente@5563d208 (pode variar)
System.out.println(cadastro.getCliente());
}
}
Dentro do método main, o valor do objeto cliente é impresso em dois momentos
distintos. Note que o acesso ao valor do objeto foi feito de forma indireta por meio de seu
método getter. No primeiro momento em que o valor do objeto cliente é impresso o objeto
ainda não havia sido instanciado. Assim, o valor null foi impresso, uma vez que de fato o valor
do objeto é nulo neste instante. 
Porém, no segundo momento em que o valor do objeto cliente é impresso o objeto já
havia sido instanciado - assim como os seus campos já haviam sido preenchidos. Toda vez que
a impressão do valor de um objeto (lembre-se: do objeto e não de um ou mais de seus
atributos) que já foi instanciado é solicitada, a máquina virtual do Java procura na classe do
objeto a existência do seguinte método:
public String toString()
Caso este método exista, o valor retornado por este método é impresso. Caso contrário,
o método toString da classe Object é invocado, o qual retorna o identificador do objeto. Isto
acontece pois toda classe criada em Java é filha da classe Object, a qual faz parte do núcleo
do Java.
Agora, observe o código modificado da classe TesteCadastroCliente abaixo:
package br.edu.ifms.lp2;
public class TesteCadastroCliente {
public static void main(String[] args) {
CadastroCliente cadastro = new CadastroCliente();
Cliente cliente = cadastro.getCliente();
// A linha abaixo vai causar uma exceção (erro) quando executada
System.out.println(cliente.getNome());
}
}
Como você já deve ter visto no comentário dentro do método main, a última linha de
código do método causa uma exceção. Mas por que isto ocorre? O fato é que no instante em
que o valor do objeto cliente é solicitado por meio de seu getter, ele ainda não havia sido
instanciado dentro da classe CadastroCliente; assim, o seu valor é null. Nenhum objeto cujo
valor seja null pode ter os seus atributos ou métodos acessados, pois isto executaria a exceção
NullPointerException (Exceção de Ponteiro Nulo). Desta forma, lembre-se sempre que para
que um objeto possa ter seus atributos e métodos acessíveis ele deve ser instanciado
anteriormente!
Exercícios:
a) Sobrecarregue o método setCliente da classe CadastroCliente de tal forma que ao
invés de receber os valores de nome, endereço e data de nascimento separadamente, ele
receba um outro objeto da classe Cliente cujos valores dos atributos devem ser copiados.
b) Inclua na classe CadastroCliente um método toString (conforme a assinatura
explicada nesta seção). Ele deve retornar uma string contendo todos os valores dos atributos
do objeto, formatados em uma única linha.
5 – Matrizes de objetos
Da mesma forma que podemos criar matrizes de tipos primitivos, também podemos
criar matrizes para armazenar objetos. A sintaxe para a criação de matrizes de objetos é
similar à sintaxe para a criação de matrizes de valores primitivos. Analogamente, podemos
percorrer uma matriz de objetos da mesma forma que percorremos uma matriz de valores
primitivos:
package br.edu.ifms.lp2;
import java.util.Calendar;
import java.util.Date;
public class ExemploMatrizObjetos {
public static void main(String[] args) {
Cliente[] vetorClientes = new Cliente[10];
for (Cliente cliente : vetorClientes) {
System.out.print(cliente + "\t");
}
System.out.println("\n");
Cliente[][] matrizBidimensionalClientes = new Cliente[10][10];
for (Cliente[] clientes : matrizBidimensionalClientes) {
for (Cliente cliente : clientes) {
System.out.print(cliente + "\t");
}
System.out.println();
}
System.out.println();
Calendar calendario = Calendar.getInstance();
calendario.set(1985, 7, 22);
Date dataNascimento = calendario.getTime();
Cliente cliente1 = new Cliente();
cliente1.setDataNascimento(dataNascimento);
cliente1.setEndereco("Rua das Oliveiras, 22, Vila Perdizes");
cliente1.setNome("Joana Andrade Dutra");
Cliente cliente2 = new Cliente();
calendario.set(1976, 3, 7);
dataNascimento = calendario.getTime();
cliente2.setDataNascimento(dataNascimento);
cliente2.setEndereco("Av. das Bandeiras, 1542, Centro");
cliente2.setNome("Ubiratan Vieira");
vetorClientes = new Cliente[2];
vetorClientes[0] = cliente1;
vetorClientes[1] = cliente2;
for (Cliente cliente : vetorClientes) {
System.out.print(cliente.getNome() + "\t");
}
Cliente[] vetorClientes2 = { cliente1, cliente2 };
for (Cliente cliente : vetorClientes2) {
System.out.print(cliente.getNome() + "\t");
}
}
}
Exercícios:
a) Escreva um programa que leia os dados de dez clientes, armazenando estes dados
emuma matriz unidimensional do tipo Cliente. Após isto, o programa deve informar quantos
clientes possuem nome iniciado com vogal.
b) Escreva um programa que leia os dados de dez clientes, armazenando estes dados
em uma matriz unidimensional do tipo Cliente. Apos isto, o programa deve informar quantos
clientes são maiores de idade.
c) Escreva um programa que leia um valor inteiro n e então leia uma matriz
bidimensional n x n do tipo Cliente. Após isto, o programa deve exibir a matriz formatada de
clientes e a matriz transposta formatada de clientes (exibir apenas o nome de cada cliente na
matriz).
d) (*) Escreva um programa que leia um valor inteiro n e então leia uma matriz
unidimensional de clientes de tamanho n. Após isto, o programa deve informar qual é o
número máximo de pessoas que possuem endereço em comum (ignorar casos quando realizar
a verificação), além dos nomes e datas de nascimento de tais pessoas.
6 – Construtores
Um construtor é um método cuja função é instanciar e inicializar o objeto. A
implementação do construtor não é obrigatória. Por exemplo, a implementação da classe
Cliente exibida anteriormente não possui um construtor. Mesmo assim, nós utilizamos o
construtor da classe Cliente quando instanciamos seus objetos:
Cliente cliente = new Cliente();
Neste caso, o objeto é instanciado e é alocado um espaço de memória para ele. Porém,
os seus atributos são inicializados com valores default. Atributos de tipos primitivos são
inicializados com os valores default de seus respectivos tipos. Por exemplo, atributos do tipo
int, short e long são inicializados com o valor 0, enquanto que atributos do tipo float e
double são inicializados com o valor 0.0. Atributos de tipos complexos, ou seja, atributos que
são instâncias de objetos são inicializados com o valor null, como por exemplo atributos do
tipo String e Date.
Uma boa utilidade para um construtor é incluir dentro dele a inicialização dos atributos
do objeto. Desta forma, podemos garantir que os atributos possuam valores default mais
apropriados. No exemplo a seguir, incluímos um construtor à classe Cliente para inicializar seus
atributos:
package br.edu.ifms.lp2;
import java.util.Calendar;
import java.util.Date;
public class Cliente {
private String nome;
private String endereco;
private Date dataNascimento;
public Cliente() {
nome = "NOME NÃO INFORMADO";
endereco = "ENDEREÇO NÃO INFORMADO";
Calendar calendario = Calendar.getInstance();
calendario.set(1900, 0, 1);
dataNascimento = calendario.getTime();
}
public String getNome() {
return nome;
}
public void setNome(String nome) {
this.nome = nome;
}
public String getEndereco() {
return endereco;
}
public void setEndereco(String endereco) {
this.endereco = endereco;
}
public Date getDataNascimento() {
return dataNascimento;
}
public void setDataNascimento(Date dataNascimento) {
this.dataNascimento = dataNascimento;
}
public void imprimeDados() {
System.out.println("Nome: " + nome);
}
}
Agora, toda vez que instanciarmos um objeto da classe Cliente, automaticamente seus
atributos serão inicializados conforme definido no construtor da classe. A classe
TesteConstrutor exibe o novo construtor da classe Cliente em uso:
package br.edu.ifms.lp2;
public class TesteConstrutor {
public static void main(String[] args) {
Cliente cliente = new Cliente();
System.out.println("Nome: " + cliente.getNome());
System.out.println("Endereco: " + cliente.getEndereco());
System.out.println("Data de nascimento: " + cliente.getDataNascimento());
}
}
Observe que diferentemente dos outros métodos, o construtor não possui valor de
retorno. Normalmente, construtores são definidos com acesso público. Definir um construtor
com acesso privado faz com que a classe não possa ter objetos, isto é, não possa ser
instanciada.
Construtores também são utilizados para simplificar a inicialização de atributos de
forma programática. Por exemplo, podemos criar um construtor que receba como parâmetros
os valores a serem definidos nos atributos da classe. Para ilustrar este exemplo, incluiremos
um construtor deste tipo na classe Cliente:
package br.edu.ifms.lp2;
import java.util.Calendar;
import java.util.Date;
public class Cliente {
private String nome;
private String endereco;
private Date dataNascimento;
public Cliente() {
nome = "NOME NÃO INFORMADO";
endereco = "ENDEREÇO NÃO INFORMADO";
Calendar calendario = Calendar.getInstance();
calendario.set(1900, 0, 1);
dataNascimento = calendario.getTime();
}
public Cliente(String nome, String endereco, Date dataNascimento) {
this.nome = nome;
this.endereco = endereco;
this.dataNascimento = dataNascimento;
}
public String getNome() {
return nome;
}
public void setNome(String nome) {
this.nome = nome;
}
public String getEndereco() {
return endereco;
}
public void setEndereco(String endereco) {
this.endereco = endereco;
}
public Date getDataNascimento() {
return dataNascimento;
}
public void setDataNascimento(Date dataNascimento) {
this.dataNascimento = dataNascimento;
}
public void imprimeDados() {
System.out.println("Nome: " + nome);
}
}
Note que agora a classe possui dois construtores. Desta forma, no momento de criar um
novo objeto da classe Cliente devemos escolher qual o construtor a ser utilizado para
instanciar o objeto:
Cliente cliente = new Cliente();
System.out.println("Nome: " + cliente.getNome());
System.out.println("Endereco: " + cliente.getEndereco());
System.out.println("Data de nascimento: " + cliente.getDataNascimento());
Calendar calendario = Calendar.getInstance();
calendario.set(1990, 10, 15);
Date dataNascimento = calendario.getTime();
Cliente outroCliente = new Cliente("Ana Maria Cardoso", 
 "Rua 7 de Setembro, 22", 
 dataNascimento);
System.out.println("Nome: " + outroCliente.getNome());
System.out.println("Endereco: " + outroCliente.getEndereco());
System.out.println("Data de nascimento: " + outroCliente.getDataNascimento());
Um fato deve ser esclarecido neste ponto. Se o primeiro construtor da classe Cliente (o
que não recebe parâmetros) for removido, não será mais possível instanciar novos objetos
utilizando o construtor sem parâmetros, como abaixo:
Cliente cliente = new Cliente();
Isto se deve ao fato de que mesmo removendo o primeiro construtor, a classe ainda
possui um construtor definido de forma explícita. Assim, toda vez que um novo objeto for
criado ele deverá ser instanciado obrigatoriamente utilizando-se o construtor com parâmetros.
Caso a classe não possua nenhum construtor, então é possível utilizar a instanciação padrão,
isto é, utilizando-se o construtor implícito sem parâmetros.
Exercícios:
a) Escreva uma classe que modele um retângulo e que possua atributos para armazenar
os dados de sua largura e altura, além dos getters e setters de tais atributos. A classe deve
possuir um construtor padrão, o qual deve inicializar os atributos de largura e altura com o
valor zero (0) e também um construtor que receba como parâmetros os valores a serem
definidos para a altura e a largura do retângulo. Por fim, a classe deve conter um método que
ofereça o cálculo da área do retângulo.
b) Escreva uma classe que modele um triângulo retângulo e que possua atributos para
armazenar os dados de sua base e altura, além dos getters e setters de tais atributos. A classe
deve possuir um construtor padrão, o qual deve inicializar os atributos de base e altura com o
valor zero (0) e também um construtor que receba como parâmetros os valores a serem
definidos para a base e a altura do triângulo retângulo. Por fim, a classe deve conter um
método que ofereça o cálculo daárea do triângulo retângulo.
c) Escreva um programa que ofereça um menu ao usuário no qual seja possível para
este escolher se ele deseja calcular a área de um retângulo ou de um triângulo retângulo. O
programa deve utilizar as classes implementadas nos exercícios a e b em sua implementação.
O programa deve conter métodos para a leitura dos dados de entrada para os cálculos.
7 – Herança
Na orientação a objetos é possível escrever uma classe que reaproveite atributos e
métodos de uma outra classe, estendendo assim esta primeira classe a fim de adicionar
atributos ou métodos mais específicos. Este conceito é chamado de herança. 
Por exemplo, imagine que desejamos estender a classe Cliente de tal forma a definir
uma segunda classe para modelar um cliente na categoria pessoa física. Imagine que
desejamos também definir uma terceira classe, desta vez para modelar um cliente na
categoria pessoa jurídica. Vamos chamar estas classes de ClientePessoaFisica e
ClientePessoaJuridica.
Bem, neste ponto vale a pena fazer uma análise a fim de levantar quais devem ser os
atributos destas classes. Vamos começar pelo cliente pessoa física. Um cliente desta categoria
possui diversas características. Porém, para fins de simplicidade da análise, diremos que este
tipo de cliente possui um nome, um sexo, um número de CPF, um número de RG, um
endereço, uma data de nascimento e um código de cliente. Da mesma forma, o cliente
pessoa jurídica também possui diversas características. Porém, para o nosso exemplo, um
cliente deste tipo possui um nome fantasia, uma razão social, um número de CNPJ, um
endereço e um código de cliente.
Se observarmos as listas de atributos que cada classe deve possuir, podemos perceber
que as duas classes possuem atributos em comum. No caso, nome, endereço e código de
cliente. Desta forma, seria redundante incluir estes três campos nas definições das duas
classes. Assim, iremos deixar estes campos inclusos na classe Cliente. Abaixo, a nova
definição da classe Cliente:
package br.edu.ifms.lp2;
public class Cliente {
private String nome;
private String endereco;
private String codigoCliente;
public String getNome() {
return nome;
}
public void setNome(String nome) {
this.nome = nome;
}
public String getEndereco() {
return endereco;
}
public void setEndereco(String endereco) {
this.endereco = endereco;
}
public String getCodigoCliente() {
return codigoCliente;
}
public void setCodigoCliente(String codigoCliente) {
this.codigoCliente = codigoCliente;
}
}
Uma vez que a classe Cliente foi definida conforme as novas especificações, podemos
criar as classes ClientePessoaFisica e ClientePessoaJuridica. A palavra reservada extends
da linguagem Java deve ser utilizada nestas classes a fim de definir que elas são
especializações da classe Cliente. Isto significa que elas herdam as características originais
da classe Cliente, porém estendendo o modelo da classe e adicionando novas características
(atributos e métodos) inerentes a cada modelo. A seguir, as definições das duas classes:
package br.edu.ifms.lp2;
import java.util.Date;
public class ClientePessoaFisica extends Cliente {
// F (feminino) ou M (masculino)
private char sexo;
private String cpf;
private String rg;
private Date dataNascimento;
public char getSexo() {
return sexo;
}
public void setSexo(char sexo) {
this.sexo = sexo;
}
public String getCpf() {
return cpf;
}
public void setCpf(String cpf) {
this.cpf = cpf;
}
public String getRg() {
return rg;
}
public void setRg(String rg) {
this.rg = rg;
}
public Date getDataNascimento() {
return dataNascimento;
}
public void setDataNascimento(Date dataNascimento) {
this.dataNascimento = dataNascimento;
}
}
package br.edu.ifms.lp2;
public class ClientePessoaJuridica extends Cliente {
private String razaoSocial;
private String cnpj;
public String getRazaoSocial() {
return razaoSocial;
}
public void setRazaoSocial(String razaoSocial) {
this.razaoSocial = razaoSocial;
}
public String getCnpj() {
return cnpj;
}
public void setCnpj(String cnpj) {
this.cnpj = cnpj;
}
}
Desta forma, todo objeto da classe ClientePessoaFisica possuirá os atributos nome,
endereco, codigoCliente, sexo, cpf, rg e dataNascimento, todos acessíveis por meio de
seus getters e setters. Por sua vez, todo objeto da classe ClientePessoaJuridica possuirá os
atributos nome, endereco, codigoCliente, razaoSocial e cnpj, também todos acessíveis por
meio de seus getters e setters.
package br.edu.ifms.lp2;
import java.util.Calendar;
import java.util.Date;
public class TesteClientes {
public static void main(String[] args) {
ClientePessoaFisica clientePF = new ClientePessoaFisica();
clientePF.setCodigoCliente("19320716");
clientePF.setCpf("18516967506");
Calendar calendario = Calendar.getInstance();
calendario.set(1982, 2, 21);
Date dataNascimento = calendario.getTime();
clientePF.setDataNascimento(dataNascimento);
clientePF.setEndereco("Av. Rebouças, Centro, 2053");
clientePF.setNome("Ana Maria Velasquez");
clientePF.setRg("911225341");
clientePF.setSexo('F');
ClientePessoaJuridica clientePJ = new ClientePessoaJuridica();
clientePJ.setCnpj("78671357000199");
clientePJ.setCodigoCliente("00183837");
clientePJ.setEndereco("Av. das Indústrias, Vila Autonomista, 23");
clientePJ.setNome("Atacado dos Tecidos");
clientePJ.setRazaoSocial("Grupo Sousa & Oliveira");
}
}
Quando utilizamos o conceito de herança em Java, devemos nos atentar a algumas
particularidades referentes a construtores. Se um construtor sem parâmetros for definido na
classe mãe, não necessariamente devemos implementar (definir explicitamente) construtores
nas classes filhas. Porém, se um construtor com parâmetros for definido na classe mãe,
devemos obrigatoriamente definir um construtor na classe filha de tal forma que este
construtor invoque o construtor da classe mãe. A questão é: como invocar o construtor da
classe mãe sem definir um objeto. Para tanto, é necessário utilizar a palavra reservada super
da linguagem Java. Considere o exemplo a seguir: criaremos um construtor com parâmetros na
classe Cliente, a fim de inicializar os atributos da classe:
public Cliente(String nome, String endereco, String codigoCliente) {
this.nome = nome;
this.endereco = endereco;
this.codigoCliente = codigoCliente;
}
Uma vez que este construtor foi definido, devemos obrigatoriamente implementar
construtores nas classes ClientePessoaFisica e ClientePessoaJuridica que invoquem o
construtor da classe Cliente. Desta forma, quando um objeto da classe ClientePessoaFisica
ou da classe ClientePessoaJuridica for instanciado, tanto o construtor da classe referente
quanto o construtor da classe Cliente serão invocados. A seguir, os construtores das classes
ClientePessoaFisica e ClientePessoaJuridica:
public ClientePessoaFisica(String nome, String endereco, String codigoCliente) {
super(nome, endereco, codigoCliente);
}
public ClientePessoaJuridica(String nome, String endereco, String codigoCliente) {
super(nome, endereco, codigoCliente);
}
A invocação do construtor da classe mãe por meio da palavra reservada super deve ser
realizada obrigatoriamente na primeira linha do construtor da classe filha.
Alternativamente, o construtor da classe filha pode conter mais parâmetros ou mesmo mais
linhas de código após a invocação do construtor da classe mãe. Para entender tal fato,
considere as seguintes versões alternativas dos construtores das classes ClientePessoaFisica
e ClientePessoaJuridica:
public ClientePessoaFisica(String nome, String endereco, String codigoCliente, 
 char sexo, String cpf, String rg, Date dataNascimento){
super(nome, endereco, codigoCliente);
this.sexo = sexo;
this.cpf = cpf;
this.rg = rg;
this.dataNascimento = dataNascimento;
}
public ClientePessoaJuridica(String nome, String endereco,
String codigoCliente, String razaoSocial, String cnpj) {
super(nome, endereco, codigoCliente);
this.razaoSocial = razaoSocial;
this.cnpj = cnpj;
}
Vale a pena salientar que caso a classe mãe não possua um construtor implementado 
explicitamente, podemos construir construtores específicos nas classes filhas naturalmente. 
Assim, a implementação de um construtor na classe filha não demanda a implementação de 
um construtor na classe mãe.
Exercícios:
a) Reescreva os setters e os construtores das classes Cliente, ClientePessoaFisica e
ClientePessoaJuridica a fim de validar os atributos necessários.
b) Implemente a classe ContaBancaria, a qual deve conter os seguintes
atributos/métodos:
• Atributos:
◦ cliente
◦ numeroConta
◦ saldo
• Métodos:
◦ sacar → deve receber o valor a ser sacado (o saldo não pode ficar negativo!)
e retornar o novo saldo da conta
◦ depositar → deve receber o valor a ser depositado e retornar o novo saldo da
conta
Após, implemente as classes ContaPoupanca e ContaCorrente, as quais devem
herdar a classe ContaBancaria. As classes devem conter os seguintes atributos/métodos:
• ContaPoupanca:
◦ Atributo: diaDeRendimento
◦ Método: calcularNovoSaldo → recebe a taxa de rendimento da poupança e
atualiza o saldo, caso o dia atual seja o dia de rendimento
• ContaCorrente:
◦ Atributo: limite
◦ Método: sacar1 → recebe o valor a ser sacado; deve permitir saldo negativo
até o valor do limite
1 Ao implementar este método, você está realizando uma sobrecarga. A sobrecarga de métodos ocorrente em caso de
herança será explicada pelo professor em sala.
8 – Métodos estáticos
Quando um método é assinado como estático ele pode ser acessado sem a necessidade
de se instanciar um objeto. Como exemplo, considere a classe FuncoesMatematicas abaixo:
package br.edu.ifms.lp2;
public class FuncoesMatematicas {
public static double delta(double a, double b, double c) {
return Math.pow(b, 2) - 4 * a * c;
}
public static int fatorial(int n) {
int resultado = 1;
for (int i = 2; i <= n; i++) {
resultado *= i;
}
return resultado;
}
public static void main(String[] args) {
System.out.println(fatorial(3));
}
}
No exemplo, a classe possui dois métodos estáticos que implementam funções
matemáticas. Observe que o método fatorial é invocado dentro do método main sem a
necessidade de se instanciar um objeto da classe FuncoesMatematicas para acessá-lo.
Opcionalmente, o método pode ser acessado por meio de um objeto, caso haja necessidade.
Porém, vale a pena ressaltar que isto não é uma boa prática de programação.
Quando um método estático é invocado dentro de outro método da mesma classe
(como no exemplo), tal invocação é realizada de forma simples, ou seja, apenas informando o
nome do método. Porém, quando um método estático é invocado dentro de um método que
pertence a outra classe, não podemos realizar a invocação simplesmente informando o nome
do método pois, quando realizamos este tipo de invocação, o compilador supõe que o método
pertence à mesma classe do método dentro do qual a linha de código de invocação foi escrita.
Desta forma, a invocação de um método estático dentro de um método de outra classe deve
ser realizada informando-se o nome da classe a qual o método estático pertence, seguida de
um ponto (.) e do nome do método, como no exemplo abaixo:
package br.edu.ifms.lp2;
public class ChamadaMetodoEstatico {
public static void main(String[] args) {
double resultado = FuncoesMatematicas.delta(4, 5, 6);
System.out.println(resultado);
}
}
Vale a pena ressaltar que métodos estáticos estão sujeitos às mesmas regras de
permissão de acesso que os métodos não estáticos. Isto significa que, por exemplo, o método
delta não poderia ser acessado pela classe acima caso ele fosse definido como privado. 
Os métodos estáticos possuem uma limitação importante. Um método estático não
pode utilizar atributos ou métodos de sua classe que não sejam estáticos. Isto se deve ao fato
de que todos os métodos e atributos não estáticos são assim definidos para que sejam
obrigatoriamente utilizáveis por meio da instanciação de objetos. Assim, se um método
estático pudesse acessá-los haveria uma violação de acesso, uma vez que os métodos
estáticos podem ser acessados sem a necessidade da instanciação de um objeto.
Desta forma, definir um método como estático ou não é uma questão de design que
exige uma análise atenciosa. Porém, a regra geral é: defina como não estático todo método
que seja influenciado pelo comportamento do objeto; caso o método não necessite ser
influenciado pelo comportamento de um objeto específico, então o defina como estático. Por
exemplo, considere a classe abaixo:
package br.edu.ifms.lp2;
public class Retangulo {
private double base;
private double altura;
public Retangulo(double base, double altura) {
this.base = base;
this.altura = altura;
}
public double calculaArea() {
return base * altura;
}
public double calculaArea(double base, double altura) {
return base * altura;
}
// Getters e setters...
}
A classe Retangulo possui uma sobrecarga no método calculaArea. Se observarmos
as duas implementações do método, podemos perceber que na primeira o cálculo da área é
realizado utilizando-se os valores da base e altura definidos no objeto, enquanto que na
segunda os valores são informados via parâmetros de entrada. Podemos perceber então que
na segunda implementação o cálculo da área não depende do comportamento de um
objeto específico, pois os valores da base e altura são informados via parâmetros. Assim, a
segunda implementação do método calculaArea poderia ser implementado como um método
estático:
package br.edu.ifms.lp2;
public class Retangulo {
private double base;
private double altura;
public Retangulo(double base, double altura) {
this.base = base;
this.altura = altura;
}
public double calculaArea() {
return base * altura;
}
public static double calculaArea(double base, double altura) {
return base * altura;
}
// Getters e setters...
}
A linguagem Java também permite a criação de atributos estáticos. As regras de
invocação de atributos estáticos – assim como as de permissão de acesso – são as mesmas às
dos métodos estáticos. Existem alguns casos em que criar atributos estáticos pode ser um
artifício útil. Uma das maiores aplicações para atributos estáticos é a definição de constantes
de uma classe específica que possam ser úteis em outras classes. Por exemplo, podemos
incluir na classe FuncoesMatematicas algumas contantes matemáticas para que possam ser
utilizadas por outras classes que as necessitarem:
package br.edu.ifms.lp2;
public class FuncoesMatematicas {
public static double PI = 3.141592653589793;
public static double NUMERO_DE_EULER = 2.718281828459045235360287;
public static double NUMERO_DE_OURO = 1.61803398874989484820458683436563811;
public static double delta(double a, double b, double c) {
return Math.pow(b, 2) - 4 * a * c;
}
public static int fatorial(int n) {
int resultado = 1;
for (int i = 2; i <= n; i++) {
resultado *= i;
}
return resultado;
}
}
Como você já deve possivelmente ter observado há algum tempo, o método main é um
método estático. Porém, por ser reservado para bootstrapping (inicialização) do programa Java,
ele não pode ser invocado dentro de outros métodos. Existem mais alguns outros métodos e
atributos estáticos “populares” dentro da JDK:
• O atributo out da classe System;
• O método pow da classe Math;
• O método parseIntda classe Integer;
• O método parseDouble da classe Double;
• O método parseFloat da classe Float;
• O método createInstance da classe Calendar;
• … dentre muitos outros.
9 - Classes com construtor privado
Um outro artifício possível na linguagem Java é definir o construtor default de uma
classe como privado a fim de bloquear a instanciação de objetos desta classe. A questão é:
para que (ou por quê) fazer isto?
Na verdade, existem algumas situações em que se torna necessário impedir o acesso
aos métodos (ou atributos) de uma classe por meio de objetos. Por exemplo, se uma classe
possuir apenas métodos estáticos, não existe a necessidade de se instanciar objetos desta
classe a fim de utilizar tais métodos. De fato, esta prática deve ser evitada – como já dito
anteriormente. Assim, a solução para este caso seria definir o construtor desta classe como
privado. Tal ação bloqueia a instanciação de objetos desta classe, dado que uma vez que um
construtor tenha sido criado explicitamente, a máquina virtual do Java não criará um construtor
(público) default para a classe e, como o construtor explícito foi definido como privado,
qualquer outro método fora desta classe não possui acesso a ele.
A fim de ilustrar o uso de construtores privados, poderíamos definir um construtor deste
tipo na classe FuncoesMatematicas para evitar que um objeto desta classe seja instanciado.
Ela é uma boa candidata a um construtor privado, visto que todos os seus métodos e atributos
são estáticos.
package br.edu.ifms.lp2;
public class FuncoesMatematicas {
public static double PI = 3.141592653589793;
public static double NUMERO_DE_EULER = 2.718281828459045235360287;
public static double NUMERO_DE_OURO = 1.61803398874989484820458683436563811;
private FuncoesMatematicas() {
}
public static double delta(double a, double b, double c) {
return Math.pow(b, 2) - 4 * a * c;
}
public static int fatorial(int n) {
int resultado = 1;
for (int i = 2; i <= n; i++) {
resultado *= i;
}
return resultado;
}
}
O uso de construtores privados deve ser reservado a casos específicos. Vale ressaltar
que ao se definir o construtor de uma classe como privado, o acesso aos seus métodos e
atributos públicos não estáticos ficará restrito às classes que a herdarem, não podendo ser
acessados por objetos nem da classe mãe ou das classes filhas. Quando uma classe herda uma
classe cujo construtor é privado, tal classe filha deve implementar explicitamente um
construtor caso se deseje permitir a instanciação de seus objetos.
Alguns desenvolvedores consideram a prática de definir o construtor de uma classe
como privado a fim de se evitar a instanciação de seus objetos como uma solução “não
totalmente” eficaz. Isto se deve ao fato de que existe um caso em que, mesmo com o seu
construtor definido como privado, é possível instanciar objetos de uma determinada classe. Tal
brecha ocorre no método main da classe. Como o método main faz parte da classe, então ele
possui total acesso a todos os métodos de tal classe2. Inclusive ao seu construtor,
independente ao fato de que este seja privado. 
Uma alternativa mais eficaz para evitar a instanciação de objetos de uma determinada
classe é defini-la como abstrata. Classes abstratas serão discutidas futuramente.
Exercícios:
a) Implemente a classe LeituraDados. Ela deve conter os seguintes métodos estáticos
para a leitura de dados de tipos distintos:
• lerInteiro(String mensagemUsuario)
• lerFloat(String mensagemUsuario)
• lerDouble(String mensagemUsuario)
• lerChar(String mensagemUsuario)
• lerString(String mensagemUsuario)
2 Lembrando que métodos não estáticos são acessíveis neste ponto por meio de objetos (instâncias) da classe, 
enquanto que métodos estáticos são acessíveis diretamente.
10 - Tratamento de exceções
Existem alguns tipos de operações em Java que podem ocasionar erros inesperados
pelo programador. Tais erros não são sintáticos, ou seja, erros de sintaxe da linguagem Java.
Assim, não são identificados no momento da compilação do código. De fato, tais erros são
semânticos, isto é, comportamentos inesperados ao programa. Em Java, erros inesperados
que causam a interrupção abrupta do programa são chamados de exceções.
Um tipo de operação que é extremamente passível a exceções é o acesso a dispositivos
secundários, como memórias ou dispositivos de I/O. Como um primeiro exemplo, considere a
leitura de dados do usuário. Neste tipo de leitura, o usuário informa os seus dados por meio da
entrada padrão do sistema – comumente, o teclado. Vamos analisar a leitura de um número
inteiro do usuário utilizando a classe Scanner do pacote java.util:
package br.edu.ifms.lp2;
import java.util.Scanner;
public class LeituraInteiro {
public static void main(String[] args) {
System.out.println("Digite um numero inteiro:");
Scanner leitor = new Scanner(System.in);
int numero = leitor.nextInt();
System.out.println("Numero informado: " + numero);
}
}
Ao se desenvolver um programa, é esperado um comportamento específico de seu
usuário. No exemplo acima, é esperado que o usuário digite um número inteiro no teclado e ao
final digite a tecla ENTER a fim de finalizar a leitura. Porém, caso o usuário digite um valor não
inteiro (por exemplo, um texto), o programa será interrompido abruptamente e a máquina
virtual do Java irá informar o seguinte na tela:
Exception in thread "main" java.util.InputMismatchException
at java.util.Scanner.throwFor(Scanner.java:909)
at java.util.Scanner.next(Scanner.java:1530)
at java.util.Scanner.nextInt(Scanner.java:2160)
at java.util.Scanner.nextInt(Scanner.java:2119)
at br.edu.ifms.lp2.LeituraInteiro.main(LeituraInteiro.java:10)
O texto informado acima representa um stack trace de erro, ou seja, um “caminho” de
invocações de métodos desde a origem da exceção até o código que iniciou todo o processo.
No exemplo acima, a exceção foi apontada na linha 909 da classe Scanner, a qual fica dentro
do método throwFor. Analisando o restante do stack trace, podemos verificar que o método
throwFor foi invocado na linha 1530 da classe Scanner, exatamente dentro do método next.
Por sua vez, o método next foi invocado na linha 2160, pelo método nextInt. Continuando a
análise, podemos verificar que o método nextInt foi invocado na linha 2119, por outra versão
do método nextInt (uma sobrecarga). Por fim, o método nextInt foi invocado na linha 10 do
método main da nossa classe do último exemplo. Assim, pudemos rastrear a exceção causada
desde a linha de código que iniciou todo o processo até a sua linha causadora.
Além disso, o stack trace nos fornece outra informação importante: qual o tipo de
exceção causada. No nosso exemplo, a exceção causada chama-se
InputMismatchException. Note que a exceção é na verdade uma classe, situada no pacote
java.util. De fato, toda exceção em Java é uma classe. Por convenção, o nome de toda classe
que representa uma exceção deve possuir o sufixo “Exception”.
Uma vez identificada a exceção causada, devemos tentar entender o que ela
representa. Se olharmos a documentação da exceção InputMismatchException, iremos
encontrar a seguinte definição:
“Thrown by a Scanner to indicate that the token retrieved does not match the pattern for the expected
type, or that the token is out of range for the expected type.”
Ou seja, a exceção ocorre quando a informação lida do usuário não é compatível com o
tipo de dados desejado ou o valor lido está fora dos limites de tal tipo de dados. No caso,
quando o usuário digita um texto e espera-se que ele digite um valor inteiro, a exceção é
lançada graças a esta incompatibilidade de tipos. 
Agora que sabemos qual exceção foi lançada e o motivo pelo qual ela foi lançada,
devemos tratá-la. “Tratar” uma exceção significa preparar o código passível a ela de tal formaque, caso a exceção ocorra, ela possa ser capturada e assim alguma tratativa possa ser
aplicada, evitando assim o encerramento abrupto do programa. Abaixo, a classe
LeituraInteiro com a tratativa à exceção:
package br.edu.ifms.lp2;
import java.util.InputMismatchException;
import java.util.Scanner;
public class LeituraInteiro {
public static void main(String[] args) {
System.out.println("Digite um numero inteiro:");
Scanner leitor = new Scanner(System.in);
try {
int numero = leitor.nextInt();
System.out.println("Numero informado: " + numero);
} catch (InputMismatchException e) {
System.out.println("O valor informado nao e inteiro!");
}
}
}
Em Java, uma exceção é capturada por meio do uso das palavras reservadas try e
catch. Observe o exemplo acima. A linha que pode vir a causar a exceção
InputMismatchException foi colocada dentro do bloco try. Com isto, deseja-se informar à
maquina virtual do Java: “Tente executar este trecho de código”. Imediatamente após o bloco
try foi colocado um bloco catch, passando como parâmetro um objeto da exceção
InputMismatchException. Com este bloco, deseja-se informar à máquina virtual do Java:
“Caso o trecho de código do bloco try lance a exceção InputMismatchException, capture
esta exceção, coloque todas as informações a seu respeito dentro do objeto e e execute o
seguinte trecho de código”. No caso, se a exceção for lançada, a mensagem “O valor
informado nao e inteiro” será impressa.
Podemos reescrever a classe LeituraInteiro de tal forma a permitir que o usuário
possa digitar novamente o número caso o valor anterior não seja inteiro. Abaixo, a nova versão
da classe, a qual utiliza um laço de repetição para forçar uma nova leitura caso o valor lido não
seja válido:
package br.edu.ifms.lp2;
import java.util.InputMismatchException;
import java.util.Scanner;
public class LeituraInteiro {
public static void main(String[] args) {
System.out.println("Digite um numero inteiro:");
boolean numeroOk = false;
do {
Scanner leitor = new Scanner(System.in);
try {
int numero = leitor.nextInt();
System.out.println("Numero informado: " + numero);
numeroOk = true;
} catch (InputMismatchException e) {
System.out.println("O valor informado nao e inteiro!");
System.out.println("Digite novamente o numero:");
}
} while (!numeroOk);
}
}
Para tratar uma exceção, não necessariamente é necessário conhecer o seu tipo. Toda
exceção em Java herda a classe Exception. Graças a isto, opcionalmente, podemos capturar a
exceção Exception ao invés da exceção InputMismatchException:
package br.edu.ifms.lp2;
import java.util.Scanner;
public class LeituraInteiro {
public static void main(String[] args) {
System.out.println("Digite um numero inteiro:");
boolean numeroOk = false;
do {
Scanner leitor = new Scanner(System.in);
try {
int numero = leitor.nextInt();
System.out.println("Numero informado: " + numero);
numeroOk = true;
} catch (Exception e) {
System.out.println("O valor informado nao e inteiro!");
System.out.println("Digite novamente o numero:");
}
} while (!numeroOk);
}
}
Existem algumas operações (ou sequência de operações) que podem vir a lançar mais
de uma exceção. Neste caso, cada exceção pode ser capturada separadamente, conforme
ilustrado abaixo, ou pode ser criado um único bloco catch que capture a exceção Exception. 
try {
// Código a ser tratado
} catch(Excecao1 e1) {
// Código para tratar a exceção
} catch(Excecao2 e2) {
// Código para tratar a exceção
} catch(Excecao3 e3) {
// Código para tratar a exceção
} ...
Para simular tal situação, escreveremos um trecho de código que realize duas
operações que podem lançar exceções distintas. Primeiramente, o programa lerá um número
real do usuário utilizando o método nextDouble da classe Scanner. Após isto, o programa
lerá outro número real do usuário, porém utilizando o método next da classe Scanner para ler
o número como uma string e depois converteremos a string obtida em um número real
utilizando o método parseDouble da classe Double: 
package br.edu.ifms.lp2;
import java.util.InputMismatchException;
import java.util.Scanner;
public class ExemploConversao {
public static void main(String[] args) {
System.out.println("Digite um numero real:");
try {
Scanner leitor = new Scanner(System.in);
double numero = leitor.nextDouble();
String numeroStr = String.valueOf(numero);
numero = Double.parseDouble(numeroStr);
} catch (InputMismatchException e1) {
System.out.println("Valor nao corresponde a um numero real.");
} catch (NumberFormatException e2) {
System.out.println("Ocorreu um erro ao converter a string.");
}
}
}
No programa acima, caso o primeiro número lido do usuário não corresponda a um
número real ou caso tal número possua casas decimais separadas por um ponto (ao invés de
uma vírgula, como espera o método nextDouble), então o programa lançará a exceção
InputMismatchException. Além disso, caso o segundo valor lido do usuário não corresponda
a um número real, a exceção NumberFormatException é lançada pelo método
parseDouble. Note que não precisamos importar a exceção NumberFormatException pois
ela reside no pacote java.lang, o qual faz parte do core da JDK.
Neste último exemplo, poderíamos capturar somente a exceção Exception a fim de
tratar indiretamente todas as exceções possíveis no trecho de código. Porém, ao se fazer isto,
não é possível (ao menos de forma simples e direta) lidar com cada exceção individualmente,
ou seja, realizar um tratamento específico a cada exceção. A versão abaixo da classe
ExemploConversao trata individualmente cada uma das duas exceções do exemplo anterior,
porém capturando-as indiretamente:
package br.edu.ifms.lp2;
import java.util.InputMismatchException;
import java.util.Scanner;
public class ExemploConversao {
public static void main(String[] args) {
try {
System.out.println("Digite um numero real:");
Scanner leitor = new Scanner(System.in);
double primeiro = leitor.nextDouble();
System.out.println("Digite outro numero real:");
String numeroStr = leitor.next();
double segundo = Double.parseDouble(numeroStr);
System.out.println("Primeiro: " + primeiro);
System.out.println("Segundo: " + segundo);
} catch (Exception e) {
if (e instanceof InputMismatchException) {
System.out.println("Valor nao corresponde a um numero real.");
} else if (e instanceof NumberFormatException) {
System.out.println("Ocorreu um erro ao converter a string.");
} else {
System.out.println("Ocorreu um erro nao identificado");
}
}
}
}
Exercícios:
a) Reescreva a classe LeituraDados de tal forma a tratar todas as possíveis exceções
que a classe venha a lançar.
11 - A palavra reservada finally
Como visto nos exemplos da seção anterior, utilizamos o bloco try-catch para tentar
executar um bloco de código passível a exceções e, caso alguma exceção esperada seja
lançada, capturar tal exceção e redirecionar o fluxo de execução do programa para um bloco
de código alternativo. Existem algumas situações em que ambos os blocos de código possuem
desfechos em comum, ou seja, linhas de código em comum que devem ser executadas no final
do bloco e que dependem do processamento resultante do bloco.
Como exemplo, considere que desejamos escrever um programa que leia um número
real do usuário e imprima o quadrado deste número. Porém, queremos que o programa permite
que o usuário digite o seu número real desejado utilizando um ponto ou uma vírgula para
separar a parte inteira do número das casas decimais.
Note que neste exemplo que, independentemente à forma a qual o usuário digitará o
número desejado, o cálculo deve ser realizado. Porém, dependendo desta forma de
representação do número real, uma tratativaadequada deve ser aplicada para a leitura correta
do número.
Para resolver este problema, utilizaremos um bloco try-catch-finally. A palavra
reservada finally da linguagem Java é utilizada para se definir um bloco de código que deve
ser executado logo após um bloco try-catch, independentemente ao fato de uma exceção ter
sido capturada ou não. A seguir, a solução proposta:
Scanner leitor = new Scanner(System.in);
System.out.println("Digite um número real");
System.out.println("(utilizando ponto ou vírgula para separar as casas decimais): ");
String numeroStr = leitor.next();
Double numero = 0D;
try {
numero = Double.parseDouble(numeroStr);
} catch (NumberFormatException e) {
numeroStr = numeroStr.replace(",", ".");
numero = Double.parseDouble(numeroStr);
} finally {
System.out.println("Quadrado do valor informado: " + Math.pow(numero, 2));
leitor.close();
}
No exemplo acima, o número do usuário é lido como uma string, utilizando-se o método
next da classe Scanner. No bloco try, é realizada a tentativa de converter a string em um
número real por meio do método parseDouble da classe Double. Caso este número tenha
casas decimais separadas da parte inteira por uma vírgula, a exceção
NumberFormatException é lançada e capturada no bloco catch. Neste bloco, a string
contendo o número do usuário tem a sua vírgula substituída por um ponto, fazendo com que
assim o número possa ser convertido adequadamente pelo método parseDouble. Por fim, o
código do bloco finally é executado, não importando se a exceção tenha sido lançada ou não. 
12 - Criando exceções
Nos últimos exemplos pudemos ver algumas das exceções mais populares da
linguagem Java. O JDK (Java Development Kit) contém uma série de classes de exceção que
abordam diversos tipos de erros comuns. 
Porém, quando desenvolvemos uma aplicação, é comum que ela contenha diversas
regras de negócio específicas, as quais devem ser garantidas para a boa execução das
funcionalidades da aplicação. A violação de muitas destas regras não faz com que exceções do
JDK sejam lançadas, devido à sua especificidade. A fim de tratar tais violações, é possível
escrever exceções customizadas as quais podem ser lançadas e tratadas futuramente na sua
aplicação. 
Para entender como é possível implementar este processo, considere como exemplo
que você foi incumbido de implementar dentro de um sistema um módulo de cadastro de
clientes. Para tanto, você decide iniciar a implementação deste módulo criando um método
que receba os dados de um determinado cliente e então retorne um objeto que encapsule tais
dados. Abaixo, o método resultante:
public Cliente cadastraCliente(String cpf, String nome, String endereco, Date dataNascimento) {
if (dataNascimento != null && !dataNascimento.after(new Date())) {
Cliente cliente = new Cliente();
cliente.setCpf(cpf);
cliente.setNome(nome);
cliente.setEndereco(endereco);
cliente.setDataNascimento(dataNascimento);
return cliente;
}
return null;
}
Analisando o método acima é possível perceber que ele possui uma regra de negócios
clara. Se a data de nascimento informada ao cliente não for informada ou se ela for informada
mas for posterior à data atual, então o objeto de cliente não é criado e o método retorna o
valor null.
Apesar do fato de que a validação é feita, ao se pretender invocar este método é
preciso saber de antemão que, caso o método retorne o valor null, isto ocorre devido ao fato
da data de nascimento pretendida ao cliente ser inválida. Uma alternativa seria retornar uma
mensagem informando de forma mais clara tal fato. Porém, o método cadastraCliente deve
retornar um objeto de cliente e não uma string de mensagem.
Uma boa alternativa a este problema seria criar uma exceção customizada que
representasse a tentativa de cadastro de uma data de nascimento inválida. Assim, caso a data
de nascimento passada ao método cadastraCliente fosse inválida, o método poderia lançar
tal exceção, a qual poderia conter uma mensagem clara a respeito da violação cometida.
Assim, utilizaremos esta abordagem por meio de três passos:
• Criar uma exceção customizada à violação de regra de negócio
• Inserir na exceção uma mensagem adequada e informativa a respeito da violação
• Lançar esta exceção caso a violação seja cometida
Para realizar o primeiro passo, é preciso criar uma classe de exceção. A forma mais
comum de criar uma classe de exceção é criar uma classe comum e fazer com que ela herde a
classe Exception do pacote java.lang. Abaixo, a exceção que utilizaremos para o nosso
exemplo:
package br.edu.ifms.lp2.excecao;
public class DataInvalidaException extends Exception {
public DataInvalidaException() {
super("A data de nascimento deve ser anterior ou igual à data atual.");
}
}
Existe uma convenção entre os programadores profissionais de utilizar o sufixo
“Exception” na nomenclatura de classes de exceção. Para fazer com que uma mensagem
customizada à violação referente à inserção de datas inválidas seja retornada pela exceção
DataInvalidaException, a classe repassa tal mensagem à classe Exception por meio de um
construtor que esta fornece para tal finalidade. Assim, de forma simples, é possível propagar a
mensagem customizada para o código infrator.
Desta forma, com a classe DataInvalidaException foi possível realizar os dois
primeiros passos do processo. Agora, é necessário fazer com que o método cadastraCliente
lance esta exceção caso ele receba uma data de nascimento inválida:
public Cliente cadastraCliente(String cpf, String nome, String endereco,
Date dataNascimento) throws DataInvalidaException {
if (dataNascimento != null && !dataNascimento.after(new Date())) {
Cliente cliente = new Cliente();
cliente.setCpf(cpf);
cliente.setNome(nome);
cliente.setEndereco(endereco);
cliente.setDataNascimento(dataNascimento);
return cliente;
} else {
throw new DataInvalidaException();
}
}
Para lançar a exceção, foram necessários dois passos. Primeiramente, foi preciso 
informar no final da assinatura do método que ele possivelmente lança a exceção, utilizando a 
palavra reservada throws da linguagem Java. Ao realizar isto, o método “obriga” que qualquer 
código que invocá-lo trate a exceção DataInvalidaException. O último passo consiste em 
efetivamente lançar a exceção caso a data seja inválida, utilizando a palavra reservada throw 
(sem o 's', não confunda!) para lançar uma nova instância da exceção. 
Note que além do retorno do objeto de cliente, caso a data de nascimento seja válida e 
o objeto de cliente seja criado, o método não possui outra instrução de retorno. Isto se deve ao 
fato de que, dentro do bloco de código da instrução else quando a exceção é lançada, a 
execução do método é interrompida instantaneamente. Assim, se uma instrução de retorno 
fosse colocada após a instrução throw ou mesmo fora do bloco else, tal linha de código seria 
inalcançável (o que é chamado em Java de Unreachable Code).
Após a implementação destes três passos, a regra de negócios sobre a data de 
nascimento do cliente foi efetivamente garantida. Assim, todo código que invocar o método 
cadastraCliente deve tratar a exceção DataInvalidaException. Por exemplo, observe a 
classe a seguir:
package br.edu.ifms.lp2;
import java.util.Calendar;
import java.util.Date;
import br.edu.ifms.lp2.excecao.DataInvalidaException;
public class TesteCadastroCliente {
public static void main(String[] args) {
RegrasCliente regras = new RegrasCliente();
String cpf = "11111111111";
String nome = "João da Silva";
String endereco = "Rua 14 de agosto, 23, Vila Inara";
Calendar calendario = Calendar.getInstance();
calendario.set(2015, 7, 28);
Date dataNascimento = calendario.getTime();
try {
Cliente cliente = regras.cadastraCliente(cpf, nome, endereco, dataNascimento);} catch (DataInvalidaException e) {
e.printStackTrace();
}
}
}
No classe acima, a data de nascimento enviada ao método cadastraCliente é posterior
à data atual3. Assim, ao executar este programa, a mensagem abaixo será impressa:
br.edu.ifms.lp2.excecao.DataInvalidaException: A data de nascimento deve ser anterior ou igual à
data atual.
at br.edu.ifms.lp2.RegrasCliente.cadastraCliente(RegrasCliente.java:19)
at br.edu.ifms.lp2.TesteCadastroCliente.main(TesteCadastroCliente.java:19)
Exercícios:
a) Escreva uma classe de exceção para cada uma das seguintes regras de negócio
abaixo:
• O número de CPF deve conter exatamente onze dígitos
• O endereço informado não pode ser nulo ou vazio
• O cliente deve ter no mínimo 18 anos
b) Após implementar as exceções do exercício a, valide o método cadastraCliente de
tal forma a lançar tais exceções quando necessário.
13 - Manipulação de arquivos
Manipular arquivos programaticamente é uma atividade comum no dia a dia de
desenvolvimento de software. Na linguagem Java, a classe File do pacote java.io é utilizada
para se realizar operações básicas sobre arquivos.
Como um primeiro exemplo, vamos considerar que desejamos escrever um programa
capaz de abrir um arquivo existente em memória secundária. Para tanto, iremos implementar
um método que seja capaz de abrir um arquivo, utilizando a classe File para tanto. Abaixo, o
método correspondente:
public File abreArquivo(String caminhoArquivo) {
File arquivo = new File(caminhoArquivo);
if (arquivo.exists()) {
return arquivo;
}
return null;
}
O método abreArquivo recebe como parâmetro de entrada o caminho do arquivo a ser
aberto. Vale a pena salientar que o caminho de um arquivo é composto pelo caminho desde a
raiz da unidade de memória secundária até o diretório onde o arquivo se encontra, seguido do
3 A seção 12 desta apostila foi escrita no dia 31 de outubro de 2013.
nome do arquivo. Se o programa estiver sendo executado no sistema operacional Windows, é
preciso substituir cada ocorrência do caractere '\' na string do caminho especificado por duas
ocorrências do mesmo caractere (“\\”). Por exemplo, se o arquivo está situado no caminho
abaixo:
C:\Users\Sidney\arquivo.txt
Então, a string correspondente para o parâmetro de entrada seria:
“C:\\Users\\Sidney\\arquivo.txt”
Opcionalmente, o mesmo caminho poderia ser definido substituindo-se cada ocorrência
do caractere '\' pelo caractere '/'. Desta forma, o caminho do exemplo acima ficaria como
abaixo:
“C:/Users/Sidney/arquivo.txt”
Na primeira linha de código do método abreArquivo é realizada a instanciação de um
objeto da classe File. Observe que a instanciação é realizada utilizando-se um construtor que
recebe como parâmetro o caminho do arquivo a ser aberto. Quando um objeto da classe File é
instanciado desta forma, instantaneamente o arquivo desejado é aberto e alocado para o
programa que realizou tal abertura. Assim, no nosso método, o arquivo desejado é aberto e
todos os seus metadados são armazenadas dentro do objeto arquivo.
Bem, se logo na primeira linha do método o arquivo já é aberto, então o que o restante
do método realiza? Quando instanciamos um objeto da classe File conforme feito no método
abreArquivo, podem ocorrer duas situações. A primeira situação ocorre quando o arquivo já
existe no caminho especificado; neste caso, o arquivo é aberto e seus metadados armazenados
no objeto. Por sua vez, a segunda situação ocorre quando o arquivo ainda não existe no
caminho especificado. Neste caso, o construtor da classe File interpreta que o solicitante
deseja criar o arquivo no caminho especificado; assim, o arquivo é “criado” em memória
primária e espera-se que seja adicionado algum tipo de conteúdo a ele posteriormente.
Desta forma, após a instanciação do objeto o programa não sabe se o arquivo foi aberto
ou se na realidade ele foi recém criado. Para verificar o que de fato ocorreu, o programa
verifica se o arquivo já existe por meio do método exists() da classe File. Este método retorna
true caso o arquivo já exista no caminho especificado ou false caso contrário. Assim, caso o
arquivo já exista, o método abreArquivo retorna o objeto referente ao arquivo desejado. Por
outro lado, caso o arquivo não exista ainda, o método retorna null.
Ao entender como o construtor da classe File se comporta quanto à preexistência do
arquivo desejado, podemos implementar um método capaz de criar um novo arquivo. O
método abaixo possui o propósito de gerar um novo arquivo no caminho especificado: 
public File criaArquivo(String caminhoArquivo) {
return abreArquivo(caminhoArquivo) == null ? new File(caminhoArquivo) : null;
}
O método criaArquivo utiliza o método abreArquivo para tentar abrir o (suposto)
arquivo inexistente no caminho especificado. Se o arquivo ainda não existir no caminho
especificado, o método abreArquivo retorna o valor null e, consequentemente, o método
criaArquivo cria o arquivo desejado em memória primária e retorna uma nova instância da
classe File contendo os metadados do arquivo recém criado. Caso o arquivo já exista no
caminho especificado, o método abreArquivo retorna uma instância da classe File contendo
os metadados do arquivo já existente, o que faz com que o método criaArquivo retorne o
valor null.
A classe File possui uma série de métodos para verificar os metadados de um
determinado arquivo. Por exemplo, o método getPath() retorna o caminho do arquivo
referenciado pelo objeto e o método getName() retorna o nome de tal arquivo. O método
abaixo utiliza o método getName() para identificar a extensão de um determinado arquivo:
public String pegaExtensaoArquivo(File arquivo) {
if (arquivo != null && arquivo.exists()) {
String nomeArquivo = arquivo.getName().trim();
String[] aux = nomeArquivo.split("\\.");
return aux.length > 0 ? aux[aux.length - 1] : "";
}
return "";
}
O método length() da classe File retorna o tamanho do arquivo em bytes. Assim,
podemos escrever um método para calcular o tamanho de um determinado arquivo em
megabytes, como abaixo:
public Double calculaTamanhoArquivoEmMegaBytes(File arquivo) {
if (arquivo != null && arquivo.exists()) {
long tamanhoArquivoEmBytes = arquivo.length();
return tamanhoArquivoEmBytes / 1048576D;
}
// Caso o arquivo não exista, retorna um valor de tamanho inválido
return -1D;
}
Como um último exemplo, utilizaremos os métodos isDirectory() e list() da classe File
para varrer recursivamente todos os subdiretórios com raiz em um determinado diretório. O
método isDirectory() retorna true caso o arquivo represente um diretório ou false caso
contrário. O método list() deve ser invocado somente em arquivos que representem diretórios.
Este método retorna uma lista contendo os caminhos de todos os arquivos ou diretórios
existentes dentro do diretório referente. Os métodos abaixo recebem o caminho de um
determinado diretório e listam todos os diretórios e arquivos alcançáveis a partir dele:
public void varreDiretorios(String caminhoDiretorio) {
File arquivo = abreArquivo(caminhoDiretorio);
if (arquivo != null && arquivo.exists() && arquivo.isDirectory()) {
varreDiretorios(arquivo, "");
}
}
private void varreDiretorios(File diretorio, String tabulacoes) {
String[] listaDiretorios = diretorio.list();
if (listaDiretorios != null && listaDiretorios.length > 0) {
for (String nomeSubDiretorio : diretorio.list()) {
nomeSubDiretorio = diretorio.getPath() + "/" + nomeSubDiretorio;
File subDiretorio = abreArquivo(nomeSubDiretorio);
if (subDiretorio != null && subDiretorio.exists()) {
if (subDiretorio.isDirectory()) {
varreDiretorios(subDiretorio, tabulacoes + "\t");
} else {
System.out.println(tabulacoes + subDiretorio.getPath());
}
}
}
}}
14 - Leitura em arquivos textuais
Apesar dos vários tipos de arquivos existentes atualmente, podemos categorizá-los em
duas categorias básicas: arquivos textuais e arquivos binários. Um arquivo textual é
totalmente composto por caracteres textuais e deve ser manipulado por meio de um editor de
textos. Por sua vez, um arquivo binário possui uma estrutura mais complexa, podendo ser
composto por vários tipos de dados. Assim, para cada tipo de arquivo binário existe um
software específico para manipulá-lo.
Como vimos na última seção, a classe File possui métodos para manipular arquivos e
diretórios. Apesar disso, não é possível realizar a leitura de um arquivo utilizando somente a
classe File. Existem classes específicas para a realização da leitura de arquivos textuais, assim
como existem classes específicas para a leitura de arquivos binários. Nesta seção,
aprenderemos as duas formas básicas para realizar a leitura de arquivos textuais.
A forma mais básica de realizar a leitura programática de um arquivo textual é utilizar a
classe Scanner para tal tarefa. A classe Scanner é responsável por realizar a leitura de dados
oriundos de fontes diversas. Por exemplo, você já utilizou a classe Scanner para realizar a
leitura de dados que o usuário informa por meio do teclado. Vamos nos recordar de como o
objeto de leitura é preparado para realizar este tipo de leitura:
Scanner leitor = new Scanner(System.in);
Note que na instanciação do objeto é passado ao construtor da classe Scanner o valor
do objeto in da classe System. Ao fazer isto, estamos informando à classe Scanner que
desejamos realizar a leitura dos dados provenientes da entrada padrão do sistema. Em um
dispositivo computacional padrão, a entrada de dados padrão do sistema é o teclado.
Desta forma, o valor passado ao construtor da classe Scanner é que define a fonte dos
dados a serem lidos. Para informar à classe que os dados a serem lidos são provenientes de um
arquivo, basta passar como parâmetro ao construtor o objeto da classe File referente ao
arquivo desejado, como exibido abaixo:
File arquivo = new File(caminhoArquivo);
Scanner leitor = new Scanner(arquivo);
Uma vez que o objeto de leitura esteja preparado, é possível realizar a leitura do
arquivo desejado. A leitura dos dados do arquivo é feita de forma análoga à leitura de dados
provenientes da entrada padrão do sistema. 
Mas antes de saber como ler os dados de um arquivo, é preciso conhecer a dinâmica da
leitura de um arquivo textual. A leitura do arquivo é sempre realizada do começo ao fim do
arquivo. Além disso, é possível determinar se a leitura será realizada token a token (palavra a
palavra) ou linha a linha. O método abaixo realiza a leitura token a token de um arquivo por
completo:
public String leArquivoTokenAToken(File arquivo) {
if (arquivo != null) {
try {
String textoArquivo = "";
Scanner leitor = new Scanner(arquivo);
while (leitor.hasNext()) {
textoArquivo += leitor.next();
}
leitor.close();
return textoArquivo;
} catch (FileNotFoundException e) {
System.out.println("Arquivo nao pode ser lido!");
e.printStackTrace();
}
}
return "";
}
Não é necessário tratar nenhuma exceção nas operações de manipulação básica de
arquivos vistas na seção anterior. Porém, ao se tentar realizar a leitura de um arquivo, a
exceção FileNotFoundException do pacote java.io pode ser lançada, o que obriga ao
programador tratá-la. A possível ocorrência desta exceção se deve ao fato de que, caso se
tente abrir um arquivo que não existe, a classe File criará este arquivo em memória e assim
não existe o risco de lançamento de uma exceção. Porém, é fácil prever que, caso o arquivo
ainda não exista em memória secundária, a leitura do seu conteúdo é impossível. Portanto, se
o arquivo ainda não existe em memória secundária, a exceção FileNotFoundException é
lançada (lembrando que “file not found” corresponde – em português – a “arquivo não
encontrado”). Note também que o leitor deve ser encerrado após a operação de leitura, a fim
de desalocar o arquivo do programa.
Para entender o funcionamento do método leArquivoTokenAToken, considere que
desejamos realizar a leitura do arquivo exemplo.txt. O conteúdo deste arquivo é exibido
abaixo:
Este é um texto de exemplo.
2
3,57
Ele possui 5 linhas de informação.
Esta é a última linha.
Se abrirmos este arquivo e passarmos ele como parâmetro ao método
leArquivoTokenAToken, o método retornará o conteúdo do arquivo formatado da seguinte
forma:
Esteéumtextodeexemplo.23,57Elepossui5linhasdeinformação.Estaéaúltimalinha.
Isto ocorre devido a forma como a leitura do arquivo é realizada. Para ler cada token do
conteúdo do arquivo por vez, foi utilizado o método next() da classe Scanner. Este método
faz a leitura do próximo token do arquivo, isto é, a próxima palavra do arquivo, ignorando os
espaços em branco, tabulações ou pulos de linha entre o token atual e o próximo token. Assim,
como o resultado a ser retornado pelo método leArquivoTokenAToken consiste na
concatenação de todos os tokens lidos, a string resultante é composta por todos os tokens sem
qualquer separação entre eles. 
Observe que existe um laço de repetição para controlar que a leitura do arquivo seja
realizada até que não haja um próximo token a ser lido, utilizando o método hasNext() da
classe Scanner para realizar tal verificação. Este método retorna true caso haja um próximo
token a ser lido ou false caso contrário. A versão abaixo do método leArquivoTokenAToken
retorna todos os tokens lidos do arquivo, porém separados em linhas distintas:
public String leArquivoTokenAToken(File arquivo) {
if (arquivo != null) {
try {
String textoArquivo = "";
Scanner leitor = new Scanner(arquivo);
while (leitor.hasNext()) {
textoArquivo += leitor.next() + "\n";
}
leitor.close();
return textoArquivo;
} catch (FileNotFoundException e) {
System.out.println("Arquivo nao pode ser lido!");
e.printStackTrace();
}
}
return "";
}
Alternativamente, é possível realizar a leitura linha a linha do arquivo. Para tanto, basta
utilizar o método hasNextLine() da classe Scanner para realizar a leitura dos dados do
arquivo. Para garantir que todas as linhas do arquivo sejam lidas, podemos criar um laço de
repetição que seja executado enquanto haja uma nova linha a ser lida, utilizando o método
hasNextLine() para realizar tal verificação. De forma análoga ao hasNext(), este método
retorna true caso haja uma nova linha a ser lida ou false caso contrário. O método abaixo
utiliza esta estratégia para ler linha por linha do arquivo desejado:
public String leArquivoLinhaALinha(File arquivo) {
if (arquivo != null) {
try {
String textoArquivo = "";
Scanner leitor = new Scanner(arquivo);
while (leitor.hasNextLine()) {
textoArquivo += leitor.nextLine() + "\n";
}
leitor.close();
return textoArquivo;
} catch (FileNotFoundException e) {
System.out.println("Arquivo nao pode ser lido!");
e.printStackTrace();
}
}
return "";
}
Ainda utilizando a classe Scanner, é possível realizar leituras com conversões
embutidas, da mesma forma que é realizado na leitura de dados da entrada padrão. Por
exemplo, analisando o arquivo exemplo.txt, é possível verificar que algumas linhas do
arquivo possuem informações numéricas. Assim, pode-se utilizar os métodos nextInt() e
nextDouble() da classe Scanner de forma estratégica para ler estes valores e, indiretamente,
convertê-los no momento da leitura. O programa abaixo realiza a leitura do arquivo
exemplo.txt, coletando seus dados e armazenando-os diretamente em variáveis apropriadas
aos seus tipos:
package br.edu.ifms.lp2.arquivo;
import java.io.File;
import java.io.FileNotFoundException;
import

Outros materiais