14—JAVA(进阶)——异常

01 异常

1.1 异常的基本概念

  • 异常:是在程序运行过程中发生的、会打断程序正常执行的事件

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

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

1.2 异常类的继承架构

异常可分为两大类:

  • java.lang.Exception
  • java.lang.Error
    这两个类均继承自 java.lang.Throwable 类
  • 异常类的继承架构图:
java.lang.Throwable
java.lang.Error
java.lang.Exception

习惯上将 ErrorException 类统称为异常类,但这两者本质上还是有不同的。

  • Error类:专门用来处理严重影响程序运行的错误,可是通常程序设计者不会设计程序代码去捕捉这种错误,其原因在于即使捕捉到它,也无法给予适当的处理,如 JAVA 虚拟机出错就属于一种 Error。

  • Exception 类:包含了一般性的异常,这些异常通常在捕捉到之后,便可做妥善的处理,以确保程序继续运行。

  • 平常我们所说的异常一般指:java.lang.Exception

Throwable中的常用方法:

  • public void printStackTrace():打印异常的详细信息。
    包含了异常的类型
    异常的原因
    异常出现的位置
  • public String getMessage():获取发生异常的原因。
    提示给用户的时候,就提示错误原因

更多的方法介绍查看API

注意:当出现异常的时候,可以把异常的类名,拷贝到API中去查看是什么异常

1.3 异常(Exception)分类

我们平常说的异常就是指Exception,因为这类异常一旦出现,我们就要对代码进行修改,并且这类异常一般都是程序员编程时犯的错误产生的异常。
异常(Exception)的分类:根据在编译时期还是运行时期去检查异常进行分类

  • 编译时期异常checked异常。在编译时期,就会去检查,如果有异常并且没有去处理,则会编译失败。
  • 运行时期异常runtime异常。在运行时期,才会检查异常,在编译时期,运行异常不会被编译器检测(不报错)。

1.4 简单的异常范例

演示数组索引越界异常:ArrayIndexOfBoundsException,此异常在程序运行的时候才会发生,属于Exception异常类中的Runtime异常

//演示数组越界异常
public class Test {
    public static void main(String[] args) {
        //定义一个数组长度为4
        int[] a={1,2,3,4};
        //打印第五个元素,发生数组越界异常
        System.out.println(a[5]);
        //java.lang.ArrayIndexOutOfBoundsException
    }
}

02 异常的处理

2.1 异常处理的两种方式:

Created with Raphaël 2.2.0 程序运行 发生异常, 是否自行处理异常? 编写try-catch-finally代码块处理异常 交由java默认的异常处理机制处理异常 yes no

2.1.1第一种处理方式

第一种处理方式是交由java默认的的异常处理机制,使用这种方式处理,Java只能输出异常信息,接着便是终止程序的运行,就像上面那个例子一样,发生数组越界异常之后,程序终止运行,抛出异常。

2.1.2第二种处理方式

  • 第二种处理方式就跟这几个关键字有关
    Java异常处理的五个关键字:try、catch、finally、throw、throws
  • 这种处理方式就是我们自己编写try…catch…finally代码块来捕捉异常
  • 异常信息被捕获后程序就可以继续向下执行

下面为此格式的三种不同的使用方式:

  • try...catch(处理异常的基本格式)
  • try...catch...finally
  • try...finally
  • try 程序块:若是有异常发生时,程序的运行便中断,并自动抛出“异常类所产生的对象”,如果不进行捕获,程序将会终止运行。
  • catch程序块:对抛出的异常对象进行捕获,如果抛出的对象属于 catch()括号内欲捕获的异常类对象,则 catch 会捕捉此异常,然后进到 catch 的块里继续运行,如果在catch代码块中继续抛出异常,则程序会终止运行,如果在catch代码块中对异常进行处理,程序就不会终止
  • finally程序块:无论 try 程序块是否有捕捉到异常,或者捕捉到的异常是否与 catch()括号里的异常相同,最后一定会运行 finally 块里的程序代码
  • 特殊情况,在执行到finally之前JVM退出(System.exit(0);)则不执行finally中的语句。
  • finally 的程序代码块运行结束后,程序再回到 try-catch-finally 块之后继续执行

2.1.2.1 使用try-catch-finally捕获异常

  • 代码演示:
    还用上面那个例子,数组越界异常,当数组发生越界时会自动抛出异常,我们将会抛出异常的语句放在try语句中,然后在catch语句中对其进行处理,使程序可以继续运行。
//演示数组越界异常
public class Test {
    public static void main(String[] args) {
        //定义一个数组长度为4
        int[] a={1,2,3,4};

        /*
        由于数组长度为4,下标索引最大不能超过3
        下面这句会发生数组索引越界异常,如果不进行捕捉,
        异常就会交由默认的处理机制进行处理,程序就会发生中断。
        为了使程序能正常执行,我们对此异常进行捕捉,让程序继续运行
        */
        try {
            int b=a[5];
        }catch (ArrayIndexOutOfBoundsException e){
            System.out.println("访问数组越界了,,,,");
        }finally{
            System.out.println("我是finally代码块");
        }
        System.out.println("main方法运行结束");
    }
}

  • 运行结果:
