本系列文章经补充和完善,已修订整理成书《Java编程的逻辑》,由机械工业出版社华章分社出版,于2018年1月上市热销,读者好评如潮!各大网店和书店有售,欢迎购买,京东自营链接http://item.jd.com/12299018.html


之前我们介绍的基本类型、类、接口、枚举都是在表示和操作数据,操作的过程中可能有很多出错的情况,出错的原因可能是多方面的,有的是不可控的内部原因,比如内存不够了、磁盘满了,有的是不可控的外部原因,比如网络连接有问题,更多的可能是程序的编程错误,比如引用变量未初始化就直接调用实例方法。

这些非正常情况在Java中统一被认为是异常,Java使用异常机制来统一处理,由于内容较多,我们分为两节来介绍,本节介绍异常的初步概念,以及异常类本身,下节主要介绍异常的处理。

我们先来通过一些例子认识一下异常。

初始异常

NullPointerException (空指针异常)

我们来看段代码:

public class ExceptionTest {
public static void main(String[] args) {
String s = null;
s.indexOf("a");
System.out.println("end");
}
}

变量s没有初始化就调用其实例方法indexOf,运行,屏幕输出为:

Exception in thread "main" java.lang.NullPointerException
at ExceptionTest.main(ExceptionTest.java:5)

输出是告诉我们:在ExceptionTest类的main函数中,代码第5行,出现了空指针异常(java.lang.NullPointerException)。

但,具体发生了什么呢?当执行s.indexOf("a")的时候,Java系统发现s的值为null,没有办法继续执行了,这时就启用异常处理机制,首先创建一个异常对象,这里是类NullPointerException的对象,然后查找看谁能处理这个异常,在示例代码中,没有代码能处理这个异常,Java就启用默认处理机制,那就是打印异常栈信息到屏幕,并退出程序。

在介绍函数调用原理的时候,我们介绍过栈,异常栈信息就包括了从异常发生点到最上层调用者的轨迹,还包括行号,可以说,这个栈信息是分析异常最为重要的信息。

Java的默认异常处理机制是退出程序,异常发生点后的代码都不会执行,所以示例代码中最后一行System.out.println("end")不会执行。

NumberFormatException (数字格式异常)

我们再来看一个例子,代码如下:

public class ExceptionTest {
public static void main(String[] args) {
if(args.length<1){
System.out.println("请输入数字");
return;
}
int num = Integer.parseInt(args[0]);
System.out.println(num);
}
}

args表示命令行参数,这段代码要求参数为一个数字,它通过Integer.parseInt将参数转换为一个整数,并输出这个整数。参数是用户输入的,我们没有办法强制用户输入什么,如果用户输的是数字,比如123,屏幕会输出123,但如果用户输的不是数字,比如abc,屏幕会输出:

Exception in thread "main" java.lang.NumberFormatException: For input string: "abc"
at java.lang.NumberFormatException.forInputString(NumberFormatException.java:65)
at java.lang.Integer.parseInt(Integer.java:492)
at java.lang.Integer.parseInt(Integer.java:527)
at ExceptionTest.main(ExceptionTest.java:7)

出现了异常NumberFormatException。这个异常是怎么产生的呢?根据异常栈信息,我们看相关代码:

这是NumberFormatException类65行附近代码:

 static NumberFormatException forInputString(String s) {
return new NumberFormatException("For input string: \"" + s + "\"");
}

这是Integer类492行附近代码:

 digit = Character.digit(s.charAt(i++),radix);
if (digit < 0) {
throw NumberFormatException.forInputString(s);
}
if (result < multmin) {
throw NumberFormatException.forInputString(s);
}

将这两处合为一行,主要代码就是:

throw new NumberFormatException(...)

new NumberFormatException(...)是我们容易理解的,就是创建了一个类的对象,只是这个类是一个异常类。throw是什么意思呢?就是抛出异常,它会触发Java的异常处理机制。在之前的空指针异常中,我们没有看到throw的代码,可以认为throw是由Java虚拟机自己实现的。

throw关键字可以与return关键字进行对比,return代表正常退出,throw代表异常退出,return的返回位置是确定的,就是上一级调用者,而throw后执行哪行代码则经常是不确定的,由异常处理机制动态确定。

