异常介绍
在程序运行时,很多问题不是靠代码能够避免的,比如用户输入数据格式不符合预期,读取文件不存在,网络不可用或不通畅等。
在Java语言中,将程序执行过程中发生的不正常情况称为“异常”。(开发过程中的语法错误和逻辑错误不是异常)
Java程序在执行过程中所发生的异常事件可分为两类:
- Error: Java虚拟机无法解决的严重问题。如:JVM系统内部错误、资源耗尽等严重情况。一般不编写针对性的代码进行处理。
- Exception: 其它因编程错误或偶然的外在因素导致的一般性问题,可以使用针对性的代码进行处理。例如:
- 空指针访问
- 试图读取不存在的文件
- 网络连接中断
对于这些异常,如果在编写程序时没有进行错误的检测以及处理(消息提示等),则会终止程序的运行,这是用户不希望看到的。
异常类型
运行时异常
是指编译器不要求强制处置的异常。一般是指编程时的逻辑错误,是程序员应该积极避免其出现的异常。
java.lang.RuntimeException类及它的子类都是运行时异常。
对于这类异常,可以不作处理,因为这类异常很普遍,若全处理可能会对程序的可读性和运行效率产生影响。
- java.lang.RuntimeException
- NullPointerException (空指针异常)
- ClassCastException (类型转换异常)
- ArrayIndexOutOfBoundsException (数组下标越界异常)
- ArithmeticException (计算异常)
- …
编译时异常
是指编译器要求必须处置的异常。即程序在运行时由于外界因素造成的一般性异常。编译器要求java程序必须捕获或声明所有编译时异常。
对于这类异常,如果程序不处理,可能会带来意想不到的结果。
- java.io.IOExeption
- FileNotFoundException
- EOFException
- java.lang.ClassNotFoundException
- java.lang.InterruptedException
- java.sql.SQLException
- …
个人总结:两种异常的差别在于,编译时异常是编译时就必须要告诉虚拟机如果万一出错了该怎么处理,要求更严格,这样可以避免在运行时可能出现异常导致程序终止。而运行时异常则没有这个限制,好处是没有太多的异常处理代码,不会影响代码的可读性和运行效率,坏处是没有捕获异常的话运行时会终止程序,所以这类异常应该在编写程序的过程中尽量避免,比如对于可能为空指针的变量要异常注意。
异常处理方式
Java采用异常处理机制,将异常处理的程序代码集中在一起,与正常的程序代码分开,使得程序简洁,并易于维护。
Java提供的是异常处理的抓抛模型。
-
throw
Java程序的执行过程中如出现异常,会生成一个异常类对象,该异常对象将被提交给Java运行时系统,这个过程称为抛出(throw)异常。
-
catch
如果一个方法内抛该异常对象会被抛出异常,给调用者方法中处理。如果异常没有在调用者方法中处理,它继续被抛给这个调用方法的上层方法。这个过程将一直继续下去,直到异常被处理。这一过程称为捕获(catch)异常。
如果一个异常回到main()方法,并且main()也不处理,则程序运行终止。
程序员通常只能处理Exception,而对Error无能为力。
try-catch-finally
异常处理是通过try-catch-finally语句实现的。
-
try
捕获异常的第一步是用try{…}语句块选定捕获异常的范围,将可能出现异常的代码放在try语句块中。 -
catch(Exceptiontype e)
在catch语句块中是对异常对象进行处理的代码。每个try语句块可以伴随一个或多个catch语句,用于处理可能产生的不同类型的异常对象。多个catch的异常类型有继承关系时,父类的catch要放在下面。
如果明确知道产生的是何种异常,可以用该异常类作为catch的参数;也可以用其父类作为catch的参数(多态的思想)。
异常对象的常用方法:
- getMessage(); 获取异常信息,返回字符串。
- printStackTrace(); 获取异常类名和异常信息,以及追踪异常出现在程序中的位置。返回值void。
-
finally
为异常处理提供一个统一的出口,使得在控制流转到程序的其它部分以前,能够对程序的状态作统一的管理。
不论在try代码块中是否发生了异常事件,catch语句是否执行,catch语句是否有异常,try或catch语句中是否有return,finally块中的语句都会被执行。
支持嵌套:
public void test3() {
try {
int j = 10 / 0;
} catch (Exception e) {
System.out.println(e.getMessage() + 1);
try {
int j = 10 / 0;
} catch (Exception e) {
System.out.println(e.getMessage() + 2);
}
} finally {
try {
int j = 10 / 0;
} catch (Exception e) {
System.out.println(e.getMessage() + 3);
}
}
}
finally一定会执行:
public static int method1() {
try {
System.out.println(10/0);
// 执行到这里会先执行finally中的代码,若finally中有return,则这里的的return 1不会执行
return 1;
} catch (Exception e) {
System.out.println("捕获到异常时这里会执行");
// 执行到这里会先执行finally中的代码,若finally中有return,则这里的的return 3不会执行
// return 3;
} finally {
System.out.println("这里一定会执行");
// return 2;
}
System.out.println("这里可能会执行");
return 4;
}
牛客网的一道题:
public class Test {
public static void main(String [] args){
System.out.println(new B().getValue());
}
static class A{
protected int value;
public A(int v) {
setValue(v);
}
public void setValue(int value){
this.value = value;
}
public int getValue(){
try{
value++;
return value;
} catch(Exception e){
System.out.println(e.toString());
} finally {
this.setValue(value);
System.out.println(value);
}
return value;
}
}
static class B extends A{
public B() {
super(5);
setValue(getValue() - 3);
}
public void setValue(int value){
super.setValue(2 * value);
}
}
}
输出为:22 34 17
注意:
(1)try中的return为value++后的值,而不是执行finally后的value值(虽然finally确实执行了)
(2)对B调用的是B中重载后的setValue
throws
如果一个方法(中的语句执行时)可能生成某种异常,但是并不能确定如何处理这种异常,则此方法应显式地声明可能抛出的异常类,表明该方法将不对这些异常进行处理,而由该方法的调用者负责处理。
public static void method1() throws FileNotFoundException, IOException{
FileInputStream fis = new FileInputStream(new File("hello1.txt"));
int b;
while((b = fis.read()) != -1){
System.out.print((char)b);
}
fis.close();
}
注意:重写方法不能抛出比被重写方法范围更大的异常类型。在多态的情况下,对重写方法的异常的捕获按父类中方法声明的异常处理。
throw
Java异常类对象除在程序执行过程中出现异常时由系统自动生成并抛出,也可根据需要人工创建并抛出。
语法为:
throw Throwable或其子类的实例;
throw单独存在,紧跟着throw语句不要定义语句,因为执行不到。(跟return道理一样)
自定义异常类
- 自定义的异常类继承现有的异常类
- 提供一个序列号,提供几个重载的构造器
public class MyException extends Exception{
static final long serialVersionUID = -70348975766939L;
public MyException(){
}
public MyException(String msg){
super(msg);
}
}
总结
Java的异常处理过程为:抛出异常 -> (往上逐层抛出) -> 捕获并处理异常。
-
抛出异常
异常类可以使用已经定义好的,也可以自行定义(必须直接或间接继承自Throwable类)。
使用代码 “throw 异常类对象” 抛出异常。
-
声明抛出异常 (往上逐层抛出)
方法中可以使用 “throws + 异常类列表” 显式声明可能抛出的异常,并由调用者处理。
-
捕获异常
try{} 包住可能会抛出异常的代码;
catch(Exception e){} 捕获相应异常并进行处理;
finally{} 为后续处理代码,一定会执行。
以上笔记参考自尚硅谷