Java基础知识(四)exception异常处理

  • 检查性异常:最具代表的检查性异常是用户错误或问题引起的异常,这是程序员无法预见的。例如要打开一个不存在文件时,一个异常就发生了,这些异常在编译时不能被简单地忽略。
  • 运行时异常: 运行时异常是可能被程序员避免的异常。与检查性异常相反,运行时异常可以在编译时被忽略。
  • 错误: 错误不是异常,而是脱离程序员控制的问题。错误在代码中通常被忽略。例如,当栈溢出时,一个错误就发生了,它们在编译也检查不到的。
    image.png

所有的异常类是从 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优化的机制的影响最小化。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,684评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,143评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,214评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,788评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,796评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,665评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,027评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,679评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,346评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,664评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,766评论 1 331
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,412评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,015评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,974评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,073评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,501评论 2 343

推荐阅读更多精彩内容