异常处理机制会从当前函数开始查找看谁"捕获"了这个异常,当前函数没有就查看上一层,直到主函数,如果主函数也没有,就使用默认机制,即输出异常栈信息并退出,这正是我们在屏幕输出中看到的。

对于屏幕输出中的异常栈信息,程序员是可以理解的,但普通用户无法理解,也不知道该怎么办,我们需要给用户一个更为友好的信息,告诉用户,他应该输入的是数字,要做到这一点,我们需要自己"捕获"异常。

"捕获"是指使用try/catch关键字,我们看捕获异常后的示例代码:

public class ExceptionTest {
public static void main(String[] args) {
if(args.length<1){
System.out.println("请输入数字");
return;
}
try{
int num = Integer.parseInt(args[0]);
System.out.println(num);
}catch(NumberFormatException e){
System.err.println("参数"+args[0]
+"不是有效的数字,请输入数字");
}
}
}

我们使用try/catch捕获并处理了异常,try后面的大括号{}内包含可能抛出异常的代码,括号后的catch语句包含能捕获的异常和处理代码,catch后面括号内是异常信息,包括异常类型和变量名,这里是NumberFormatException e,通过它可以获取更多异常信息,大括号{}内是处理代码,这里输出了一个更为友好的提示信息。

捕获异常后,程序就不会异常退出了,但try语句内异常点之后的其他代码就不会执行了,执行完catch内的语句后,程序会继续执行catch大括号外的代码。

这样,我们就对异常有了一个初步的了解,异常是相对于return的一种退出机制,可以由系统触发,也可以由程序通过throw语句触发,异常可以通过try/catch语句进行捕获并处理,如果没有捕获,则会导致程序退出并输出异常栈信息。异常有不同的类型,接下来,我们来认识一下。

异常类

Throwable

NullPointerException和NumberFormatException都是异常类,所有异常类都有一个共同的父类Throwable,它有4个public构造方法:

  1. public Throwable()
  2. public Throwable(String message)
  3. public Throwable(String message, Throwable cause)
  4. public Throwable(Throwable cause)

有两个主要参数,一个是message,表示异常消息,另一个是cause,表示触发该异常的其他异常。异常可以形成一个异常链,上层的异常由底层异常触发,cause表示底层异常。

Throwable还有一个public方法用于设置cause:

Throwable initCause(Throwable cause)

Throwable的某些子类没有带cause参数的构造方法,就可以通过这个方法来设置,这个方法最多只能被调用一次。

所有构造方法中都有一句重要的函数调用:

fillInStackTrace();

它会将异常栈信息保存下来,这是我们能看到异常栈的关键。

Throwable有一些常用方法用于获取异常信息:

void printStackTrace()

打印异常栈信息到标准错误输出流,它还有两个重载的方法:

void printStackTrace(PrintStream s)
void printStackTrace(PrintWriter s)

打印栈信息到指定的流,关于PrintStream和PrintWriter我们后续文章介绍。

String getMessage()
Throwable getCause()

获取设置的异常message和cause

StackTraceElement[] getStackTrace()

获取异常栈每一层的信息,每个StackTraceElement包括文件名、类名、函数名、行号等信息。

异常类体系

以Throwable为根,Java API中定义了非常多的异常类,表示各种类型的异常,部分类示意如下:

Throwable是所有异常的基类,它有两个子类Error和Exception。

Error表示系统错误或资源耗尽,由Java系统自己使用,应用程序不应抛出和处理,比如图中列出的虚拟机错误(VirtualMacheError)及其子类内存溢出错误(OutOfMemoryError)和栈溢出错误(StackOverflowError)。

Exception表示应用程序错误,它有很多子类,应用程序也可以通过继承Exception或其子类创建自定义异常,图中列出了三个直接子类:IOException(输入输出I/O异常),SQLException(数据库SQL异常),RuntimeException(运行时异常)。

RuntimeException(运行时异常)比较特殊,它的名字有点误导,因为其他异常也是运行时产生的,它表示的实际含义是unchecked exception (未受检异常),相对而言,Exception的其他子类和Exception自身则是checked exception (受检异常),Error及其子类也是unchecked exception。

checked还是unchecked,区别在于Java如何处理这两种异常,对于checked异常,Java会强制要求程序员进行处理,否则会有编译错误,而对于unchecked异常则没有这个要求。下节我们会进一步解释。

RuntimeException也有很多子类,下表列出了其中常见的一些:

