- 检查性异常:最具代表的检查性异常是用户错误或问题引起的异常,这是程序员无法预见的。例如要打开一个不存在文件时,一个异常就发生了,这些异常在编译时不能被简单地忽略。
- 运行时异常: 运行时异常是可能被程序员避免的异常。与检查性异常相反,运行时异常可以在编译时被忽略。
-
错误: 错误不是异常,而是脱离程序员控制的问题。错误在代码中通常被忽略。例如,当栈溢出时,一个错误就发生了,它们在编译也检查不到的。
所有的异常类是从 java.lang.Exception 类继承的子类。Exception 类是 Throwable 类的子类。除了Exception类外,Throwable还有一个子类Error 。
Java 程序通常不捕获错误。错误一般发生在严重故障时,它们在Java程序处理的范畴之外。
Error 用来指示运行时环境发生的错误。
例如,JVM 内存溢出。一般地,程序不会从错误中恢复。
异常类有两个主要的子类:IOException 类和 RuntimeException 类。
我们所谓的异常处理,基本是指IOException , RuntimeException 和自定义异常处理
非检查性异常
异常 | 描述 |
---|---|
ArithmeticException | 当出现异常的运算条件时,抛出此异常。例如,一个整数"除以零"时,抛出此类的一个实例。 |
ArrayIndexOutOfBoundsException | 用非法索引访问数组时抛出的异常。如果索引为负或大于等于数组大小,则该索引为非法索引。 |
ArrayStoreException | 试图将错误类型的对象存储到一个对象数组时抛出的异常。 |
ClassCastException | 当试图将对象强制转换为不是实例的子类时,抛出该异常。 |
IllegalArgumentException | 抛出的异常表明向方法传递了一个不合法或不正确的参数。 |
IllegalMonitorStateException | 抛出的异常表明某一线程已经试图等待对象的监视器,或者试图通知其他正在等待对象的监视器而本身没有指定监视器的线程。 |
IllegalStateException | 在非法或不适当的时间调用方法时产生的信号。换句话说,即 Java 环境或 Java 应用程序没有处于请求操作所要求的适当状态下。 |
IllegalThreadStateException | 线程没有处于请求操作所要求的适当状态时抛出的异常。 |
IndexOutOfBoundsException | 指示某排序索引(例如对数组、字符串或向量的排序)超出范围时抛出。 |
NegativeArraySizeException | 如果应用程序试图创建大小为负的数组,则抛出该异常。 |
NullPointerException | 当应用程序试图在需要对象的地方使用 null 时,抛出该异常 |
NumberFormatException | 当应用程序试图将字符串转换成一种数值类型,但该字符串不能转换为适当格式时,抛出该异常。 |
SecurityException | 由安全管理器抛出的异常,指示存在安全侵犯。 |
StringIndexOutOfBoundsException | 此异常由 String 方法抛出,指示索引或者为负,或者超出字符串的大小。 |
UnsupportedOperationException | 当不支持请求的操作时,抛出该异常。 |
检查性异常
异常 | 描述 |
---|---|
ClassNotFoundException | 应用程序试图加载类时,找不到相应的类,抛出该异常。 |
CloneNotSupportedException | 当调用 Object 类中的 clone 方法克隆对象,但该对象的类无法实现 Cloneable 接口时,抛出该异常。 |
IllegalAccessException | 拒绝访问一个类的时候,抛出该异常。 |
InstantiationException | 当试图使用 Class 类中的 newInstance 方法创建一个类的实例,而指定的类对象因为是一个接口或是一个抽象类而无法实例化时,抛出该异常。 |
InterruptedException | 一个线程被另一个线程中断,抛出该异常。 |
NoSuchFieldException | 请求的变量不存在 |
NoSuchMethodException | 请求的方法不存在 |
异常方法
序号 | 方法及说明 |
---|---|
1 | public String getMessage() 返回关于发生的异常的详细信息。这个消息在Throwable 类的构造函数中初始化了。 |
2 | public Throwable getCause() 返回一个Throwable 对象代表异常原因。 |
3 | public String toString() 使用getMessage()的结果返回类的串级名字。 |
4 | public void printStackTrace() 打印toString()结果和栈层次到System.err,即错误输出流。 |
5 | public StackTraceElement [] getStackTrace() 返回一个包含堆栈层次的数组。下标为0的元素代表栈顶,最后一个元素代表方法调用堆栈的栈底。 |
6 | public Throwable fillInStackTrace() 用当前的调用栈层次填充Throwable 对象栈层次,添加到栈层次任何先前信息中。 |
捕捉异常
try-catch
使用 try 和 catch 关键字可以捕获异常。try/catch 代码块放在异常可能发生的地方。
try/catch代码块中的代码称为保护代码,使用 try/catch 的语法如下:
try
{
// 程序代码
}catch(ExceptionName e1)
{
//Catch 块
}
Catch 语句包含要捕获异常类型的声明。当保护代码块中发生一个异常时,try 后面的 catch 块就会被检查。
如果发生的异常包含在 catch 块中,异常会被传递到该 catch 块,这和传递一个参数到方法是一样。
throws/throw 关键字:
也可以使用 throw 关键字抛出一个异常,无论它是新实例化的还是刚捕获到的。
下面方法的声明抛出一个 RemoteException 异常:
import java.io.*;
public class className
{
public void deposit(double amount) throws RemoteException
{
// Method implementation
throw new RemoteException();
}
//Remainder of class definition
}
一个方法可以声明抛出多个异常,多个异常之间用逗号隔开。
finally关键字
finally 关键字用来创建在 try 代码块后面执行的代码块。
无论是否发生异常,finally 代码块中的代码总会被执行。
try{
// 程序代码
}catch(异常类型1 异常的变量名1){
// 程序代码
}catch(异常类型2 异常的变量名2){
// 程序代码
}finally{
// 程序代码
}
如何定义一个全局异常处理类
1.先定义异常枚举类
public enum ErrorEnum {
/**
* 成功
*/
SUCCESS("1", 1, "SUCCESS", "成功"),
/**
* 失败
*/
FAILED("-1", -1, "FAILED", "失败"),
//---------------- 一般错误(0001-0999) ----------------
/**
* 未知错误
*/
UNKNOWN("0001", -1, "UNKNOWN", "未知错误"),
//---------------- 业务逻辑错误(8000-9999) ----------------
COMMON_BUSINESS_ERROR("8000", -8000, "BUSINESS_COMMON_ERROR", "业务问题"),
;
/**
* 内部错误代码
*/
private String internalErrorCode;
/**
* 外部错误码
*/
private Integer externalErrorCode;
/**
* 错误名称
*/
private String errorName;
/**
* 错误描述
*/
private String errorDescription;
}
2.自定义异常处理类
public class CommonException extends RuntimeException {
/**
* 错误枚举
*/
private ErrorEnum error;
/**
* 错误详情
*/
private String detail;
/**
* 系统内部异常类构造器
*
* @param error 错误枚举
*/
public CommonException(ErrorEnum error) {
this(error, error.getErrorDescription());
}
/**
* 系统内部异常类构造器
*
* @param error 错误枚举
*/
public CommonException(ErrorEnum error, Throwable throwable) {
super(error.getErrorDescription(), throwable);
this.error = error;
this.detail = error.getErrorDescription();
}
/**
* 系统内部异常类构造器
*
* @param error 错误枚举
* @param detail 错误详情
*/
public CommonException(ErrorEnum error, String detail) {
super(detail);
this.error = error;
this.detail = detail;
}
/**
* 系统内部异常类构造器(公共业务问题)
*
* @param detail
*/
public CommonException(String detail) {
super(detail);
this.error = ErrorEnum.COMMON_BUSINESS_ERROR;
this.detail = detail;
}
public ErrorEnum getError() {
return error;
}
public void setError(ErrorEnum error) {
this.error = error;
}
public String getDetail() {
return detail;
}
public void setDetail(String detail) {
this.detail = detail;
}
}
3.全局异常监听处理类
/**
* @ControllerAdvice 捕获 Controller 层抛出的异常,如果添加 @ResponseBody 返回信息则为JSON格式。
* @RestControllerAdvice 相当于 @ControllerAdvice 与 @ResponseBody 的结合体。
* @ExceptionHandler 统一处理一种类的异常,减少代码重复率,降低复杂度。
*/
@RestControllerAdvice
public class GlobalDefaultExceptionHandler extends BaseController {
/**
* 拦截自定义错误信息
*
* @param exception CommonException
* @return 错误信息
*/
@ExceptionHandler(value = CommonException.class)
@ResponseStatus(HttpStatus.OK)
public Map<String, Object> handlerNoHandlerFoundException(CommonException exception) {
return returnResultMap(ResultMapInfo.ADDFAIL, exception.getMessage());
}
/**
* 拦截NoHandlerFoundException
* 拦截controller对应的mapping 路径找到,即平时所说的 404
* @param exception NoHandlerFoundException
* @return 错误信息
* 如何想 404 生效,必须进行下面俩行配置
* spring.mvc.throw-exception-if-no-handler-found=true
* spring.mvc.resources.add-mappings=false
*
*/
@ExceptionHandler(value = NoHandlerFoundException.class)
@ResponseStatus(HttpStatus.NOT_FOUND)
public Map<String, Object> handlerNoHandlerFoundException(NoHandlerFoundException exception) {
return returnResultMap(ResultMapInfo.NOTFOUND, exception.getMessage());
}
/**
* 拦截HttpRequestMethodNotSupportedException
* 拦截 controller 请求方式不匹配导致的异常
* @param exception HttpRequestMethodNotSupportedException
* @return 错误信息
*/
@ExceptionHandler(HttpRequestMethodNotSupportedException.class)
@ResponseStatus(HttpStatus.METHOD_NOT_ALLOWED)
public Map<String, Object> handlerHttpRequestMethodNotSupportedException(HttpRequestMethodNotSupportedException exception) {
return returnResultMap(ResultMapInfo.ADDFAIL, exception.getMessage());
}
/**
* 拦截Exception
* 拦截 其他不可预测的错误异常
* @param exception Exception
* @return 错误信息
*/
@ExceptionHandler(Exception.class)
@ResponseStatus(HttpStatus.OK)
public Map<String, Object> handlerException(Exception exception) {
return returnResultMap(ResultMapInfo.UNKNOW, exception.getMessage());
}
}
注意,如果需要配置404 异常拦截,请配置
spring.mvc.throw-exception-if-no-handler-found=true
spring.mvc.resources.add-mappings=false
但是配置上面俩行之后,项目下面的静态资源也全部无法访问,如果是前后端分离那无所谓,如果是使用了java模板文件,需进行对应的下面配置
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/static/**").addResourceLocations("classpath:/static/");
}
常见面试题
1.Error 和 Exception 区别是什么?
Error 类型的错误通常为虚拟机相关错误,如系统崩溃,内存不足,堆栈溢出等,编译器不会对这类错误进行检测,JAVA 应用程序也不应对这类错误进行捕获,一旦这类错误发生,通常应用程序会被终止,仅靠应用程序本身无法恢复;
Exception 类的错误是可以在应用程序中进行捕获并处理的,通常遇到这种错误,应对其进行处理,使应用程序可以继续正常运行。
2.JVM 是如何处理异常的?
在一个方法中如果发生异常,这个方法会创建一个异常对象,并转交给 JVM,该异常对象包含异常名称,异常描述以及异常发生时应用程序的状态。创建异常对象并转交给 JVM 的过程称为抛出异常。可能有一系列的方法调用,最终才进入抛出异常的方法,这一系列方法调用的有序列表叫做调用栈。
JVM 会顺着调用栈去查找看是否有可以处理异常的代码,如果有,则调用异常处理代码。当 JVM 发现可以处理异常的代码时,会把发生的异常传递给它。如果 JVM 没有找到可以处理该异常的代码块,JVM 就会将该异常转交给默认的异常处理器(默认处理器为 JVM 的一部分),默认异常处理器打印出异常信息并终止应用程序。
3.throw 和 throws 的区别是什么?
throw 语句用在方法体内,表示抛出异常,由方法体内的语句处理。
throw 是具体向外抛出异常的动作,所以它抛出的是一个异常实例,执行 throw 一定是抛出了某种异常。
throws 语句是用在方法声明后面,表示如果抛出异常,由该方法的调用者来进行异常的处理。
throws 主要是声明这个方法会抛出某种类型的异常,让它的使用者要知道需要捕获的异常的类型。
throws 表示出现异常的一种可能性,并不一定会发生这种异常。
4.final、finally、finalize的区别?
- final:用于声明属性,方法和类,分别表示属性不可变,方法不可覆盖,被其修饰的类不可继承。
- finally:异常处理语句结构的一部分,表示总是执行。
- finalize:Object 类的一个方法,在垃圾回收器执行的时候会调用被回收对象的此方法,可以覆盖此方法提供垃圾收集时的其他资源回收,例如关闭文件等。该方法更像是一个对象生命周期的临终方法,当该方法被系统调用则代表该对象即将"死亡",但是需要注意的是,我们主动行为上去调用该方法并不会导致该对象"死亡",这是一个被动的方法(其实就是回调方法),不需要我们调用。
5.NoClassDefFoundError 和 ClassNotFoundException 区别?
- NoClassDefFoundError 是一个 Error 类型的异常,是由 JVM 引起的,不应该尝试捕获这个异常。引起该异常的原因是 JVM 或 ClassLoader 尝试加载某类时在内存中找不到该类的定义,该动作发生在运行期间,即编译时该类存在,但是在运行时却找不到了,可能是变异后被删除了等原因导致;
- ClassNotFoundException 是一个受查异常,需要显式地使用 try-catch 对其进行捕获和处理,或在方法签名中用 throws 关键字进行声明。当使用 Class.forName, ClassLoader.loadClass 或 ClassLoader.findSystemClass 动态加载类到内存的时候,通过传入的类路径参数没有找到该类,就会抛出该异常;另一种抛出该异常的可能原因是某个类已经由一个类加载器加载至内存中,另一个加载器又尝试去加载它。
6.try-catch-finally 中哪个部分可以省略?
catch
更为严格的说法其实是:try只适合处理运行时异常,try+catch适合处理运行时异常+普通异常。也就是说,如果你只用try去处理普通异常却不加以catch处理,编译是通不过的,因为编译器硬性规定,普通异常如果选择捕获,则必须用catch显示声明以便进一步处理。而运行时异常在编译时没有如此规定,所以catch可以省略,你加上catch编译器也觉得无可厚非。
理论上,编译器看任何代码都不顺眼,都觉得可能有潜在的问题,所以你即使对所有代码加上try,代码在运行期时也只不过是在正常运行的基础上加一层皮。但是你一旦对一段代码加上try,就等于显示地承诺编译器,对这段代码可能抛出的异常进行捕获而非向上抛出处理。如果是普通异常,编译器要求必须用catch捕获以便进一步处理;如果运行时异常,捕获然后丢弃并且+finally扫尾处理,或者加上catch捕获以便进一步处理。
至于加上finally,则是在不管有没捕获异常,都要进行的“扫尾”处理。
7.Java异常处理最佳实践
- 在 finally 块中清理资源或者使用 try-with-resource 语句
- 优先明确的异常
- 对异常进行文档说明
- 使用描述性消息抛出异常
- 优先捕获最具体的异常
- 不要捕获 Throwable 类
8.代码加了try-catch 是否影响系统性能
在Java中如果不发生异常的话,try/catch其实不会造成任何性能损失。
在Java文件编译成为字节码之后,其实正常流程与异常处理部分是分开来的,每个有异常处理的方法其实在class文件中都是一个异常表,这个表中的内容与每个try/catch块相对应,每一行由四个内容组成:开始位置、结束位置、异常处理的偏移位、一个异常缓冲池
当代码在运行时抛出了异常时,首先拿着抛出位置到异常表中查找是否可以被catch(例如看位置是不是处于任何一栏中的开始和结束位置之间),如果可以则跑到异常处理的起始位置开始处理,如果没有找到则原地return,并且copy异常的引用给父调用方,接着看父调用的异常表,以此类推。
try catch对性能还是有一定的影响,那就是try块会阻止java的优化(例如重排序)。当然重排序是需要一定的条件触发。一般而言,只要try块范围越小,对java的优化机制的影响是就越小。所以保证try块范围尽量只覆盖抛出异常的地方,就可以使得异常对java优化的机制的影响最小化。