异常
在java中提供了处理异常的机制,能够帮助我们避免程序崩溃。
- Throwable可以用来表示任何可以作为异常抛出的类,分为两种: Error和Exception。
- 其中Error用来表示JVM无法处理的错误。程序被强制终止。
Exception又分为两种:
受检异常:需要用try...catch..语句捕获并进行处理,并且可以从异常中恢复;
非受检异常:是程序运行时错误,例如除0会引发Arithmetic Exception,此时程序崩溃并且无法恢复。
Throwable
- Throwable 是 Java语言中所有的错误与异常的超类。
- Throwable 包含两个子类:Error(错误)和 Exception(异常),它们通常用于指示发生了异常情况。
- Throwable 包含了其线程创建时线程线程执行堆栈的快照,它提供了printStackTrace()等接口用于获取堆栈跟踪等信息。
Error
- Error是Java中Throwable类的一个直接子类,表示严重错误,通常是无法恢复的。
- Error类型的错误通常由Java虚拟机(JVM)或Java运行时环境(JRE)自身引发,而不是程序员引发。
- 当出现Error时,程序的正常执行流程将被中断,而且无法通过捕获和处理来恢复。
- 一旦出现Error错误,程序通常会被强制终止,因此,在编写Java程序时,通常不建议显示地捕获和处理Error错误。
Exception
程序本身可以捕获并且可以处理的异常。Exception这种异常又分为两类:运行时异常和编译时异常。
运行时异常
- 都是RuntimeException类及其子类异常,如NullPointerException(空指针异常)、IndexOutOfBoundsException(下标越界异常)等,这些异常是不检查异常,程序中可以选择捕获处理,也可以不处理。这些异常一般是由程序逻辑错误引起的,程序应该从逻辑角度尽可能避免这类异常的发生。
- 运行时异常的特点是Java编译器不会检查它,也就是说,当程序中可能出现这类异常,即使没有用try-catch语句捕获它,也没有用throws字句抛出它,也会编译通过。
编译时异常
是RuntimeException以外的异常,类型上都属于Exception类及其子类。从程序语法角度讲是必须进行处理的异常,如果不处理,程序就不能通过。如IOException、SQLException等以及用户自定义的Exception异常,一般情况下不自定义检查异常。
可查的、不可察的异常
可查的异常(check exception)
- 在方法声明的throws子句中列出的异常,编译器要求开发人员处理这些异常,要么通过try-catch块捕获并处理,要么通过在方法签名中使用throws关键字声明方法将异常抛出。
- 可查异常是指在编译时可以即时捕获和处理的异常,这意味着程序员要做出相应的处理,否则编译器会报错。
例如:IOException、SQLException等都是可查的异常,这些异常通常与外部资源(如文件、网络连接、数据库等)有关。
不可察的异常(unchecked exception)
不可察的异常,也称为运行时异常(runtime exceptions),不要求在方法声明的throws自居中声明或捕获。
运行时异常通常是由程序错误或逻辑错误引起的,而不是外部资源。
当程序出现运行时异常时,它们通常指示程序存在错误或不合理的操作。
例如:NullPointerException、ArrayIndexOutBoundsException、NumberFormatException等都是不可察的异常。
异常的基本使用
异常关键字
Java异常处理涉及到五个关键字,分别是:try、catch、finally、throw、throws。都不能单独使用。
- try:用于监听。将要被监听的代码放在try语句块之内,当try语句块内发生异常时,异常就被抛出。
- catch:用于捕获异常。catch用来捕获try语句块中发生的异常。
- finally:finally语句块总是会被执行。它主要用于回收在try块里打开的资源(如数据库连接、网络连接和磁盘文件)。只有finally块执行完成之后,才会回来执行try或者catch块中的return和throw语句,如果finally中使用了return或者throw等终止方法的语句,则就不会跳回执行,直接停止。
- throw:用于抛出异常。
- throws:用在方法签名中,用于声明该方法可能抛出的异常。
声明异常
如果一个方法可以导致一个异常但不处理它,它必须使方法的调用者可以保护它们自己而不发生异常。要做到这点,可以在方法声明中包含一个throws语句。一个trhows字句列举了一个方法可能引发的所有异常类型。这对与除了Error或RuntimeException及它们子类以外类型的所有异常是必要的。一个方法可以引发的所有其他类型的异常必须在throws字句中声明,否则会导致编译错误。
在方法中声明一个异常,方法头中使用关键字throws,后面接上要声明的异常。若声明多个异常,则使用逗号分割。
Throws抛出异常的规则
- 如果是不受检查异常(unchecked exception),即Error、RuntimeException或它们的子类,那么可以不使用throws关键字来声明要抛出的异常,编译仍能顺利通过,但在运行时会被系统抛出。
- 必须声明方法可抛出的任何检查异常(checked exception)。即如果一个方法可能出现受可查异常,要么用try-catch语句捕获,要么用throws字句将它抛出,否则会导致编译错误。
- 仅当抛出了异常,该方法的调用者才必须处理或重新抛出该异常。当方法的调用者无力处理该异常的时候,应该继续抛出,而不是囫囵吞枣。
- 调用方法必须遵循任何可查异常的处理和声明规则。若覆盖(override)一个方法,则不能声明与覆盖方法不同的异常。声明的任何异常必须是被覆盖方法所声明异常的同类或子类。
抛出异常
如果代码可能会引发某种错误,可以创建一个合适的异常类实例并抛出它,这就是抛出异常。
程序执行完throw语句之后立即停止;throw后面的任何语句不被执行,最邻近的try块用来检查它是否含有一个与异常类型匹配的catch语句。如果发现了匹配的块,控制转向该语句;如果没有发现,次包围的try块来检查,以此类推。如果没有发现匹配的catch块,默认异常处理程序中断程序的执行并且打印堆栈轨迹。
异常链
异常链就是将异常发生的原因一个传一个连起来,即把底层的异常信息传给上层,逐层抛出。
try {
lowLevelOp();
} catch (LowLevelException le) {
throw (HighLevelException) new HighLevelException().initCause(le);
}
当程序捕获到了一个底层异常,在处理部分选择了继续抛出一个更高级别的新异常给此方法的调用者。这样异常的原因就会逐层传递。这样,位于高层的异常递归调用getCause()方法,就可以遍历各层的异常原因。异常链的实际应用很少,发生异常逐层往上抛会消耗大量资源,因为要保存一个完整的异常链信息。
自定义异常
使用Java内置的异常类可以描述在编程中出现的大部分异常。除此之外,用户还可以自定义异常。用户自定义异常,只需要继承Exception类即可。
创建自定义异常类,可以分为以下几个步骤:
- 创建自定义异常类。
- 在方法中通过throw关键字抛出异常对象。
- 如果在当前抛出异常的方法中处理异常,可以使用try-catch语句捕获并处理;否则在方法的声明处通过throws关键字知名要抛出给方法调用者的异常,继续进行下一步操作。
- 在出现异常方法的调用者中捕获并处理异常。
捕获异常
异常捕获常用的处理方法有:
- try-catch
- try-catch-finally
- try-finally
- try-with-resource
try-catch
在一个try-catch语句块中可以捕获多个类型的,并对不同类型的异常做出不同的处理。
public static void readFile(String filePath) {
try {
// code
} catch (FileNotFoundException e) {
// handle FileNotFoundException
} catch (IOException e){
// handle IOException
}
}
同一个catch也可以捕获多种类型异常,用 | 隔开
public static void readFile(String filePath) {
try {
// code
} catch (FileNotFoundException | UnknownHostException e) {
// handle FileNotFoundException or UnknownHostException
} catch (IOException e){
// handle IOException
}
}
try-catch-finally
try-catch-finally语句块中的finally语句块总是会被执行,除非JVM退出。
try {
//执行程序代码,可能会出现异常
} catch(Exception e) {
//捕获异常并处理
} finally {
//必执行的代码
}
- 当try没有捕获到异常时:try语句块中的语句逐一被执行,程序将跳过catch语句块,执行finally语句块和其后的语句。
- 当try捕获到异常,catch语句块没有处理此异常的情况:此异常将会抛给JVM处理,finally语句块里的语句还是会被执行,但finally语句块后的语句不会被执行。
- 当try捕获到异常,catch语句块里有处理此异常的情况:在try语句块中是按照顺序来执行的,当执行到某一条语句出现异常时,程序将跳到catch语句块,并与catch语句块逐一匹配,找到与之对应的处理程序,其他的catch语句块将不会被执行,而try语句块中,出现异常之后的语句也不会被执行,catch语句块执行完后,执行finally语句块里的语句,最后执行finally语句块后的语句;
try-finally
try块中引起异常,异常代码之后的语句不再执行,直接执行finally语句。try块没有引发异常,则执行完try块就执行finally语句。
try-finally可用在不需要捕获异常的代码,可以保证资源在使用后被关闭。例如IO流执行完相应操作后,关闭相应资源;使用Lock对象保证线程同步,通过finally可以保证锁会被释放;数据库连接代码时,关闭连接操作等等。
//以Lock加锁为例,演示try-finally
ReentrantLock lock = new ReentrantLock();
try {
//需要加锁的代码
} finally {
lock.unlock(); //保证锁一定被释放
}
finally遇见如下情况不会执行
- 在前面的代码中用了System.exit()退出程序。
- finally语句块中发生了异常。
- 程序所在的线程死亡。
- 关闭CPU。
try-with-resource
在finally中的close方法也有可能抛出IOException,从而覆盖了原始异常。Java7提供了一种新的方法来实现资源的自动释放,自动释放的资源需要是实现了AutoCloseable接口的类。
private static void tryWithResourceTest(){
try (Scanner scanner = new Scanner(new FileInputStream("c:/abc"),"UTF-8")){
// code
} catch (IOException e){
// handle exception
}
}
Scanner类如下:
public final class Scanner implements Iterator<String>, Closeable {
// ...
}
public interface Closeable extends AutoCloseable {
public void close() throws IOException;
}
try代码块退出时,会自动调用scanner.close方法,和把scanner.close方法放在finally代码块中不同的是,若scanner.close抛出异常,则会被抑制,抛出的仍是原始异常。被抑制的异常会由addSuppressed方法添加到原来的异常,如果想要获取被抑制的异常列表,可以调用getSuppressed方法来获取。
常用的异常
- RuntimeException
- java.lang.ArrayIndexOutOfBoundsException:数组索引越界异常。当对数组的索引值为负数或大于等于数组大小时抛出。
- java.lang.ArithmeticException:算术条件异常。比如:整数除零等。
- java.lang.NullPointerException:空指针异常。当应用试图在要求使用对象的地方使用了null时,抛出该异常。譬如:调用null对象的实例方法、访问null对象的属性、计算null对象的长度、使用throw语句抛出null等等。
- java.lang.ClassNotFoundException:找不到类异常。当应用试图根据字符串形式的类名构造类,而在遍历CLASSPAH之后找不到对应名称的class文件时,抛出该异常。
- java.lang.NegativeArraySizeException:数组长度为负异常。
- java.lang.ArrayStor代码eException:数组中包含不兼容的值抛出的异常。
- java.lang.SecurityException:安全性异常。
- java.lang.IllegalArgumentException:非法参数异常。
- IOException
- IOException:操作输入流和输出流时可能出现的异常。
- EOFException:文件已结束异常。
- FileNotFoundException:文件未找到异常。
- 其他
- ClassCastException:类型转换异常类
- ArrayStoreException:数组中包含不兼容的值抛出的异常
- SQLException:操作数据库异常类
- NoSuchFieldException:字段未找到异常
- NoSuchMethodException:方法未找到抛出的异常
- NumberFormatException:字符串转换为数字抛出的异常
- StringIndexOutOfBoundsException:字符串索引超出范围抛出的异常
- IllegalAccessException:不允许访问某类异常
- InstantiationException:当应用程序试图使用Class类中的newInstance()方法创建一个类的实例,而指定的类对象无法被实例化时,抛出该异常