Oi! Odeio mencionar isso, mas uma grande parte do trabalho de um programador é lidar com erros. Na maioria das vezes, o próprio. Acontece que não há quem não erre. E também não existem tais programas. Claro, ao lidar com um erro, o principal é entender sua causa. E muitas coisas podem causar bugs em um programa. Em algum momento, os criadores do Java se perguntaram o que deveria ser feito com os erros de programação mais prováveis? Evitá-los totalmente não é realista, os programadores são capazes de escrever coisas que você nem imagina. :) Então, precisamos dar à linguagem um mecanismo para trabalhar com erros. Em outras palavras, se houver um erro em seu programa, você precisará de algum tipo de script para saber o que fazer a seguir. O que exatamente um programa deve fazer quando ocorre um erro? Hoje vamos nos familiarizar com esse mecanismo. É chamado de " exceções em Java ".
O que é uma exceção?
Uma exceção é uma situação excepcional e não planejada que ocorre durante a execução de um programa. Existem muitas exceções. Por exemplo, você escreveu um código que lê o texto de um arquivo e exibe a primeira linha.
public class Main {
public static void main(String[] args) throws IOException {
BufferedReader reader = new BufferedReader(new FileReader("C:\\Users\\Username\\Desktop\\test.txt"));
String firstString = reader.readLine();
System.out.println(firstString);
}
}
Mas e se não houver tal arquivo! O programa irá gerar uma exceção: FileNotFoundException
. Saída: Exceção no encadeamento "principal" java.io.FileNotFoundException: C:\Users\Username\Desktop\test.txt (O sistema não pode encontrar o caminho especificado) Em Java, cada exceção é representada por uma classe separada. Todas essas classes de exceção derivam de um "ancestral" comum - a Throwable
classe pai. O nome de uma classe de exceção geralmente reflete de forma concisa por que a exceção ocorreu:
FileNotFoundException
(o arquivo não foi encontrado)ArithmeticException
(ocorreu uma exceção durante a execução de uma operação matemática)ArrayIndexOutOfBoundsException
(o índice está além dos limites da matriz). Por exemplo, esta exceção ocorre se você tentar exibir a posição 23 de uma matriz que possui apenas 10 elementos.
Exception in thread "main"
Uhhhh. :/ Isso não ajuda muito. Não está claro o que o erro significa ou de onde veio. Não há informações úteis aqui. Mas a grande variedade de classes de exceção em Java dá ao programador o que mais importa: o tipo de erro e sua causa provável (embutida no nome da classe). É outra coisa para ver
Exception in thread "main" java.io.FileNotFoundException: C:\Users\Username\Desktop\test.txt (The system cannot find the specified path)
Fica imediatamente claro qual pode ser o problema e onde começar a cavar para resolvê-lo! Exceções, como instâncias de quaisquer classes, são objetos.
Capturando e tratando exceções
Java possui blocos especiais de código para trabalhar com exceções:try
, catch
e finally
. O código onde o programador acredita que pode ocorrer uma exceção é colocado no try
bloco. Isso não significa que uma exceção ocorrerá aqui. Isso significa que pode ocorrer aqui, e o programador está ciente dessa possibilidade. O tipo de erro que você espera que ocorra é colocado no catch
bloco. Isso também contém todo o código que deve ser executado se ocorrer uma exceção. Aqui está um exemplo:
public static void main(String[] args) throws IOException {
try {
BufferedReader reader = new BufferedReader(new FileReader("C:\\Users\\Username\\Desktop\\test.txt"));
String firstString = reader.readLine();
System.out.println(firstString);
} catch (FileNotFoundException e) {
System.out.println("Error! File not found!");
}
}
Saída: Erro! Arquivo não encontrado! Colocamos nosso código em dois blocos. No primeiro bloco, antecipamos que pode ocorrer um erro "Arquivo não encontrado". Este é o try
bloco. Na segunda, dizemos ao programa o que fazer se ocorrer um erro. E o tipo de erro específico: FileNotFoundException
. Se colocarmos uma classe de exceção diferente entre parênteses do catch
bloco, ela FileNotFoundException
não será capturada.
public static void main(String[] args) throws IOException {
try {
BufferedReader reader = new BufferedReader(new FileReader("C:\\Users\\Username\\Desktop\\test.txt"));
String firstString = reader.readLine();
System.out.println(firstString);
} catch (ArithmeticException e) {
System.out.println("Error! File not found!");
}
}
Saída: Exceção no thread "main" java.io.FileNotFoundException: C:\Users\Username\Desktop\test.txt (O sistema não pode encontrar o caminho especificado) O código no catch
bloco não foi executado porque nós "configuramos" este bloco para catch ArithmeticException
, e o código no try
bloco lançou um tipo diferente: FileNotFoundException
. Não escrevemos nenhum código para manipular FileNotFoundException
, então o programa exibe as informações padrão para FileNotFoundException
. Aqui você precisa prestar atenção a três coisas. Número um. Assim que ocorrer uma exceção em alguma linha do try
bloco, o código a seguir não será executado. A execução do programa "pula" imediatamente para o catch
bloco. Por exemplo:
public static void main(String[] args) {
try {
System.out.println("Divide by zero");
System.out.println(366/0);// This line of code will throw an exception
System.out.println("This");
System.out.println("code");
System.out.println("will not");
System.out.println("be");
System.out.println("executed!");
} catch (ArithmeticException e) {
System.out.println("The program jumped to the catch block!");
System.out.println("Error! You can't divide by zero!");
}
}
Saída: Dividir por zero O programa saltou para o bloco catch! Erro! Você não pode dividir por zero! Na segunda linha do try
bloco, tentamos dividir por 0, resultando em um ArithmeticException
. Consequentemente, as linhas 3-9 do try
bloco não serão executadas. Como dissemos, o programa imediatamente começa a executar o catch
bloco. Número dois. Pode haver vários catch
blocos. Se o código no try
bloco pode lançar não um, mas vários tipos diferentes de exceções, você pode escrever um catch
bloco para cada um deles.
public static void main(String[] args) throws IOException {
try {
BufferedReader reader = new BufferedReader(new FileReader("C:\\Users\\Username\\Desktop\\test.txt"));
System.out.println(366/0);
String firstString = reader.readLine();
System.out.println(firstString);
} catch (FileNotFoundException e) {
System.out.println("Error! File not found!");
} catch (ArithmeticException e) {
System.out.println("Error! Division by 0!");
}
}
Neste exemplo, escrevemos dois catch
blocos. Se FileNotFoundException
ocorrer um no try
bloco, o primeiro catch
bloco será executado. Se ArithmeticException
ocorrer um, o segundo bloco será executado. Você poderia escrever 50 catch
blocos se quisesse. Obviamente, é melhor não escrever código que possa lançar 50 tipos diferentes de exceções. :) Terceiro. Como você sabe quais exceções seu código pode lançar? Bem, você pode adivinhar alguns deles, mas é impossível para você manter tudo em sua cabeça. O compilador Java, portanto, conhece as exceções mais comuns e as situações em que elas podem ocorrer. Por exemplo, se você escrever um código que o compilador sabe que pode gerar dois tipos de exceções, seu código não será compilado até que você os trate. Veremos exemplos disso a seguir. Agora algumas palavras sobre tratamento de exceções. Existem 2 maneiras de lidar com exceções. Já encontramos a primeira: o próprio método pode tratar a exceção em um catch()
bloco. Existe uma segunda opção: o método pode lançar novamente a exceção na pilha de chamadas. O que isso significa? Por exemplo, temos uma classe com o mesmo printFirstString()
método, que lê um arquivo e exibe sua primeira linha:
public static void printFirstString(String filePath) {
BufferedReader reader = new BufferedReader(new FileReader(filePath));
String firstString = reader.readLine();
System.out.println(firstString);
}
No momento, nosso código não compila, pois possui exceções não tratadas. Na linha 1, você especifica o caminho para o arquivo. O compilador sabe que tal código poderia facilmente produzir um arquivo FileNotFoundException
. Na linha 3, você lê o texto do arquivo. Este processo pode facilmente resultar em um IOException
(erro de entrada/saída). Agora o compilador diz para você: "Cara, não vou aprovar esse código e não vou compilá-lo até que você me diga o que devo fazer se uma dessas exceções ocorrer. E elas certamente podem acontecer com base no código que você escreveu !" Não tem como contornar: você precisa dar conta dos dois! Já conhecemos o primeiro método de tratamento de exceção: precisamos colocar nosso código em um try
bloco e adicionar dois catch
blocos:
public static void printFirstString(String filePath) {
try {
BufferedReader reader = new BufferedReader(new FileReader(filePath));
String firstString = reader.readLine();
System.out.println(firstString);
} catch (FileNotFoundException e) {
System.out.println("Error, file not found!");
e.printStackTrace();
} catch (IOException e) {
System.out.println("File input/output error!");
e.printStackTrace();
}
}
Mas esta não é a única opção. Poderíamos simplesmente lançar a exceção mais alto em vez de escrever o código de tratamento de erros dentro do método. Isso é feito usando a palavra-chave throws
na declaração do método:
public static void printFirstString(String filePath) throws FileNotFoundException, IOException {
BufferedReader reader = new BufferedReader(new FileReader(filePath));
String firstString = reader.readLine();
System.out.println(firstString);
}
Após a palavra-chave throws
, indicamos uma lista separada por vírgulas de todos os tipos de exceções que o método pode lançar. Por que? Agora, se alguém quiser chamar o printFirstString()
método no programa, ele ou ela (não você) terá que implementar o tratamento de exceção. Por exemplo, suponha que em algum lugar do programa um de seus colegas escreveu um método que chama seu printFirstString()
método:
public static void yourColleagueMethod() {
// Your colleague's method does something
//...and then calls your printFirstString() method with the file it needs
printFirstString("C:\\Users\\Henry\\Desktop\\testFile.txt");
}
Recebemos um erro! Este código não compila! Não escrevemos código de tratamento de exceção no printFirstString()
método. Com isso, essa tarefa agora recai sobre os ombros de quem utiliza o método. Em outras palavras, o methodWrittenByYourColleague()
método agora tem as mesmas 2 opções: ele deve usar um try-catch
bloco para lidar com ambas as exceções ou lançá-las novamente.
public static void yourColleagueMethod() throws FileNotFoundException, IOException {
// The method does something
//...and then calls your printFirstString() method with the file it needs
printFirstString("C:\\Users\\Henry\\Desktop\\testFile.txt");
}
No segundo caso, o próximo método na pilha de chamadas — aquele que está chamando methodWrittenByYourColleague()
— terá que lidar com as exceções. É por isso que chamamos isso de "lançar ou passar a exceção". Se você lançar exceções usando a palavra-chave throws
, seu código será compilado. Nesse ponto, o compilador parece estar dizendo: "Ok, ok. Seu código contém várias exceções em potencial, mas vou compilá-lo. Mas voltaremos a esta conversa!" E quando você chama qualquer método que tenha exceções não tratadas, o compilador cumpre sua promessa e o lembra novamente. Por fim, falaremos sobre o finally
bloqueio (desculpe o trocadilho). Esta é a última parte do try-catch-finally
triunvirato de tratamento de exceções..
public static void main(String[] args) throws IOException {
try {
BufferedReader reader = new BufferedReader(new FileReader("C:\\Users\\Username\\Desktop\\test.txt"));
String firstString = reader.readLine();
System.out.println(firstString);
} catch (FileNotFoundException e) {
System.out.println("Error! File not found!");
e.printStackTrace();
} finally {
System.out.println ("And here's the finally block!");
}
}
Neste exemplo, o código dentro do finally
bloco será executado em ambos os casos. Se o código no try
bloco for executado por completo sem lançar nenhuma exceção, o finally
bloco será executado no final. Se o código dentro do try
bloco for interrompido por uma exceção e o programa pular para o catch
bloco, o finally
bloco ainda será executado após o código dentro do catch
bloco. Por que isso é necessário? Seu principal objetivo é executar código obrigatório: código que deve ser executado independentemente das circunstâncias. Por exemplo, muitas vezes libera alguns recursos usados pelo programa. Em nosso código, abrimos um stream para ler as informações do arquivo e passá-las para o BufferedReader
objeto. Devemos fechar nosso leitor e liberar os recursos. Isso deve ser feito não importa o que aconteça - quando o programa funcionar como deveria e quando lançar uma exceção. O finally
bloco é um local muito conveniente para fazer isso:
public static void main(String[] args) throws IOException {
BufferedReader reader = null;
try {
reader = new BufferedReader(new FileReader("C:\\Users\\Username\\Desktop\\test.txt"));
String firstString = reader.readLine();
System.out.println(firstString);
} catch (FileNotFoundException e) {
e.printStackTrace();
} finally {
System.out.println ("And here's the finally block!");
if (reader != null) {
reader.close();
}
}
}
Agora temos a certeza de que cuidaremos dos recursos, independentemente do que aconteça durante a execução do programa. :) Isso não é tudo que você precisa saber sobre exceções. O tratamento de erros é um tópico super importante na programação. Muitos artigos são dedicados a ele. Na próxima lição, descobriremos quais tipos de exceções existem e como criar suas próprias exceções. :) Vejo você então!
GO TO FULL VERSION