Olá! Odeio dizer isso a você, mas uma grande parte do trabalho de um programador é lidar com erros. E na maioria das vezes - com os seus próprios. Acontece que não existem pessoas que não cometam erros. E também não existem tais programas. Claro, o principal ao trabalhar com um erro é entender sua causa. E pode haver uma série de razões para isso no programa. A certa altura, os criadores do Java se depararam com uma pergunta: o que fazer com esses possíveis erros nos programas? Evitá-los completamente não é realista. Os programadores podem escrever algo que é impossível imaginar :) Isso significa que é necessário incorporar na linguagem um mecanismo para lidar com erros. Em outras palavras, se ocorreu algum erro no programa, um script é necessário para trabalhos futuros. O que exatamente o programa deve fazer quando ocorre um erro? Hoje conheceremos esse mecanismo. E é chamado de “Exceções ” .
O que é uma exceção em Java
Uma exceção é alguma situação excepcional e não planejada que ocorreu durante a operação do programa. Pode haver muitos exemplos de exceções em Java. Por exemplo, você escreveu um código que lê texto de um arquivo e exibe a primeira linha no console.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 tal arquivo não existe! O resultado do programa será uma exceção - FileNotFoundException
. Conclusão:
Exception in thread "main" java.io.FileNotFoundException: C:\Users\Username\Desktop\test.txt (Системе не удается найти указанный путь)
Cada exceção é representada por uma classe separada em Java. Todas as classes de exceção vêm de um “ancestral” comum – a classe pai Throwable
. O nome da classe de exceção geralmente reflete brevemente o motivo de sua ocorrência:
FileNotFoundException
(arquivo não encontrado)ArithmeticException
(exceção ao realizar uma operação matemática)ArrayIndexOutOfBoundsException
(o número da célula da matriz é especificado além do seu comprimento). Por exemplo, se você tentar exibir cell array[23] no console para um array array de comprimento 10.
Exception in thread "main"
Uh-uh :/ Nada está claro. Que tipo de erro é e de onde veio não está claro. Não há informações úteis. Mas graças a essa variedade de classes, o programador obtém para si o principal - o tipo de erro e sua causa provável, que está contida no nome da classe. Afinal, é algo completamente diferente de se ver no console:
Exception in thread "main" java.io.FileNotFoundException: C:\Users\Username\Desktop\test.txt (Системе не удается найти указанный путь)
Imediatamente fica claro qual pode ser o problema e “em que direção cavar” para resolvê-lo! As exceções, como quaisquer instâncias de classes, são objetos.
Capturando e tratando exceções
Para trabalhar com exceções em Java, existem blocos de código especiais:try
, catch
e finally
. O código no qual o programador espera que ocorram exceções é colocado em um bloco try
. Isso não significa que necessariamente ocorrerá uma exceção neste local. Isso significa que isso pode acontecer ali e o programador está ciente disso. O tipo de erro que você espera receber é colocado em um bloco catch
(“catch”). É aqui também que é colocado todo o código que precisa 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!");
}
}
Conclusão:
Ошибка! Файл не найден!
Colocamos nosso código em dois blocos. No primeiro bloco esperamos que possa ocorrer um erro “Arquivo não encontrado”. Isto é um bloco try
. Na segunda, informamos ao programa o que fazer se ocorrer um erro. Além disso, existe um tipo específico de erro - FileNotFoundException
. Se passarmos catch
outra classe de exceção entre colchetes de bloco, ela 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!");
}
}
Conclusão:
Exception in thread "main" java.io.FileNotFoundException: C:\Users\Username\Desktop\test.txt (Системе не удается найти указанный путь)
O código no bloco catch
não funcionou porque “configuramos” esse bloco para interceptar ArithmeticException
, e o código no bloco try
gerou outro tipo - FileNotFoundException
. Não escrevemos um script para FileNotFoundException
, então o programa exibiu no console as informações que são exibidas por padrão para FileNotFoundException
. Aqui você precisa prestar atenção em 3 coisas. Primeiro. Assim que ocorrer uma exceção em qualquer linha de código em um bloco try, o código seguinte não será mais executado. A execução do programa “saltará” imediatamente para o bloco catch
. Por exemplo:
public static void main(String[] args) {
try {
System.out.println("Divide a number 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("Not");
System.out.println("will");
System.out.println("done!");
} catch (ArithmeticException e) {
System.out.println("The program jumped to the catch block!");
System.out.println("Error! You can't divide by zero!");
}
}
Conclusão:
Делим число на ноль
Программа перепрыгнула в блок catch!
Ошибка! Нельзя делить на ноль!
No bloco try
da segunda linha, tentamos dividir um número por 0, o que resultou em uma exceção ArithmeticException
. Depois disso, as linhas 6 a 10 do bloco try
não serão mais executadas. Como dissemos, o programa começou imediatamente a executar o bloco catch
. Segundo. Pode haver vários blocos catch
. Se o código em um bloco try
puder lançar não um, mas vários tipos de exceções, você poderá escrever seu próprio bloco para cada um deles catch
.
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 blocos catch
. Se , try
ocorrer no bloco FileNotFoundException
, o primeiro bloco será executado catch
. Se acontecer ArithmeticException
, o segundo será executado. Você pode escrever pelo menos 50 blocos catch
... Mas, claro, é melhor não escrever código que possa gerar 50 tipos diferentes de erros :) Terceiro. Como você sabe quais exceções seu código pode gerar? Bem, é claro que você pode adivinhar alguns, mas é impossível manter tudo na cabeça. Portanto, o compilador Java conhece as exceções mais comuns e sabe em quais situações elas podem ocorrer. Por exemplo, se você escreveu código e o compilador sabe que 2 tipos de exceções podem ocorrer durante sua operação, seu código não será compilado até que você os trate. Veremos exemplos disso a seguir. Agora, com relação ao tratamento de exceções. Existem 2 maneiras de processá-los. Já conhecemos o primeiro - o método pode tratar a exceção de forma independente no bloco catch()
. Existe uma segunda opção - o método pode gerar uma exceção na pilha de chamadas. O que isso significa? Por exemplo, em nossa classe temos um método - o mesmo printFirstString()
- que lê um arquivo e exibe sua primeira linha no console:
public static void printFirstString(String filePath) {
BufferedReader reader = new BufferedReader(new FileReader(filePath));
String firstString = reader.readLine();
System.out.println(firstString);
}
Atualmente nosso código não compila porque possui exceções não tratadas. Na linha 1 você indica o caminho para o arquivo. O compilador sabe que esse código pode facilmente levar a arquivos FileNotFoundException
. Na linha 3 você lê o texto do arquivo. IOException
Neste processo , pode ocorrer facilmente um erro durante a entrada-saída (Entrada-Saída). Agora o compilador está lhe dizendo: “Cara, não vou aprovar este código nem compilá-lo até que você me diga o que devo fazer se uma dessas exceções ocorrer. E eles definitivamente podem acontecer com base no código que você escreveu!” . Não há para onde ir, você precisa processar os dois! A primeira opção de processamento já nos é familiar: precisamos colocar nosso código em um bloco try
e adicionar dois blocos catch
:
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("Error while inputting/outputting data from file!");
e.printStackTrace();
}
}
Mas esta não é a única opção. Podemos evitar escrever um script para o erro dentro do método e simplesmente lançar a exceção para o topo. Isso é feito usando a palavra-chave throws
, que está escrita 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 throws
listamos, separados por vírgulas, todos os tipos de exceções que este método pode lançar durante a operação. Por que isso está sendo feito? Agora, se alguém no programa quiser chamar o método printFirstString()
, ele mesmo terá que implementar o tratamento de exceções. Por exemplo, em outra parte do programa, um de seus colegas escreveu um método dentro do qual chama seu método printFirstString()
:
public static void yourColleagueMethod() {
//...your colleague's method does something
//...and at one moment calls your printFirstString() method with the file it needs
printFirstString("C:\\Users\\Eugene\\Desktop\\testFile.txt");
}
Erro, o código não compila! printFirstString()
Não escrevemos um script de tratamento de erros no método . Portanto, a tarefa recai sobre quem utilizará esse método. Ou seja, o método yourColleagueMethod()
agora enfrenta as mesmas 2 opções: ele deve processar ambas as exceções que “voaram” para ele usando try-catch
, ou encaminhá-las posteriormente.
public static void yourColleagueMethod() throws FileNotFoundException, IOException {
//...the method does something
//...and at one moment calls your printFirstString() method with the file it needs
printFirstString("C:\\Users\\Eugene\\Desktop\\testFile.txt");
}
No segundo caso, o processamento recairá sobre os ombros do próximo método na pilha - aquele que chamará yourColleagueMethod()
. É por isso que tal mecanismo é chamado de “lançar uma exceção para cima” ou “passar para o topo”. Quando você lança exceções usando throws
, o código é compilado. Neste momento, o compilador parece dizer: “Ok, ok. Seu código contém várias exceções em potencial, mas vou compilá-lo mesmo assim. Voltaremos a esta conversa!” E quando você chama um método em algum lugar do programa que não tratou suas exceções, o compilador cumpre sua promessa e o lembra delas novamente. Por fim, falaremos sobre o bloqueio finally
(desculpem o trocadilho). Esta é a última parte do triunvirato de tratamento de exceções try-catch-finally
. Sua peculiaridade é que pode ser executado em qualquer cenário de operação do programa.
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 is the finally block!");
}
}
Neste exemplo, o código dentro do bloco finally
é executado em ambos os casos. Se o código do bloco try
for executado inteiramente e não lançar uma exceção, o bloco será acionado no final finally
. Se o código interno try
for interrompido e o programa pular para o bloco catch
, após a execução do código interno catch
, o bloco ainda estará selecionado finally
. Por que é necessário? Seu principal objetivo é executar a parte necessária do código; aquela parte que deve ser concluída independentemente das circunstâncias. Por exemplo, muitas vezes libera alguns recursos usados pelo programa. Em nosso código, abrimos um stream para ler informações de um arquivo e passá-las para um arquivo BufferedReader
. O nosso reader
precisa ser fechado e os recursos liberados. Isso deve ser feito em qualquer caso: não importa se o programa funciona conforme o esperado ou lança uma exceção. É conveniente fazer isso em um bloco finally
:
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 is the finally block!");
if (reader != null) {
reader.close();
}
}
}
Agora temos certeza absoluta de que cuidamos dos recursos ocupados, 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 muito importante em programação: mais de um artigo é dedicado a ele. Na próxima lição aprenderemos quais tipos de exceções existem e como criar sua própria exceção :) Nos vemos lá!
GO TO FULL VERSION