访问数组越界了,,,,
我是finally代码块
main方法运行结束
  • 注意:finally不能单独使用。

2.1.2.2 抛出异常

前面说过Java异常处理有五个关键字:try、catch、finally、throw、throws
已经说过三个了,我们再来说一下最后两个,throw和throws

抛出异常有两种方式:

  • 在程序中手动抛出异常:throw关键字
  • 指定方法抛出异常:throws关键字

第一种:在程序中手动抛出异常

  • 在程序中抛出异常使用throw关键字
  • 格式:
throw new 异常类名(参数);
  • 例如:
throw new NullPointerException("要访问的arr数组不存在");
throw new ArrayIndexOutOfBoundsException("该索引在数组中不存在,已超出范围");
  • 代码演示
//算数除零异常
public class Test {
    public static void main(String[] args) {
        int a=1;
        int b=0;
        try{
            if(b==0){//若b为0,则抛出异常
                throw new ArithmeticException("发生算术异常");
            }else {//若b不为0,则输出a/b的值
                System.out.println(a+"/"+b+"="+a/b);
            }
        }catch (ArithmeticException e){
            //如果try程序块中抛出的异常和catch中捕获的一样,catch就会被打印出来
            System.out.println("抛出异常为:"+e);
        }
        System.out.println("main方法运行结束");
    }
}


运行结果:
抛出异常为:java.lang.ArithmeticException: 发生算术异常
main方法运行结束
  • 上面的程序中try语句中出现的异常我们是使用throw关键字手动进行抛出,然后在catch语句中进行处理,如果不手动抛出,我们也可以在catch语句中使用使用Exception异常来捕获所有子类异常(因为Runtime为Exception的子类),程序也可以运行,如下:
public class Test {
    public static void main(String[] args) {
        int a=1;
        int b=0;
        try{
            System.out.println(a+"/"+b+"="+a/b);
        }catch (Exception e){
            System.out.println("抛出异常为:"+e);
        }
        System.out.println("main方法运行结束");
    }
}

运行结果:
抛出异常为:java.lang.ArithmeticException: / by zero
main方法运行结束
  • 如果我们在try语句中抛出了异常,在catch语句中捕获到了异常,我们不想处理,这时我们就可以在catch语句中将此异常抛出,此时程序就会停止运行,这样做和不捕获异常使用默认异常处理机制的结果是一样的,程序会停止运行,此做法没有实际意义,但要知道,例如下面的:demo
public class demo {
    public static void main(String[] args) {
        int a=1;
        int b=0;
        try{
            System.out.println(a+"/"+b+"="+a/b);
        }catch (Exception e){
           throw new RuntimeException(e);
        }
        System.out.println("main方法运行结束");
    }
}

运行结果:
Exception in thread "main" java.lang.RuntimeException: java.lang.ArithmeticException: / by zero
  • 如果有方法调用会抛出异常并且自己不处理的方法,在调用的时候对异常进行处理,程序也是可以运行的,就算前面的所有调用方法都不对异常进行处理,只要在最后调用别的方法的方法中对异常进行处理程序都是可以运行的,例如下面的demo
public class demo {
    public static void main(String[] args) {
        //在main方法中调用divMethod方法,divMethod方法是会抛出异常并且自己不处理的方法
        try {
            divMethod(2,0);//会发生除零异常
        }catch (Exception e){
            System.out.println(e);
        }
        System.out.println("main方法运行结束");
    }

	//可能发生异但不处理
    public static void divMethod(int a,int b){
        int result=a/b;
        System.out.println(result);
    }
}

运行结果(报错):
java.lang.ArithmeticException: / by zero
main方法运行结束

第二种:指定方法抛出异常

  • 指定方法抛出异常使用throws关键字
  • 如果方法内的程序代码可能会发生异常,且方法内又没有使用任何的代码块来捕捉这些异常时,则必须在声明方法时一并指明所有可能发生的异常,并使用throws关键字抛出,以便让调用此方法的程序得以做好准备来捕捉异常。
  • 也就是可能产生异常的方法自己不捕获,让调用此方法的的方法进行捕获,如果调用此方法的方法不进行捕获,也可将其声明出去,再让别调用的方法进行捕获,大家都不处理,逐层抛出,不过这样最后会导致程序终止运行。
  • 格式:
修饰符 返回值类型 方法名(参数) throws 异常类名1,异常类名2{   }	
  • 调用的方法不捕获,将其声明出去,例如:
public class demo {
    public static void main(String[] args) throws Exception {
        Test01 t=new Test01();
        t.add(1,0);
        System.out.println("main方法结束");
        }
    }
