最近发现很多人连异常 Exception
都“不会处理”,不是说不会try catch
,而是不知道try catch、throw、throws
的作用,以及在实际项目中主流的处理方案是怎样,兼容业务异常、返回格式、异常码、兜底异常处理等等。
我准备做个总结,希望帮到一部分人,还有未来可能要面试,到时可以翻出来看看
Java异常
简单的我就直接贴图片了,我们普遍说的异常是Exception
,它的父类是Throwable
,Error
代表错误,和异常不一样,不管是图片中提到的内存溢出还是栈溢出都代表一时半会无法处理,需要优化程序。而Exception
可以被处理,分成两类:
RuntimeException
,称作运行时异常,这类异常可以被编译,但代表着代码的“漏洞”,我们编写代码时就应该考虑到,程序运行时,会不会因为某种可能没判断,导致异常,程序中止。
像NullPointerException
就是没对值判断null
,导致被调用。
String s = null;
s.substring(0,1);
Checked Exception
,它也被叫做非RuntimeException
异常,这种异常不处理,编译没法通过,程序不能运行,处理方案有两个,要么 try catch
捕获异常,要么声明出去。
这种异常你肯定也碰到过,因为 IDE 能识别,提醒我们处理,这就是编译异常。
捕捉、处理异常
就用上个例子来看,它会提醒我们用以下两种方案来处理
第一种处理:
private static void testException2(String s) throws FileNotFoundException {
FileInputStream file = new FileInputStream(s);
}
碰到异常,我们可以throws
将异常声明出去,也就是自己不处理,向外声明,声明该方法可能存在异常,上级调用后,就会收到异常提醒(Checked Exception
会收到提醒)。上面提到的编译异常Checked Exception
就是这么来的。为啥只要将代码编写出来就会收到编辑器的提醒呢?
点进去就会发现,这个方法内部就向外声明了异常。
public FileInputStream(String name) throws FileNotFoundException {
this(name != null ? new File(name) : null);
}
第二种处理:
private static void testException2(String s) {
try {
FileInputStream file = new FileInputStream(s);
} catch (FileNotFoundException e) {
e.printStackTrace();
}
}
这种方案采取两步处理,首先 try catch
捕捉异常,接着e.printStackTrace()
打印异常的堆栈信息。这种默认处理方式,它的作用是啥呢?
写个简单小代码做测试,相信很容易看懂
System.out.println("1111");
testException2("");
System.out.println("2222");
运行程序后,控制台输出如下:
很明显,处理是有效的,首先程序不会因为异常中止运行,接着还打印出堆栈信息,这样排查时,可以定位原因。
那这样做是不是就解决问题了呢?不,这只是初级方案。
很多人喜欢把try catch
看作捕捉异常,但其实只要捕捉异常,就算处理异常了,因为异常一旦被try catch
,就不会再使程序中止崩溃。所以,处理异常的关键是try catch
后的处理方案。
先来看最蠢的方案:
private static void testException2(String s) {
try {
FileInputStream file = new FileInputStream(s);
} catch (FileNotFoundException e) {
}
}
捕捉到异常后,不做任何处理。虽然使程序能成功运行,并且保证触发异常后不会中止程序,但却没做任何处理,像个黑洞,没给出任何有效信息,异常就一直在那。所以,还不如让程序中止崩溃,我们能发现问题。
稍微好点的方案刚才提过,就是打印堆栈信息。
还可以再优化,比如在里面再抛出异常。不一样的是,非运行时异常(编译异常)不能try catch
后再抛出异常。
所以,我这替换成运行时异常(RuntimeException
)
private static void testException(String s){
try {
Integer.parseInt(s);
}catch (NumberFormatException exception){
throw new NumberFormatException();
}
}
这种做法是不是很有意思,捕捉到异常后做了啥呢?再构造异常对象抛出去。
我们来看看,代码运行,当某个条件触发异常后,会发生啥呢?
System.out.println("1111");
testException("ss");
System.out.println("2222");
控制台输出如下:
程序中止运行,打印堆栈信息。这个结果,是不是挺合理的,虽然try catch
处理了异常,但处理方式是再抛出去。而上级调用者又没再处理,那触发异常,程序崩溃是符合逻辑的。
现在我们可以对比 throw
和throws
的区别了。
同样是“抛出去”,但两者结果很不一样。
throw
是抛出异常对象,而throws
是将【异常声明】抛出去。
严格意义上,它两都没真正处理异常,但throws
做了声明,声明存在异常的可能性,上级如果要调用此方法,记得处理异常,处理方式同样可以再throws
声明出去,也可以try catch
。
而throw
抛出异常对象,上级调用者也可以处理try catch
这个异常,这样程序就不会报错了。
比如上面代码,我重新修改如下
System.out.println("1111");
try {
testException("ss");
}catch (NumberFormatException e){
e.printStackTrace();
}
System.out.println("2222");
得到结果:
所以,throw
和throws
单独使用效果差不多,但一般两者结合一起使用,因为多人开发时,这样能告诉调用者,里面可能存在异常,这叫做好人好事。
private static void testException(String s) throws NumberFormatException{
try{
Integer.parseInt(s);
}catch(NumberFormatException e){
throw new NumberFormatException();
}
}
本文由老郭种树原创,转载请注明:https://guozh.net/java-exception-1/