异常 说明
NullPointerException 空指针异常
IllegalStateException 非法状态
ClassCastException 非法强制类型转换
IllegalArgumentException 参数错误
NumberFormatException 数字格式错误
IndexOutOfBoundsException 索引越界
ArrayIndexOutOfBoundsException 数组索引越界
StringIndexOutOfBoundsException 字符串索引越界

这么多不同的异常类其实并没有比Throwable这个基类多多少属性和方法,大部分类在继承父类后只是定义了几个构造方法,这些构造方法也只是调用了父类的构造方法,并没有额外的操作。

那为什么定义这么多不同的类呢?主要是为了名字不同,异常类的名字本身就代表了异常的关键信息,无论是抛出还是捕获异常时,使用合适的名字都有助于代码的可读性和可维护性。

自定义异常

除了Java API中定义的异常类,我们也可以自己定义异常类,一般通过继承Exception或者它的某个子类,如果父类是RuntimeException或它的某个子类,则自定义异常也是unchecked exception,如果是Exception或Exception的其他子类,则自定义异常是checked exception。

我们通过继承Exception来定义一个异常,代码如下:

public class AppException extends Exception {
public AppException() {
super();
} public AppException(String message,
Throwable cause) {
super(message, cause);
} public AppException(String message) {
super(message);
} public AppException(Throwable cause) {
super(cause);
}
}

和很多其他异常类一样,我们没有定义额外的属性和代码,只是继承了Exception,定义了构造方法并调用了父类的构造方法。

小结

本节,我们通过两个例子对异常做了基本介绍,介绍了try/catch和throw关键字及其含义,同时介绍了Throwable以及以它为根的异常类体系。

下一节,让我们进一步探讨异常。

----------------

未完待续,查看最新文章,敬请关注微信公众号“老马说编程”(扫描下方二维码),从入门到高级,深入浅出,老马和你一起探索Java编程及计算机技术的本质。用心写作,原创文章,保留所有版权。

-----------

更多好评原创文章

计算机程序的思维逻辑 (1) - 数据和变量

计算机程序的思维逻辑 (5) - 小数计算为什么会出错?

计算机程序的思维逻辑 (6) - 如何从乱码中恢复 (上)?

计算机程序的思维逻辑 (8) - char的真正含义

计算机程序的思维逻辑 (12) - 函数调用的基本原理

计算机程序的思维逻辑 (17) - 继承实现的基本原理

计算机程序的思维逻辑 (18) - 为什么说继承是把双刃剑

计算机程序的思维逻辑 (19) - 接口的本质

计算机程序的思维逻辑 (20) - 为什么要有抽象类?

计算机程序的思维逻辑 (21) - 内部类的本质

计算机程序的思维逻辑 (23) - 枚举的本质

Java编程的逻辑 (24) - 异常 (上)的更多相关文章

  1. Java编程的逻辑 (25) - 异常 (下)

    ​本系列文章经补充和完善,已修订整理成书<Java编程的逻辑>,由机械工业出版社华章分社出版,于2018年1月上市热销,读者好评如潮!各大网店和书店有售,欢迎购买,京东自营链接:http: ...

  2. Java编程的逻辑 (35) - 泛型 (上) - 基本概念和原理

    本系列文章经补充和完善,已修订整理成书<Java编程的逻辑>,由机械工业出版社华章分社出版,于2018年1月上市热销,读者好评如潮!各大网店和书店有售,欢迎购买,京东自营链接:http:/ ...

  3. Java编程的逻辑 (88) - 正则表达式 (上)

    本系列文章经补充和完善,已修订整理成书<Java编程的逻辑>,由机械工业出版社华章分社出版,于2018年1月上市热销,读者好评如潮!各大网店和书店有售,欢迎购买,京东自营链接:http:/ ...

  4. Java编程的逻辑 (26) - 剖析包装类 (上)

    本系列文章经补充和完善,已修订整理成书<Java编程的逻辑>,由机械工业出版社华章分社出版,于2018年1月上市热销,读者好评如潮!各大网店和书店有售,欢迎购买,京东自营链接:http:/ ...

  5. 《Java编程的逻辑》 - 文章列表

    <计算机程序的思维逻辑>系列文章已整理成书<Java编程的逻辑>,由机械工业出版社出版,2018年1月上市,各大网店有售,敬请关注! 京东自营链接:https://item.j ...

  6. Java编程的逻辑 (27) - 剖析包装类 (中)

    ​本系列文章经补充和完善,已修订整理成书<Java编程的逻辑>,由机械工业出版社华章分社出版,于2018年1月上市热销,读者好评如潮!各大网店和书店有售,欢迎购买,京东自营链接:http: ...

  7. Java编程的逻辑 (67) - 线程的基本协作机制 (上)

    ​本系列文章经补充和完善,已修订整理成书<Java编程的逻辑>,由机械工业出版社华章分社出版,于2018年1月上市热销,读者好评如潮!各大网店和书店有售,欢迎购买,京东自营链接:http: ...

  8. Java编程的逻辑 (92) - 函数式数据处理 (上)

    本系列文章经补充和完善,已修订整理成书<Java编程的逻辑>,由机械工业出版社华章分社出版,于2018年1月上市热销,读者好评如潮!各大网店和书店有售,欢迎购买,京东自营链接:http:/ ...

  9. Java编程的逻辑 (6) - 如何从乱码中恢复 (上)?

    ​本系列文章经补充和完善,已修订整理成书<Java编程的逻辑>,由机械工业出版社华章分社出版,于2018年1月上市热销,读者好评如潮!各大网店和书店有售,欢迎购买,京东自营链接:http: ...

