JavaRush /Java 博客 /Random-ZH /Java中的异常:捕获和处理

Java中的异常:捕获和处理

已在 Random-ZH 群组中发布
你好!我不想告诉你,但程序员工作的很大一部分就是处理错误。最常见的是 - 与他们自己的。偏偏没有不犯错误的人。而且也没有这样的程序。当然,处理错误时最重要的是了解其原因。程序中可能有很多原因。Java 的创建者一度面临一个问题:如何处理程序中这些非常潜在的错误?完全避免它们是不现实的。程序员可以编写一些无法想象的东西:)这意味着有必要在语言中构建一种处理错误的机制。换句话说,如果程序中出现错误,则需要脚本来进行进一步的工作。当发生错误时程序到底应该做什么?今天我们就来熟悉一下这个机制。这就是所谓的“例外

Java中什么是异常

例外是程序运行过程中发生的一些异常的、计划外的情况。 Java 中有很多异常的例子。例如,您编写了一段从文件中读取文本并将第一行显示到控制台的代码。
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);
   }
}
但这样的文件不存在!程序的结果将是一个异常 - FileNotFoundException。结论:

Exception in thread "main" java.io.FileNotFoundException: C:\Users\Username\Desktop\test.txt (Системе не удается найти указанный путь)
在 Java 中,每个异常都由一个单独的类表示。所有异常类都来自一个共同的“祖先”——父类Throwable。异常类的名称通常简要反映其发生的原因:
  • FileNotFoundException(文件未找到)
  • ArithmeticException(执行数学运算时例外)
  • ArrayIndexOutOfBoundsException(数组单元的数量指定超出其长度)。例如,如果您尝试将长度为 10 的数组 array 输出到控制台。
Java 中有近 400 个这样的类!为什么这么多?正是为了让程序员更方便的使用它们。想象一下:您编写了一个程序,当它运行时,它抛出一个如下所示的异常:
Exception in thread "main"
呃呃:/什么都不清楚。目前还不清楚它是什么类型的错误以及它来自哪里。没有有用的信息。但由于类的种类繁多,程序员自己可以得到主要的信息 - 错误的类型及其可能的原因,这些信息包含在类的名称中。毕竟,在控制台中看到的是完全不同的东西:
Exception in thread "main" java.io.FileNotFoundException: C:\Users\Username\Desktop\test.txt (Системе не удается найти указанный путь)
问题可能是什么以及“往哪个方向挖掘”来解决问题立刻就清楚了!与任何类实例一样,异常也是对象。

捕获和处理异常

为了在 Java 中处理异常,有一些特殊的代码块:trycatchfinally例外:拦截和处理 - 2程序员期望发生异常的代码被放置在一个块中try。这并不意味着该位置一定会发生异常。这意味着它可能在那里发生,并且程序员意识到了这一点。您期望收到的错误类型被放置在一个块中catch(“catch”)。这也是发生异常时需要执行的所有代码的放置位置。这是一个例子:
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!");
   }
}
结论:

Ошибка! Файл не найден!
我们将代码放在两个块中。在第一个块中,我们预计可能会出现“文件未找到”错误。这是一个块try。在第二个中,我们告诉程序如果发生错误该怎么办。此外,还有一种特定类型的错误 - FileNotFoundException。如果我们将catch另一个异常类传递到块括号中,它将不会被捕获。
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!");
   }
}
结论:

Exception in thread "main" java.io.FileNotFoundException: C:\Users\Username\Desktop\test.txt (Системе не удается найти указанный путь)
该块中的代码catch不起作用,因为我们“配置”了该块以拦截ArithmeticException,并且该块中的代码try抛出了另一种类型 - FileNotFoundException。我们没有为编写脚本FileNotFoundException,因此程序在控制台中显示的是默认显示的信息FileNotFoundException。这里需要注意3件事。 第一的。 一旦try块中的任何一行代码发生异常,则该行后面的代码将不再被执行。程序的执行会立即“跳转”到该块catch。例如:
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!");
   }
}
结论:

