Java异常处理
异常处理基本概念
所谓的异常简单来看就是非正常状态,和正常情况不一样有错误产生。而在Java上异常就是程序上的错误,我们编写程序时经常会产生错误,这些错误分成了编译期间的错误和运行时的错误。而异常处理就是对这些错误进行捕获和解决。
为了更直观的认识什么时异常处理,我们举一个例子来看:我们学习过数组,知道数组是由索引值来获取数组元素的,而索引值越界就会产生错误提示。
Java异常体系
Throwable 类
Throwable 位于 java.lang 包下,它是 Java 语言中所有错误(Error)和异常(Exception)的父类。Throwable 包含了其线程创建时线程执行堆栈的快照,它提供了 printStackTrace() 等接口用于获取堆栈跟踪数据等信息。
主要方法:
fillInStackTrace: 用当前的调用栈层次填充 Throwable 对象栈层次,添加到栈层次任何先前信息中;
getMessage: 返回关于发生的异常的详细信息。这个消息在 Throwable 类的构造函数中初始化了;
getCause: 返回一个 Throwable 对象代表异常原因;
getStackTrace: 返回一个包含堆栈层次的数组。下标为 0 的元素代表栈顶,最后一个元素代表方法调用堆栈的栈底;
printStackTrace: 打印 toString() 结果和栈层次到 System.err,即错误输出流。
Error 类
Error 是 Throwable 的一个直接子类,它可以指示合理的应用程序不应该尝试捕获的严重问题。这些错误在应用程序的控制和处理能力之外,编译器不会检查 Error,对于设计合理的应用程序来说,即使发生了错误,本质上也无法通过异常处理来解决其所引起的异常状况。
常见 Error:
AssertionError:断言错误;
VirtualMachineError:虚拟机错误;
UnsupportedClassVersionError:Java 类版本错误;
OutOfMemoryError :内存溢出错误。
Exception 类
Exception 是 Throwable 的一个直接子类。它指示合理的应用程序可能希望捕获的条件。
Exception 又包括 Unchecked Exception(非检查异常)和 Checked Exception(检查异常)两大类别。
Unchecked Exception (非检查异常)
Unchecked Exception 是编译器不要求强制处理的异常,包含 RuntimeException 以及它的相关子类。我们编写代码时即使不去处理此类异常,程序还是会编译通过。
常见非检查异常:
NullPointerException:空指针异常;
public class NullTest {
public static void main(String[] args) {
// TODO Auto-generated method stub
Date d = null;
try {
System.out.println(d.after(new Date()));
}
catch(NullPointerException ne) {//NUllPointerException异常
System.out.println("空指针异常");
}
catch(Exception e) {//Exception异常
System.out.println("未知错误");
}
}
输出结果:空指针异常
ArithmeticException:算数异常;
ArrayIndexOutOfBoundsException:数组下标越界异常;
ClassCastException:类型转换异常。
public class DivTest {
public static void main(String[] args) {
// TODO Auto-generated method stub
try {
int a = Integer.parseInt(args[0]);
int b = Integer.parseInt(args[1]);
int c = a/b;
System.out.println("你输入的两个数相除的结果时:"+c);
}
catch(IndexOutOfBoundsException ie) {
System.out.println("数组越界,运行程序时输入参数个数不够!");
}
catch(NumberFormatException ne) {
System.out.println("数字格式异常,程序只能接收整形参数!");
}
catch(ArithmeticException ae) {
System.out.println("算术异常!");
}
catch(Exception e) {
System.out.println("未知异常!");
}
}
2.3.2 Checked Exception(检查异常)
Checked Exception 是编译器要求必须处理的异常,除了 RuntimeException 以及它的子类,都是 Checked Exception 异常。我们在程序编写时就必须处理此类异常,否则程序无法编译通过。
常见检查异常:
IOException:IO 异常
SQLException:SQL 异常
Java异常机制五个重要关键字及用法
Java的五个关键字分别为:
try: try块里面放置可能引发异常的代码
catch: 里面放置对应异常处理的代码
finally: 用于回收try块中打卡的物理资源
throws: 用于声明该方法可能抛出的异常,主要在方法签名中使用
throw: 用于抛出一个实际的异常
throw抛出异常
如果执行try块里的业务逻辑代码时出现异常,系统会自动生成一个异常对象,该异常对象被提交给Java运行环境时,这个过程被称为抛出(throw)异常。
public class ExceptionDemo1 {
public static void divide(int a,int b) {
if(b==0) {
throw new ArithmeticException("除数不能为零");
}
System.out.println(a/b);
}
public static void main(String[] args) {
// TODO Auto-generated method stub
divide(2,0);
}
}
try-catch捕获处理异常
当Java运行时环境收到异常对象时,会寻找能处理该异常对象的catch块,如果找到合适的catch块,则把异常对象交给该catch块处理,这个给过程被称为捕获(catch)异常;如果Java运行时环境找不到捕获异常的catch块,则运行时环境终止,Java程序也将退出。
try{
//业务实现代码
}
catch(Exveption e){
//异常处理代码
}
注意
1.try和catch块后的花括号{。。。}是不可以省略的。
2.try块里声明的变量是代码块内局部变量,只能在try块内有效。
使用throws声明抛出异常
使用throws抛出异常的思路是:当前方法不知道如何处理这种类型的异常,该异常应该阻止一级调用者处理;如果main方法也不知如何处理这种类型的异常也可以使用throws声明来抛出异常。然后会将该异常交给JVM处理,JVM对异常的处理方法就是,打印异常的跟踪栈信息,这就是前面程序遇到异常后自动结束的原因。
public class ThrowsTest {
public static void main(String[] args) throws IOException{
// TODO Auto-generated method stub
Object fis = new FileInputStream("a.txt");
}
}
运行结果:
Exception in thread "main" java.io.FileNotFoundException: a.txt (系统找不到指定的文件。)
at java.io.FileInputStream.open0(Native Method)
at java.io.FileInputStream.open(Unknown Source)
at java.io.FileInputStream.<init>(Unknown Source)
at java.io.FileInputStream.<init>(Unknown Source)
使用finally回收资源
有些时候,程序会在try块中打卡一些物理资源,例如数据库连接,网络连接和磁盘文件等,这些物理资源必须显式回收。你会说Java不是有Java的垃圾回收机制嘛。当Java的垃圾回收机制不会回收任何物理资源,垃圾回收机制只能回收堆内存中对象所占用的内存。所以为了保证一定可以回收try块中打卡的物理资源,异常处理机制提供了finally块。不管try块中代码是否出现异常,也不管哪一个catch块被执行,甚至在try块中或catch块中执行了return 语句,finally块总是会被执行。但有一种情况不会,看下面示例代码。
public class FinallyTest {
public static void main(String[] args) {
// TODO Auto-generated method stub
FileInputStream fis = null;
try {
fis = new FileInputStream("a.txt");
}
catch(IOException ioe) {
System.out.println(ioe.getMessage());
//使用return强制放回
return;
//使用exit退出Java虚拟机
//System.exit(1);
}
finally {
if(fis != null)
{
try {
fis.close();
}catch(IOException ioe) {
ioe.printStackTrace();
}
}
System.out.println("执行finally块里的资源回收!");
}
}
}
如果在catch块中Java中加入return,虽然return语句会强制方法结束,但一定会先执行finally块里的代码,结果如下:
a.txt (系统找不到指定的文件。)
执行finally块里的资源回收!
但如何使用exit直接退出Java虚拟机,我们会看到finally块并没有被执行,所以如果在异常处理代码中使用System.exit(1)语言来退出虚拟机,则finally块将会失去执行机会。
a.txt (系统找不到指定的文件。)
如何自定义异常类
在通常情况下,程序很少自行抛出系统异常,因为异常的类名也就包含了该异常的有用信息。所以在选择抛出异常时,因选择合适的异常类,从而可以明确的描述该异常情况。所以给出了一种用户自定义的异常方式。
用户自定的异常类通常应该继承Exception类,但如果需要自定义Runtime 异常,则应该继承RuntimeException基类。
定义异常类通常需要提供两个构造器:
1.无参数构造器
2.带一个字符串参数的构造器,这个字符串作为该异常对象的描述信息,也就是异常对象getMessage()方法的放回值。
public class AuctionException extends Exception
{
//无参构造器
public AuctionException(){}
//带一个字符串参数的构造器
public AuctionException(String msg)
{
super(msg);
}
异常处理规则
成功的异常处理应该具有以下特点
1.使程序代码混乱最小化
2.捕获并保留诊断信息
3.通知合适人员
4.采用合适的方式结束异常活动
不要过度使用异常
过度使用异常主要表现在:
1.把异常和普通错误混淆在一起,不再编写任何错误处理代码。而是以简单地抛出异常来代替所有的错误处理。
2.使用异常处理来代替流程控制
不要使用过于庞大的try块
避免使用CatchAll语句
不要忽略捕获到的异常
既然已经捕获到异常,那catch块理应处理并修复这个错误,而不是瞒天过海,所有人都看不到任何异常,但整个应用可能已经彻底坏了。仅在catch块里打印错误跟踪信息稍微好点,但仅仅比空白异常多了几行异常处理,从瞒天过海变成了简单期满。
通常建议对异常处理采取适当措施:
1.处理异常:对异常进行合适的修复,然后绕过异常发生的地方继续执行;或者用别的数据镜像计算,以代替期望的返回值,或者提示用户重新操作,,,总之,对于Checked异常,程序应该尽量修复。
2.重新抛出异常:把当前运行环境下能做的事情尽量做完,然后进行异常转译,把异常包装成但前层的异常,重新抛出给上层调用者。
3.在合适的层处理异常:如果当前层不清楚如何处理异常,就不要在当前层使用catch语句来捕获该异常,直接使用throws声明抛出异常,让上层调用者来负责处理该异常。