Java中所有的异常都由Throwable继承而来,从而分为Error与Exception两类
Error
在Java中,Error层次的错误一般都比较严重,属于系统内部错误或者是资源错误,一旦程序出现这种错误,用户是没有能力去解决的,系统也只能告知用户出现了这种错误,并尽量安全的使程序终止。比如
当用户定义了一个 int[] num = new int[10000000000]; 如此大的整型数组,而堆内存并不能存下长度过长的数组,因此出现了Error,并且这种错误,用户是无能为力的,因为用户也没有有效的办法扩大堆内存的大小
Exception
在Java中更应该考虑Exception这类结构,而Exception又分为两个分支:
- RuntimeException:运行期异常,这种类型的异常,用户可以选择不处理,也可以选择处理,如果出现了这种异常,那么一定是写程序的人的问题。
观察一个方法 public static int parseInt(String s) throws NumberFormatException; 如果想使用这个方法,按照道理我们需要使用try…catch结构来捕获该异常,但我们不需要这样,因为NumberFormatException是属于RuntimeException类下的异常,因此我们也可以顺利有:
public class Error {
public static void main(String[] args) {
//public static int parseInt(String s) throws NumberFormatException
int x = Integer.parseInt("100");
System.out.println(x);
}
}
因此,RuntimeException类异常不要求用户强制处理异常,但是若发生了异常,则交付JVM进行默认处理(JVM的默认处理将在后文叙述)
常见的RuntimeException几种情况:
- 错误的类型转换,ClassCastException,这个十分常见,原因也非常之多,不多赘述
- 数组越界,ArrayIndexOutOfBoundException,细心检查数组下标来杜绝此类异常发生
- 空指针异常,NullPointerException,检查变量是否为null
- 算数异常,ArithmeticException ,检查数学运算中是否存在分母为零等错误
- 并发修改异常,ConcurrentModificationException,一般出现在集合遍历时,如果线程在使用快速迭代器迭代集合时直接修改集合,则迭代器将抛出此异常,解决办法一般使用迭代器本身的方法或者选择使用其他方案遍历集合即可。
- 非RuntimeException:编译期异常,这种异常是用户必须要去处理的一类异常,不处理程序无法编译通过
这种异常大多属于IO流中的异常。
在这里需要说一下什么是JVM的默认处理,main方法当遇到无法处理的问题时,便会交给JVM进行默认处理,将该异常的名称,异常的信息.异常出现的位置打印在了控制台上,同时将程序停止运行。
当用户遇到需要处理的异常时有两种处理方法:
- 使用try…catch…finally语句来捕获异常,并交代如何处理该异常
有如下格式:
try {
可能出现问题的代码 ;
}catch(异常名 变量名){
针对问题的解决办法 ;
}catch(异常名 变量名){
针对问题的解决办法 ;
}catch(异常名 变量名){
针对问题的解决办法 ;
}finally{
释放资源等善后操作 ;
}
public class Error {
public static void main(String[] args) {
try {
int x = 1/0;
System.out.println("Hello World");
}catch (ArithmeticException e){
e.printStackTrace();
}finally {
System.out.println("x1");
}
System.out.println("x2");
}
}
结果打印:
x1
java.lang.ArithmeticException: / by zero
x2
at Demo.Error.main(Error.java:4)\
再来对比该程序:
public class Error {
public static void main(String[] args) {
try {
int x = 1/0;
System.out.println("Hello World");
}catch (ClassCastException e){
e.printStackTrace();
}finally {
System.out.println("x1");
}
System.out.println("x2");
}
}
结果打印:
x1
Exception in thread “main” java.lang.ArithmeticException: / by zero
at Map.Error.main(Error.java:5)
在这里我们可以得到结论:
当try中语句出现异常,那么异常语句逻辑之后的都不再执行
其次,当catch语句没有捕获到对应的异常时,剩余代码将不再进行
finally语句中的代码无论如何都会执行
备注:
在try语句中尽量只加入可能会发生错误的代码。
catch语句中最好做处理,即使只是使用 e.printStackTrace() 打印异常信息。
catch语句可以多条并列写,会依次匹配catch中的异常类型,但是范围小的异常类要写在范围大的异常类以前
- 使用throws关键字向上抛出异常,交给调用处去处理该异常
class Math{
public static int div(int x,int y) throws ArithmeticException{ //这里抛出算数异常,让调用者去解决
return x/y;
}
}
class Demo{
public static void main(String[] args) {
try {
System.out.println(Math.div(1,0));
}catch (Exception e){
e.printStackTrace();
}
}
}
主方法调用div方法,因此这里需要try…catch结构捕获异常,主方法也可以选择继续向上抛出该异常,则交由JVM进行默认处理异常,一般不建议这样做。
下面简述异常处理流程
Java异常处理流程:
上图没有写throws抛出异常,因为到头也是这个流程。
1.若程序运行发现异常,先判断当前程序是否存在异常处理,没有则交给JVM进行默认处理。
2.如果存在异常捕获,那么会一一匹配catch语句中的异常类型,如果捕获到符合的异常,则使用当前catch下的异常处理,若不符合,则继续向下比较其他catch。
3.无论最后能否匹配到,都会向后执行,如果存在finally,先执行finally代码,之后程序如何执行需要根据之前catch匹配结果而定。
4.如果之前成功匹配,则继续执行finally之后的代码,若没有成功匹配,则交由jvm进行默认处理,其他语句不再执行。