一、异常机制概述
异常机制是指当程序出现错误后,程序如何处理。具体来说,异常机制提供了程序退出的安全通道。当出现错误后,程序执行的流程发生改变,程序的控制权转移到异常处理器。
异常处理的流程
当程序中抛出一个异常后,程序从程序中导致异常的代码处跳出,java虚拟机检测寻找和try关键字匹配的处理该异常的catch块,如果找到,将控制权交到catch块中的代码,然后继续往下执行程序,try块中发生异常的代码不会被重新执行。如果没有找到处理该异常的catch块,在所有的finally块代码被执行和当前线程的所属的ThreadGroup的uncaughtException方法被调用后,遇到异常的当前线程被中止。
异常的结构
异常的继承结构:Throwable为基类,Error和Exception继承Throwable,RuntimeException和IOException等继承Exception。Error和RuntimeException及其子类成为未检查异常(unchecked),其它异常成为已检查异常(checked)
异常是程序中的一些错误,但并不是所有的错误都是异常,并且错误有时候是可以避免的。
比如说,你的代码少了一个分号,那么运行出来结果是提示是错误 java.lang.Error;如果你用System.out.println(11/0),那么你是因为你用0做了除数,会抛出 java.lang.ArithmeticException 的异常。
异常发生的原因有很多,通常包含以下几大类:
用户输入了非法数据。
要打开的文件不存在。
网络通信时连接中断,或者JVM内存溢出。
这些异常有的是因为用户错误引起,有的是程序错误引起的,还有其它一些是因为物理错误引起的。
要理解Java异常处理是如何工作的,你需要掌握以下三种类型的异常:
1、Error异常
Error表示程序在运行期间出现了十分严重、不可恢复的错误,在这种情况下应用程序只能中止运行,例如JAVA 虚拟机出现错误。Error是一种unchecked Exception,编译器不会检查Error是否被处理,在程序中不用捕获Error类型的异常。一般情况下,在程序中也不应该抛出Error类型的异常。
2、RuntimeException异常
Exception异常包括RuntimeException异常和其他非RuntimeException的异常。
RuntimeException类及其子类都被称为运行时异常,这种异常的特点是Java编译器不去检查它也就是说,当程序中可能出现这类异常时,即使没有用try…catch语句捕获它,也没有用throws
字句声明抛出它,还是会编译通过。因为最终JVM虚拟机会处理它
public int getLine() {
int f = 0;
try {
f = 2;
}
catch(Exception e) {
e.printStackTrace( );
throw new RuntimeException(e);
}
return f;
}
e.printStackTrace();
在实际开发时意义不大,因为部署以后不会有人看控制台,这句很多情况下会被记录日志的代码代替。 throw new RuntimeException
就是要把异常继续抛出,要么由上层方法解决,要么会终止程序运行,比如这里,如果初始化都无法正确完成,再继续运行下去也没有必要了。至于说多打印一句话,还是因为在工具环境下,你比较关注控制台,实际部署环境,没人看控制台信息,都会去看日志中记录的异常信息。 有结束进程的作用。
try catch如果不写throw new RuntimeException 只是不执行本方法后面的代码,然后跳出本方法后继续执行其他方法,不会结束程序。 如果在其他应用中,还可以把异常抛给上层调用者来处理。
如果出现RuntimeException异常,那么就一定是你的问题,例如,当除数为零时,就会抛出java.lang.ArithmeticException异常。运行时异常是可能被程序员避免的异常。 与检查性异常相反,运行时异常可以在编译时被忽略 ,RuntimeException发生的时候,表示程序中出现了编程错误,所以应该找出错误修改程序,而不是去捕获RuntimeException。当然,运行时异常是可以通过程序来捕获并处理的,比如除数为零的运行时异常
3、Checked Exception异常
Checked Exception异常,这也是在编程中使用最多的Exception,所有继承自Exception并且不是RuntimeException的异常都是checked Exception,上图中的IOException和ClassNotFoundException。JAVA 语言规定必须对checked Exception作处理,编译器会对此作检查,要么在方法体中声明抛出checked Exception,要么使用catch语句捕获checked Exception进行处理,不然不能通过编译。最具代表的检查性异常是用户错误或问题引起的异常,这是程序员无法预见的。例如要打开一个不存在文件时,一个异常就发生了,这些异常在编译时不能被简单地忽略。
public int getLine() throws Exception {
int f;
try{
f = 2;
}
catch(Exception e) {
throw new Exception(e);
}
return f;
}
二、Throwable
1、构造器
Throwable() //构造一个将 null 作为其详细消息的新 throwable。
Throwable(String message) //构造带指定详细消息的新 throwable。
Throwable(String message, Throwable cause)//构造一个带指定详细消息和 cause 的新 throwable。
Throwable(Throwable cause)
2、方法
//打印异常
void printStackTrace() //将此 throwable 及其追踪输出至标准错误流。
//将此 throwable 及其追踪输出到指定的输出流。
//注意:System.err 比把错误信息输出给System.out要好,
//因为System.out也许会被重定向
void printStackTrace(PrintStream s)
//将此 throwable 及其追踪输出到指定的 PrintWriter。
void printStackTrace(PrintWriter s)
//异常原因
Throwable getCause()
//设置异常原因
Throwable initCause(Throwable cause)
//在异常堆栈跟踪中填充。
//此方法在 Throwable 对象信息中记录有关当前线程堆栈帧的当前状态。
Throwable fillInStackTrace()
String getLocalizedMessage()//创建此 throwable 的本地化描述。
String getMessage() //返回此 throwable 的详细消息字符串。
//提供编程访问由 printStackTrace() 输出的堆栈跟踪信息。
StackTraceElement[] getStackTrace()
//设置将由 getStackTrace() 返回,并由 printStackTrace() 和相关方法输出的堆栈跟踪元素。
void setStackTrace(StackTraceElement[] stackTrace)
三、Exception
Exception 类及其子类是 Throwable 的一种形式,它指出了合理的应用程序想要捕获的条件
1、构造器
Exception() //构造详细消息为 null 的新异常。
Exception(String message) //构造带指定详细消息的新异常。
Exception(String message, Throwable cause)// 构造带指定详细消息和原因的新异常。
Exception(Throwable cause)
四、自定义异常
所谓自定义异常,通常就是定义了一个继承自Exception类的子类,那么这个类就是一个自定义异常类。通常情况下,我们都会直接继承自Exception类,一般不会继承某个运行时的异常类。
我们可以使用多个catch块来捕获异常,这时需要将父类型的catch块放到子类型的catch块之后,这样才能保证后续的catch可能被执行,否则子类型的catch将永远无法到达,Java编译器会报编译错误;如果多个catch块的异常类型是独立的(MyException, MyException2), 那么谁前谁后都是可以的。
public class MyException extends Exception{
public MyException(){
super();
}
public MyException(String message){
super(message);
}
}
public class ExceptionTest4{
public void method(String str) throws Exception{
if(null == str) {
throw new MyException("传入的字符串参数不能为null");
}
.在类继承的时候,方法覆盖时如何进行异常抛出声明
本小节讨论子类重写父类方法的时候,如何确定异常抛出声明的类型。下面是三点原则:
1、父类的方法没有声明异常,子类在重写该方法的时候不能声明异常;
2、如果父类的方法声明一个异常exception1,则子类在重写该方法的时候声明的异常不能是exception1的父类;
3、如果父类的方法声明的异常类型只有非运行时异常(运行时异常),则子类在重写该方法的时候声明的异常也只能有非运行时异常(运行时异常),不能含有运行时异常(非运行时异常)。
五、异常处理
异常处理有两个过程,一个是抛出异常;一个是捕捉异常。
处理原则:捕捉并处理哪些知道如何处理的异常,而传递哪些不知道如何处理的异常
1、try-catch-finally处理异常
如果在方法内部抛出了异常(或者在方法内部调用其他方法抛出了异常),这个方法将在抛出异常的过程中结束,要是不希望此方法结束,可以在方法内设置一个特殊的try块来捕获异常。
try{
放置可能会发生异常的的语句块,如可能出现异常的函数,也可以是一般的程序语句
(注意:try 语句中发生异常语句后面的代码不会执行)
}
catch(Exception e){
…… 用于抓住异常,并处理异常 ,如果抛出的异常在catch中没有声明,那么方法就会退出
}
finally{
……
}
try…catch… 捕获异常时,大的异常(Exception类)放在下方,小的异常放在上方,否则(报错),在异常捕获时,小的异常将不能被捕获,因为全在大的异常类中捕获到。即: 如果多个 catch 块中的异常出现继承关系,父类异常 catch 块放在最下面
使用finally块释放资源
finally关键字保证无论程序使用任何方式离开try块,finally中的语句都会被执行。在以下三种情况下会进入finally块:
1、 try块中的代码正常执行完毕。
2、 在try块中抛出异常。
3、在try块中执行return、break、continue。
在以下4种特殊情况下,finally块不会被执行:
1.在finally语句块第一行发生了异常。因为在其他行,finally块还是会得到执行
2.在前面的代码中用了System.exit(int)已退出程序。exit是带参函数;若该语句在异常语句之后,finally会执行
3.程序所在的线程死亡。
4.关闭CPU。
2、try-with-resources处理异常
面对必须要关闭的资源,我们,总是应该优先使用ty-with-resources而不是try-catch-finally。随之产生的代码更简短,更清晰,产生的异常对我们也更有用。try-with-resources语句让我们更容易编写必须要关闭的资源的代码,若采用try-catch-finally则几乎做不到这点。
Java中类似于InputStream、OutputStream、Scanner、PrintWriter等的资源都需要我们调用close()方法来手动关闭,一般情况下我们都是通过try-catch-finally语句来实现这个需求,如下:
private void correctWriting() throws IOException {
DataOutputStream out = null;
try {
out = new DataOutputStream(new FileOutputStream("data"));
out.writeInt(666);
out.writeUTF("Hello");
} catch (Exception e){
e.printStackTrace();
}finally {
if (out != null) {
out.close();
}
}
}
使用Java7之后的try-with-resources语句改造上面的代码:
private void correctWriting() throws IOException {
try (DataOutputStream out = new DataOutputStream(new FileOutputStream("data"));){
out.writeInt(666);
out.writeUTF("Hello");
}
}
当然多个资源需要关闭的时候,使用try-with-resources实现起来也非常简单,如果你还是用try-catch-finally可能会带来很多问题。
通过使用分号分隔,可以在try-with-resources块中声明多个资源:
3、抛出异常
抛出异常有三种形式,一是throw,一个throws,还有一种系统自动抛异常。下面它们之间的异同。
2.1.系统自动抛异常
当程序语句出现一些逻辑错误、主义错误或类型转换错误时,系统会自动抛出异常。
2.2 throw
throw是语句抛出一个异常。
语法:throw 异常对象; 如: throw e
一般会用于程序出现某种逻辑时程序员主动抛出某种特定类型的异常。
抛出什么异常?
对于一个异常对象,真正有用的信息是异常的对象类型,而异常对象本身毫无意义。比如一个异常对象的类型是ClassCastException,那么这个类名就是唯一有用的信息。所以,在选择抛出什么异常时,最关键的就是选择异常的类名能够明确说明异常情况的类。
异常对象通常有两种构造函数:一种是无参数的构造函数;另一种是带一个字符串的构造函数,这个字符串将作为这个异常对象除了类型名以外的额外说明。
2.3 throws
throws是方法可能抛出异常的声明。(用在声明方法时,表示该方法可能要抛出异常的种类)
为什么要在声明方法抛出异常?
假设方法抛出异常却没有声明该方法将抛出异常,那么客户程序员可以调用这个方法而且不用编写处理异常的代码。那么,一旦出现异常,那么这个异常就没有合适的异常控制器来解决。
为什么抛出的异常一定是已检查异常?
RuntimeException与Error可以在任何代码中产生,它们不需要由程序员显示的抛出,一旦出现错误,那么相应的异常会被自动抛出。遇到Error,程序员一般是无能为力的;遇到RuntimeException,那么一定是程序存在逻辑错误,要对程序进行修改;只有已检查异常才是程序员所关心的,程序应该且仅应该抛出或处理已检查异常。而已检查异常是由程序员抛出的,这分为两种情况:客户程序员调用会抛出异常的库函数;客户程序员自己使用throw语句抛出异常。
注意:
覆盖父类某方法的子类方法不能抛出比父类方法更多的异常,所以,有时设计父类的方法时会声明抛出异常,但实际的实现方法的代码却并不抛出异常,这样做的目的就是为了方便子类方法覆盖父类方法时可以抛出异常
问题1:throw 和 throws 的区别
答:
1、throws出现在方法函数头;而throw出现在函数体。
2、throws表示出现异常的一种可能性,并不一定会发生这些异常;throw则是抛出了异常,执行throw则一定抛出了某种异常对象。
3、两者都是消极处理异常的方式(这里的消极并不是说这种方式不好),只是抛出或者可能抛出异常,但是不会由函数去处理异常,真正的处理异常由函数的上层调用处理。
4,throws抛出的是一个异常类名,可以有多个 throw抛出的是一个异常对象,只能有一个
好的编程习惯:
1.在写程序时,对可能会出现异常的部分通常要用try{…}catch{…}去捕捉它并对它进行处理;
2.用try{…}catch{…}捕捉了异常之后一定要对在catch{…}中对其进行处理,那怕是最简单的一句输出语句,或栈输入e.printStackTrace();
3.如果是捕捉IO输入输出流中的异常,一定要在try{…}catch{…}后加finally{…}把输入输出流关闭;
4.如果在函数体内用throw抛出了某种异常,最好要在函数名中加throws抛异常声明,然后交给调用它的上层函数进行处理。