Делим число на ноль 
Программа перепрыгнула в блок catch! 
Ошибка! Нельзя делить на ноль! 
在第二行的块中try,我们尝试将数字除以 0,这导致了异常ArithmeticException。此后,该块的第 6-10 行将try不再执行。正如我们所说,程序立即开始执行该块catch第二。 可以有多个块catch。如果块中的代码try可以抛出不止一种而是多种类型的异常,那么您可以为每种异常编写自己的块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!");

   }
}
在这个例子中我们写了两个块catch。如果,try出现在块中FileNotFoundException,则将执行第一个块catch。如果发生ArithmeticException,第二个将被执行。你可以编写至少 50 个块catch。但是,当然,最好不要编写可能引发 50 种不同类型错误的代码:) 第三。 您如何知道您的代码可能会抛出哪些异常?嗯,当然,你可以猜测一些,但不可能把所有事情都记在脑子里。因此,Java 编译器了解最常见的异常,并知道它们在什么情况下会发生。例如,如果您编写了代码,并且编译器知道在其运行过程中可能会出现两种类型的异常,则在您处理它们之前,您的代码将不会编译。我们将在下面看到这样的例子。现在关于异常处理。有两种方法可以处理它们。我们已经遇到了第一个——该方法可以在块中独立处理异常catch()。还有第二个选项 - 该方法可以在调用堆栈上抛出异常。这是什么意思?例如,在我们的类中,我们有一个方法 - 相同的方法printFirstString()- 读取文件并将其第一行显示到控制台:
public static void printFirstString(String filePath) {

   BufferedReader reader = new BufferedReader(new FileReader(filePath));
   String firstString = reader.readLine();
   System.out.println(firstString);
}
目前我们的代码无法编译,因为它有未处理的异常。在第 1 行指定文件的路径。编译器知道这样的代码很容易导致FileNotFoundException. 在第 3 行,您从文件中读取文本。在这个过程中,IOException输入输出数据(Input-Output)时很容易出现错误。现在编译器告诉你,“伙计,我不会批准这段代码或编译它,除非你告诉我如果发生这些异常之一我应该做什么。根据你编写的代码,它们肯定会发生!” 。无处可去,你需要处理两者!第一个处理选项我们已经很熟悉了:我们需要将代码放在一个块中try并添加两个块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();
   }
}
但这不是唯一的选择。我们可以避免为方法内部的错误编写脚本,而只需将异常抛出到顶部。这是使用关键字 完成的throws,该关键字写在方法声明中:
public static void printFirstString(String filePath) throws FileNotFoundException, IOException {
   BufferedReader reader = new BufferedReader(new FileReader(filePath));
   String firstString = reader.readLine();
   System.out.println(firstString);
}
在该单词之后,throws我们列出了该方法在操作期间可能抛出的所有类型的异常,并用逗号分隔。为什么要这样做?现在,如果程序中的某人想要调用该方法printFirstString(),他将必须自己实现异常处理。例如,在程序的另一部分中,您的一位同事编写了一个方法,在该方法中调用您的方法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");
}
错误,代码无法编译!printFirstString()我们没有在方法中编写错误处理脚本。因此,这个任务就落在了使用这种方法的人的肩上。也就是说,该方法yourColleagueMethod()现在面临相同的两个选项:它必须处理使用“飞向”它的两个异常try-catch,或者进一步转发它们。
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");
}
在第二种情况下,处理将落在堆栈上下一个方法的肩上 - 将调用 的方法yourColleagueMethod()。这就是为什么这样的机制被称为“向上抛出异常”,或者“传递到顶部”。当您使用 引发异常时throws,代码将进行编译。这一刻,编译器似乎在说:“好吧,好吧。您的代码包含一堆潜在的异常,但无论如何我都会编译它。我们会回来讨论这个话题的!” 当您在程序中的某个位置调用未处理异常的方法时,编译器会履行其承诺并再次提醒您。最后,我们将讨论块finally(请原谅双关语)。这是异常处理三巨头的最后一部分try-catch-finally。它的特点是可以在任何程序运行场景下执行。
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!");
   }
}
在此示例中,块内的代码finally在两种情况下都会执行。如果块中的代码try被完全执行并且没有抛出异常,则该块将在最后触发finally。如果里面的代码try被中断,程序跳转到该块catch,执行完里面的代码后catch,该块仍然会被选择finally。为什么需要它?它的主要目的是执行代码所需的部分;无论情况如何都必须完成的部分。例如,它经常释放程序使用的一些资源。在我们的代码中,我们打开一个流来从文件中读取信息并将其传递给BufferedReader. 我们reader需要关闭并释放资源。在任何情况下都必须这样做:程序是否按预期工作或抛出异常并不重要。在块中执行此操作很方便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();
       }
   }
}
现在我们绝对确定我们已经处理了占用的资源,无论程序运行时发生什么:) 这并不是您需要了解的有关异常的全部信息。错误处理是编程中一个非常重要的主题:不止一篇文章专门讨论它。在下一课中,我们将学习有哪些类型的异常以及如何创建您自己的异常:) 再见!
评论
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION