Java编程的逻辑 (24) - 异常 (上)
本系列文章经补充和完善,已修订整理成书《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构造方法:
- public Throwable()
- public Throwable(String message)
- public Throwable(String message, Throwable cause)
- 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编程及计算机技术的本质。用心写作,原创文章,保留所有版权。
-----------
更多好评原创文章
计算机程序的思维逻辑 (6) - 如何从乱码中恢复 (上)?
Java编程的逻辑 (24) - 异常 (上)的更多相关文章
- Java编程的逻辑 (25) - 异常 (下)
本系列文章经补充和完善,已修订整理成书<Java编程的逻辑>,由机械工业出版社华章分社出版,于2018年1月上市热销,读者好评如潮!各大网店和书店有售,欢迎购买,京东自营链接:http: ...
- Java编程的逻辑 (35) - 泛型 (上) - 基本概念和原理
本系列文章经补充和完善,已修订整理成书<Java编程的逻辑>,由机械工业出版社华章分社出版,于2018年1月上市热销,读者好评如潮!各大网店和书店有售,欢迎购买,京东自营链接:http:/ ...
- Java编程的逻辑 (88) - 正则表达式 (上)
本系列文章经补充和完善,已修订整理成书<Java编程的逻辑>,由机械工业出版社华章分社出版,于2018年1月上市热销,读者好评如潮!各大网店和书店有售,欢迎购买,京东自营链接:http:/ ...
- Java编程的逻辑 (26) - 剖析包装类 (上)
本系列文章经补充和完善,已修订整理成书<Java编程的逻辑>,由机械工业出版社华章分社出版,于2018年1月上市热销,读者好评如潮!各大网店和书店有售,欢迎购买,京东自营链接:http:/ ...
- 《Java编程的逻辑》 - 文章列表
<计算机程序的思维逻辑>系列文章已整理成书<Java编程的逻辑>,由机械工业出版社出版,2018年1月上市,各大网店有售,敬请关注! 京东自营链接:https://item.j ...
- Java编程的逻辑 (27) - 剖析包装类 (中)
本系列文章经补充和完善,已修订整理成书<Java编程的逻辑>,由机械工业出版社华章分社出版,于2018年1月上市热销,读者好评如潮!各大网店和书店有售,欢迎购买,京东自营链接:http: ...
- Java编程的逻辑 (67) - 线程的基本协作机制 (上)
本系列文章经补充和完善,已修订整理成书<Java编程的逻辑>,由机械工业出版社华章分社出版,于2018年1月上市热销,读者好评如潮!各大网店和书店有售,欢迎购买,京东自营链接:http: ...
- Java编程的逻辑 (92) - 函数式数据处理 (上)
本系列文章经补充和完善,已修订整理成书<Java编程的逻辑>,由机械工业出版社华章分社出版,于2018年1月上市热销,读者好评如潮!各大网店和书店有售,欢迎购买,京东自营链接:http:/ ...
- Java编程的逻辑 (6) - 如何从乱码中恢复 (上)?
本系列文章经补充和完善,已修订整理成书<Java编程的逻辑>,由机械工业出版社华章分社出版,于2018年1月上市热销,读者好评如潮!各大网店和书店有售,欢迎购买,京东自营链接:http: ...
随机推荐
- pom.xml文件中,添加自定义参数Properties
<properties> <powermock.version>1.6.6</powermock.version> </properties> < ...
- MT【184】$\epsilon$助力必要性
已知满足不等式$|x^2-4x+a|+|x-3|\le5$的最大值为$3$,求实数$a$的值,并解该不等式. 证明:1)当$x=3$时,$|a-3|\le5$,得$a\in[-2,8]$2)$\for ...
- MT【107】立体几何中用阿波罗尼乌斯圆的一道题
分析:利用内外圆知识知道,B,C两点到 AD 的距离$\le4$. 利用体积公式$V=\frac{1}{3}S_{截面}|AD|\le2\sqrt{15}$
- 【刷题】洛谷 P4320 道路相遇
题目描述 在 H 国的小 w 决定到从城市 \(u\) 到城市 \(v\) 旅行,但是此时小 c 由于各种原因不在城市 \(u\),但是小 c 决定到在中途与小 w 相遇 由于 H 国道路的原因,小 ...
- 【BZOJ1449】[JSOI2009]球队收益(网络流,费用流)
[BZOJ1449][JSOI2009]球队收益(网络流,费用流) 题面 BZOJ 洛谷 题解 首先对于一支队伍而言,总共进行多少场比赛显然是已知的,假设是\(n_i\)场,那么它的贡献是:\(C_i ...
- Python 线程同步
#-*-coding:utf-8-*- '''如果多个线程共同对某个数据修改,则可能出现不可预料的结果,为了保证数据的正确性, 需要对多个线程进行同步. 线程同步所使用的的方法: Lock RLock ...
- 【模板】LCA
代码如下 #include <bits/stdc++.h> using namespace std; const int maxn=5e5+10; inline int read(){ i ...
- python装饰器中@wraps作用--修复被装饰后的函数名等属性的改变
Python装饰器(decorator)在实现的时候,被装饰后的函数其实已经是另外一个函数了(函数名等函数属性会发生改变),为了不影响,Python的functools包中提供了一个叫wraps的de ...
- 函数和常用模块【day06】:模块特殊变量(十四)
from test import test ''' __mame__ # 当前文件为主文件是等于__main__.用于调用时不执行一些命令 __file__ # 当前文件的路径,相对路径 __cach ...
- JS中一些常用的事件(笔记)
window.onload事件:当文档和其所有外部资源(如图片)完全加载并显示给用户时就会触发它. window.onload = function (){ //当加载完当前页面和其所有外部资源(如图 ...