随机推荐

  1. Nginx upstream 配置

    1.轮询(默认)每个请求按时间顺序逐一分配到不同的后端服务器,如果后端服务器down掉,能自动剔除. 2.weight指定轮询几率,weight和访问比率成正比,用于后端服务器性能不均的情况.例如:u ...

  2. Could not load file or assembly 'Microsoft.ReportViewer.WebForms, Version=11.0.0.0, Culture=neutral, PublicKeyToken=89845dcd8080cc91' or one of its dependencies

    my shurufa  huai diao le 1\ first   you need  install     " SQLSysClrTypes_x86.msi  " 2\   ...

  3. jquery 绑定文本即时查询功能

    bindFilterFunc: function () {             if ("\v" == "v") { // IE only          ...

  4. 洛谷P3275 [SCOI2011]糖果(差分约束,最长路,Tarjan,拓扑排序)

    洛谷题目传送门 差分约束模板题,等于双向连0边,小于等于单向连0边,小于单向连1边,我太蒻了,总喜欢正边权跑最长路...... 看遍了讨论版,我是真的不敢再入复杂度有点超级伪的SPFA的坑了 为了保证 ...

  5. 【题解】 [HNOI2009] 最小圈 (01分数规划,二分答案,负环)

    题目背景 如果你能提供题面或者题意简述,请直接在讨论区发帖,感谢你的贡献. 题目描述 对于一张有向图,要你求图中最小圈的平均值最小是多少,即若一个圈经过k个节点,那么一个圈的平均值为圈上k条边权的和除 ...

  6. JavaScript--序列化以及转义

    一.序列化 1.1 序列化 将其他对象转换为字符串,用法:JSON.stringify() var li = [1,2,3,4]; var new_li = JSON.stringify(li); n ...

  7. bzoj千题计划250:bzoj3670: [Noi2014]动物园

    http://www.lydsy.com/JudgeOnline/problem.php?id=3670 法一:KMP+st表 抽离nxt数组,构成一棵树 若nxt[i]=j,则i作为j的子节点 那么 ...

  8. 【51Nod】1510 最小化序列 贪心+动态规划

    [题目]1510 最小化序列 [题意]给定长度为n的数组A和数字k,要求重排列数组从而最小化: \[ans=\sum_{i=1}^{n-k}|A_i-A_{i+k}|\] 输出最小的ans,\(n \ ...

  9. 【BZOJ】1095: [ZJOI2007]Hide 捉迷藏 括号序列+线段树

    [题目]BZOJ 1095 [题意]给定n个黑白点的树,初始全为黑点,Q次操作翻转一个点的颜色,或询问最远的两个黑点的距离,\(n \leq 10^5,Q \leq 5*10^5\). [算法]括号序 ...

  10. 20155237 2016-2017-2 《Java程序设计》第7周学习总结

    20155237 2016-2017-2 <Java程序设计>第7周学习总结 教材学习内容总结 认识Lambda语法 Lambda 教材的引入循序渐近.深入浅出 Lambda去重复,回忆D ...