异常处理 订阅
异常处理,英文名为exceptional handling, 是代替日渐衰落的error code方法的新法,提供error code 所未能具体的优势。异常处理分离了接收和处理错误代码。这个功能理清了编程者的思绪,也帮助代码增强了可读性,方便了维护者的阅读和理解。 异常处理(又称为错误处理)功能提供了处理程序运行时出现的任何意外或异常情况的方法。异常处理使用 try、catch 和 finally 关键字来尝试可能未成功的操作,处理失败,以及在事后清理资源。异常处理,是编程语言或计算机硬件里的一种机制,用于处理软件或信息系统中出现的异常状况(即超出程序正常执行流程的某些特殊条件)。 展开全文
异常处理,英文名为exceptional handling, 是代替日渐衰落的error code方法的新法,提供error code 所未能具体的优势。异常处理分离了接收和处理错误代码。这个功能理清了编程者的思绪,也帮助代码增强了可读性,方便了维护者的阅读和理解。 异常处理(又称为错误处理)功能提供了处理程序运行时出现的任何意外或异常情况的方法。异常处理使用 try、catch 和 finally 关键字来尝试可能未成功的操作,处理失败,以及在事后清理资源。异常处理,是编程语言或计算机硬件里的一种机制,用于处理软件或信息系统中出现的异常状况(即超出程序正常执行流程的某些特殊条件)。
信息
领    域
计算机
关键字
try、catch 和 finally 关键字
代    替
日渐衰落的error code方法
作    用
提供了处理异常情况的方法
中文名
异常处理
外文名
exceptional handling
又    称
错误处理
异常处理概述
异常处理,是编程语言或计算机硬件里的一种机制,用于处理软件或信息系统中出现的异常状况(即超出程序正常执行流程的某些特殊条件)。各种编程语言在处理异常方面具有非常显著的不同点(错误检测与异常处理区别在于:错误检测是在正常的程序流中,处理不可预见问题的代码,例如一个调用操作未能成功结束)。某些编程语言有这样的函数:当输入存在非法数据时不能被安全地调用,或者返回值不能与异常进行有效的区别。例如,C语言中的atoi函数(ASCII串到整数的转换)在输入非法时可以返回0。在这种情况下编程者需要另外进行错误检测(可能通过某些辅助全局变量如C的errno),或进行输入检验(如通过正则表达式),或者共同使用这两种方法。通过异常处理,我们可以对用户在程序中的非法输入进行控制和提示,以防程序崩溃。从进程的视角,硬件中断相当于可恢复异常,虽然中断一般与程序流本身无关。从子程序编程者的视角,异常是很有用的一种机制,用于通知外界该子程序不能正常执行。如输入的数据无效(例如除数是0),或所需资源不可用(例如文件丢失)。如果系统没有异常机制,则编程者需要用返回值来标示发生了哪些错误。
收起全文
精华内容
下载资源
问答
  • 异常处理
    千次阅读
    2022-01-22 18:48:42

    有时候不可避免服务器报错的情况,如果不配置异常处理机制,就会默认返回tomcat或者nginx的5XX页面,对普通用户来说,不太友好,用户也不懂什么情况。这时候需要我们程序员设计返回一个友好简单的格式给前端。

    处理办法如下:通过使用@ControllerAdvice来进行统一异常处理,@ExceptionHandler(value = RuntimeException.class)来指定捕获的Exception各个类型异常 ,这个异常的处理,是全局的,所有类似的异常,都会跑到这个地方处理。

    步骤二、定义全局异常处理,@ControllerAdvice表示定义全局控制器异常处理,@ExceptionHandler表示针对性异常处理,可对每种异常针对性处理。

    /**
     * 全局异常处理
     */
    @Slf4j
    @RestControllerAdvice
    public class GlobalExceptionHandler {
        @ResponseStatus(HttpStatus.FORBIDDEN)
        @ExceptionHandler(value = AccessDeniedException.class)
        public Result handler(AccessDeniedException e) {
            log.info("security权限不足:----------------{}", e.getMessage());
            return Result.fail("权限不足");
        }
    
        @ResponseStatus(HttpStatus.BAD_REQUEST)
        @ExceptionHandler(value = MethodArgumentNotValidException.class)
        public Result handler(MethodArgumentNotValidException e) {
            log.info("实体校验异常:----------------{}", e.getMessage());
            BindingResult bindingResult = e.getBindingResult();
            ObjectError objectError = bindingResult.getAllErrors().stream().findFirst().get();
            return Result.fail(objectError.getDefaultMessage());
        }
    
        @ResponseStatus(HttpStatus.BAD_REQUEST)
        @ExceptionHandler(value = IllegalArgumentException.class)
        public Result handler(IllegalArgumentException e) {
            log.error("Assert异常:----------------{}", e.getMessage());
            return Result.fail(e.getMessage());
        }
    
        @ResponseStatus(HttpStatus.BAD_REQUEST)
        @ExceptionHandler(value = RuntimeException.class)
        public Result handler(RuntimeException e) {
            log.error("运行时异常:----------------{}", e);
            return Result.fail(e.getMessage());
        }
    }

    上面我们捕捉了几个异常:

    • ShiroException:shiro抛出的异常,比如没有权限,用户登录异常
    • IllegalArgumentException:处理Assert的异常
    • MethodArgumentNotValidException:处理实体校验的异常
    • RuntimeException:捕捉其他异常

    更多相关内容
  • Springboot 全局异常处理

    千次阅读 热门讨论 2021-02-06 18:40:10
    二、开发环境三、添加依赖四、自定义异常错误类4.1 自定义异常基础接口类4.2 通用异常信息枚举类4.3 业务异常信息枚举类4.4 自定义业务异常类五、接口返回统一格式六、全局异常处理七、测试7.1 辅助类7.2 测试结果 ...


    一、为何要处理全局异常?

    在平常项目开发过程中,程序难免会出现运行时异常,或者业务异常。难道要针对每一处可能出现的异常进行编写代码进行处理?或者直接不处理异常,将一大屏堆满英文的异常信息显示给用户?那用户体验性是何等极差。

    所以,当程序抛异常时,为了日志的可读性排查 Bug简单,以及更好的用户体验性,所以我们要对全局异常进行处理。

    二、开发环境

    1. JDK 1.8 或者1.8以上
    2. Springboot (此演示版本为 Springboot 2.1.18.RELEASE)
    3. Gradle (当然也可用Maven,其实目的都是为构建项目,管理依赖等)

    三、添加依赖

    plugins {
        id "org.springframework.boot" version "2.1.18.RELEASE"
        id "io.spring.dependency-management" version "1.0.10.RELEASE"
        id "java"
    }
    
    group = 'com.nobody'
    version = '0.0.1-SNAPSHOT'
    sourceCompatibility = '1.8'
    
    configurations {
        developmentOnly
        runtimeClasspath {
            extendsFrom developmentOnly
        }
        compileOnly {
            extendsFrom annotationProcessor
        }
    }
    
    repositories {
        mavenLocal()
        maven { url "http://maven.aliyun.com/nexus/content/groups/public/" }
        mavenCentral()
    }
    
    dependencies {
        implementation 'org.springframework.boot:spring-boot-starter-web'
        // 添加lombok,主要为程序中通过注解,不用编写getter和setter等代码
        compileOnly 'org.projectlombok:lombok'
        annotationProcessor 'org.springframework.boot:spring-boot-configuration-processor'
        annotationProcessor 'org.projectlombok:lombok'
    }
    

    四、自定义异常错误类

    在我们项目开发中,肯定会有跟业务相关的异常,例如添加用户的业务,系统要求用户名不能为空,但是添加用户的请求接口,用户名值为空,这时我们程序要报用户名不能为空的异常错误;或者查询用户信息的接口,可能会报用户不存在的错误异常等等。

    4.1 自定义异常基础接口类

    因为要做成通用性,所以我们定义一个异常基础接口类,自定义的异常枚举类需实现该接口。

    package com.nobody.exception;
    
    /**
     * @Description 自定义异常基础接口类,自定义的异常信息枚举类需实现该接口。
     * @Author Mr.nobody
     * @Date 2021/2/6
     * @Version 1.0
     */
    public interface BaseErrorInfo {
    
        /**
         * 获取错误码
         * 
         * @return 错误码
         */
        String getErrorCode();
    
        /**
         * 获取错误信息
         * 
         * @return 错误信息
         */
        String getErrorMsg();
    
    }
    

    4.2 通用异常信息枚举类

    通用异常信息枚举类,这里定义的所有异常信息是整个程序通用的。

    package com.nobody.exception;
    
    import lombok.Getter;
    
    /**
     * @Description 自定义通用异常信息枚举类
     * @Author Mr.nobody
     * @Date 2020/10/23
     * @Version 1.0
     */
    @Getter
    public enum CommonErrorEnum implements BaseErrorInfo {
    
        /**
         * 成功
         */
        SUCCESS("200", "成功!"),
        /**
         * 请求的数据格式不符!
         */
        BODY_NOT_MATCH("400", "请求的数据格式不符!"),
        /**
         * 未找到该资源!
         */
        NOT_FOUND("404", "未找到该资源!"),
        /**
         * 服务器内部错误!
         */
        INTERNAL_SERVER_ERROR("500", "服务器内部错误!"),
        /**
         * 服务器正忙,请稍后再试!
         */
        SERVER_BUSY("503", "服务器正忙,请稍后再试!");
    
        private String errorCode;
        private String errorMsg;
    
        CommonErrorEnum(String errorCode, String errorMsg) {
            this.errorCode = errorCode;
            this.errorMsg = errorMsg;
        }
    }
    

    4.3 业务异常信息枚举类

    如果程序中异常信息太多,可以针对每个模块功能定义业务异常枚举类,方便维护,例如和用户相关的异常信息枚举类如下。

    package com.nobody.exception;
    
    import lombok.Getter;
    
    /**
     * @Description 自定义用户相关异常信息枚举类
     * @Author Mr.nobody
     * @Date 2020/10/23
     * @Version 1.0
     */
    @Getter
    public enum UserErrorEnum implements BaseErrorInfo {
    
        /**
         * 用户不存在
         */
        USER_NOT_FOUND("1001", "用户不存在!");
    
        private String errorCode;
        private String errorMsg;
    
        UserErrorEnum(String errorCode, String errorMsg) {
            this.errorCode = errorCode;
            this.errorMsg = errorMsg;
        }
    }
    

    4.4 自定义业务异常类

    业务异常类,主要用于业务错误,或者异常时手动抛出的异常。

    package com.nobody.exception;
    
    import lombok.Getter;
    import lombok.Setter;
    import org.slf4j.MDC;
    
    /**
     * @Description 自定义业务异常类
     * @Author Mr.nobody
     * @Date 2020/10/23
     * @Version 1.0
     */
    @Getter
    @Setter
    public class BizException extends RuntimeException {
    
        private static final long serialVersionUID = 5564446583860234738L;
    
        // 错误码
        private String errorCode;
        // 错误信息
        private String errorMsg;
        // 日志追踪ID
        private String traceId = MDC.get("traceId");
    
        public BizException(BaseErrorInfo errorInfo) {
            super(errorInfo.getErrorMsg());
            this.errorCode = errorInfo.getErrorCode();
            this.errorMsg = errorInfo.getErrorMsg();
        }
    
        public BizException(BaseErrorInfo errorInfo, String errorMsg) {
            super(errorMsg);
            this.errorCode = errorInfo.getErrorCode();
            this.errorMsg = errorMsg;
        }
    
        public BizException(BaseErrorInfo errorInfo, Throwable cause) {
            super(errorInfo.getErrorMsg(), cause);
            this.errorCode = errorInfo.getErrorCode();
            this.errorMsg = errorInfo.getErrorMsg();
        }
    
        public BizException(String errorCode, String errorMsg) {
            super(errorMsg);
            this.errorCode = errorCode;
            this.errorMsg = errorMsg;
        }
    
        public BizException(String errorCode, String errorMsg, Throwable cause) {
            super(errorMsg, cause);
            this.errorCode = errorCode;
            this.errorMsg = errorMsg;
        }
    }
    

    五、接口返回统一格式

    为方便前端对接口返回的数据进行处理,也是规范问题,所以我们要定义接口返回统一格式。

    package com.nobody.pojo.vo;
    
    import lombok.Getter;
    import lombok.Setter;
    
    /**
     * @Description 接口返回统一格式
     * @Author Mr.nobody
     * @Date 2021/2/6
     * @Version 1.0
     */
    @Getter
    @Setter
    public class GeneralResult<T> {
    
        private boolean success;
        private String errorCode;
        private String message;
        private T data;
        private String traceId;
    
        private GeneralResult(boolean success, T data, String message, String errorCode) {
            this.success = success;
            this.data = data;
            this.message = message;
            this.errorCode = errorCode;
        }
    
        public static <T> GeneralResult<T> genResult(boolean success, T data, String message) {
            return genResult(success, data, message, null);
        }
    
        public static <T> GeneralResult<T> genSuccessResult(T data) {
            return genResult(true, data, null, null);
        }
    
        public static <T> GeneralResult<T> genErrorResult(String message) {
            return genResult(false, null, message, null);
        }
    
        public static <T> GeneralResult<T> genSuccessResult() {
            return genResult(true, null, null, null);
        }
    
        public static <T> GeneralResult<T> genErrorResult(String message, String errorCode) {
            return genResult(false, null, message, errorCode);
        }
    
        public static <T> GeneralResult<T> genResult(boolean success, T data, String message,
                String errorCode) {
            return new GeneralResult<>(success, data, message, errorCode);
        }
    
        public static <T> GeneralResult<T> genErrorResult(String message, String errorCode,
                String traceId) {
            GeneralResult<T> result = genResult(false, null, message, errorCode);
            result.setTraceId(traceId);
            return result;
        }
    
    }
    

    六、全局异常处理

    此类是对全局异常的处理,根据自己情况,是否对不同种类的异常进行处理。例如以下是单独对业务异常,接口参数异常,以及剩余的所有异常进行处理,并生成接口统一格式信息,返回给调用接口的客户端,进行展示。

    首先我们需要在处理全局异常的类上面,加上 @ControllerAdvice 或者 @RestControllerAdvice注解。@ControllerAdvice 注解能处理 @Controller@RestController 类型的接口调用时产生的异常,而 @RestControllerAdvice 注解只能处理 @RestController 类型接口调用时产生的异常。我们一般用 @ControllerAdvice 注解。

    @ExceptionHandler 只能注解在方法上,表示这是一个处理异常的方法,value 属性可以填写需要处理的异常类,可以是数组。

    @ResponseBody 注解表示我们返回的信息是响应体数据。

    package com.nobody.exception;
    
    import javax.servlet.http.HttpServletRequest;
    
    import com.nobody.pojo.vo.GeneralResult;
    import org.slf4j.MDC;
    import org.springframework.util.StringUtils;
    import org.springframework.web.bind.MethodArgumentNotValidException;
    import org.springframework.web.bind.annotation.ControllerAdvice;
    import org.springframework.web.bind.annotation.ExceptionHandler;
    import org.springframework.web.bind.annotation.ResponseBody;
    
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.web.bind.annotation.RestControllerAdvice;
    
    /**
     * @Description 统一异常处理
     * @Author Mr.nobody
     * @Date 2020/10/23
     * @Version 1.0
     */
    @ControllerAdvice
    @Slf4j
    public class GlobalExceptionHandler {
    
        // 处理自定义的业务异常
        @ExceptionHandler(value = BizException.class)
        @ResponseBody
        public GeneralResult<Object> restErrorHandler(HttpServletRequest request, BizException e) {
            String err = "requestURI:" + request.getRequestURI() + ",errorCode:" + e.getErrorCode()
                    + ",errorMsg:" + e.getErrorMsg();
            log.error(err, e);
            return GeneralResult.genErrorResult(e.getMessage(), e.getErrorCode(), e.getTraceId());
        }
    
        // 处理接口参数数据格式错误异常
        @ExceptionHandler(value = MethodArgumentNotValidException.class)
        @ResponseBody
        public GeneralResult<Object> errorHandler(HttpServletRequest request,
                MethodArgumentNotValidException e) {
            StringBuilder message = new StringBuilder();
            String err = null;
            e.getBindingResult().getAllErrors()
                    .forEach(error -> message.append(error.getDefaultMessage()).append(";"));
            String des = message.toString();
            if (!StringUtils.isEmpty(des)) {
                err = des.substring(0, des.length() - 1);
            }
            log.error(err + ",requestURI:" + request.getRequestURI(), e);
            return GeneralResult.genErrorResult(CommonErrorEnum.BODY_NOT_MATCH.getErrorMsg(),
                    CommonErrorEnum.BODY_NOT_MATCH.getErrorCode(), MDC.get("traceId"));
        }
    
        // 处理其他异常
        @ExceptionHandler(value = Exception.class)
        @ResponseBody
        public GeneralResult<Object> errorHandler(HttpServletRequest request, Exception e) {
            log.error("internal server error,requestURI:" + request.getRequestURI(), e);
            return GeneralResult.genErrorResult(CommonErrorEnum.INTERNAL_SERVER_ERROR.getErrorMsg(),
                    CommonErrorEnum.INTERNAL_SERVER_ERROR.getErrorCode(), MDC.get("traceId"));
        }
    }
    

    七、测试

    7.1 辅助类

    测试会针对不同情况进行验证,以下是一些测试需要用到的类。

    package com.nobody.pojo.entity;
    
    import lombok.AllArgsConstructor;
    import lombok.Getter;
    import lombok.Setter;
    
    import java.io.Serializable;
    
    /**
     * @Description 用户实体类
     * @Author Mr.nobody
     * @Date 2021/2/6
     * @Version 1.0
     */
    @AllArgsConstructor
    @Getter
    @Setter
    public class UserEntity implements Serializable {
    
        private static final long serialVersionUID = 5564446583860234738L;
    
        private String id;
        private String name;
        private int age;
    
    }
    
    package com.nobody.pojo.dto;
    
    import lombok.Getter;
    import lombok.Setter;
    
    import javax.validation.constraints.Min;
    import javax.validation.constraints.NotEmpty;
    
    /**
     * @Description 添加用户时参数类
     * @Author Mr.nobody
     * @Date 2021/2/6
     * @Version 1.0
     */
    @Getter
    @Setter
    public class UserDTO {
        @NotEmpty(message = "用户名不能为空")
        private String name;
        @Min(value = 0, message = "年龄最小不能低于0")
        private int age;
    }
    

    以下简单模拟 User 相关业务,然后产生不同的异常。

    package com.nobody.service;
    
    import com.nobody.pojo.dto.UserDTO;
    import com.nobody.pojo.entity.UserEntity;
    
    /**
     * @Description
     * @Author Mr.nobody
     * @Date 2021/2/6
     * @Version 1.0
     */
    public interface UserService {
        UserEntity add(UserDTO userDTO);
    
        UserEntity getById(String id);
    
        void marry(String age);
    }
    
    package com.nobody.service.impl;
    
    import com.nobody.exception.BizException;
    import com.nobody.exception.UserErrorEnum;
    import com.nobody.pojo.dto.UserDTO;
    import com.nobody.pojo.entity.UserEntity;
    import com.nobody.service.UserService;
    import org.springframework.stereotype.Service;
    
    import java.util.Objects;
    import java.util.UUID;
    
    /**
     * @Description
     * @Author Mr.nobody
     * @Date 2021/2/6
     * @Version 1.0
     */
    @Service
    public class UserServiceImpl implements UserService {
        @Override
        public UserEntity add(UserDTO userDTO) {
            String userId = UUID.randomUUID().toString();
            return new UserEntity(userId, userDTO.getName(), userDTO.getAge());
        }
    
        @Override
        public UserEntity getById(String id) {
            // 模拟业务异常 
            if (Objects.equals(id, "000")) {
                throw new BizException(UserErrorEnum.USER_NOT_FOUND);
            }
            return new UserEntity(id, "Mr.nobody", 18);
        }
    
        @Override
        public void marry(String age) {
            // 当age不是数字字符串时,抛出异常
            Integer integerAge = Integer.valueOf(age);
            System.out.println(integerAge);
        }
    }
    

    接口类定义,根据不同参数调用接口,可产生不同的异常错误。

    package com.nobody.controller;
    
    import com.nobody.pojo.dto.UserDTO;
    import com.nobody.pojo.entity.UserEntity;
    import com.nobody.pojo.vo.GeneralResult;
    import com.nobody.service.UserService;
    import org.springframework.stereotype.Controller;
    import org.springframework.web.bind.annotation.*;
    
    import javax.validation.Valid;
    
    /**
     * @Description
     * @Author Mr.nobody
     * @Date 2021/2/6
     * @Version 1.0
     */
    @RestController
    @RequestMapping("user")
    public class UserController {
    
        private UserService userService;
    
        public UserController(final UserService userService) {
            this.userService = userService;
        }
    
        @PostMapping("add")
        public GeneralResult<UserEntity> add(@RequestBody @Valid UserDTO userDTO) {
            UserEntity user = userService.add(userDTO);
            return GeneralResult.genSuccessResult(user);
        }
    
        @GetMapping("find/{userId}")
        public GeneralResult<UserEntity> find(@PathVariable String userId) {
            UserEntity user = userService.getById(userId);
            return GeneralResult.genSuccessResult(user);
        }
    
        @GetMapping("marry/{age}")
        public GeneralResult<UserEntity> marry(@PathVariable String age) {
            userService.marry(age);
            return GeneralResult.genSuccessResult();
        }
    
    }
    

    7.2 测试结果

    启动服务,进行接口调用,本此演示用的 IDEA 自带的 HTTP Client 工具进行调用,当然你也可以使用 Postman 进行调用。

    在这里插入图片描述

    首先演示正常的接口调用,服务没有报错,接口也返回正常数据。

    在这里插入图片描述

    还是调用查询用户接口,演示用户不存在情况,服务报错打印日志,接口也返回错误信息。

    在这里插入图片描述
    在这里插入图片描述

    再演示添加用户操作,用户名不填值,程序报错打印日志,接口也返回错误信息。

    在这里插入图片描述
    在这里插入图片描述

    在这里插入图片描述

    再演示其他异常情况,例如解析数字出错。

    在这里插入图片描述
    在这里插入图片描述

    此演示项目已上传到Github,如有需要可自行下载,欢迎 Star

    https://github.com/LucioChn/springboot-global-exception-handler

    展开全文
  • Java-异常处理大全(万字宝典)

    千次阅读 多人点赞 2022-01-10 21:44:51
    什么是异常异常产生了之后如何处理?如何自定义异常?抓紧收藏,这篇文章看了你还不懂来打我!!

    目录

    没有花里胡哨,纯干货,小白也能看懂的异常最详细总结!!

    Java中异常的那点事

    引入

    异常概念

    异常体系

    异常分类

    一个栗子

     两种方式去处理这个例子中的异常

    第一种方法

    第二种方法

    了解一下错误Error

    异常产生过程解析

    异常的处理

    java异常处理的五个关键字:try,catch,finally,throw,throws

    throw

    throws(异常处理的第一种方式)

    try{}catch(){}(异常处理的第二种方式)

    finally

    异常处理注意事项1

    1,多个异常分别处理。

    2,多个异常一次捕获,多次处理。

    3,多个异常一次捕获,一次处理。

    异常处理注意事项2

    异常处理注意事项3

    自定义异常类

    为什么要自定义异常类:

    什么是自定义异常类:

    异常类如何定义:

    一个栗子

    自定义异常类的练习

    补充知识

    Throwable类中的三个处理异常的方法

    Objects非空判断


    Java中异常的那点事

    引入

    在理想的状态下,用户输入数据的格式永远都是正确的,选择打开的文件也一定存在,并且永远不会出现bug。然而,在现实世界中却充满了不良的数据和带有问题的代码。

    如果一个用户在运行程序期间,由于程序的错误或一些外部环境的影响造成用户数据的丢失,用户就有可能不再使用这个程序了。为了避免这类事情的发生,至少应该做到以下几点:

    向用户通告错误;

    保存所有工作结果;

    允许用户以妥善的形式退出程序。

    Java使用一种称为异常处理(exception handing)的错误捕获机制处理。

    异常概念

    异常指的是什么?

    异常字面上就是不正常的意思。

    在程序中的意思就是

    异常:即指在程序执行的过程中,出现非正常情况,最终导致JVM的非正常停止。

    在Java等面向对象的编程语言中,异常本身是一个类,产生异常就是创建一个异常对象并抛出一个异常对象。Java虚拟机处理异常的方式就是中断处理。

    异常指的不是语法错误,语法错误时,编译不通过,不会产生字节码文件,根本不能运行。

    异常体系

    异常机制是在帮我们找到程序中的问题

    异常的根类是java.lang.Throwable

    这个根类下有两个子类,分别是java.lang.Error      java.lang.Exception,平常我们所说的异常即java.lang.Exception。

    Throwable体系   

    1,Error:严重错误Error,无法处理,只能事先避免,相当于绝症这种无法治愈的问题。必须修改源代码,程序才能继续执行。

    2,Exception:表示异常,异常产生后,程序员可以通过代码去纠正,使得程序继续去运行,相当于感冒发烧这种小毛病,进行处理后可以恢复。

    异常分类

    java.lang.Throwable类是Java中所有异常或者错误的超类。

    一般Exception指的是编译期异常,进行编译(写代码时)java程序出现的问题。

    其中Exception下有一个特殊的子类:RuntimeException指的是运行期异常。即程序运行的时候抛出的异常。

    一个栗子

    Demo1  

    产生了编译期异常:

     两种方式去处理这个例子中的异常

    第一种方法

    throws关键字

    通过throws关键字声明抛出这个异常,交给方法的调用者去处理,在这里main方法的调用者是JVM,即交给JVM去处理。

     添加了 throws ParseException后,此时发现红线没有了,程序可以正常执行了。

    注意:此时我们的"2022-01-01"与它的"yyyy-MM-dd"格式是一致的,所以只要解决编译时的异常就可以正常执行程序。

    那么当我们把格式改成不一致的时候,即格式不匹配,比如给它一个"2022-0101",它还会抛出异常。

    因为此时我们使用的是第一种处理异常的方式,即交给JVM虚拟机去处理,而虚拟机处理的方式就是中断程序,并把异常打印出来,所以出现异常的语句后面的语句就无法执行了,若我们想让出现异常的语句后的语句依然继续执行,我们需要来了解第二种异常处理方式。

    第二种方法

    try{可能会出现异常的代码

                                         } catch(Exception e){异常的处理逻辑}

    此时可以看到,除了打印了异常的信息也执行了后续的代码

     再来看一下运行期异常:

    此代码编译时并不会有错误提醒,但是在运行中,很明显会产生索引越界异常 ,这就是运行期异常。我们依然可以使用try,catch来处理这个异常。处理之后,依然可以执行后续代码。

    了解一下错误Error

    当空间数为1024时,此时是没有问题的,也可以执行到后续代码。

     但是当我们把空间数增加为1024*1024*1024时

     此时出现了一个以Error结尾的OutOfMemoryError,这就是一个错误,名称为内存溢出错误,即创建的数组太大,超出了给JVM分配的内存。产生错误必须修改源代码,否则是不会继续执行下去的,在这里即把数组修改的小一点就可以了。


    异常产生过程解析

    再来看一个例子:

    因为我定义的数组下标最大为2,很明显,此时会产生异常

    程序执行的结果:

     仔细观察,我们可以发现,异常是在 int ele = arr[index];  这一行代码产生的

    这时访问了数组中的3索引(下标),但是数组中并没有3索引,这时JVM就会检测出程序出现了异常。

    1,

    JVM会做两件事:

    1)JVM会根据异常产生的原因创建一个异常对象,这个异常对象包含了异常产生的(内容,原因,位置) new ArrayIndexOutOfBoundsException("3");

    2)在getElement方法中,没有异常的处理逻辑(try,catch),那么JVM就会把异常对象抛出给方法的调用者,也就是让main方法来处理异常

    getElement方法把异常对象抛出给main方法

    2,

    回到main方法中的这行语句,int e =getElement(arr,3);

    main方法接收到了这个异常对象(new ArrayIndexOutOfBoundsException("3")),但是main方法也没有异常的处理逻辑,继续把对象抛出给main方法的调用者,即JVM处理

    main方法把异常对象抛出给 JVM

    3,

    JVM接收到了这个异常对象(new ArrayIndexOutOfBoundsException("3")),做了两件事情:

    1,把异常对象(内容,原因,位置)以红色的字体打印在控制台

    2,JVM会终止当前正在执行的java程序 ——>中断处理


    异常的处理

    java异常处理的五个关键字:try,catch,finally,throw,throws

    接下来我们挨个来介绍:


    throw


    关于throw关键字的介绍 

    作用:使用throw关键字可以在指定方法中抛出指定的异常
        使用格式:
        throw  new xxxException("异常产生的原因");
        注意事项:
        1,throw关键字必须写在方法的内部
        2,throw关键字后边new的对象必须是Exception或者Exception的子类对象
        3,throw关键字抛出指定的异常对象,我们就必须处理这个异常对象
        throw关键字后边创建的是RuntimeException或者是RuntimeException的子类对象我们可以不处理,默认交给JVM去处理
        throw关键字后边创建的是编译器异常,我们就必须处理这个异常,要么throws,要么try...catch
    

    我们依然用一个例子来解释它:

    执行结果:

    这是我并没有处理这个异常,那它是谁处理的呢?

    上边我们提到了

    throw关键字后边创建的是RuntimeException或者是RuntimeException的子类对象我们可以不处理,默认交给JVM去处理

    此时的NullPointerException就是一个运行期异常,即RuntimeException的子类,我们不用处理,默认交给JVM去处理

    小tips:
    在工作中,我们首先必须对方法传递过来的参数做合法性校验
    如果参数不合法,那么我们就必须要使用抛出异常的方式,告诉方法的调用者,传递的参数有问题

    在上边的例子中我们判断了数组arr的值是否为空,我们还有另外一个参数,即index,我们接着再来对index进行合法性校验。

     把上边的空数组改为

     此时若传递参数为(arr,3)

    会抛出ArrayIndexOutOfBoundsException,即数组索引越界异常

     ArrayIndexOutOfBoundsException也是一个运行时异常,默认交给JVM去处理。


    throws(异常处理的第一种方式)


    此种方法即声明异常

    throws关键字:是异常处理的第一种方式,即交给别人去处理

    作用:

    当方法内部抛出异常对象时,我们就必须处理这个异常对象

    可以使用throws关键字进行异常处理,会把异常对象抛出给方法的调用者处理(自己不处理,交给别人处理),若没人处理,最终交给JVM处理——>中断处理

    使用格式:在方法声明时使用

                            修饰符   返回值类型  方法名(参数列表)throws AAAException,BBBException...{

                                                                    throw new AAAException("产生异常的原因");

                                                                    throw new BBBException("产生异常的原因");

                                                                                    ....

                                                                                                 }

    注意事项:

    1,throws关键字必须写在方法声明处

    2,throws关键字后边的异常必须是Exception或者Exception的子类

    3,方法内部如果抛出了多个异常对象,throws后面也必须声明多个异常

          如果抛出的异常有子父类关系,只需声明父类异常即可

    4,调用一个声明异常的方法,就必须处理声明的异常

    如何处理:1)继续使用throws关键字进行声明抛出,交给方法的调用者处理,最终交给JVM处理

                      2)要么try...catch自己处理异常

    举个栗子

    定义一个方法对传递的文件路径进行一个合法性判断

    如果路径不是"c:\\.java.txt"我们就抛出文件找不到这个异常(   ),告诉方法的调用者

    此时发现程序已经标了红线,原因是FileNotFoundException是编译器异常,上面我们说过,只要出现编译期异常,我们就必须进行处理

    此时就可以使用throws关键字继续声明抛出FileNotFoundException这个异常对象,让方法的调用者来处理。

    接着我们来补全main方法来调用readFile方法 

    此时我传给readFile的是正确的路径,但是发现readFile仍然下边依然有红线 ,这是因为我们刚才介绍的注意事项的第四点

    那我们就得在main来处理这个异常 ,我们依旧使用第一种方法,即继续使用throws关键字进行声明抛出,此时main方法把异常对象交给它的调用者处理,即让JVM去处理。

    public class Demo5 {
        public static void main(String[] args) throws FileNotFoundException {
        readFile("c:\\.java.txt");
        }
        public static void readFile (String fileName)throws FileNotFoundException{
            if (!fileName.equals("c:\\.java.txt")){
                throw new FileNotFoundException("传递的文件路径不是c:\\.java.txt");
            }
            System.out.println("路径没有问题,读取文件");
        }
    }
    

     此时代码就没有问题了

    我们再来加一个if语句,如果传递的路径不是.txt结尾

    我们抛出IO异常对象,告诉方法的调用者,文件的后缀名不对

     此时我们把传递的文件路径后缀名改为.tx,它就会报IO异常,我们要像上边声明抛出FileNotFoundException异常对象一样,声明抛出IOException异常对象

    注意:由于FileNotFoundException是IOException的子类,所以只需声明抛出IOException,即父类异常即可!!


    try{}catch(){}(异常处理的第二种方式)


    此种方法即捕获异常

    上边我们介绍过的第一种异常处理方式-声明异常,不难发现,它是有一定缺陷的。

    如果我们在上面Demo5的例子中,给main方法中的readFile("c:\\.java.tx");

    这条语句后边加一个

    System.out.println("后续代码");

    即让程序执行后续代码,发现后续代码是不能执行的。原因也很简单,就是我们上边讲过的,若没人去处理这个异常,最后会交给JVM去处理,而JVM处理的方式是中断程序,所以后续代码自然就不能执行了。

    而try...catch是自己去处理异常,后续代码也可以继续执行。

    try...catch,异常处理的第二种方式,自己处理异常

    格式:(一个try中可以对应多个catch)

                    try{可能产生异常的代码

                      } catch(定义一个异常的变量,用来接收try中抛出的异常对象){

                                    异常的处理逻辑,产生异常之后,怎么处理异常对象

                                    一般在工作中,会把异常信息记录在日志中

                                    }

                                    ...

                           catch(异常类名 变量名){               }

    注意事项:

    1,try中可能会出现多个异常对象,可以使用多个catch来处理这些异常对象

    2,如果try中产生了异常,那么就会执行catch中的异常处理逻辑,执行完catch中的异常处理逻辑,继续执行try...catch后的代码

          如果try中没有产生异常,那么不执行catch的异常处理逻辑,即执行完try中的语句,继续处理try...catch后的代码          

    举个栗子 

    依然是上边的文件路径的例子,只是 此时我们的main方法在收到readFile传递的异常对象之后,不再声明抛出给JVM来处理,而是使用try...catch自己进行处理。

    当传递的参数为正确的文件路径时,此时,程序没有异常产生,不执行catch中的异常处理逻辑,程序正常执行。

    此时打印:

    当传的参数为错误的文件路径时,此时,程序有异常产生,catch捕捉到try中产生的异常,并执行了异常处理逻辑,执行完catch后,程序依然继续执行后续代码(不同于throws的地方)。


     此时打印:


    finally


    finally:有一些特定的代码无论异常是否发生,都需要执行。另外,因为异常会导致程序跳转,导致有些语句执行不到。finally就是用来解决这个问题的,放在finally中的语句块一定会被执行到。

    我们写在try中的代码如果出现了异常,就会直接把异常抛给catch来处理,那么在try中出现异常的位置之后的代码就是执行不到的。

    如图:

    此时我想打印这个"释放空间",是执行不到的,因为产生了异常,直接跳到了catch语句中 。

    如果我想把"释放空间"打印出来,此时就可以使用finally语句。

    finally代码块:

    格式:

       try{可能产生异常的代码

                      } catch(定义一个异常的变量,用来接收try中抛出的异常对象){

                                    异常的处理逻辑,产生异常之后,怎么处理异常对象

                                    一般在工作中,会把异常信息记录在日志中

                                    }

                                    ...

                           catch(异常类名 变量名){             

                             }finally{

                                  无论是否出现异常都会执行}

    注意事项:

                    1,finally必须和try一起使用,不能单独使用

                    2,finally一般用于资源释放(资源回收),无论程序是否出现异常,最后都要资源释放


    异常处理注意事项1

    多个异常如何捕获与处理?

    共有三种方法:

    1,多个异常分别处理。

    2,多个异常一次捕获,多次处理。

    3,多个异常一次捕获,一次处理。

    1,多个异常分别处理。

    即有一个异常就要写一个try...catch

    即格式为

    try{

            }catch(){

            }

       try{

            catch(){

            }

    System.out.println("后续代码")

    此种方式有个优点:就是可以执行到后续代码

    2,多个异常一次捕获,多次处理。

    即一个try对应多个catch

    即格式为

    try{

            }catch(){

               ......

            } catch() {

                   }

    此种方法使用时要注意:

    catch里边定义的异常变量,如果有子父类关系,那么包含子类异常变量的catch语句必须写在父类的上边,否则会报错。

    例如如图的情况,就报错了。 

    原因是:

    例如:try中可能会产生以下两个异常对象:

    new ArrayIndexOutOfBoundsException("3");

    new IndexOutOfBoundsException("3");

    try中如果出现了异常对象,会把异常对象抛出给catch处理

    抛出的异常对象,会从上到下赋值给catch中定义的异常变量。

    如果父类异常变量的catch语句在子类的上边,此时无论是产生子类异常还是产生父类异常,都会赋给父类catch语句中的异常变量(多态的体现),而下边子类catch语句中的异常变量就没有被使用所以会报错,这并不是我们想要的结果。

    所以,在catch里边定义的异常变量,如果有子父类关系,那么包含子类异常变量的catch语句必须写在父类的上边。

         

    3,多个异常一次捕获,一次处理。

    即只有一个try和一个catch 

    即格式为

    try{

            }catch(此时这里的异常变亮一般为父类异常即可以处理多个异常对象或者直接写Exception){

            } 

    特殊的:运行时异常(RuntimeException)可以不处理也不声明抛出

                   默认交给虚拟机去处理,终止程序,什么时候不抛出运行时异常了,再执行程序。

    异常处理注意事项2

    如果finally中有return语句,永远返回finally中的结果,我们应该避免该种情况。

    public class Demo6 {
        public static void main(String[] args) {
           int a =getA();
            System.out.println(a);
        }
        public static int getA(){
            int a = 10;
            try{return a;
        }catch(Exception e){
                System.out.println(e);
            }finally {
                a = 100;
                return a;
            }
            }
    
    }
    

    此时打印结果为100,我们应该去避免这种情况的发生,即不在finally里写return语句。

    异常处理注意事项3

    关于子父类的异常问题

    (此部分代码较简单,不进行演示,只要记住下边两条,自然就会使用了)

    1)如果父类抛出了多个异常,子类重写父类方法时,抛出和父类相同的异常,或者是父类异常的子类或者是不抛出异常。

    2)父类方法没有抛出异常,子类重写父类该方法时也不可抛出异常,此时子类产生该异常,只能捕获处理,而不能声明抛出。


    自定义异常类

    为什么要自定义异常类:

    Java中的不同的异常类,分别表示着某一种具体的异常情况。但是在具体的开发过程中,我们总会用到一些Java中没有的异常类,比如我们要考虑考试成绩是负数的问题。这时就需要我们自己去定义一个异常类。

    什么是自定义异常类:

    在开发中自己业务的异常情况来定义异常类。

    自定义一个业务逻辑异常:RegisterException,即一个注册异常类。

    异常类如何定义:

    1,自定义一个编译期异常:自定义类并继承于java.lang.Exception。

    2,自定义一个运行时期的异常类:自定义类并继承于java.lang.RuntimeException

    格式:

            public class Exception extends Exception/RuntimeExcetion{

                    添加一个空参数的构造方法

                    添加一个带异常信息的构造方法

    }

    注意:

    1,自定义异常类一般都是以Exception结尾,说明该类是一个异常类

    2,自定义异常类,必须得继承自Exception或者RuntimeException

            继承自Exception:那么定义的异常类就是一个编译期异常,如果方法内部抛出了编译期异常,就必须处理这个异常,要么throws ,要么try ...catch

            继承自RuntimeException:那么定义的异常就是一个运行期异常,无需处理,交给虚拟机处理(中断处理)

    下面我们来自定义一个异常类

    一个栗子

    public class RegisterException extends Exception {
    //    添加一个空参数的构造方法
    //    public RegisterException(){}
        public RegisterException(){super();}
    //    实际上我们此时默认调用的是空参的父类的构造方法,以上两条语句等价
        /*添加一个带异常信息的构造方法,这个怎么添加呢
        我们可以参照一下jdk中的NullpointerException源码中的构造方法
        查看NullpointerException的源码后发现,所有异常类都会有一个带异常信息的构造方法
        在方法内部会调用父类带异常信息的构造方法,让父类来处理这个异常信息*/
        public RegisterException (String message){
            super(message);
    
        }
    }
    

    自定义异常类的练习

    我们使用上面我们定义好的异常类RegisterException 进行练习

    import java.util.Scanner;
    /*要求:模拟注册操作,如果用户名已存在,抛出异常并提示,该用户名已被注册。
        分析:
        1,使用数组保存注册过的用户名
        2,使用Scanner获取用户输入的注册的用户名
        3,定义一个方法,对用户输入的注册的用户名进行判断
        遍历存储已经注册过用户名的数组,获取每一个用户名
        使用获取到的用户名和用户输入的用户名比较
        true:
        用户名已经存在,抛出RegisterException,告知用户该用户名已经注册
        false:
        继续遍历比较
        如果循环结束,还没找到重复的,提示用户,注册成功!
     */
    public class RegisterException2 {
        static String[] usernames = {"张三","李四","王五"};
        public static void main(String[] args) throws RegisterException {
            Scanner sc = new Scanner(System.in);
            System.out.println("请输入你要注册的用户名");
            String username = sc.next();
            checkUsername(username);
        }
            public static void checkUsername(String username) throws RegisterException {
                for (String name:usernames) {
                    if (name.equals(username)){
                        throw new RegisterException("该用户名已经注册");
                    }
                    
                }
                System.out.println("注册成功!");
    
            }
        }
    }
    

    此时,我们输入不存在的用户名,运行结果如下

    输入已经存在的用户名,运行结果如下

     达到了我们想要的结果。

    上边使用的是throws一直声明抛出,最终交给了JVM去处理。

    我们也可以使用try... catch来处理

    public class RegisterException2 {
        static String[] usernames = {"张三","李四","王五"};
        public static void main(String[] args)  {
            Scanner sc = new Scanner(System.in);
            System.out.println("请输入你要注册的用户名");
            String username = sc.next();
            checkUsername(username);
        }
            public static void checkUsername(String username)  {
                for (String name:usernames) {
                    if (name.equals(username)){
                        try {
                            throw new RegisterException("该用户名已经注册");
                        } catch (RegisterException e) {
                            e.printStackTrace();
                        }
                    }
                    
                }
                System.out.println("注册成功!");
    
            }
        }
    

    运行结果如下 

    此时我们发现了一个问题,即当我输入已存在的用户名,程序抛出异常后,依然会打印注册成功,这显然不符合预想。

    如果抛出了异常,我们应该让方法停下来,不再继续执行后面的语句

    这里只需要在catch里添加一个return即可。

    刚才我们继承的是Exception,现在让我们自定义的RegisterException再来继承一下RuntimeException。

    继承之后,发现及时不加try..catch语句,也不声明,程序也不会报错,原因很简单,就是我们在上边一直在讲的,抛出RuntimeException(运行期异常)可以不处理,默认交给JVM来处理(中断程序)。

    运行结果


    补充知识

    Throwable类中的三个处理异常的方法

    Throwable类中定义了三个处理异常的方法

    分别是以下三个:

    StringgetMessage()
               返回此 throwable 的简短描述。
    StringtoString()
            返回此 throwable 的详细消息字符串 
     void  

    printStackTrace()
              将此 throwable 及其追踪输出至标准错误流。

    JVM打印异常对象默认使用此方法,打印的异常 信息是最全面的

     举个栗子

    我们分别来打印它们进行观察

    依然使用上边的代码Demo5例子,我们此时给它传递一个错误的文件路径(后缀名是错误的)。我们来看一下三种处理异常的方法的区别。

    1)get Message方法

    此时打印:

      可以看到只有很简短的描述

    2)toString 方法

    此时打印:

    可以看到比上边的getMessage方法详细了一点

     3)printStackTrace方法

     

    此时打印:

     可以看到此时打印了最详细的异常信息。


    Objects非空判断


    Objects类是一个由一些静态的实用方法组成的类,这些方法是non-save(空指针安全的)或non-tolerant(容忍空指针的),那么在它的源码中,对对象为null的值进行了抛异常操作。

    Objects类中的静态方法

    public static <T> T requireNonNull(T obj):查看指定引用对象不是null

    源码:

    public static <T> T requireNonNull(T obj){

                    if(obj == null){

                    throw new NullPointerException();

                    return obj;

    }

    举个栗子:

    上边的代码可对传过来参数进行合法性校验,判断其是否为空

    当我们了解了 Objects类的requireNonNull方法后,可对代码进行一个简化

    即把注释掉的这两行if语句替换成了Objects.requireNonNull(obj);

     以后如果我们在合法性判断时,如果要判断它是否为空,可以直接用Objects类里的静态方法即requireNonNull,可以简化书写代码。


    到这里,异常部分全部介绍完毕,本人才疏学浅,若各位发现错误,请尽情批评指正!!!

    若对您有帮助,请点赞,收藏,加关注!!!我们一起努力!!谢谢!!!

    展开全文
  • 最全最详细的Java异常处理机制

    千次阅读 多人点赞 2022-01-27 11:21:36
    一、异常概述与异常体系结构 异常概述 在使用计算机语言进行项目开发的过程中,即使程序员把代码写得尽善尽美,在系统的运行过程中仍然会遇到一些问题,因为很多问题不是靠代码能够避免的,比如:客户输入数据的...

    一、异常概述与异常体系结构

    异常概述

            在使用计算机语言进行项目开发的过程中,即使程序员把代码写得尽善尽美,在系统的运行过程中仍然会遇到一些问题,因为很多问题不是靠代码能够避免的,比如:客户输入数据的格式,读取文件是否存在,网络是否始终保持通畅等等。
        
            程序运行时,发生的不被期望的事件,它阻止了程序按照程序员的预期正常执行,这就是异常(开发过程中的语法错误和逻辑错误不是异常)。

            Java程序在执行过程中所发生的异常事件可分为两类:
            Error:Java虚拟机无法解决的严重问题。如:JVM系统内部错误、资源耗尽等严重情况。比如:StackOverflowError(栈溢出)和OOM(内存溢出)。一般不编写针对性的代码进行处理。
            Exception: 其它因编程错误或偶然的外在因素导致的一般性问题,可以使用针对性的代码进行处理。例如:
                空指针访问
                试图读取不存在的文件
                网络连接中断
                数组角标越界

    Error示例代码:

    public class ErrorTest {
        public static void main(String[] args) {
            /*
             * 1、栈溢出:java.lang.StackOverflowError
             *   原因 : 函数调用栈太深了,注意代码中是否有了循环调用方法而无法退出的情况
             *   StackOverflowError 是一个java中常出现的错误:在jvm运行时的数据区域中有一个java虚拟机栈,当执行java方法时会进行压栈弹栈的操作。在栈中会保存局部变量,操作数栈,方法出口等等。jvm规定了栈的最大深度,当执行时栈的深度大于了规定的深度,就会抛出StackOverflowError错误。
             * */
            main(args);
    
            /*
             * 2、堆溢出:java.lang.OutOfMemoryError
             *   原因:Java中所有的对象都存储在堆中,通常如果JVM无法再分配新的内存,内存耗尽,垃圾回收无法及时回收内存,就会抛出OutOfMemoryError。
             * */
            Integer[] arr = new Integer[1024 * 1024 * 1024];
    
        }
    }

    Exception示例代码:

    import java.io.FileInputStream;
    
    public class ExceptionTest {
        public static void main(String[] args) {
            /*
             * 1、运行时异常:java.lang.ArithmeticException
             *  原因:ArithmeticException
             * */
            int a = 10;
            int b = 0;
            System.out.println(a / b);
    
            /*
             * 2、编译期异常:java.io.FileNotFoundException
             *   原因:文件找不到异常通常是两种情况:1、系统找不到指定的路径 2、拒绝访问(指定的是目录时,就会报拒绝访问异常)
             * */
            FileInputStream fis = new FileInputStream("a.txt");
    
        }
    }

            异常发生时,是任程序自生自灭,立刻退出终止,还是输出错误给用户?或者用C语言风格:用函数返回值作为执行状态?

            Java提供了更加优秀的解决办法:异常处理机制。异常处理机制能让程序在异常发生时,按照代码的预先设定的异常处理逻辑,针对性地处理异常,让程序尽最大可能恢复正常并继续执行,且保持代码的清晰。

            Java中的异常可以是函数中的语句执行时引发的,也可以是程序员通过throw 语句手动抛出的,只要在Java程序中产生了异常,就会用一个对应类型的异常对象来封装异常,JRE就会试图寻找异常处理程序来处理异常。
     
            Throwable类是Java异常类型的顶层父类,一个对象只有是 Throwable 类的(直接或者间接)实例,他才是一个异常对象,才能被异常处理机制识别。JDK中内建了一些常用的异常类,我们也可以自定义异常。

    异常体系结构

            Java标准库内建了一些通用的异常,这些类以Throwable为顶层父类。

            Throwable又派生出Error类和Exception类。

            错误:Error类以及它的子类的实例,代表了JVM本身的错误。错误不能被程序员通过代码处理,Error很少出现。因此,程序员应该关注Exception为父类的分支下的各种异常类。

            异常:Exception以及它的子类,代表程序运行时发生的各种不期望发生的事件。可以被Java异常处理机制使用,是异常处理的核心。

    Error和Exception的区别:

            Error和Exception都有一个共同的根类是Throwable类。
        
            Error是系统中的错误,程序员是不能改变的和处理的,一般是指与虚拟机相关的问题,如系统崩溃,虚拟机错误,内存空间不足,方法调用栈溢等。对于这类错误的导致的应用程序中断,仅靠程序本身无法恢复和和预防,遇到这样的错误,建议让程序终止。因此我们编写程序时不需要关心这类错误。
        
            Exception,也就是我们经常见到的一些异常情况,表示程序可以处理的异常,可以捕获且可能恢复。遇到这类异常,应该尽可能处理异常,使程序恢复运行,而不应该随意终止异常。

    异常体系结构图:

    异常分类

            总体上我们根据Javac对异常的处理要求,将异常类分为2类。
        
            我们常说的异常是狭义上的:就是指Exception及其子类,但是广义上的异常是包括Exception和Error;

            Java的异常(包括Exception和Error)从广义上分为检查异常(checked exceptions)和非检查的异常(unchecked exceptions)。

            其中根据Exception异常进行划分,可分为运行时异常和非运行时异常。

            需要明确的是:检查和非检查是对于javac来说的,这样就很好理解和区分了。

    检查异常

    What:什么是检查异常(checked exception)?

            就是编译器要求你必须处置的异常。不知道你编程的时候有没有遇到过,你写的某段代码,编译器要求你必须要对这段代码try...catch,或者throws exception,如果你遇见过,没错,这就是检查异常,也就是说,你代码还没运行呢,编译器就会检查你的代码,会不会出现异常,要求你对可能出现的异常必须做出相应的处理。

            javac强制要求程序员为这样的异常做预备处理工作(使用try...catch...finally或者throws)。在方法中要么用try-catch语句捕获它并处理,要么用throws子句声明抛出它,否则编译不会通过。这样的异常一般是由程序的运行环境导致的。因为程序可能被运行在各种未知的环境下,而程序员无法干预用户如何使用他编写的程序,于是程序员就应该为这样的异常时刻准备着。如SQLException , IOException,ClassNotFoundException 等。

            比如:我们调用日期格式化类解析字符串的时候;

    How:怎样处理检查异常(checked exception)?

            1、继续抛出,消极的方法,一直可以抛到java虚拟机来处理,就是通过throws Exception抛出。

            2、用try...catch捕获

            注意,对于检查的异常必须处理,或者必须捕获或者必须抛出

    Where:检查异常有哪些呢?

            除了RuntimeException与其子类,以及错误(Error),其他的都是检查异常(绝对的大家族)。

    非检查异常

    What:什么是非检查异常(unchecked exceptions)?

            编译器不要求强制处置的异常,虽然你有可能出现错误,但是编译器不会在编译的时候检查,没必要,也不可能。

            javac在编译时,不会提示和发现这样的异常,不要求在程序处理这些异常。所以如果愿意,我们可以编写代码处理(使用try...catch...finally)这样的异常,也可以不处理。

            对于这些异常,我们应该修正代码,而不是去通过异常处理器处理。这样的异常发生的原因多半是代码写的有问题。如除0错误ArithmeticException,错误的强制类型转换错误ClassCastException,数组索引越界ArrayIndexOutOfBoundsException,使用了空对象NullPointerException等等。

    How:对非检查的异常(unchecked exception )怎样处理?

            1、用try...catch捕获
            2、继续抛出
            3、不处理
            4、通过代码处理

            一般我们是通过代码处理的,因为你很难判断会出什么问题,而且有些异常你也无法运行时处理,比如空指针,需要人手动的去查找。

            而且,捕捉异常并处理的代价远远大于直接抛出。

    Why:为什么有非检查异常?

            你想想非检查异常都有哪些?NullPointerException,IndexOutOfBoundsException,VirtualMachineError等,这些异常你编译的时候检查吗?再说了,明明可以运行时检查,都在编译的时候检查,你写的代码还能看吗?而且有些异常只能在运行时才能检查出来,比如空指针,堆溢出等。

    Where:非检查异常有哪些?

            RuntimeException与其子类,以及错误(Error)。

    Exception异常划分

            Exception异常进行划分,它可分为运行时异常和编译期异常。

            运行时异常:
                    是RuntimeException类及其子类异常,如NullPointerException(空指针异常)、IndexOutOfBoundsException(下标越界异常)等,这些异常是非检查异常,程序中可以选择捕获处理,也可以不处理。这些异常一般是由程序逻辑错误引起的,程序应该从逻辑角度尽可能避免这类异常的发生。

                    运行时异常的特点是Java编译器不会检查它,也就是说,当程序中可能出现这类异常,即使没有用try-catch语句捕获它,也没有用throws子句声明抛出它,也会编译通过。

            编译期异常:
                    是RuntimeException以外的异常,类型上都属于Exception类及其子类。从程序语法角度讲是必须进行处理的异常,如果不处理,程序就不能编译通过。如IOException、SQLException等以及用户自定义的Exception异常,一般情况下不要自定义检查异常。

    二、初识异常与常见异常

    初识异常

            下面的代码会演示2个异常类型:ArithmeticException 和 InputMismatchException。

            前者由于整数除0引发,后者是输入的数据不能被转换为int类型引发。

    import java.util.Scanner;
    
    public class AllDemo {
        public static void main(String[] args) {
            System.out.println("----欢迎使用命令行除法计算器----");
            CMDCalculate();
        }
    
        public static void CMDCalculate() {
            Scanner scan = new Scanner(System.in);
            int num1 = scan.nextInt();
            int num2 = scan.nextInt();
            int result = devide(num1, num2);
            System.out.println("result:" + result);
            scan.close();
        }
    
        public static int devide(int num1, int num2) {
            return num1 / num2;
        }
    }
    
    /*****************************************
    ----欢迎使用命令行除法计算器----
    2
    0
    Exception in thread "main" java.lang.ArithmeticException: / by zero
    	at AllDemo.devide(AllDemo.java:19)
    	at AllDemo.CMDCalculate(AllDemo.java:13)
    	at AllDemo.main(AllDemo.java:6)
    
    ----欢迎使用命令行除法计算器----
    ----欢迎使用命令行除法计算器----
    1
    r
    Exception in thread "main" java.util.InputMismatchException
    	at java.util.Scanner.throwFor(Scanner.java:864)
    	at java.util.Scanner.next(Scanner.java:1485)
    	at java.util.Scanner.nextInt(Scanner.java:2117)
    	at java.util.Scanner.nextInt(Scanner.java:2076)
    	at AllDemo.CMDCalculate(AllDemo.java:12)
    	at AllDemo.main(AllDemo.java:6)
    *****************************************/

            异常是在执行某个函数时引发的,而函数又是层级调用,形成调用栈的,因为,只要一个函数发生了异常,那么他的所有的caller都会被异常影响。当这些被影响的函数以异常信息输出时,就形成的了异常追踪栈。

            异常最先发生的地方,叫做异常抛出点。

            从上面的例子可以看出,当devide函数发生除0异常时,devide函数将抛出ArithmeticException异常,因此调用它的CMDCalculate函数也无法正常完成,因此也发送异常,而CMDCalculate的caller——main 因为CMDCalculate抛出异常,也发生了异常,这样一直向调用栈的栈底回溯。这种行为叫做异常的冒泡,异常的冒泡是为了在当前发生异常的函数或者这个函数的caller中找到最近的异常处理程序。由于这个例子中没有使用任何异常处理机制,因此异常最终由main函数抛给JRE,导致程序终止。

            上面的代码不使用异常处理机制,也可以顺利编译,因为2个异常都是非检查异常。但是下面的例子就必须使用异常处理机制,因为异常是检查异常。

            代码中我选择使用throws声明异常,让函数的调用者去处理可能发生的异常。但是为什么只throws了IOException呢?因为FileNotFoundException是IOException的子类,在处理范围内。

    import java.io.FileInputStream;
    import java.io.IOException;
    
    public class ExceptionTest {
        public void testException() throws IOException {
            //FileInputStream的构造函数会抛出FileNotFoundException
            FileInputStream fileIn = new FileInputStream("E:\\a.txt");
    
            int word;
            //read方法会抛出IOException
            while ((word = fileIn.read()) != -1) {
                System.out.print((char) word);
            }
            //close方法会抛出IOException
            fileIn.close();
        }
    }

    常见异常

            java.lang.RuntimeException: 运行时异常
                    ClassCastException: 类类型转换异常,当试图将对象强制转换为不是实例的子类时,抛出该异常;
                    ArrayIndexOutOfBoundsException: 数组下标越界异常,当你使用不合法的索引访问数组时会抛出该异常;
                    NullPointerException: 空指针异常,通过null进行方法和属性调用会抛出该异常;
                    ArithmeticException: 算术运算异常,除数为0,抛出该异常;
                    NumberFormatException: 数字转换异常,当试图将一个String转换为指定的数字类型,而该字符串确不满足数字类型要求的格式时,抛出该异常;
                    InputMismatchException: 输入不匹配异常,输入的值数据类型与设置的值数据类型不能匹配。
            ...

    import java.io.FileInputStream;
    import java.io.IOException;
    import java.util.Date;
    import java.util.Scanner;
    
    public class ExceptionTest {
        //ClassCastException
        public void test1() {
            Object obj = new Date();
            String str = (String) obj;
        }
    
        //IndexOutOfBoundsException
        public void test2() {
            //ArrayIndexOutOfBoundsException
            int[] arr = new int[10];
            System.out.println(arr[10]);
    
            //StringIndexOutOfBoundsException
            String str = "abc";
            System.out.println(str.charAt(3));
        }
    
        //NullPointerException
        public void test3() {
            int[] arr = null;
            System.out.println(arr[3]);
    
            String str = "abc";
            str = null;
            System.out.println(str.charAt(0));
        }
    
        //ArithmeticException
        public void test4() {
            int a = 10;
            int b = 0;
            System.out.println(a / b);
        }
    
        //NumberFormatException
        public void test5() {
            String str = "123";
            str = "abc";
            int num = Integer.parseInt(str);
        }
    
        //InputMismatchException
        public void test6() {
            Scanner scanner = new Scanner(System.in);
            int score = scanner.nextInt();
            System.out.println(score);
            scanner.close();
        }
    }

            java.io.IOExeption: 输入输出异常
                    FileNotFoundException: 文件找不到异常,通常是两种情况:1、系统找不到指定的路径 2、拒绝访问(指定的是目录时,就会报拒绝访问异常)
                    EOFException: 文件已结束异常,抛出EOFException一定是因为连接断了还在继续read;

            java.lang.ClassNotFoundException: 类找不到异常,当我们通过配置文件去查找一个类的时候,如果配置路径写错,就会抛出该异常,比如:web.xml文件中根本就不存在该类的配置或者配置的路径写错;(比较常见)
            
            java.sql.SQLException: SQL异常,数据库的各种信息的异常;

    import java.io.File;
    import java.io.FileInputStream;
    
    public class ExceptionTest {
        
        public void test7() {
            File file = new File("hello.txt");
            FileInputStream fis = new FileInputStream(file);
    
            int data = fis.read();
            while (data != -1) {
                System.out.print((char) data);
                data = fis.read();
            }
    
            fis.close();
        }
    
    }

    三、异常处理的处理机制

            在编写程序时,经常要在可能出现错误的地方加上检测的代码,如进行x/y运算时,要检测分母为0,数据为空,输入的不是数据而是字符等。过多的if-else分支会导致程序的代码加长、臃肿,可读性差。因此采用异常处理机制。    

            在编写代码处理异常时,对于检查异常/非检查异常,都有2种不同的处理方式:
                    1、使用try...catch...finally语句块处理它。
                    2、在函数签名中使用throws 声明交给函数调用者caller去解决。
        
            比如现有一辆车,这个车你可以是方法,这辆车在可能存在各种风险,那么对于这些风险的处理方式,就相当于异常的处理方式:
            1、使用try...catch...finally语句块处理它。
                    我们把这辆车可能出现的问题都考虑清楚了,并提供了备选方案(出现问题怎么做),如果没有出现问题,那么用不到备选方案;
                    如果出现了问题,根据问题去找对应的备选方案,以保证车的正常运行;
                    如果出现了问题,但是又没备选方案,那么车就跑不了;
            2、在函数签名中使用throws 声明交给函数调用者caller去解决。
                    我知道车可能又问题,但是我不处理,谁来使用了,告诉调用者,这里可能有问题;
                    那么调用者可以处理这个问题,也可以不处理;如果它不处理,还是会出现问题,如果处理了,肯定没问题;
                    A -》 B -》 C

            Java采用的异常处理机制,是将异常处理的程序代码集中在一起,与正常的程序代码分开,使得程序简洁、优雅,并易于维护。

            Java提供的是异常处理的抓抛模型。
        
            Java程序的执行过程中如出现异常,会生成一个异常类对象,该异常对象将被提交给Java运行时系统,这个过程称为抛出(throw)异常。

            异常对象的生成:
                    由虚拟机自动生成:程序运行过程中,虚拟机检测到程序发生了问题,如果在当前代码中没有找到相应的处理程序,就会在后台自动创建一个对应异常类的实例对象并抛出——自动抛出。
                    由开发人员手动创建:Exception exception = new ClassCastException();——创建好的异常对象不抛出对程序没有任何影响,和创建一个普通对象一样。

            异常的抛出机制:
                    如果一个方法内抛出异常,该异常对象会被抛给调用者方法中处理。如果异常没有在调用者方法中处理,它继续被抛给这个调用方法的上层方法。这个过程将一直继续下去,直到异常被处理。这一过程称为捕获(catch)异常。
                    如果一个异常回到main()方法,并且main()也不处理,则程序运行终止。
                    程序员通常只能处理Exception,而对Error无能为力。

    异常处理机制一:try-catch-finally

    	语法格式:
            try{
                ...... //可能产生异常的代码
            }
            catch( ExceptionName1 e ){
                ...... //当产生ExceptionName1型异常时的处置措施
            }
            catch( ExceptionName2 e ){
                ...... //当产生ExceptionName2型异常时的处置措施
            }
            [ finally{
                ...... //无论是否发生异常,都无条件执行的语句
            } ]
    
    	语法解释:
    		try:
    			捕获异常的第一步是用try{…}语句块选定捕获异常的范围,将可能出现异常的代码放在try语句块中。
    			如果发生异常,则尝试去匹配catch块,catch块可以有多个(因为try块可以出现多个不同类型异常);
    			如果执行完try不管有没有发生异常,则接着去执行finally块和finally后面的代码(如果有的话)。			
    		catch (Exceptiontype e):
    			在catch语句块中是对异常对象进行处理的代码。每个try语句块可以伴随一个或多个catch语句,用于处理可能产生的不同类型的异常对象。
    			每一个catch块用于捕获并处理一个特定的异常,或者这异常类型的子类。Java可以将多个异常声明在一个catch中。 catch(Exception1 | Exception2 | Exception3 e)
    			catch后面的括号定义了异常类型和异常参数。如果异常与之匹配且是最先匹配到的,则虚拟机将使用这个catch块来处理异常。
    			在catch块中可以使用这个块的异常参数来获取异常的相关信息。异常参数是这个catch块中的局部变量,其它块不能访问。与其它对象一样,可以访问一个异常对象的成员变量或调用它的方法。
    				①、getMessage() 获取异常信息,返回字符串。
    				②、printStackTrace() 获取异常类名和异常信息,以及异常出现在程序中的位置。返回值void。
    			如果当前try块中发生的异常在后续的所有catch中都没捕获到,则先去执行finally,然后到这个函数的外部caller中去匹配异常处理器。
    			如果try中没有发生异常,则所有的catch块将被忽略。
    			注意:如果明确知道产生的是何种异常,可以用该异常类作为catch的参数;也可以用其父类作为catch的参数。比如:可以用 ArithmeticException 类作为参数的地方,就可以用RuntimeException类作为参数,或者用所有异常的父类Exception类作为参数。但不能是与ArithmeticException类无关的异常,如NullPointerException(catch中的语句将不会执行)。
    
    		finally:
    			finally块通常是可选的。捕获异常的最后一步是通过finally语句为异常处理提供一个统一的出口,使得在控制流转到程序的其它部分以前,能够对程序的状态作统一的管理。
    			不论在try代码块中是否发生了异常事件,catch语句是否执行,catch语句是否有异常,catch语句中是否有return,finally块中的语句都会被执行。
    			一个try至少要有一个catch块,否则, 至少要有1个finally块。但是finally不是用来处理异常的,finally不会捕获异常。
    			finally主要做一些清理工作,如流的关闭,数据库连接的关闭等。 
    public class Test_Input_01 {
        public static void main(String[] args) {
            Scanner sc = new Scanner(System.in);
            /*
    		 * 当我们只写一个try{}的时候,会报错:
    		 * 	Syntax error, insert "Finally" to complete TryStatement
    		 * 	语法错误,插入“Finally”来完成Try块
    		 * 
    		 * 总结: try不能单独存在,必须结合catch或finally来使用
    		 * 	try块中没有异常,会执行finally(如果有)
    		 *  try块中有异常,会执行对应的catch块(如果有),try中异常发生点之后的代码将不会执行
    		 */
            try{
                //try块中放可能发生异常的代码。
                //如果执行完try且不发生异常,则接着去执行finally块和finally后面的代码(如果有的话)。
                //如果发生异常,则尝试去匹配catch块。异常点之后的代码不会运行
    
                //这行代码有可能出错
                int num1 = sc.nextInt(); //这行代码如果出现异常,那么后面的输出语句就不执行
    
                int num2 = sc.nextInt();
    
                System.out.println(num1+"\t"+num2);
    
                System.out.println(num1/num2);
    
    
                String str = null;
                System.out.println(str.charAt(0));
    
            }catch(InputMismatchException | NullPointerException e) { 
                // catch(异常对象)	当异常发生了,catch就会捕获对应的异常
                // 每一个catch块用于捕获并处理一个特定的异常,或者这异常类型的子类。Java7中可以将多个异常声明在一个catch中。 catch(Exception1 | Exception2 | Exception3 e)
                // catch后面的括号定义了异常类型和异常参数。如果异常与之匹配且是最先匹配到的,则虚拟机将使用这个catch块来处理异常。
                // 在catch块中可以使用这个块的异常参数来获取异常的相关信息。异常参数是这个catch块中的局部变量,其它块不能访问。
                // 如果当前try块中发生的异常在后续的所有catch中都没捕获到,则先去执行finally,然后到这个函数的外部caller中去匹配异常处理器。
                // 如果try中没有发生异常,则所有的catch块将被忽略。
                System.out.println("catch块 - try里面发生了异常 - 空指针和输入不匹配异常都走这个Catch");
            }catch(java.lang.ArithmeticException e){
                e.getMessage();
                System.out.println("算数异常:除数不能为0");
    
            }catch(Exception e){
                System.out.println("程序发生未知异常");
            }finally {
                // finally块通常是可选的。
                // 无论异常是否发生,异常是否匹配被处理,finally都会执行。
                // 一个try至少要有一个catch块,否则, 至少要有1个finally块。但是finally不是用来处理异常的,finally不会捕获异常。
                // finally主要做一些清理工作,如流的关闭,数据库连接的关闭等。 
                System.out.println("finally块");
            }
    
            System.out.println("异常捕获之后的代码");
        }
    }	

    需要注意的地方

            1、try块中的局部变量和catch块中的局部变量(包括异常变量),以及finally中的局部变量,他们之间不可共享使用。

            2、每一个catch块用于处理一个异常。异常匹配是按照catch块的顺序从上往下寻找的,只有第一个匹配的catch会得到执行。匹配时,不仅运行精确匹配,也支持父类匹配,因此,如果同一个try块下的多个catch异常类型有父子关系,应该将子类异常放在前面,父类异常放在后面,这样保证每个catch块都有存在的意义。

            3、java中,异常处理的任务就是将执行控制流从异常发生的地方转移到能够处理这种异常的地方去。也就是说:当一个函数的某条语句发生异常时,这条语句的后面的语句不会再执行,它失去了焦点。执行流跳转到最近的匹配的异常处理catch代码块去执行,异常被处理完后,执行流会接着在“处理了这个异常的catch代码块”后面接着执行。

            有的编程语言当异常被处理后,控制流会恢复到异常抛出点接着执行,这种策略叫做:resumption model of exception handling(恢复式异常处理模式)

            而Java则是让执行流恢复到处理了异常的catch块后接着执行,这种策略叫做:termination model of exception handling(终结式异常处理模式)

    public static void main(String[] args){
        try {
            foo();
        }catch(ArithmeticException ae) {
            System.out.println("处理异常");
        }
    }
    public static void foo(){
        int a = 5/0;  //异常抛出点
        System.out.println("Look me!!!");  //不会执行
    }

            finally块不管异常是否发生,只要对应的try执行了,则它一定也执行。只有一种方法让finally块不执行:System.exit()。因此finally块通常用来做资源释放操作:关闭文件,关闭数据库连接等等。

            良好的编程习惯是:在try块中打开资源,在finally块中清理释放这些资源。

            需要注意的地方:

            1、finally块没有处理异常的能力。处理异常的只能是catch块。

            2、在同一try...catch...finally块中 ,如果try中抛出异常,且有匹配的catch块,则先执行catch块,再执行finally块。如果没有catch块匹配,则先执行finally,然后去外面的调用者中寻找合适的catch块。

            3、在同一try...catch...finally块中 ,try发生异常,且匹配的catch块中处理异常时也抛出异常,那么后面的finally也会执行:首先执行finally块,然后去外围调用者中寻找合适的catch块。

            这是正常的情况,但是也有特例。关于finally有很多恶心,偏、怪、难的问题.

            具体在最后一节:finally块和return 讲解

    不捕获异常时的情况:

            前面使用的异常都是RuntimeException类或是它的子类,这些类的异常的特点是:即使没有使用try和catch捕获,Java自己也能捕获,并且编译通过( 但运行时会发生异常使得程序运行终止 )。

            如果抛出的异常是IOException等类型的非运行时异常,则必须捕获,否则编译错误。也就是说,我们必须处理编译时异常,将异常进行捕捉,转化为运行时异常。

    import java.io.*;
    
    public class IOExp {
        public static void main(String[] args) {
            try {
                FileInputStream in = new FileInputStream("hello.txt");
                int b;
                b = in.read();
                while (b != -1) {
                    System.out.print((char) b);
                    b = in.read();
                }
                in.close();
            } catch (IOException e) {
                System.out.println(e);
            } finally {
                System.out.println(" It’s ok!");
            }
        }
    }

    异常处理机制二:throws

            throws是另一种处理异常的方式,它不同于try...catch...finally,throws仅仅是将函数中可能出现的异常向调用者声明,而自己则不具体处理。

            throws声明:如果一个方法内部的代码会抛出检查异常(checked exception),而方法自己又没有完全处理掉或并不能确定如何处理这种异常,则javac保证你必须在方法的签名上使用throws关键字声明这些可能抛出的异常,表明该方法将不对这些异常进行处理,而由该方法的调用者负责处理,否则编译不通过。

            在方法声明中用throws语句可以声明抛出异常的列表,throws后面的异常类型可以是方法中产生的异常类型,也可以是它的父类。

            采取这种异常处理的原因可能是:方法本身不知道如何处理这样的异常,或者说让调用者处理更好,调用者需要为可能发生的异常负责。

            语法格式:
            修饰符 返回值类型 方法名() throws ExceptionType1 , ExceptionType2 ,ExceptionTypeN{ 
                //方法内部可以抛出 ExceptionType1 , ExceptionType2 ,ExceptionTypeN 类的异常,或者他们的子类的异常对象。
            }

    import java.io.*;
    
    public class ThrowsTest {
        public static void main(String[] args) {
            ThrowsTest t = new ThrowsTest();
            try {
                t.readFile();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    
        public void readFile() throws IOException {
            FileInputStream in = new FileInputStream("hello.txt");
            int b;
            b = in.read();
            while (b != -1) {
                System.out.print((char) b);
                b = in.read();
            }
            in.close();
        }
    }

    四、手动抛出异常:throw

            Java异常类对象除在程序执行过程中出现异常时由系统自动生成并抛出,也可根据需要使用人工创建并抛出。

            首先要生成异常类对象,然后通过throw语句实现抛出操作(提交给Java运行环境)。
                    throw exceptionObject

            程序员也可以通过throw语句手动显式的抛出一个异常。throw语句的后面可以抛出的异常必须是Throwable或其子类的实例。下面的语句在编译时将会产生语法错误:
                    throw new String("你抛我试试.");

            throw 语句必须写在函数中,执行throw 语句的地方就是一个异常抛出点,它和由JRE自动形成的异常抛出点没有任何差别。

    public class StudentTest {
        public static void main(String[] args) {
            try {
                Student s = new Student();
                s.regist(-1001);
                System.out.println(s);
            } catch (Exception e) {
                System.out.println(e.getMessage());
            }
        }
    }
    
    class Student {
        private int id;
    
        public void regist(int id) throws Exception {
            if (id > 0) {
                this.id = id;
            } else {
                // System.out.println("您输入的数据非法!");
    
                //手动抛出异常对象
                // throw new RuntimeException("您输入的数据非法!");
                throw new Exception("您输入的数据非法!");
    
                //错误的
                // throw new String("不能输入负数");
            }
    
        }
    
        @Override
        public String toString() {
            return "Student [id=" + id + "]";
        }
    }

    throws和throw的区别:

            throw是语句抛出一个异常。
                    语法:throw (异常对象);
                        throw e;

            throws是方法可能抛出异常的声明。(用在声明方法时,表示该方法可能要抛出异常)
                    语法:[(修饰符)](返回值类型)(方法名)([参数列表])[throws(异常类)]{......}
                        public void doA(int a) throws Exception1,Exception3{......}

            throw语句用在方法体内,表示抛出异常,由方法体内的语句处理。
            throws出现在方法函数头,表示在抛出异常,由该方法的调用者来处理。

            throws主要是声明这个方法会抛出这种类型的异常,使它的调用者知道要捕获这个异常。
            throw是具体向外抛异常的动作,所以它是抛出一个异常实例。

            throws说明你有那个可能,倾向。
            throw的话,那就是你把那个倾向变成真实的了。

            两者都是消极处理异常的方式(这里的消极并不是说这种方式不好),只是抛出或者可能抛出异常,但是不会由函数去处理异常,真正的处理异常由函数的上层调用处理。

    五、异常的链化

            在一些大型的,模块化的软件开发中,一旦一个地方发生异常,则如骨牌效应一样,将导致一连串的异常。假设B模块完成自己的逻辑需要调用A模块的方法,如果A模块发生异常,则B也将不能完成而发生异常,但是B在抛出异常时,会将A的异常信息掩盖掉,这将使得异常的根源信息丢失。异常的链化可以将多个模块的异常串联起来,使得异常信息不会丢失。

            异常链化:以一个异常对象为参数构造新的异常对象。新的异对象将包含先前异常的信息。这项技术主要是异常类的一个带Throwable参数的函数来实现的。这个当做参数的异常,我们叫它根源异常(cause)。

            查看Throwable类源码,可以发现里面有一个Throwable字段cause,就是它保存了构造时传递的根源异常参数。这种设计和链表的结点类设计如出一辙,因此形成链也是自然的了。

    public class Throwable implements Serializable {
        private Throwable cause = this;
    
        public Throwable(String message, Throwable cause) {
            fillInStackTrace();
            detailMessage = message;
            this.cause = cause;
        }
    
        public Throwable(Throwable cause) {
            fillInStackTrace();
            detailMessage = (cause == null ? null : cause.toString());
            this.cause = cause;
        }
    
        //........
    }

            下面是一个例子,演示了异常的链化:从命令行输入2个int,将他们相加,输出。输入的数不是int,则导致getInputNumbers异常,从而导致add函数异常,则可以在add函数中抛出一个链化的异常。

    public static void main(String[] args){
        System.out.println("请输入2个加数");
        int result;
        try{
            result = add();
            System.out.println("结果:"+result);
        } catch (Exception e){
            e.printStackTrace();
        }
    }
    //获取输入的2个整数返回
    private static List<Integer> getInputNumbers(){
        List<Integer> nums = new ArrayList<>();
        Scanner scan = new Scanner(System.in);
        try {
            int num1 = scan.nextInt();
            int num2 = scan.nextInt();
            nums.add(new Integer(num1));
            nums.add(new Integer(num2));
        }catch(InputMismatchException immExp){
            throw immExp;
        }finally {
            scan.close();
        }
        return nums;
    }
    
    //执行加法计算
    private static int add() throws Exception{
        int result;
        try {
            List<Integer> nums =getInputNumbers();
            result = nums.get(0)  + nums.get(1);
        }catch(InputMismatchException immExp){
            throw new Exception("计算失败",immExp);  /链化:以一个异常对象为参数构造新的异常对象。
        }
        return  result;
    }
    
    /*
    请输入2个加数
    r 1
    java.lang.Exception: 计算失败
        at practise.ExceptionTest.add(ExceptionTest.java:53)
        at practise.ExceptionTest.main(ExceptionTest.java:18)
    Caused by: java.util.InputMismatchException
        at java.util.Scanner.throwFor(Scanner.java:864)
        at java.util.Scanner.next(Scanner.java:1485)
        at java.util.Scanner.nextInt(Scanner.java:2117)
        at java.util.Scanner.nextInt(Scanner.java:2076)
        at practise.ExceptionTest.getInputNumbers(ExceptionTest.java:30)
        at practise.ExceptionTest.add(ExceptionTest.java:48)
        ... 1 more
    
    */

    六、自定义异常

    自定义异常规则

            如果要自定义异常类,则扩展Exception类即可,因此这样的自定义异常都属于检查异常(checked exception)。如果要自定义非检查异常,则扩展自RuntimeException。

            按照国际惯例,自定义的异常应该总是包含如下的构造函数:
                    一个无参构造函数
                    一个带有String参数的构造函数,并传递给父类的构造函数。
                    一个带有String参数和Throwable参数,并都传递给父类构造函数。
                    一个带有Throwable 参数的构造函数,并传递给父类的构造函数。

    下面是IOException类的完整源代码,可以借鉴。

    public class IOException extends Exception {
        static final long serialVersionUID = 7818375828146090155L;
    
        public IOException() {
            super();
        }
    
        public IOException(String message) {
            super(message);
        }
    
        public IOException(String message, Throwable cause) {
            super(message, cause);
        }
    
        public IOException(Throwable cause) {
            super(cause);
        }
    }

    自定义异常构建

    首先写一个自定义异常,继承Exception,代码如下:

    public class MyException extends Exception {
    
        public MyException() {
            super();
        }
    
        public MyException(String message) {
            super(message);
        }
    
        public MyException(String message, Throwable cause) {
            super(message, cause);
        }
    
    
        public MyException(Throwable cause) {
            super(cause);
        }
    }

    使用自定义异常

            如果自定义异常是为了提示,在使用的时候,一定要用try..catch,不要直接用throw往外抛。

    public class Test {
        public static void main(String[] args) {
            A a = new A();
            try {
                a.show(-2);
            } catch (MyException e) {
                System.out.println(e.getMessage());
            }
        }
    }
    
    class A {
        public void show(int num) throws MyException {
            if (num < 0) {
                MyException me = new MyException("异常:" + num + "不是正数");
                throw me;//抛出异常,结束方法show()的执行
            }
            System.out.println(num);
        }
    }

    七、异常的注意事项

            1、当子类重写父类的带有 throws声明的函数时,其throws声明的异常必须在父类异常的可控范围内——用于处理父类的throws方法的异常处理器,必须也适用于子类的这个带throws方法 。这是为了支持多态。

            例如,父类方法throws 的是2个异常,子类就不能throws 3个及以上的异常。父类throws IOException,子类就必须throws IOException或者IOException的子类。

            至于为什么?我想,也许下面的例子可以说明。

    class Father {
        public void start() throws IOException {
            throw new IOException();
        }
    }
    
    class Son extends Father {
        public void start() throws Exception {
            throw new SQLException();
        }
    }
    
    /**********************假设上面的代码是允许的(实质是错误的)***********************/
    class Test {
        public static void main(String[] args) {
            Father[] objs = new Father[2];
            objs[0] = new Father();
            objs[1] = new Son();
    
            for (Father obj : objs) {
                // 因为Son类抛出的实质是SQLException,而IOException无法处理它。
                // 那么这里的try。。catch就不能处理Son中的异常。
                // 多态就不能实现了。
                try {
                    obj.start();
                } catch (IOException ioException) {
                    // 处理IOException
                }
            }
        }
    }

            2、Java程序可以是多线程的。每一个线程都是一个独立的执行流,独立的函数调用栈。如果程序只有一个线程,那么没有被任何代码处理的异常会导致程序终止。如果是多线程的,那么没有被任何代码处理的异常仅仅会导致异常所在的线程结束。

            也就是说,Java中的异常是线程独立的,线程的问题应该由线程自己来解决,而不要委托到外部,也不会直接影响到其它线程的执行。

    八、finally块和return

    1、一个不容易理解的事实:在 try块中即便有return,break,continue等改变执行流的语句,finally也会执行。

    public static void main(String[] args){
        int re = bar();
        System.out.println(re);
    }
    private static int bar() {
        try{
            return 5;
        } finally{
            System.out.println("finally");
        }
    }
    /*输出:
    finally
    5
    */

    2、finally中的return 会覆盖 try 或者catch中的返回值。

    public static void main(String[] args){
        int result;
    
        result  =  foo();
        System.out.println(result);     /2
    
        result = bar();
        System.out.println(result);    /2
    }
    
    @SuppressWarnings("finally")
    public static int foo(){
        try{
            int a = 5 / 0;
        } catch (Exception e){
            return 1;
        } finally{
            return 2;
        }
    
    }
    
    @SuppressWarnings("finally")
    public static int bar(){
        try {
            return 1;
        }finally {
            return 2;
        }
    }

    3、finally中的return会抑制(消灭)前面try或者catch块中的异常

    class TestException {
        public static void main(String[] args) {
            int result;
            try {
                result = foo();
                System.out.println(result);           //输出100
            } catch (Exception e) {
                System.out.println(e.getMessage());    //没有捕获到异常
            }
    
    
            try {
                result = bar();
                System.out.println(result);           //输出100
            } catch (Exception e) {
                System.out.println(e.getMessage());    //没有捕获到异常
            }
        }
    
        //catch中的异常被抑制
        @SuppressWarnings("finally")
        public static int foo() throws Exception {
            try {
                int a = 5 / 0;
                return 1;
            } catch (ArithmeticException amExp) {
                throw new Exception("我将被忽略,因为下面的finally中使用了return");
            } finally {
                return 100;
            }
        }
    
        //try中的异常被抑制
        //J2SE 提供的最后一个批注是 @SuppressWarnings。该批注的作用是给编译器一条指令,告诉它对被批注的代码元素内部的某些警告保持静默。
        @SuppressWarnings("finally")
        public static int bar() throws Exception {
            try {
                int a = 5 / 0;
                return 1;
            } finally {
                return 100;
            }
        }
    }

    4、finally中的异常会覆盖(消灭)前面try或者catch中的异常

    public class TestException {
        public static void main(String[] args) {
            int result;
            try {
                result = foo();
            } catch (Exception e) {
                System.out.println(e.getMessage());    //输出:我是finaly中的Exception
            }
    
    
            try {
                result = bar();
            } catch (Exception e) {
                System.out.println(e.getMessage());    //输出:我是finaly中的Exception
            }
        }
    
        //catch中的异常被抑制
        @SuppressWarnings("finally")
        public static int foo() throws Exception {
            try {
                int a = 5 / 0;
                return 1;
            } catch (ArithmeticException amExp) {
                throw new Exception("我将被忽略,因为下面的finally中抛出了新的异常");
            } finally {
                throw new Exception("我是finally中的Exception");
            }
        }
    
        //try中的异常被抑制
        @SuppressWarnings("finally")
        public static int bar() throws Exception {
            try {
                int a = 5 / 0;
                return 1;
            } finally {
                throw new Exception("我是finally中的Exception");
            }
    
        }
    }

    上面的3个例子都异于常人的编码思维,因此我建议:

            不要在fianlly中使用return。

            不要在finally中抛出异常。

            减轻finally的任务,不要在finally中做一些其它的事情,finally块仅仅用来释放资源是最合适的。

            将尽量将所有的return写在函数的最后面,而不是try ... catch ... finally中。

    展开全文
  • 永远不要期待程序在完全理想的状态下运行,异常往往不期而遇,如果没有完善的异常处理机制,后果可能是灾难性的。对于 Java 工程师而言,合理地处理异常是一种基本而重要的能力,然而,在近来的面试中,笔者发现很多...
  • 1.首先我们先自定义一个异常以及相应属性 package org.xz.test.exception; //定义自定义异常 public class MyException extends Exception { private String code; private String msg; public MyException...
  • Java Stream中的异常处理

    千次阅读 2021-03-09 05:35:24
    现在,在使用了这些语言特性一段时间之后,我们经常面临的一个问题是如何在lambda里处理checkedException。你很可能已经知道,直接在lambda里调用抛出checkedException的方法是不行的,我们需要catch住checkedExcept....
  • 但是,关于在使用协程的时候,个人觉得异常处理这一块是相对来讲是需要花时间去了解的地方,因为在使用过程中还是会遇到一些小坑的,这里记录下之前遇到的坑。 踩一个使用async await时异常处理的坑 kotlin 协程的...
  • 如何处理公共异常处理

    万次阅读 2021-11-16 19:08:21
    一般而言为了使我们的代码更容易维护,会创建一个类集中处理异常,该异常类可以... * 异常处理 * @param e * @return */ @ExceptionHandler(value = Exception.class) @ResponseBody public Result error(Exceptio
  • springboot 统一异常处理

    千次阅读 2020-06-05 10:15:04
    什么是统一异常处理 Spring在3.2版本增加了一个注解@ControllerAdvice,可以与@ExceptionHandler、@InitBinder、@ModelAttribute 等注解注解配套使用,对于这几个注解的作用,这里不做过多赘述,若有不了解的,可以...
  • SpringBoot 全局异常处理

    千次阅读 2022-04-12 00:43:48
    SpringBoot 全局异常处理 在我们写项目时候,肯定会遇到各种各样的异常报错和用户传值错误需要返回对应的错误提示,如果我们都手动进行返回Result对象的话就会出现两个比较麻烦的问题: 1、各种的异常无法返回Result...
  • Spring的全局(统一)异常处理

    千次阅读 2021-08-11 22:52:28
    一,什么是全局异常处理 二,try...catch的弊端 三,异常处理的三种方式 1,实现HandlerExceptionResolver接口 2,@ExceptionHandler 3,@ControllerAdvice+@ExceptionHandler 四,
  • Java学习-详谈Java异常和异常处理

    千次阅读 2020-09-13 17:14:17
    1、异常 1.1异常概念 异常,就是不正常的意思。在生活中:医生说,你的身体某个部位有异常,该部位和正常相比有点不同,该部位的...Java处理异常的方式是中断处理。 2、异常体系 2.1Throwable类 异常的根类: java.lang.T
  • Kotlin 异常处理

    千次阅读 2021-12-06 21:47:28
    kotlin 先处理异常,再处理异常3. kotlin 使用 throw 抛出异常4. kotlin 自定义异常附 Github 源码: 1. kotlin 捕获异常 不论在 try 块、catch 块中执行怎样的代码(除非退出虚拟机 System.exit(1) ),...
  • python异常处理(一)

    千次阅读 2020-12-02 13:41:41
    Python的异常处理能力是很强大的,可向用户准确反馈出错信息。在Python中,异常也是对象,可对它进行操作。所有异常都是基类Exception的成员。所有异常都从基类Exception继承,而且都在exceptions模块中定义。Python...
  • SpringMVC的异常处理(超详细)

    千次阅读 多人点赞 2021-02-06 15:13:26
    SpringMVC的异常处理异常处理思路异常处理先看下项目的目录结构演示程序异常演示效果异常处理步骤1、自定义异常类2、自定义异常处理器3、配置异常处理器异常处理效果演示 异常处理思路 Controller调用service,...
  • C#异常处理总结

    万次阅读 多人点赞 2017-10-12 23:27:33
    关于异常,从我们一开始写代码的时候就开始伴随着我们,只不过那时还没入门,脑子里并没有产生意识这就是异常异常:程序运行期间发生错误, 异常对象: 将程序发生的各种错误封装成对象
  • SpringBoot全局异常处理

    万次阅读 2022-03-05 16:59:20
    SpringBoot全局异常处理
  • Java中的异常处理机制

    千次阅读 2022-03-15 00:24:15
    异常基础ErrorError是Throwable的子类,这代表了Java程序运行过程中的一种未知原因的错误,当程序运行遇到Error的时候,不应该在代码中捕获。跟运行时异常(Runtime...
  • 异常处理: 捕获异常,简单的捕获异常语法,在程序开发时,可以尝试加如 try来捕获异常 try: 尝试执行的代码 except: 出现错误的处理 try尝试 下方填写要尝试的密码,不确定能不能够正常执行的代码 except 如果...
  • Python之异常处理方法

    万次阅读 2021-03-15 10:32:45
    Python之异常处理
  • Java异常处理的两种方式

    千次阅读 2021-11-15 12:43:43
    异常处理方式一:在当前方法中直接用try…catch处理 异常处理方式二:在当前方法中不处理,throws 异常抛给调用者处理 一. try-catch的方式就是捕获异常。 try:该代码块中编写可能产生异常的代码。 ...
  • C语言中的异常处理

    万次阅读 2018-08-05 13:59:00
    一 前言: 异常处理,对于做面向对象开发的开发者来说是再熟悉不过了,例如在C#中有 try { ... } catch( Exception e){...} finally{ ..... }
  • SpringBoot中如何进行统一异常处理

    千次阅读 热门讨论 2022-02-02 22:03:41
    如何在SpringBoot项目里进行统一异常处理 需要了解的知识 @ControllerAdvice的作用 1、处理前 异常代码 /** * 根据id获取医院设置 * * @param id 查看的id编号 * @return */ @ApiOperation(value = "根据id...
  • java异常处理机制原理和应用

    千次阅读 多人点赞 2021-11-20 13:50:48
    Exception和Error都继承于Throwable 类,在 Java 中只有 Throwable 类型的实例才可以被抛出或捕获,它是异常处理机制的基本组成类型。 Exception是可预料的异常情况,可以获取到这种异常,并对其进行
  • java异常处理机制

    千次阅读 2021-01-11 19:42:29
    异常机制是指当程序出现错误后,程序如何处理。具体来说就是程序发生异常异常机制提供程序的退出安全通道。 通俗来说:就是为了让程序继续执行下去,不至于中断。 程序错误: 程序错误分为三种:1.编译错误 2....
  • 【Python】异常处理

    千次阅读 2021-03-06 04:12:37
    异常处理内容概要异常的概念捕获异常异常的传递抛出异常01.异常的概念程序在运行的时候,遇到错误导致程序停止运行时,这就是异常。程序停止运行,并且提示错误信息这个动作,我们称为:抛出(raise)异常。程序开发的...
  • Feign调用全局异常处理解决

    万次阅读 热门讨论 2020-06-22 20:08:55
    异常信息形如: TestService#addRecord(ParamVO) failed and no fallback available.; 对于failed and no fallback available.这种异常信息,是因为项目开启了熔断: feign.hystrix.enabled: true 当调用服务时抛...
  • Java异常处理(Java必备知识)

    千次阅读 2021-02-01 01:27:09
    Java提供了异常处理机制来帮助程序员检查可能出现的错误,提高了程序的可读性和可维护性。Java中将异常封装到一个类中,出现错误就会抛出异常。 目录 异常概述 捕捉处理异常 在方法中抛出异常 ......
  • gRPC 异常处理

    千次阅读 2020-02-29 12:59:34
    最近第一次使用 gRPC 创建一个服务,在服务端我想将一个自定义异常直接抛出去,让客户端能看到。一开始,我这样尝试: // responseObserver.onError(new CustomException("custom exception")); throw new ...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 1,908,397
精华内容 763,358
关键字:

异常处理

友情链接: Game.rar