Java学习之旅-异常
概念:Java当中有异常处理机制,来处理程序运行过程中可能发生的意外,很好地提高了程序的健壮性
Throwable及其子类
Throwable下有Error和Exception两个子类:
- Error:有VirtualMachineError(虚拟机错误)、OutOfMemoryError(内存溢出)、ThreadDeath(线程死锁)
- Exception:有检查异常和非检查异常两个部分:
- 其中检查异常有IOException(IO异常)、SQLException(SQL异常),当该类异常被抛出时, 编译器会强制要求处理
- 非检查异常也就是RuntimeException又有四种,当该类异常被抛出时, 编译器不会强制要求处理 (如果出现RuntimeException,那么一定是你的问题,因此应该在使用变量之前通过检测它是否为NULL来避免NullPointerException;在使用数组前检测数组下标是否越界):
- NullPointerException(空指针异常)
- ArrayIndexOutOfBoundsException(数组下标越界异常)
- ArithmeticException(算数异常)
- ClassCastException(类型转换异常)
try-catch-finally
这是一个关键字的组合,try{}代码块内存放可能出现异常的代码;如果出现异常对象,并且和catch的异常类型匹配,则执行catch{}内存放的代码块,可以有多重catch;finally{}里存放的代码是无论是否有异常对象被抛出,也会执行。
如果想要不执行finally里的语句呢?
这里有两个办法:System.exit(1);和return,但是return比较特殊,不论return在哪个位置,都会把finally执行完再return。
要点
- 最后的一个catch最好使用Exception这个父类对象,避免自己遗漏的异常类型没法被捕获
- 在处理异常的时候,根据具体需要的业务逻辑来决定
- finally语句块中尽量用来释放占用的资源,从而显得逻辑性更强
throws和throw
通过这两个关键字来抛出异常,给外部捕获,从而进行处理。可以做到“谁调用这个方法,谁来处理”,而不是自己调用,自己发现异常,自己处理。
public static void main(String args) {
try {
int result = test();
System.out.println("one 和 twe 的商是:" + result);
} catch(InputMismatchException e){
System.out.println("输入格式错误");
} catch(ArithmeticException h){
System.out.println("这又是什么异常");
}catch (Exception a ){
System.out.println("什么异常?");
}
}
/**
* 这里会把要写的异常注解出来,这样在鼠标停在需要处理异常的方法上的时候,可以看到提示
* @return
* @throws Exception
*/
public static int test()throws Exception {
Scanner input = new Scanner(System.in);
System.out.println("------START-------");
System.out.print("输入第一个数字one:");
int one =input.nextInt();
System.out.print("输入第二个数字two:");
int two = input.nextInt();
System.out.println("-------运算结束------");
return one/two;
}
自抛自接也可:
public static int test()throws Exception {
try {Scanner input = new Scanner(System.in);
System.out.println("------START-------");
System.out.print("输入第一个数字one:");
int one =input.nextInt();
System.out.print("输入第二个数字two:");
int two = input.nextInt();
System.out.println("-------运算结束------");
return one/two;
}catch(InputMismatchException e){
System.out.println("输入格式错误");
} catch(ArithmeticException h){
System.out.println("这又是什么异常");
}catch (Exception a ){
System.out.println("什么异常?");
}
}
编译器未提醒程序员来处理非检查异常,有什么别的办法吗?
/**
* 这里会把要写的异常注解出来,这样在鼠标停在需要处理异常的方法上的时候,可以看到提示
* @return
* @throws Exception
*/
以上的的注解形式,在IDEA中会呈现特殊颜色,会自动检测你的方法中是否有需要处理的异常。从而可以让程序员的鼠标放在方法调用的地方稍微久一点,就可以看到这个方法有什么异常需要处理,是一种提醒程序员编程不出现遗漏的方法。
子类重写父类方法时,如果父类方法中有声明特定的检查型异常该怎么办呢?
- 首先,子类的重写方法不可以声明父类方法中异常对象的父类,比如父类中声明了RuntimeException,子类的重写方法就可以用ArrayIndexOutOfBoundsException等(只可以写更特定的异常或者跑不出任何异常)。
- 其次,如果父类的方法没有抛出任何异常,子类方法也不可以抛出任何异常。
创建自己的异常类
通过创建自己的异常类,来描述自己特定的业务需要。定义一个派生于Exception的类,或者派生于Exception的某个子类。而这个自定义的类中会有两个构造器,一个是默认的,另一个是包含详细信息的构造器(超类Throwable的toString方法会返回一个字符串,包含这个详细信息),因为Exception也是Throwable的子类。
异常链
可以在catch中继续抛出新的异常,来改变异常的类型,有时候不一定想知道故障异常的具体细节,只是想知道是否出了故障。假设之前的代码中,抛出的是ArithmeticException异常,我们来对他做一个包装,而不是直接抛出另一种异常对象,因为不包装直接抛出会丢失原本的异常信息。如果我们如下图一样,包装后再抛出,就可以再利用 Throwable original = caughException.getCause(); 这一句来获取原始异常。
public static void main(String args) {
try {
int result = test();
System.out.println("one 和 twe 的商是:" + result);
} catch (ArithmeticException a ){
var a=new SerletException("database error");
e.initCause(original);
throw e;
}
}
或者,只是记录一个异常,再重新抛出:
try {
....
} catch (Exception a ){
logger.log(level,maeeage,e)
throw e;
}
var
上面的案例中出现了var这个关键字,做一个记录:var是Java10的新特性,它可以暂时代替各种数据类型的关键字:
- 只能用在局部变量
- 声明时必须初始化
- 不能作方法的参数
在编译时,var会被代替成所赋值的类型。
如果try语句中抛出异常被catch接到,catch又抛出一个异常给这个方法的调用者呢?
public static void main(String[] args) throws Exception {
try {
int result = test();
System.out.println("one 和 twe 的商是:" + result);
} catch (ArithmeticException e){
System.out.println("出现了算数异常");
throw new Exception("新的错误");
System.out.println("又出现了算数异常");//这一句会报错
} finally {
System.out.println("新的错误结束");
}
}
很显然,抛出了新错误后,catch后面的语句就不会继续执行了。
try-with-Resources
似乎现在try-with-Resources语句要比finally子句更常用,它的特点是可以省略finally,在出现异常和退出时自动调用System.in.closa()方法,从而达到使用的finally块的效果。这里的try with Resources并不是三个关键字的意思,而是带有资源的try语句:
try(Resource res=...)
{
work with res
}
还有一个有趣的地方,如果try抛出一个异常的同时,close方法也来一个异常,好家伙,这就有趣了。这时候,try-with-Resources会自动抑制close的异常,而try的异常会被重新抛出。也可以通过调用getSuppressed方法来调出close的异常。
总之,如果需要使用并关闭资源,用try-with-Resources