异常处理

目前碰到的几种异常

  1. 下标越界异常
  2. 空指针异常
  3. 类型转换异常
  4. 数字格式化异常
  5. 算术异常(数学异常)

编程界的几种异常

  • 除数为0,数学异常
  • IO流,输入输出没有关闭
  • 停电,物理异常
当一个程序抛出异常时,抛异常后面的语句不再执行
类似于return的功能,将会终止方法的执行

在使用之前,我创建了三个自定义异常类

MyException类

public class MyException extends Exception  {

    public MyException(String message) {
        super(message);
    }
}

MyRuntimeException类

public class MyRuntimeException extends RuntimeException {

    public MyRuntimeException(String message) {
        super(message);
    }
}

ServiceException类

public class ServiceException extends RuntimeException {

    // 错误码
    private Integer code;

    // 异常信息
    private String message;

    public ServiceException() {
    }

    public ServiceException(Integer code, String message) {
        this.code = code;
        this.message = message;
    }

    public Integer getCode() {
        return code;
    }

    public void setCode(Integer code) {
        this.code = code;
    }

    @Override
    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }
}

简单的运用

public static void main(String[] args) {
    int num1 = 10;
    int num2 = 0;
    if(num2 != 0){
        System.out.println(num1 / num2);
    }
    double num3 = 0;
    BigDecimal bigDecimal1 = new BigDecimal(13.0);
    BigDecimal bigDecimal2 = new BigDecimal(0);
    System.out.println(bigDecimal1.divide(bigDecimal2));
    System.out.println("我很重要...");
}

异常的继承体系结构

最顶级的异常错误类 Throwable
Error 正常情况下,不太可能出现的。绝大多数Error都会导致程序处于
非正常的状态下,很难恢复。外力的作用下,不考虑。
ErrorThrowable的子类。它是在Java程序处理范围之外的。

Exception 在Java语言中,将程序执行中发生的不正常的情况称之为异常。
编译期异常:写代码的时候,抛异常。如果编译器不解决,会编译不通过,一直报红。
运行期异常:RuntimeException,运行时会抛异常,平时没事


自定义异常

Java中异常机制,需要结合实际业务。
那么该如何自定义异常呢?

  1. 所有异常必须是Throwable的子类(大材小用,没必要)
  2. 如果要定义一个编译期异常,需要继承Exception类。
  3. 如果要定义一个运行期异常,需要继承RuntimeException类。
public static void main(String[] args) {
    int num1 = 10;
    int num2 = 2;
    try{
        // 把有可能抛异常的代码放到try语句块里
        System.out.println(num1 / num2);
    }catch (Exception e){
        System.out.println("除数不能为0");
        e.printStackTrace();
    }
    System.out.println("我很重要...");
}

在一个语句块中,如果使用throw抛出一个编译期异常
就必须在方法的声明处使用throw关键字来标记异常类型
还有一种处理方式,就是直接try...catch

我们为什么要手动抛异常?
因为要配合全局异常处理机制来解决问题,后面的事。

public static void fun(int i,int j) throws MyException {
    if(j == 0){
        throw new MyException("除数不能为0");
    }
    System.out.println(i / j);
}

public static void main(String[] args) {
    try {
        fun(1,1);
    } catch (MyException e) {
        // 打印异常信息
        e.printStackTrace();
    }
}
throw语句是可以当做方法的返回值的
在一个有返回值的方法中,如果有条件分支
一定要保证每种情况下都有返回值,哪怕是抛异常
开发中,大部分情况下使用的都是运行期异常!!!
public static String show(String str) {
    if(!Objects.isNull(str)){
        return str.concat("hello");
    }
//        throw new RuntimeException("参数不能是空");
    throw new ServiceException(101,"账号不能为空。");
}

public static void main(String[] args) {
    show(null);
}

异常链

一个异常被抛出去之后会继续被调用这个方法的方法捕获或抛出,异常会扩散。
只要说解决异常,处理异常,捕获,就是try...catch
class A{
    public void a() {
        throw new ServiceException(201,"业务异常...");
    }
}
class B {
    public void b() {
        A aa = new A();
        try {
            aa.a();
        } catch (Exception e) {
           e.printStackTrace();
        }
        System.out.println("b方法的语句");
    }
}
class C {
    public void c(){
        B b = new B();
        b.b();
        System.out.println("c方法的语句..");
    }
}
public class Ch05 {

    public static void main(String[] args) {
        C c = new C();
        c.c();
        System.out.println("Ch05的语句");
    }
}

如果一个方法没有捕获一个编译期异常,该方法必须使用throws来声明。
throws并不是真正的解决了异常,只是把异常抛给了下一级调用者。
方法的声明处,抛出多个异常,用逗号隔开。
public void show() throws MyException,RuntimeException,NullPointerException,IndexOutOfBoundsException {

}

public void info() throws MyException {
    show();
}

// JVM
public static void main(String[] args) {
    try {
        new Ch06().info();
    } catch (MyException e) {
        throw new RuntimeException(e);
    }
}

finally关键字

finally用来创建在try代码块后面执行的代码块
无论是否发生异常,finally代码块中的代码一定会执行。
一般finally中的代码都是用来释放资源
语句:try...catch...finally
public static void main(String[] args) {
    int num1 = 10;
    int num2 = 2;
    String str = null;
    try {
        System.out.println(num1 / num2);
        System.out.println(str.length());
        main(args);
    }finally{
        System.out.println("finally...");
    }
}
catch可以写多个,有顺序问题。
先写小的,再写大的。
public static void main(String[] args) {
    int num1 = 10;
    int num2 = 0;
    try {
        System.out.println(num1 / num2);
        // 开发角度来说,应该写指定的。
    } catch (ArithmeticException e) {
        e.printStackTrace();
        System.out.println("除数不能为0");
    } catch (Exception e){
        System.out.println("未知错误");
    }
}