class Test01{
        //此方法可能会发生除0异常
    public void add(int a,int b)throws Exception{
        int c;
        c=a/b;
        System.out.println(a+"/"+b+"="+c);
        }
}

运行结果(报错):
Exception in thread "main" java.lang.ArithmeticException: / by zero
  • 调用的方法进行捕获,例如:
public class demo {
    public static void main(String[] args) throws Exception {
        Test01 t=new Test01();
        try {
            t.add(1,0);//这个方法可能会抛出异常所以将其写在try-catch代码块中
        } catch (Exception e) {
            System.out.println(e);
        }
        System.out.println("main方法结束");
    }
}
class Test01{
    //此方法可能会发生除0异常
    public void add(int a,int b)throws Exception{
        int c;
        c=a/b;
        System.out.println(a+"/"+b+"="+c);
    }
}

运行结果:
java.lang.ArithmeticException: / by zero
main方法结束

2.2 异常注意事项

  • 多个异常使用捕获又该如何处理呢?

    1. 多个异常分别处理。
    2. 多个异常一次捕获,多次处理。
    3. 多个异常一次捕获一次处理。

    一般我们是使用一次捕获多次处理方式,格式如下:

    try{
         编写可能会出现异常的代码
    }catch(异常类型A  e){try中出现A类型异常,就用该catch来捕获.
         处理异常的代码
         //记录日志/打印异常信息/继续抛出异常
    }catch(异常类型B  e){try中出现B类型异常,就用该catch来捕获.
         处理异常的代码
         //记录日志/打印异常信息/继续抛出异常
    }
    
  • 注意:这种异常处理方式,要求多个catch中的异常不能相同,并且若catch中的多个异常之间有子父类异常的关系,那么子类异常要求在上面的catch处理,父类异常在下面的catch处理。
  • 运行时异常被抛出可以不处理。即不捕获也不声明抛出。
  • 如果finally有return语句,永远返回finally中的结果,避免该情况.
  • 如果父类抛出了多个异常,子类重写父类方法时,抛出和父类相同的异常或者是父类异常的子类或者不抛出异常。
  • 父类方法没有抛出异常,子类重写父类该方法时也不可抛出异常。此时子类产生该异常,只能捕获处理,不能声明抛出

03 自定义异常

3.1 概述

为什么需要自定义异常类:

在上演示的例子中,发现这些异常都是JDK内部定义好的,但是实际开发中也会出现很多异常,这些异常很可能在JDK中没有定义过,例如年龄负数问题,考试成绩负数问题,这时候就需要我们自己定义异常类来处理这些错误。
异常类如何定义:

  1. 自定义一个编译时期的异常类: 自定义类要继承于java.lang.Exception
  2. 自定义一个运行时期的异常类:自定义类要继承于java.lang.RuntimeException
  3. 也可以直接继承java.lang.Exception类,因为它是java.lang.RuntimeException类的父类
  • 编写自定义异常类的格式:
class 自定义异常类名称 extends Exception{
	..... ....
}

3.2 自定义异常的练习

  • 要求:我们模拟注册操作,如果用户名已存在,则抛出异常并提示:亲,该用户名已经被注册
public class Test {
    //创建数组模拟数据库
    private static String[] names = {"AISMALL_01","AISMALL_02","AISMALL_03"};
    public static void main(String[] args) throws Exception {
        while (true){
            Scanner sc = new Scanner(System.in);
            System.out.println("请输入您要注册的名字:");
            String str=sc.nextLine();
            //调用测试方法
            try{
                // 可能出现异常的代码
                checkUsername(str);
                //如果没有异常就是注册成功
                System.out.println("注册成功");
            }catch(RegisterException e){
                //处理异常
                e.printStackTrace();
            }
        }
    }
    //定义一个测试方法,判断当前注册账号是否存在
    public static boolean checkUsername(String uname) throws RegisterException {
        for (String name : names) {
            //如果名字在这里面 就抛出登陆异常
            if(name.equals(uname)){
                throw new RegisterException("亲"+name+"已经被注册了!");
            }
        }
        return true;
    }
}

//自定义异常类
class RegisterException extends Exception{
    //异常类的空参构造
    public RegisterException(){}
    //异常类的有参构造
    public RegisterException(String message) {
        ////使用super关键字调用Exception类的有参构造方法,存入异常信息
        super(message);
    }
}
  • 代码分析:
    1,我们先定义一个异常类,此类继承与Exception类
    2,在此类中定义一个有参构造和一个空参构造,其中有参构造是使用super关键字去调用父类的有参构造。
    3,为了在主方法中的代码简洁一点,我们把用于判断的代码封装到了checkUsername()方法中。
    4,在主方法中我们定义了一个while死循环用于无限从键盘录入注册信息,并判断该信息是否与数组中的信息相同,也就是该用户是否已经注册。

猜你喜欢

转载自blog.csdn.net/weixin_45583303/article/details/105673505