Java 的异常与处理
关于异常
在软件开发的过程中, 开发者往往要需要考虑很多在开发过程中不会出现, 但是在实际使用时却又会实实在在出现的, 导致程序无法正常执行的情况, 比如:
1. 计算人体的BMI值, 但用户在输入身高的时候, 输入了 0, 我们知道0是不能做除数的.
2. 用户使用的时候需要网络, 却忽然没有了网络信号.
3. 用户的硬件中的内存不足以支撑当前软件的运行崩溃了.
所有这些现实中有概率会出现的, 影响用户使用的, 非正常的使用情况, 被归为两类:
一类叫 错误(Error
)
这是系统或更高级别限制导致的, 程序无能为力应对的, 最终可能会导致程序崩溃的情况.
比如: 内存不足以支撑当前软件的运行了.
另一类叫 异常(Exception
)
这是程序可以捕获辨别输入的, 但却和预想的输入情况不一致的.
比如: 需要有网络的时候, 没网络信号; 需要导航的时候, 没GPS信号; 需要用户输入数字, 用户却输入了 "abc" 字母.
在 Java 中它们均继承自 Throwable
类, 而 Exception
类又被分为两类:
一类是 运行时异常(RuntimeException
)
此类异常, 是应该在获取输入的时候, 开发人员就该考虑到的, 不该忽略的, 易于检测的, 不需要确认的异常. 所以这些异常不是强制捕获或在方法生声明外抛(抛出给调用者)的.
比如:
ArithmeticException
: 做除法前, 没能判断一个数字能否作为除数
IndexOutOfBoundsException
: 使用数组前, 没能判断索引是否超过数组的边界
另一类是 非运行时异常(除RuntimeException
以外的异常, 包括但不限于 IOException
, DataFormatException
等)
此类异常又叫 确认异常(Checked Exception), 是需要必须确认捕获或在方法上声明外抛的. 倘若不做异常捕获或抛出, 那么在执行时会报编译错误:
Exception in thread "main" java.lang.Error: Unresolved compilation problems: Unhandled exception type XXXException
比如:
FileNotFoundException
: 打开文件可能不存在的异常
IOException
: 输入输出异常
下面我们用一张图来理解上面的关系:
如何捕获异常
在 Java 中, 如果我们需要处理异常, 首先我们需要捕获 (catch) 它们, 捕获异常的通用结构如下:
try { // 包含异常的代码A } catch(异常1 e1) { //捕获到 异常1 类的情况下, 做如何处理 } catch(异常2 e2) { //捕获到 异常1 类的情况下, 做如何处理 } finally { //无论异常是否发生都会执行的代码 }
- 首先需要将会抛出异常的代码A, 放到
try { }
代码块下. - 然后通过
catch(异常 异常变量)
的方式捕获异常, 异常可以是特定的异常类, 比如FileNotFoundException
, 也可以异常基类Exception
(但是我们不建议这么做). 然后在接下来的花括号中定义如果捕获了此类异常需要做些什么, 比如给用户一些提示, 或者留下一些开发日志啥的. - 如果需要捕获多个异常, 则新增一个
catch(异常 异常变量)
的代码块 - 最后, 无论是否捕获到异常都会执行
finally{}
中的代码, 但是块finally{}
是不是必须的.
下面我们用个例子来看看异常如何捕获:
import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; public class Yi21ExceptionDemo { public static void main(String[] args) { try { FileInputStream fStream = new FileInputStream(new File("21yi_not_exists.file")); fStream.close(); System.out.println("Done."); } catch (FileNotFoundException e) { System.out.println("文件不存在: " + e.getMessage()); System.out.println("异常类: " + e.getClass()); System.out.println("引起异常的原因: " + e.getCause()); System.out.println("被抑制的数组: " + e.getSuppressed()); System.out.println("出错追溯: " + e.getStackTrace()); } catch (IOException e) { System.out.println("输入输出异常: " + e.getMessage()); } finally { System.err.println("我总是会被执行."); } } }
上述代码中, 我们尝试访问一个不存在的文件 21yi_not_exists.file , 将这块代码放于 try {}
块下.
我们需要捕获的异常有, 初始化 File
对象会抛出的异常 FileNotFoundException
以及 stream
关闭时会抛出的异常 IOException
.
实际上 FileNotFoundException
是 IOException
的一个子类, 这里我们为了捕获更加精确的异常, 所以优先去捕获 FileNotFoundException
.
如果我们 捕获到了 FileNotFoundException
会展示, 异常的各种方法.
最终, 无论我们是否捕获到了异常, 总是会输出一句 "我总是会被执行.".
代码执行的结果如下:
文件不存在: not_exists.file (系统找不到指定的文件。) 异常类: class java.io.FileNotFoundException 引起异常的原因: null 被抑制的数组: [Ljava.lang.Throwable;@6f75e721 出错追溯: [Ljava.lang.StackTraceElement;@782830e 我总是会被执行.
如何外抛异常
我们在写代码的时候, 并不总是需要在当下解决异常的处理, 我们可以将异常外抛出去, 交给方法的调用者去处理.
比如, 我们定义了处理一些文件的方法, 调用这个方法的有的是命令行调用, 有的需要封装一套程序界面来处理这个异常, 因此我们可以延后处理这个异常, 将异常处理的选择权交给调用者.
我们将这种把异常交由外部调用者处理的方式, 称为外抛(throws).
throws 声明在方法的签名后, 他的使用示例如下:
import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; public class Yi21ExceptionThrows { public static void initFile() throws FileNotFoundException, IOException { FileInputStream fStream = new FileInputStream(new File("21yi_not_exists.file")); fStream.close(); System.out.println("Done."); } public static void main(String[] args) { try { initFile(); } catch (FileNotFoundException e) { System.out.println("文件不存在: " + e.getMessage()); } catch (IOException e) { System.out.println("输入输出异常: " + e.getMessage()); } finally { System.err.println("我总是会被执行."); } } }
在上述代码中, 我们定义了一个 initFile()
方法来初始化那个不存在的文件, 但是我们不急于处理其中的异常, 于是我们将代码中会出现的各种异常 FileNotFoundException
和 IOException
外抛.
然后我们在 main() 中调用了 initFile()
方法, 这时候我们就可以针对这些异常来做特别的处理了. 代码执行结果如下:
文件不存在: 21yi_not_exists.file (系统找不到指定的文件。) 我总是会被执行.
如何自定义异常与抛出异常
所有异常都是 Exception 的基类, 因此我们只要继承 Exception 类即可.
同样的如果我们想定义一些不是必须捕获的异常, 可以继承 RuntimeException.
此外我们可以根据具体的需求继承其他的异常类.
下面我们还是以代码示例来展示自定义异常与抛出异常:
import java.io.IOException; public class Yi21ExceptionSub { private static class Yi21Exception extends IOException { public Yi21Exception(String message) { super(message); } } private static void demo() throws Yi21Exception { int i = 10; if (i > 1) { throw new Yi21Exception("i > 1"); } } public static void main(String[] args) { try { demo(); System.out.println("Done."); } catch(Yi21Exception e) { System.out.println(e.getMessage()); } } }
在上述代码中, 我们通过继承 IOException
声明了一个新的异常 Yi21Exception
同时实现了 Yi21Exception
的构造方法, 通过提供一个字符串, 作为异常的消息来初始化这个异常.
然后定义了外抛 Yi21Exception
的方法 demo()
, 在这个方法中 我们通过 throw
初始化的 Yi21Exception
对象, 抛出了异常.
最后在 main()
方法中捕获 Yi21Exception
这个异常, 来在异常捕获的处理.
以上就是, Java 的异常与处理的全部内容了.
从下一节开始, 我们将介绍 Java 自带的一些常用的类型了, 首先我们先看看: Java 的String类