- capítulo dedicado a conceitos avançados de orientação a objetos e sua aplicabilidade em Java (possibilidade do polimorfismo, classes abstratas e o uso de exceções para tratar as falhas às quais todo software está sujeito).
- durante a execução de um programa, é possível que algumas exceções ou erros aconteçam.
- alguns dos problemas mais comuns:
- falha na aquisição de algum recurso, como abrir um arquivo, conectar com o banco de dados ou acessar um web service.
- tentativa de realizar algo impossível, como a divisão de um número por zero, acessar uma posição que não existe em um array.
- outras condições inválidas, como evocar um método de um objeto que não foi instanciado ou realizar um cast inválido.
- esses eventos não esperados geralmente interrompem o fluxo da execução do código.
- o tratamento de exceções permite verificar esses eventos (erros) e realizar uma ação sem prejudicar o fluxo do programa.
- em geral, o fluxo para o tratamento de exceções no Java ocorre em
três passos
:- uma exceção é lançada: um comando do código dispara uma condição inesperada de erro.
- a exceção é capturada: em algum ponto do código, podemos adicionar um comando para capturar a possível exceção.
- o tratamento de erro é realizado: após a captura da exceção, o tratamento de erro adequado é executado.
IMPORTANTE: Realizando o tratamento das exceções, o programa consegue continuar a execução normalmente. Exceções não capturadas provocam a finalização do programa.
- uma exceção é um objeto do tipo Exception.
- no polimorfismo, um objeto desse tipo pode ser qualquer instância de uma subclasse de Exception.
- dentro da plataforma Java, podemos classificar as exceções em:
Checked
: exceção que deve ser tratada ou relançada pelo desenvolvedor, geralmente herda da classe Exception.Unchecked
: exceção que pode ser tratada ou relançada pelo programador. Essa exceção é gerada pelo código mal escrito. Caso a exceção não seja tratada, ela será automaticamente relançada. Geralmente, esse tipo de exceção herda de RuntimeException.Error
: erro crítico, utilizado pela JVM para indicar que existe um problema que não permite a continuação da execução do programa.
Classe | Objetivo | Erro |
---|---|---|
Error | Erro que não pode ser tratado na aplicação. Lançado pela JVM indicando que o programa não pode continuar a execução. | OutOfMemoryError: indica que não há memória suficiente na máquina para continuar a execução do programa. |
Exception | Exceção que deve ser tratada pelo desenvolvedor. | ArithmeticException: indica que houve alguma operação aritmética inválida, exemplo: divisão por zero. |
RuntimeException | Exceção que pode ser tratada pelo desenvolvedor. | NullpointerException: indica que há uma tentativa de acessar algum método ou atributo de uma classe que não foi instanciada. |
- além das exceções existentes na plataforma, podemos criar as nossas próprias exceções, sejam elas checked (filhas de exception) ou unchecked(filhas de RuntimeException).
- hierarquia de classes de exceções do Java:
- a classe Throwable tem duas subclasses: Exception e Error
- Exception é a classe base para as subclasses de exceções checked e unchecked, exceções que devem ou podem ser tratadas.
- Error é a base para as classes de exceções que não podem ser tratadas.
exceções mais comuns em Java
:
- ArithmeticException:
- exceção unchecked.
- ocorre quando alguma operação aritmética é inválida; operação esta que não pode ser resolvida, como é o caso da divisão de um número por zero.
- ArrayIndexOutOfBoundsException:
- exceção do tipo unchecked (o desenvolvedor não é obrigado a tratar a exceção).
- acontece quando tentamos acessar uma posição inválida de uma matriz ou vetor (array).
- uma posição inválida é uma posição que não existe (negativa ou valor igual ou maior que o tamanho do vetor).
- lembre-se: o índice do vetor sempre começa em zero, ou seja, não existe posição negativa e o último elemento de um array está posicionado no tamanho do vetor menos um.
- NullpointerException:
- é uma exceção unchecked, mais conhecida e comum durante o desenvolvimento.
- ocorre na tentativa de acessar um objeto que ainda não foi instanciado.
- exemplo: quando tentamos acessar o método size() de um ArrayList que ainda não foi instanciado.
- FileNotFoundException:
- exceção checked.
- precisamos tratar quando tentamos acessar um arquivo que não foi encontrado.
- NumberFormatException:
- não precisa ser tratada (unchecked).
- ocorre quando tentamos transformar uma string inválida em algum tipo numérico.
- para tratar as exceções (checked ou unchecked) em tempo de execução, elas devem ser capturadas e tratadas.
- o Java possui duas estruturas importantes para o tratamento de exceções:
try-catch
etry-catch-finally
. - essas estruturas têm a finalidade de separar o código que executa as tarefas desejadas das rotinas de tratamento das exceções.
try {
//Código
} catch(Excecao) {
//Tratamento da exceção
}
- o
bloco try
tem um código que pode gerar uma exceção, ou seja, esse trecho de código será monitorado pela JVM.- se um erro for gerado, o fluxo da execução será desviado para o bloco catch, para o tratamento do erro.
- o uso do try indica que o código está tentando realizar algo “perigoso”, passível de erro.
- o
bloco catch
só é executado se uma exceção for gerada.- caso nenhuma exceção seja lançada, a execução pula o bloco catch e continua normalmente.
- se uma exceção for lançada, o bloco try é finalizado e o fluxo de execução procura por um bloco catch adequado para tratar a exceção.
- depois de executar o bloco catch, a execução do programa continua na primeira instrução após o último bloco catch.
- podemos adicionar vários blocos catch para capturar diferentes tipos de exceções:
try {
// Código
} catch (Excecao1) {
// Tratamento da exceção 1
} catch (Excecao2) {
//Tratamento da exceção 2
} catch (Excecao3) {
//Tratamento da exceção 3
}
- os catches são testados de cima para baixo, um por um, até que o catch apropriado seja executado, por isso as exceções mais específicas devem ser colocadas nos primeiros catches, sempre obedecendo à ordem das exceções mais específicas para as mais genéricas.
- a
exceção Exception
é a mais genérica possível, pois todas as exceções (checked ou unchecked) são filhas dela.- portanto, a captura dessa exceção deve ser colocada no último catch, pois ela captura qualquer tipo de exceção que for lançada.
- se nenhum catch conseguir capturar a exceção lançada, ela não será tratada, como se não existisse o bloco try-catch.
- exemplo:
Scanner sc= new Scanner(System.in);
//Lê os dois números
int numero1 = sc.nextInt();
int numero2 = sc.nextInt();
//Realiza a divisão
int divisao = numero1/numero2;
//Exibe o resultado
System.out.println("O resultado é: "+ divisao);
sc.close();
- código é simples, porém pode lançar uma exceção, caso o segundo número informado pelo usuário seja zero; caso isso aconteça, o erro gerado será:
2
0
Exception in thread "main" java.lang.ArithmeticException: / by zero
at br.com.fiap.tds.View.main(View.java:16)
- a
exceção ArithmeticException
foi lançada, pois não é possível realizar uma divisão por zero! - essa exceção é unchecked, já que não fomos obrigados a tratá-la.
- realizando alteração no código, para realizar o tratamento da exceção:
Scanner sc = new Scanner(System.in);
// Lê os dois números
int numero1 = sc.nextInt();
int numero2 = sc.nextInt();
try {
// Realiza a divisão
int divisao = numero1 / numero2;
// Exibe o resultado
System.out.println("O resultado é: "+ divisao);
} catch(ArithmeticException e) {
System.err.println("Erro ao dividir por zero!");
}
sc.close();
- neste caso, o bloco try-catch captura a exceção: caso o usuário insira um divisor igual a zero, a divisão vai gerar a ArithmeticException.
- assim, o fluxo da execução será desviado para o bloco catch, e a linha do código que exibe o resultado não será executada; o tratamento de exceção previsto no catch para o erro consiste na apresentação da mensagem Erro ao dividir por zero, retornando:
2
0
Erro ao dividirpor zero!
- se o programa rodar sem lançar exceção (se o usuário informar o dividendo diferente de zero), o resultado da divisão será exibido e o bloco catch não será executado.
- dentro do bloco catch, podemos recuperar a exceção gerada, por meio do parâmetro;
no exemplo dado, a exceção é recuperada no parâmetro com o nome "e"
. - A
classe Throwable
possui alguns métodos que podem exibir informações dos erros gerados, então, por herança, a exceção ArithmeticException também tem estes métodos:- printStackTrace():
- imprime a pilha de erro encontrada na exceção.
- nesta pilha, podemos verificar o número da linha e a classe na quala exceção foi gerada.
- getMessage():
- retorna uma mensagem contendo a lista de erros armazenados em uma exceção.
- printStackTrace():
- resultado retornado pelo método printStackTrace e a mensagem de erro da exceção:
int[] array = new int[2];
try {
//Tentaa cessar uma posição inexistente do vetor
array[2] = 10;
} catch (ArrayIndexOutOfBoundsException e) {
System.err.println("Mensagem de erro: "+ e.getMessage());
e.printStackTrace();
}
- a exceção é gerada quando tentamos acessar uma posição inválida de um array: ArrayIndexOutOfBoundsException.
- no bloco catch, utilizamos os métodos getMessage() e printStackTrace(), resultando em:
Mensagem de erro: 2
java.lang.ArrayIndexOutOfBoundsException: 2
at br.com.fiap.tds.View.main(View.java:10)
- a mensagem tem o valor do índice inválido que tentamos acessar no array.
- o método printStackTrace imprime o rastro da pilha e exibe a exceção, a classe e a linha que gerou a exceção: View.java:10. Classe View, linha 10.
- utilizamos o
System.err
para exibir a mensagem de erro:- é uma classe utilizada para exibir os erros em um programa Java.
- podemos enviar o fluxo de saída para um arquivo de log, enquanto o fluxo de saída padrão de um System.out é a tela da aplicação.
- além do try-catch, podemos utilizar o bloco try-catch-finally para tratar as exceções.
- o terceiro bloco finally é opcional e utilizado sempre que precisamos executar um código, independentemente de ter ou não acontecido uma exceção.
- se uma exceção for lançada: o fluxo de execução passa do bloco try para o bloco catch adequado e, após a sua execução, o bloco finally será processado.
- se não for gerado uma exceção: após executar todo o bloco try, o bloco finally será processado.
- exemplo:
try {
//Fluxo normal que pode gerar uma exceção
} catch (Exception e) {
//Fluxo alternativo, para tratamento da exceção
} finally {
//Fluxo normal, que sempre será executado
}
- um exemplo clássico da utilização do bloco finally é o fechamento da conexão com o banco de dados (sempre devemos fechar a conexão, independentemente da concretização ou não da operação no banco).
- voltando ao primeiro exemplo, vamos tratar as exceções de divisão por zero e entrada de dados inválida, quando o usuário digita um número inválido; também vamos adicionar um bloco finally para exibir uma mensagem para o usuário e fechar o objeto scanner:
Scanner sc = new Scanner(System.in);
try {
// Lê os dois números
int numero1 = sc.nextInt();
int numero2 = sc.nextInt();
// Realiza a divisão
int divisao = numero1 / numero2;
// Exibe o resultado
System.out.println("O resultado é: "+ divisao);
} catch (ArithmeticException e) {
System.err.println("Erro ao dividir por zero!");
} catch (InputMismatchException e) {
System.err.println("Erro de entrada de dados!");
} finally {
System.out.println("Finalizando a execução do programa!");
sc.close();
}
- portanto, há 3 possíveis fluxos de execução:
- Sem gerar exceção, a saída será:
2
2
O resultado é: 1
Finalizando a execução do programa!
- Em exceção causada pela divisão por zero, a saída será:
2
0
Erro ao dividir por zero!
Finalizando a execução do programa!
- Em exceção causada pelo valor inserido inválido, a saída será:
a
Erro de entrada de dados!
Finalizando a execução do programa!
Observe que todos os fluxos sempre executam o bloco finally!
- umm método pode optar por não tratar a exceção e simplesmente propagá-la, ou melhor, delegá-la para o método que a chamou.
- podemos notificar o método que invocou outro método em que alguma exceção ocorreu.
- exemplo: criar uma classe chamada Calculadora que será responsável por implementar as operações aritméticas, entre eles a divisão.
public class Calculadora {
public int dividir (int n1, int n2) {
return n1/n2;
}
}
- esse método pode lançar uma exceção, caso o valor de n2 seja zero.
- podemos utilizar o try-catch para tratar a exceção, porém quem chamar o método dividir não saberá se a operação ocorreu de forma correta ou se aconteceu algum erro.
public int dividir (int n1, int n2) {
try {
return n1/ n2;
} catch (ArithmeticException e) {
e.printStackTrace();
}
return 0;
}
- nesse exemplo, quando o método dividir for acionado e o usuário informar para n2 o valor zero, internamente, no método dividir, será feito o tratamento do erro e ele retornará para o ambiente de chamada o valor zero e, nesse caso, o usuário não será informado sobre a ocorrência do erro!!!
- logo, a melhor maneira de tratar a exceção é não a tratar; no caso, somente propagar a exceção, notificando ao ambiente de chamada que algum problema aconteceu na execução: adicionar na assinatura do método o throws, com a exceção que queremos propagar!
public int dividir (int n1, int n2) throws Exception {
return n1 / n2;
}
- um método pode propagar mais de um tipo de exceção.
- para isso, adicionar as exceções separadas por vírgula.
public void gravarArquivo (String valor) throws SecurityException, FileNotFoundException, IOException {
//Código...
}
- portanto, a cláusula throws declara exceções que podem ser lançadas em determinados métodos.
- é uma vantagem para os devs, pois deixamos de modo explícito os eventuais erros que podem ocorrer na chamada do método, permitindo que o tratamento adequado para o erro seja implementado.
- podemos também lançar uma nova exceção nesse método, utilizando o comando throw:
public void depositar (double valor) {
if (valor < 0) {
throw newIllegalArgumentException();
}
saldo = saldo + valor;
}
- no exemplo, estamos validando se o valor depositado é maior do que zero.
- em caso positivo, o valor é adicionado ao saldo.
- caso contrário, uma exceção do tipo IllegalArgumentException será lançada, o que indica que o valor passado como parâmetro para o método é inválido.
- não foi preciso adicionar o throws na assinatura do método, pois essa exceção é unchecked. Caso a exceção seja checked, é necessário declará-la na assinatura do método!
public void sacar (double valor) throws Exception {
if (valor > saldo) {
throw new Exception ("Saldo insuficiente");
}
saldo = saldo - valor;
}
- o método acima valida se o valor a ser retirado é maior do que o valor do saldo.
- caso o valor do saldo seja insuficiente, uma exceção será lançada (foi necessário adicionar o throws na assinatura do método, para que, quem chamar o método sacar possa tratar a exceção ou lançá-la novamente).
public static void main (String[] args) {
// Cria uma nova instância de Conta
Conta c = new Conta();
try {
// Saca
c.sacar(100);
} catch (Exception e) {
e.printStackTrace();
}
// Deposita
c.depositar(200);
}
- após criar um novo objeto conta, chamamos o método sacar e, como lança uma exceção checked, precisamos tratá-la ou lançá-la.
- nesse caso, optamos por tratar a exceção com o bloco try-catch.
- já o método depositar lança uma exceção unchecked, não sendo obrigados a tratá-la.
- porém, se uma exceção ocorrer, ela será relançada automaticamente pelo método main, que imprimirá o erro no console!
- é possível também criar as nossas próprias classes de exceções.
- exemplo "Método sacar":
- lança a exceção mais genérica possível (Exception).
- para melhorar o uso das exceções e não utilizar uma exceção que serve para tudo, podemos criar uma exceção específica para o erro que pode acontecer.
- para criar uma exceção, criar uma classe que herde de Exception(checked) ou RuntimeException (unchecked).
- vamos criar nossas exceções: uma para identificar que o valor de saque é inválido e outra para dizer que o saldo da conta é insuficiente.
- a primeira exceção será unchecked (vai descender de RuntimeException). Por padrão, as exceções no Java terminam com Exception.
public class ValorInvalidoException extends RuntimeException {
// ...
}
- para utilizá-la, modificar o método sacar para que lance a exceção customizada:
public void depositar (double valor) {
if (valor < 0) {
throw new ValorInvalidoException();
}
saldo = saldo + valor;
}
- agora vamos criar uma exceção checked que verifique se o saldo é insuficiente:
public class SaldoInsuficienteException extends Exception{
// ...
}
- para utilizá-la, podemos relançá-la, não nos esquecendo de modificar o throws na assinatura do método:
public void sacar (double valor) throws SaldoInsuficienteException {
if (valor > saldo) {
throw new SaldoInsuficienteException();
}
saldo = saldo - valor;
}
- nos exemplos anteriores, criamos exceções checked e unchecked para ilustrar nossos exemplos, mas nada impede de elaborar somente um tipo de exceção para a classe Conta.
- procure sempre utilizar as exceções customizadas com informações do motivo do erro!
- o armazenamento de dados em variáveis, arrays, coleções ou outra estrutura de dados em memória é temporário (são perdidas quando o programa termina).
- os arquivos são utilizados para persistência de dados (armazenamento mesmo após o término da execução do programa).
- para armazenar grandes quantidades de informações, como clientes, produtos ou vendas de uma empresa, os lugares mais adequados são os bancos de dados.
- vantagens dos arquivos:
- úteis para armazenar as configurações do programa, ao invés de colocar as configurações diretamente no código-fonte.
- possibilidade de alterar o arquivo de configurações sem a necessidade de recompilar e empacotar todo o programa novamente.
- integração de sistemas diferentes (um sistema pode realizar um processamento de informações e gerar um arquivo final, enquanto outro sistema pode ler esse arquivo e apresentar as informações, gerando a integração).
- a plataforma Java possui facilidade na leitura e gravação de arquivos, pois é independente de plataforma, logo não precisamos nos preocupar com o tipo de sistema operacional em que o programa será executado.
- todas entradas e saídas em Java são definidas em termos de
fluxos
, oustreams
: sequências ordenadas de dados que possuem uma fonte de origem (streams de entrada), ou um destino (streams de saída).- uma stream é uma conexão para uma fonte de dados ou para um destino de dados.
- a plataforma Java trata cada arquivo como uma stream!
- há
dois tipos de streams
, dependendo da operação que será realizada:- Output stream: para gravar em um destino.
- Input stream: para ler de uma fonte.
- arquivos baseados em um fluxo de caracteres são chamados
arquivos textos
:- pode ser lido e entendido pelas pessoas, e também manipulado na plataforma Java.
- geralmente, registros contidos neles são representados pelas linhas, enquanto os campos o são por colunas ou valores separados por vírgula.
- para gravar dados em um arquivo texto, é preciso executar três passos: abrir o arquivo > gravar os dados > fechar o arquivo.
- as classes para manipular os arquivos ficam dentro do pacote java.io:
- java.io.FileWriter e
- java.io.PrintWriter.
classe FileWriter
:
- filha da classe OutputStreamWriter.
- utilizar essa classe para abrir o arquivo para escrita.
classe PrintWriter
:
- usada para escrever no arquivo.
- para criar um objeto do tipo PrintWriter, é preciso passar o arquivo, ou seja, o objeto FileWriter.
public static void main(String[] args) {
try {
//Abre o arquivo
FileWriter stream = new FileWriter("arquivo.txt");
PrintWriter print = new PrintWriter (stream);
//Escreve no arquivo
print.println("Teste");
print.println("Escrevendo no arquivo");
print.close();
//Fecha o arquivo
stream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
- o exemplo acima apresenta o método main, que abre, escreve e fecha o "arquivo.txt" (aberto, se existir, ou criado, caso não exista).
- na instanciação do FileWriter, é passado o caminho e nome do arquivo. Como não especificamos nenhum caminho, o arquivo estará na pasta raiz do programa.
- podemos também especificar o caminho completo do arquivo:
FileWriter stream = new FileWriter("C://arquivo.txt");
- após criar o objeto FileWriter, instanciado o objeto PrintWriter, passando como parâmetro o objeto que encapsula o arquivo:
PrintWriter print = new PrintWriter(stream);
- com o objeto PrintWriter, é possível utilizar os métodos print ou println para escrever no arquivo:
print.println("Teste");
print.println("Escrevendo no arquivo");
- para finalizar, chamamos o método close do objeto PrintWriter e FileWriter, fechando o arquivo texto:
print.close();
stream.close();
- foi preciso tratar a exceção IOException, pois podemos ter problemas na hora de abrir ou manipular o arquivo.
- o resultado da execução será o arquivo texto com as informações gravadas.
- para ler o arquivo, três passos: abrir o arquivo > utilizar o arquivo (ler os dados) > fechar o arquivo.
classes que podemos utilizar
:- java.io.FileReader (descende da classe InputStreamReader) e
- java.io.BufferedReader.
- exemplo:
public static void main(String[] args) {
try {
//Abre o arquivo
FileReader stream = new FileReader("arquivo.txt");
BufferedReader reader = new BufferedReader(stream);
//Lê uma linha do arquivo
String linha = reader.readLine();
while (linha != null) {
System.out.println(linha);
//Lê a próxima linha do arquivo
linha = reader.readLine();
}
reader.close();
//Fecha o arquivo
stream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
- a mesma regra do caminho se aplica: caso o arquivo esteja em outro diretório, devemos especificá-lo:
FileReader stream= new FileReader("C://arquivo.txt");
- para finalizar, é preciso fechar o FileReader e o BufferedReader.
- a plataforma Java oferece várias Classes para leitura/escrita de arquivos textos.
- as principais classes desse pacote são:
- Classes para entrada ou saída baseada em bytes:
- FileInputStream: para entrada baseada em bytes de um arquivo.
- FileOutputStream: para saída baseada em bytes para um arquivo.
- RandomAccessFile: para entrada e saída baseada em bytes de e para um arquivo.
- Classes para entrada e saída baseada em caracteres:
-
FileReader: para entrada baseada em caracteres de um arquivo.
-
FileWriter: para saída baseada em caracteres para um arquivo.
-
outra classe importante da API de entrada e saída (IO) do Java é o
File
:- representa um arquivo ou um diretório no sistema de arquivo nativo.
- permite obter informações sobre o arquivo ou diretório, e não sobre o seu conteúdo (não abre o arquivo ou permite processamento de conteúdo).
- sua função é gerenciar o arquivo ou diretório, o seu caminho (path), verificar permissões, existência, criar, renomear etc.
- um objeto da classe File representa o arquivo/diretório, não quer dizer que o arquivo ou diretório exista de fato.
- principais métodos da classe File:
Método | Descrição |
---|---|
exists | Verifica se um arquivo ou diretório existe |
isDirectory | Verifica se é um diretório |
isFile | Verifica se é um arquivo |
canRead | Verifica se pode ser lido |
canWrite | Verifica se pode ser gravado |
mkdir | Cria um diretório |
renameTo | Renomeia um arquivo ou diretório |
length | Retorna o tamanho do arquivo |
delete | Deleta um arquivo ou diretório |
getPath | Retorna o caminho |
public static void main(String[] args) {
File arquivo = new File("arquivo.txt");
// Verifica se o arquivo existe
if (arquivo.exists()) {
System.out.println("O arquivo existe!"+
"\nPode ser lido: "+ arquivo.canRead() +
"\nPodeser gravado: "+ arquivo.canWrite() +
"\nTamanho: "+ arquivo.length() +
"\nCaminho: "+ arquivo.getPath());
} else {
// Cria o arquivo
try {
if(arquivo.createNewFile())
System.out.println("Arquivo criado!");
else
System.out.println("Arquivo não criado!");
} catch (IOException e) {
e.printStackTrace();
}
}
}
- podemos também utilizar o objeto File para verificar se um diretório existe, ou então para criá-lo:
public static void main(String[] args) {
File diretorio = new File("fiap");
if(diretorio.exists()) {
System.out.println("Diretório existe!");
} else {
if(diretorio.mkdir())
System.out.println("Diretório criado!");
else
System.out.println("Diretório não criado.");
}
}
- após criar um diretório, podemos criar um arquivo dentro dele, conforme:
public static void main(String[] args) {
File diretorio = new File("fiap");
if (diretorio.exists()) {
System.out.println("Diretório existe!");
} else {
if (diretorio.mkdir())
System.out.println("Diretório criado!");
else
System.out.println("Diretório não criado.");
}
File arquivo = new File (diretorio,"file.txt");
try {
if(arquivo.createNewFile())
System.out.println("Arquivo criado!");
else
System.out.println("Arquivo não criado!");
} catch(IOException e) {
e.printStackTrace();
}
}
- primeiro verificamos se o diretório existe e, caso necessário, o criamos. Depois, instanciamos uma classe File passando o nome do arquivo e o objeto File que representa o diretório.
- com esse novo objeto File, tentamos criar o arquivo dentro do diretório.
- a classe File pode ser utilizada em conjunto com as outras classes de manipulação do conteúdo do arquivo, basta utilizar o objeto File no momento de criar um FileWriter ou FileReader:
try {
//Abre o arquivo para escrita
FileWriter writer = new FileWriter(arquivo);
//Abre o arquivo para leitura
FileReader reader = new FileReader(arquivo);
//Código...
} catch(IOException e) {
e.printStackTrace();
}
- depois, basta instanciar a classe PrintWriter para escrever no arquivo ou um BufferedReader para ler.
- significa: "qualidade ou estado de ser capaz de assumir diferentes formas".
- na programação orientada a objetos, polimorfismo significa ter múltiplos comportamentos.
- um método polimórfico resulta em diferentes ações, dependendo do objeto que está sendo referenciado.
- a capacidade polimórfica decorre diretamente da herança, pois permite que uma variável de referência e o objeto sejam diferentes.
- exemplo:
- podemos definir uma variável do tipo Conta, e armazenar um objeto de ContaCorrente, se este herdar da classe Conta.
- portanto, o tipo da variável de referência pode ser uma superclasse para o tipo do objeto real.
- qualquer objeto de uma classe que herde o tipo declarado da variável pode ser atribuído a ela!
- a utilização mais importante do polimorfismo se dá nas ocasiões em que dois objetos, sendo um da superclasse e outro da subclasse, executam ações diferentes quando o mesmo método é invocado, o que é possível por meio da
sobrescrita de métodos
(quando a subclasse sobrescreve o método implementado na superclasse).
public class Conta { //superclasse conta
protected double saldo;
public void sacar(double valor) throws SaldoInsuficienteException {
if(valor > saldo) {
throw new SaldoInsuficienteException();
}
saldo = saldo - valor;
}
}
public class ContaCorrente extends Conta { // subclasse cc
private double limite;
@Override
public void sacar(double valor) throws SaldoInsuficienteException {
if(valor > saldo + limite) {
throw new SaldoInsuficienteException();
}
saldo = saldo-valor;
}
}
public static void main(String[] args) {
Conta cc = new ContaCorrente();
try {
cc.sacar(20);
} catch(SaldoInsuficienteException e) {
e.printStackTrace();
}
}
// O método do objeto armazenado na variável será executado,
// ou seja, o método definido na ContaCorrente.
- a execução sempre acontece com o objeto armazenado e não com o tipo da variável!
- a classe abstrata possui algumas características:
- não pode ser instanciada (não podemos utilizar o operador new).
- pode possuir métodos abstratos.
- o propósito de uma classe abstrata é atuar como uma superclasse (existe para ser herdada, será a base para as outras classes que serão desenvolvidas).
- exemplo: a classe Conta é perfeita para ser abstrata, pois será a base para todas as outras contas em nosso sistema, como contas corrente, poupança, investimento, etc (não instanciamos uma classe Conta, não existe uma "conta genérica").
- uma classe abstrata pode conter métodos abstratos (não possui implementação, define somente a assinatura do método). A sua subclasse, obrigatoriamente, precisará implementar o método, caso esta seja concreta.
- em uma classe concreta, não é permitido definir métodos abstratos.
- é possível ter uma herança com várias classes abstratas, porém a primeira classe concreta da hierarquia será obrigada a implementar todos os métodos abstratos definidos pelas suas superclasses (é igual a sobrescrever, devemos definir um método com o mesmo nome e parâmetros (assinatura) na subclasse e implementá-la).
public abstract class Conta {
protected double saldo;
public void sacar(double valor) throws SaldoInsuficienteException {
if (valor > saldo) {
throw new SaldoInsuficienteException();
}
saldo = saldo - valor;
}
public abstract double verificarSaldo();
}
- o nome da classe abstrata e o método abstrato ficam em itálico.
- as classes concretas e os métodos implementados são exibidos sem nenhuma formatação diferente.
- para definir uma classe ou um método abstrato, adicionar o modificador abstract.
- no exemplo, definir a classe Conta como abstrata e adicionar o método “depositar” como abstrato:
public abstract class Conta {
protected double saldo;
public void sacar (double valor) throws SaldoInsuficienteException {
if (valor > saldo){
throw new SaldoInsuficienteException();
}
saldo = saldo - valor;
}
public abstract double verificarSaldo();
}
- o método verificarSaldo é abstrato (não há implementação), então a assinatura do método termina com ponto e vírgula (;).
- a subclasse ContaCorrente está com erro de compilação devido à adição do método abstrato na classe Conta; portanto, implementar o método verificarSaldo na classe ContaCorrente:
public class ContaCorrente extends Conta{
private double limite;
@Override
public void sacar(double valor) throws SaldoInsuficienteException {
if (valor > saldo + limite){
throw new SaldoInsuficienteException();
}
saldo = saldo - valor;
}
@Override
public double verificarSaldo() {
return saldo + limite;
}
//Gets e Sets
}
- implementar a classe ContaPoupanca:
public class ContaPoupanca extends Conta {
@Override
public double verificarSaldo() {
return saldo;
}
}
- a classe ContaPoupanca é obrigada a implementar o método verificarSaldo, pois ela é concreta.
- porém, ela não foi obrigada a implementar o método sacar, pois esse método não é abstrato e está implementado na superclasse Conta!
- dessa forma, a classe ContaPoupanca possui o mesmo método sacar da classe Conta e implementa um comportamento específico no método verificarSaldo.
- trabalha de forma contrária em relação ao modificador abstract.
- uma classe Java marcada como final não pode ser estendida.
- exemplo: classe ContaPoupanca não pode possuir nenhuma subclasse.
public final class ContaPoupanca extends Conta {
@Override
public double verificarSaldo() {
return saldo;
}
}
- além das classes, também podemos utilizar a palavra-chave final em atributos, e seu valor será imutável (ou seja, não podemos alterar o valor do atributo durante o ciclo de vida do objeto).
- atributos declarados como final são chamados de atributos constantes e sua inicialização deve ser feita no momento da declaração!
public class Circulo {
private final double NUMERO_PI = 3.1416;
}
-
por convenção,
atributos constantes
devem ser criados com todas as letras em maiúsculas, e caso o nome seja composto por mais de uma palavra, separa-las pelo caractere underline. -
além de classes e atributos, podemos utilizar o modificador final também
em métodos
(não poderá ser sobrescrito).
public final double calcularArea(){
return NUMERO_PI*raio*raio;
}
- o modificador final é importante quando queremos que nenhum outro desenvolvedor estenda a nossa classe ou modifique o comportamento de um método em uma classe filha.
- é utilizado também para criar valores constantes.
- principais características da palavra-chave final, dependendo de onde está sendo utilizada:
Elemento | Comportamento |
---|---|
Classe | A classe não poderá ser estendida, ou seja, não possuirá subclasses. A classe String, por exemplo, é final. |
Método | O método não poderá ser sobrescrito. |
Atributo | O valor do atributo definido na sua declaração não poderá ser alterado. |
- pode ser aplicado aos membros de uma classe: métodos e atributos.
- é compartilhado por todas as instâncias de uma determinada classe.
- quando um atributo é declarado como estático, ele passa a se referir à Classe e não mais à instância da Classe, ou seja, o atributo será igual para todos os objetos, independentemente dos respectivos números de instâncias.
- por isso, se um objeto mudar o valor do atributo estático, todos os outros objetos terão acesso ao novo valor.
- um método marcado como estático não depende das características de cada objeto. Podem ser invocados sem precisar de uma instância da classe e são utilizados para realizar uma tarefa comum para todos os objetos:
public static void main(String[] args) {
// ...
}
- um método estático utiliza apenas informações contidas em seus parâmetros e nos atributos estáticos, ou seja, pode acessar somente atributos estáticos. Porém, o contrário é possível: pode-se acessar um atributo estático dentro de um método não estático.
public class AcessoCatraca {
private static int totalAcesso;
private String nome;
public void entrar(String nome){
this.nome = nome;
totalAcesso = totalAcesso + 1;
}
public static int recuperarTotal(){
return totalAcesso;
}
}
- um exemplo de utilização de métodos estáticos é a classe java.lang.Math, que possui vários métodos estáticos que auxiliam o dev nas operações matemáticas.
- para utilizar um método estático não é preciso de uma instância da classe:
public static void main(String[] args) {
int total = AcessoCatraca.recuperarTotal();
System.out.println("Total " + total);
long numero = Math.round(2.9);
System.out.println("Número arredondado: " + numero);
}
- o exemplo acima acessa o método estático recuperarTotal, da classe AcessoCatraca, e utiliza um método de arredondamento da classe Math.Em ambos os casos, não foi preciso instanciar suas respectivas classes.
- para acessar o método entrar, da classe AcessoCatraca, será preciso criar o objeto:
public static void main(String[] args) {
AcessoCatraca a1 = new AcessoCatraca();
a1.entrar("Thiago");
AcessoCatraca a2 = new AcessoCatraca();
a2.entrar("Leandro");
int total = AcessoCatraca.recuperarTotal();
System.out.println("Total " + total);
}
- Qual será o resultado da execução acima? O método entrar incrementa o valor do atributo estático totalAcesso (um atributo estático se refere à classe e não ao objeto, então compartilharãoo mesmo valor).
- como o método foi chamado duas vezes, independentemente do objeto, no final da execução o valor total será 2.
- é comum usar modificadores public, static e final ao declarar uma constante no Java.
- constantes podem ser:
- públicas (todos podem ter acesso),
- estáticas (pois não existe necessidade de cada objeto ter sua própria constante)
- e finais (para que o valor da constante nunca se altere).
- por convenção, o nome de uma constante é sempre escrito em maiúsculo, com as palavras separadas por underline.
- para acessar as constantes, utilizar o nome da classe.
public class Constantes {
public static final String JANEIRO = "Janeiro";
public static final double TAXA_RETIRADA = 10;
public static final int NUMERO_DIAS_SEMANA = 7;
public static final Estado SAO_PAULO = new Estado("São Paulo","SP");
}
public static void main(String[] args) {
System.out.println(Constantes.JANEIRO);
System.out.println(Constantes.TAXA_RETIRADA);
}
- uma interface define um conjunto de requisitos para as classes implementá-las. Porém,
interface não é classe
. - interface é um contato entre a classe e o mundo externo.
- quando uma classe implementa uma interface, está comprometida a fornecer todos os comportamentos definidos na interface.
- uma interface em Java não pode ser instanciada; também pode ser composta de atributos e métodos, porém, como não é instanciada, não apresenta construtores.
- a plataforma Java não permite herança múltipla: não é possível herdar duas classes diretamente. Porém,
uma classe Java pode implementar uma ou mais interfaces
, devendo definir todos os métodos publicados por todas as interfaces implementadas. - uma classe abstrata não é obrigada a implementar todos os métodos definidos na interface. Porém, em uma classe concreta, é obrigatório.
- todos os atributos e métodos de uma interface são implicitamente públicos. Todo atributo em uma interface é implicitamente público, final e estático (Constante). Como esses qualificadores são fixos, não precisamos declará-los.
public interface Autenticavel {
String MSG_LOGOUT = "Saindo";
boolean login(String usuario, String senha);
void logout();
}
public class Usuario implements Autenticavel {
@Override
public boolean login(String usuario, String senha) {
// TODO Auto-generated method stub
return false;
}
@Override
public void logout() {
// TODO Auto-generated method stub
}
}
- como a classe Usuario implementa a interface Autenticavel, ela deve implementar todos os métodos definidos pela interface, senão teremos um erro de compilação.
se uma classe implementar mais de uma interface, devemos separá-las por vírgula
.- e se uma classe implementar uma ou mais interfaces e estender uma classe, primeiro precisamos definir a herança:
public class Usuario extends Pessoa implements Autenticavel {
// Código...
}
- sempre utilizar interfaces quando for preciso prover operações comuns a classes de hierarquia diferentes!!!
- utilizar interfaces permite o uso do polimorfismo. Por exemplo, duas classes que implementam uma mesma interface podem ser atribuídas a uma variável do tipo da Interface. Porém, a execução dependerá do tipo do objeto que essa variável armazena.
- podemos ter métodos concretos em uma interface, utilizando o modificador default; esses métodos serão herdados por todas as classes que implementarem a interface, não sendo necessário implementá-la na classe (as classes que implementarem a interface já receberão o método, sem precisar implementá-lo).
- Static é outro método que possui implementação em uma interface.
- a diferença entre o método default e o método estático é que o estático pertence à interface e não pode ser sobrescrito (para executar o método estático, devemos referenciar a interface e não a classe ou oobjeto).
- um default method pode ser sobrescrito em uma classe e precisa de uma de suas instâncias para ser executada.
- em uma interface, podemos definir todos os tipos de métodos em conjunto: abstratos (sem implementação, que deverá ser implementado na classe), métodos defaults e métodos estáticos.
- podemos utilizar o operador
instance of
para verificar se um objeto é do tipo de uma interface ou de uma classe (herança). Esse operador retorna true se o objeto à esquerda do operador é do tipo da classe ou interface especificada à direita do operador.
1. Sobre a exceção NullPointerException, selecione a alternativa correta que representa o cenário em que ela pode acontecer.
Ocorre quando se tenta acessar, em tempo de execução, o conteúdo de uma variável ou a propriedade de objeto que ainda não tenha sido instanciado na memória.
Checked, Unchecked e Error.
Realiza a gravação de um arquivo e realiza a leitura de um arquivo.
Pode ser tratada pelo desenvolvedor ou relançada para o fluxo de execução.
5. Através do polimorfismo é possível estender classes e reaproveitar funcionalidades já implementadas. Dentro desse conceito, qual alternativa a seguir descreve a função de uma classe abstrata na linguagem Java.
A classe abstrata não pode ser instanciada diretamente, ou seja, não pode ser utilizada para a criação de novos objetos na memória.
6. Em relação aos famosos blocos try-catch e try-catch-finally, no ambiente de desenvolvimento Java, podemos afirmar, verdadeiramente, que:
São estruturas de captura e tratamento das exceções de tipo Checked e Unchecked.
É uma exceção lançada, normalmente, quando se está lendo ou gravando dados em um arquivo.
8. Escolha a alternativa que representa a principal diferença entre uma classe e uma interface no ambiente Java.
A interface é um contrato estabelecido com o objetivo de definir requisitos às classes que forem implementá-lo.
9. Selecione a alternativa que representa o fluxo convencional de captura de uma exceção no ambiente de desenvolvimento utilizando a linguagem Java.
Exceção lançada, exceção capturada e tratamento realizado.