一、JAVA 异常类型结构分类
在Java
中Throwable
是所有异常类型的基类,并且Throwable
异常下一层分为两个分支,分别为Error
和 Exception
,Error
和 Exception
都继承自基类Throwable
。
其关系结构图如下:
二、Error 和 Exeption区别
1. Error
Error
表示不可恢复的情况,例如Java
虚拟机(JVM
)内存不足,内存泄漏,堆栈溢出错误,库不兼容,无限递归等,描述了 JAVA
程序运行时系统的内部错误,通常比较严重,该异常产生的错误通常是程序员无法控制的,我们不应该尝试处理错误,除了通知用户和尽力使应用程序安全地终止之外,无能为力,应用程序不应该尝试去捕获这种异常。通常为一些虚拟机异常,如 StackOverflowError
等。
2. Exception
Exception
类型下面又分为两个分支,一个分支派生自 RuntimeException
,这种异常通常为程序错误导致的异常;
另一个分支为非派生自 RuntimeException
的异常,这种异常通常是程序本身没有问题,由于像 I/O
错误等问题导致的异常,每个异常类用逗号隔开。
Exception
又分为两种:
-
受检异常 : 需要用
try…catch…
语句捕获并进行处理,并且可以从异常中恢复,受查异常会在编译时被检测。如果一个方法中的代码会抛出受检异常,则该方法必须包含异常处理,即try-catch
代码块,或在方法签名中用throws
关键字声明该方法可能会抛出的受查异常,否则编译无法通过(例如IOException
也称为受检异常)。如果一个方法可能抛出多个受查异常类型,就必须在方法的签名处列出所有的异常类。通过 throws 关键字声明可能抛出的异常;-
通过 throws 关键字声明可能抛出的异常
/** * 通过 throws 关键字声明可能抛出的异常 */ private static void readFile(String filePath) throws IOException { File file = new File(filePath); String result; BufferedReader reader = new BufferedReader(new FileReader(file)); while ((result = reader.readLine()) != null) { System.out.println(result); } reader.close(); }
-
通过try-catch 处理异常
/** * try-catch 处理异常 * * @param filePath */ private static void readFile(String filePath) { File file = new File(filePath); String result; BufferedReader reader; try { reader = new BufferedReader(new FileReader(file)); while ((result = reader.readLine()) != null) { System.out.println(result); } reader.close(); } catch (IOException e) { e.printStackTrace(); } }
-
-
非受检异常 : 非受检异常是程序运行时错误,例如空指针异常。非受查异常不会在编译时被检测。
JAVA
中Error
和RuntimeException
类的子类属于非受检异常,除此之外继承自Exception
的类型为受检异常。这些异常不在编译时检查,而是在运行时检查。一些常见的运行时异常是:
API
使用不当 -IllegalArgumentException
- 空指针访问(缺少变量的初始化)-
NullPointerException
- 越界数组访问 -
ArrayIndexOutOfBoundsException
- 将数字除以0 -
ArithmeticException
你可以这样想:“如果这是一个运行时异常,那就是你的错”。
如果在使用变量之前检查变量是否已初始化,则不会发生
NullPointerException
。
如果根据数组边界测试数组索引,则不会发生ArrayIndexOutOfBoundsException
。
三、异常的处理
对于不同的异常,java采用不同的异常处理方式:
(1)Error(错误):一般表示代码运行时JVM出现问题。比如NoClassDefFoundError等。比如说当jvm耗完可用内存时,将出现OutOfMemoryError。此类错误发生时,JVM将终止线程。
(2)运行异常(RuntimeException
)将由系统自动抛出,应用本身可以选择捕获处理或者忽略该异常。
(3)受检异常(IOException
)必须进行捕获或者在方法签名处使用 throws
关键字声明可能会抛出的异常,抛出该方法之外交给上一层处理。或者说要么使用try-catch
进行捕获处理,二选其一。
1. throws
直接抛出异常
通常,应该捕获那些知道如何处理的异常,将不知道如何处理的异常继续传递下去。传递异常可以在方法签名处使用 throws 关键字声明可能会抛出的异常。
private static void readFile(String filePath) throws IOException {
File file = new File(filePath);
String result;
BufferedReader reader = new BufferedReader(new FileReader(file));
while((result = reader.readLine())!=null) {
System.out.println(result);
}
reader.close();}
2. 封装异常再抛出
有时我们会从 catch 中抛出一个异常,目的是为了改变异常的类型。多用于在多系统集成时,当某个子系统故障,异常类型可能有多种,可以用统一的异常类型向外暴露,不需暴露太多内部异常细节。
private static void readFile(String filePath) throws MyException {
try {
// code
} catch (IOException e) {
MyException ex = new MyException("read file failed.");
ex.initCause(e);
throw ex;
}}
3. try-catch
捕获异常
在一个 try-catch
语句块中可以捕获多个异常类型,并对不同类型的异常做出不同的处理。
private static void readFile(String filePath) {
try {
// code
} catch (FileNotFoundException e) {
// handle FileNotFoundException
} catch (IOException e){
// handle IOException
}}
同一个 catch 也可以捕获多种类型异常,用 | 隔开:
private static void readFile(String filePath) {
try {
// code
} catch (FileNotFoundException | UnknownHostException e) {
// handle FileNotFoundException or UnknownHostException
} catch (IOException e){
// handle IOException
}}
4. try-catch-finally
当方法中发生异常,异常处之后的代码不会再执行,如果之前获取了一些本地资源需要释放,则需要在方法正常结束时和 catch 语句中都调用释放本地资源的代码,显得代码比较繁琐,finally 语句可以解决这个问题。
private static void readFile(String filePath) throws MyException {
File file = new File(filePath);
String result;
BufferedReader reader = null;
try {
reader = new BufferedReader(new FileReader(file));
while((result = reader.readLine())!=null) {
System.out.println(result);
}
} catch (IOException e) {
System.out.println("readFile method catch block.");
MyException ex = new MyException("read file failed.");
ex.initCause(e);
throw ex;
} finally {
System.out.println("readFile method finally block.");
if (null != reader) {
try {
reader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}}
调用该方法时,读取文件时若发生异常,代码会进入 catch
代码块,之后进入finally
代码块;若读取文件时未发生异常,则会跳过catch
代码块直接进入 finally
代码块。所以无论代码中是否发生异常,fianlly
中的代码都会执行。
若 catch
代码块中包含 return
语句,finally
中的代码还会执行吗?将以上代码中的 catch
子句修改如下
catch (IOException e) {
System.out.println("readFile method catch block.");
return;}
调用 readFile
方法,观察当catch
子句中调用 return
语句时,finally
子句是否执行
可见,即使 catch
中包含了 return
语句,finally
子句依然会执行。若 finally
中也包含 return
语句,finally
中的return
会覆盖前面的 return
。
四、常见的几种RuntimeException
一般面试中java Exception(runtimeException )
是必会被问到的问题。常见的异常列出四五种,是基本要求。常见的几种如下:
NullPointerException - 空指针引用异常
ClassCastException - 类型强制转换异常。
IllegalArgumentException - 传递非法参数异常。
ArithmeticException - 算术运算异常
ArrayStoreException - 向数组中存放与声明类型不兼容对象异常
IndexOutOfBoundsException - 下标越界异常
NegativeArraySizeException - 创建一个大小为负数的数组错误异常
NumberFormatException - 数字格式异常
SecurityException - 安全异常
UnsupportedOperationException - 不支持的操作异常
NegativeArrayException —— 数组负下标异常
ArrayIndexOutOfBoundsException - 数组下标越界异常
EOFException - 文件已结束异常
FileNotFoundException - 文件未找到异常
NumberFormatException - 字符串转换为数字异常
SQLException - 操作数据库异常
IOException - 输入输出异常
NoSuchMethodException - 方法未找到异常
五、如何自定义异常
如果希望写一个检查性异常类,则需要继承 Exception 类。
如果你想写一个运行时异常类,那么需要继承 RuntimeException 类。
继承Exception
还是继承RuntimeException
是由异常本身的特点决定的,而不是由是否是自定义的异常决定的。在Java
的规范里说明了,Java Exception
分为两种, unchecked exception 和 checked exception ,显然 unchecked exception 是运行时异常继承自RuntimeException
,**checked exception **是受检异常继承自Exception
。因此在我们决定业务异常的处理时,自定义异常继承RuntimeException
。
1. 自定义业务异常BusinessException
(非受检异常)
/**
* 业务异常
*
* @author cao_wencao
*/
public class BusinessException extends RuntimeException
{
private static final long serialVersionUID = 1L;
protected final String message;
public BusinessException(String message)
{
this.message = message;
}
public BusinessException(String message, Throwable e)
{
super(message, e);
this.message = message;
}
@Override
public String getMessage()
{
return message;
}
}
业务场景:
public static void checkScore(int score) throws BusinessException{
if(score < 0 || score > 100) {
throw new BusinessException("考试成绩不符合要求");
}
System.out.println("考试成绩符合要求");
}
2. 自定义异常(受检异常)
/**
* 业务异常-受检异常
*
* @author cao_wencao
*/
public class MyException extends Exception
{
private static final long serialVersionUID = -7041169491254546905L;
public MyException() {
super();
}
public MyException(String message) {
super(message);
}
public MyException(String message, Throwable cause) {
super(message, cause);
}
public MyException(Throwable cause) {
super(cause);
}
}
场景:
/**
* //调用自定义的受检异常, 根据解析好的content,转化json对象
* @param content
* @return
*/
private static JSONObject getJsonResponse(String content) throws MyException {
//TODO: code
JSONObject jsonObject = new JSONObject();
return jsonObject;
}
3. 受检异常和非受检异常的应用示例
-
使用自定义的非受检异常处理
-
使用自定义的受检异常
-
两种异常方法示例调用
调用getJsonResponse("json字符串");
方法时,IDEA工具提示我们需要显示处理该方法抛出来的受检异常,可以使用try..catch
或者throws
往外抛异常。
4. 完整code
package com.example.demo;
import com.alibaba.fastjson.JSONObject;
import com.example.demo.exception.BusinessException;
import com.example.demo.exception.MyException;
import lombok.extern.slf4j.Slf4j;
/**
* @desc:
* @author: cao_wencao
* @date: 2020-11-25 15:51
*/
@Slf4j
public class ExceptionTest {
/**
* 调用自定义的非受检异常
* @param score
* @throws BusinessException
*/
public static void checkScore(int score) throws BusinessException {
if (score < 0 || score > 100) {
throw new BusinessException("考试成绩不符合要求");
}
System.out.println("考试成绩符合要求");
}
public void exceptionTest() throws MyException {
//调用非受检异常
checkScore(99);
//调用受检异常,编译器红色波浪线提示需要显示处理受检异常
getJsonResponse("json字符串");
}
/**
* //调用自定义的受检异常, 根据解析好的content,转化json对象
* @param content
* @return
*/
private static JSONObject getJsonResponse(String content) throws MyException {
//TODO: code
JSONObject jsonObject = new JSONObject();
return jsonObject;
}
}