方法的重写

重写的方法不能抛出比被重写方法更大的异常类型
interface Inter01 {

    void show() throws Exception;
}

public class Ch09 implements Inter01  {

    @Override
    public void show() throws NullPointerException {

    }
}

身份证的一些判断

传入的身份证为String类型的idCard
首先要判断身份证是否完全为数字,那么该如何判断呢?
简单的用Long.parseLong()即可,若字符串中存在不是数字的内容
将会抛出异常,无法赋值。确认身份证完全都是数字后再进行其他判断

身份证需要是18位数字、第一位不能是0
生日的年份不能大于LocalDate.now(),月份要对应年份
不能小于1,也不可以大于该月份的正常最大值(要考虑闰年的问题)
年龄(当日年份-身份证年份)需要在18-60区间,否则抛出异常

public void judgeIdCard(String idCard) {
    long id=0L;
    try {
        id = Long.parseLong(idCard);
    } catch (Exception e) {
        throw new MyRuntimeException("身份证中存在未知的其他字符");
    }

    if (idCard.length() != 18 || idCard.charAt(0) == '0') {
        throw new RuntimeException("身份证号错误!(位数错误)");
    }
    int year = Integer.parseInt(idCard.substring(6,10));
    int month = Integer.parseInt(idCard.substring(10,12));
    int day = Integer.parseInt(idCard.substring(12,14));

    LocalDate localDate = LocalDate.now();
    if (year >localDate.getYear()) {
        throw new RuntimeException("身份证号错误!(年份错误)");
    }

    if (month >12 || month <1) {
        throw new RuntimeException("身份证号错误!(月份错误)");
    }
    int verifyDay;
    if (month == 1|| month ==3 || month==5||
            month==7 || month==8 ||month==10||month==12) {
        verifyDay = 31;
    }else if (month == 4 || month==6 || month==9 || month==11) {
        verifyDay = 30;
    }else {
        if ((year %4 ==0 && year%100 !=0) || year%400 ==0) {
            verifyDay = 29;
        } else {
            verifyDay = 28;
        }
    }
    if (day<0 || day >verifyDay) {
        throw new RuntimeException("身份证号错误!(日期错误)");
    }

    int age = localDate.getYear() - year;
    if (age > 60) {
        throw new MyRuntimeException("年龄过高");
    }
    if (age < 18) {
        throw new MyRuntimeException("年龄过低");
    }

    this.age = localDate.getYear() - year;
    this.birth = year+"-"+idCard.substring(10,12)+"-"+idCard.substring(12,14);
}

整个判断中没有太多要考虑的事情,前期完成Long.parseLong()的操作
后期就可以随便赋值了,通过String中的subString方法
来准确的拿到相应的年份、月份、日期、性别等信息
整个判断没有太多细节,正规的身份证号码应该有更多的判断
以上代码只是提供一些小思路,不完全正确,仅供参考!


“异常”常见习题

请描述异常的继承体系

异常继承体系为:异常的根类是 java.lang.Throwable,其下有两个子类: java.lang.Errorjava.util.Exception 。而Exception又分为编译时期异常:checked异常,与运行时期异常:runtime异常。

请描述你对错误(Error)的理解

Error:表示不可修复的恶性的错误,只能通过修改代码规避错误的产生,通常是系统级别的,所以很严重。

请描述你对异常(Expection的理解)

Exception:表示可修复的良性(相对于错误)的异常,异常产生后程序员可以并且应该通过代码的方式纠正,使程序继续运行,是必须要处理的。

请描述你对运行时异常(RuntimeException)的理解

运行时期异常:runtime异常。编译期不报错,运行时出现异常信息,也可以提前在编译期处理,而checked异常 必须在编译期处理。

请描述throw的使用位置,作用是什么?

throw关键字通常用在方法体中,并且抛出一个异常对象。程序在执行到throw语句时立即 停止,它后面的语句都不执行。

请描述throws的使用位置,作用是什么?

throws关键字通常被应用在声明方法时,用来指定可能抛出的异常。多个异常可以使用逗号隔开。当在主函数中调用该方法时,如果发生异常,就会将异常对象抛给方法调用处。

异常处理方式有几种,分别是什么?

异常的处理方式有两种,分别是使用throwstry...catch...finally

详细阐述每种方式对异常是如何处理的

throws用在方法的声明上后接异常类名,是把异常抛给调用者进行处理
try...catch...finally是捕获异常,自己处理,处理完毕后面的程序可以继续运行

a. try代码块中是可能出现异常的代码
b. catch代码块,是遇到异常,对异常进行处理的代码
c. finally代码块是无论是否发生异常,都必须执行的代码,用于释放资源.

请列举常见异常,并说明产生原因。

NullPointerException:空指针异常。 当应用试图在要求使用对象的地方使用了null时,抛出该异常。
譬如:调用null对象的实例方 法、访问null对象的属性、计算null对象的长度等等。

ArrayIndexOutOfBoundsException:数组索引越界异常。 当对数组的索引值为负数或大于等于数组大小时抛出此异常。

ArithmeticException:算术运算异常。 程序中出现了除以零这样的运算就会出这样的异常,对这种异常,大家就要好好检查一下自己程序中涉及到数学运算的地方,公式是不是有不妥了。

NumberFormatException:数字格式异常。
当试图将一个String转换为指定的数字类型,而该字符串确不满足数字类型要求的格式时,抛出该异常。

最后修改:2023 年 01 月 09 日
如果觉得我的文章对你有用,请随意赞赏