Java中异常的处理

什么是异常?

Java异常体系

异常的用法

异常的处理流程

抛出异常

自定义异常


什么是异常?

我们在之前的学习中已经接触过异常,例如:

数组下标访问越界时:

public class Test2 {
    public static void main(String[] args) {
        int[] array = {1,2,3,4,5};
        System.out.println(array[5]);
    }
}
//结果:
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 5

将0作为除数时:

public class Test2 {
    public static void main(String[] args) {
        int i = 0;
        int j = 1;
        System.out.println(j/i);
    }
}
//结果:
Exception in thread "main" java.lang.ArithmeticException: / by zero

    上述结果都发生了异常,那么,到底什么才算异常呢?

    异常指的是在程序运行时出现错误并通知调用者的一种机制。

那么,有哪些错误属于异常呢?我们可以通过java异常体系来了解。

Java异常体系

请添加图片描述
    上图是Java异常的体系结构,Throwable类是Java中所有异常类的父类,它派生出了Exception类和Error类

    Error类指的是Java运行时的内部错误和资源耗尽错误,应用程序不抛出此类异常,这种内部错误一旦出现,就只能告知用户并终止程序,但这种内部错误很少出现。

    Exception类是程序员使用的异常类的父类,Exception类派生出了一个RuntimeException类,这里面抛出了我们常见的异常类,如NullPointerException,IndexOutOfBoundsException等。

    Java中所有继承于Error或RuntimeException类的异常称为非受查异常,非首查异常又叫运行时异常,其他异常称为受查异常,即图片中浅蓝色部分为非受查异常,其余异常均为受查异常。

    受查异常可以理解为编译时发生的异常,当出现受查异常时,编译器会划红线,此时无法编译通过。

    而非受查异常是在运行时才发生的异常,这种异常在程序编译时无法被检测出来,例如数组下标越界异常等。

异常的用法

    我们对异常有两种处理方式,一种是让JVM自动处理:

public class Test2 {
    public static void main(String[] args) {
        int[] array = {1,2,3,4,5};
        System.out.println(array[5]);
    }
}
//Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 5

当JVM自动处理异常时,程序会直接结束,并不会执行后面的步骤,但有时我们希望程序出现异常后能正常执行,这时就需要我们使用try—catch语句来处理异常:

public class Test1 {
    public static void main(String[] args) {
        int[] array = {1,2,3,4,5};
        int i = 100;
        System.out.println(func(array,i));
    }
    public static int func(int[] array,int i){
        try {	//try{ }中存放可能发生异常的语句
            System.out.println(array[i]);
            return array[i];
        }
        //catch用来捕获异常
        catch (ArrayIndexOutOfBoundsException e){	//( )内存放可能发生异常的类型
        	//固定用法,用于打印出现异常的数据栈
            e.printStackTrace();
            System.out.println("程序发生异常了");
            return -1;
        }
}
//结果:
java.lang.ArrayIndexOutOfBoundsException: 100
	at Test.Test1.func(Test1.java:20)
	at Test.Test1.main(Test1.java:16)
程序发生异常了
-1

    我们可以看出,当我们使用try—catch语句处理异常时,当程序发生异常后并没有直接结束,而是继续执行了后面的语句。

    当我们使用try—catch语句时,try{ }中存放可能发生异常的语句,catch后面的( )内存放可能产生异常的类型,例如上述代码中,try语句中可能会产生数组下标越界异常,catch后的( )存放数组下标异常类,catch方法内部存放发生异常时要执行的操作,e.printStackTrace()用于打印出现的异常。

但是, catch()中填写了错误的异常类型时, 就无法捕获到正确的异常, 这时程序同样会直接结束:

public class Test1 {
    public static void main(String[] args) {
        int[] array = {1,2,3,4,5};
        int i = 100;
        System.out.println(func(array,i));
    }
    public static int func(int[] array,int i){
        try {	//try{ }中存放可能发生异常的语句
            System.out.println(array[i]);
            return array[i];
        }
        //catch用来捕获异常
        catch (NullPointerException e){	//将这里更改为空指针异常
            e.printStackTrace();
            System.out.println("程序发生异常了");
            return -1;
        }
}
//结果:
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 100
	at Test.Test1.func(Test1.java:20)
	at Test.Test1.main(Test1.java:16)

    可以发现,我们使用了try-catch语句来捕获异常, 但catch并没有捕获到对应的异常,这次异常是由编译器捕获的,这是因为catch( )中的异常类型与try{ }中产生的异常类型不匹配。

我们可以同时进行多次catch():

public class Test1 {
    public static void main(String[] args) {
        int[] array = {1,2,3,4,5};
        int i = 100;
        System.out.println(func(array,i));
    }
    public static int func(int[] array,int i){
        try {
            System.out.println(array[i]);
            return array[i];
        }
        catch (NullPointerException e){
            e.printStackTrace();
            System.out.println("程序发生空指针异常");
            return -1;
        }
        catch (ArrayIndexOutOfBoundsException e){
            e.printStackTrace();
            System.out.println("程序发生数组下标越界异常");
            return -1;
        }
    }
}
//结果:
java.lang.ArrayIndexOutOfBoundsException: 100
	at Test.Test1.func(Test1.java:20)
	at Test.Test1.main(Test1.java:16)
程序发生数组下标越界异常
-1

    可以看到,这次catch( )成功捕获了异常,第一次catch( )没有捕获到异常, 这时程序会继续执行第二次catch( ),直到所有catch( )全部完成, 如果所有的catch全部执行完之后依然没有捕获到对应的异常, 这时程序也会立即终止.

     如果本方法中没有合适的处理异常的方式, 就会沿着调用栈向上传递:

public class Test1 {
    public static void main(String[] args) {
        int[] array = {1,2,3,4,5};
        int i = 100;
        try {
            System.out.println(func(array, i));
        }
        catch(ArrayIndexOutOfBoundsException e){
            e.printStackTrace();
            System.out.println("程序发生数组下标越界异常");
        }
    }
    public static int func(int[] array,int i){
            System.out.println(array[i]);
            return array[i];
    }
}
//结果:
java.lang.ArrayIndexOutOfBoundsException: 100
	at Test.Test1.func(Test1.java:25)
	at Test.Test1.main(Test1.java:17)
程序发生数组下标越界异常

     我们通常用finally来进行最后的工作, 如释放资源等:

public class Test1 {
    public static void main(String[] args) {
        int[] array = {1,2,3,4,5};
        int i = 100;
            System.out.println(func(array, i));
    }
    public static int func(int[] array,int i){
        try {
            System.out.println(array[i]);
            return array[i];
        }
        catch(ArrayIndexOutOfBoundsException e){
            e.printStackTrace();
            System.out.println("程序发生数组下标越界异常");
            return -1;
        }
        finally {
            System.out.println("finally");
        }
    }
}

在上述例子中,如果没有finally,catch结束后会直接return -1,但有了finally后,catch会先执行finally,再返回。

    值得注意的是,我们不应该将返回值放在finally中,如果finally中有返回值,那么就一定会返回finally中的return,而不会返回try和catch中的返回值。原因就是在执行try和catch的返回值之前一定会执行finally,但finally中存在返回值,那么就会直接返回finally中的return。

异常的处理流程

  1. 程序先执行 try 中的代码
  2. 如果 try 中的代码出现异常, 就会结束 try 中的代码, 看和 catch 中的异常类型是否匹配.
  3. 如果找到匹配的异常类型, 就会执行 catch 中的代码
  4. 如果没有找到匹配的异常类型, 就会将异常向上传递到上层调用者.
  5. 无论是否找到匹配的异常类型, finally 中的代码都会被执行到(在该方法结束之前执行).
  6. 如果上层调用者也没有对应的catch, 就继续向上传递.
  7. 一直到 main 方法也没有合适的代码处理异常, 就会交给 JVM 来进行处理, 此时程序就会异常终止.

抛出异常

    除了Java内置的类会抛出异常之外,我们也可以通过throw关键字手动抛出某个异常:

public class Test {
    public static void main(String[] args) {
        int i = 0;
        int j = 1;
        if(i==0){
            throw new IndexOutOfBoundsException("你的除数是0");
        }
    }
}
//结果:
Exception in thread "main" java.lang.IndexOutOfBoundsException: 你的除数是0
	at Test.Test.main(Test.java:15)

    在这个代码中, 我们可以根据实际情况来抛出需要的异常. 在构造异常对象同时可以指定一些描述性信息.

自定义异常

Java 中虽然已经内置了丰富的异常类, 但是我们实际场景中可能还有一些情况需要我们对异常类进行扩展, 创建符合我们实际情况的异常.

例如, 我们实现一个用户登陆功能:

class UserNameError extends Exception{
    public UserNameError(String message) {
        super(message);
    }
}
class PasswdError extends Exception{
    public PasswdError(String message) {
        super(message);
    }
}
public class Test2 {
    static String userName = "1999";
    static String passwd = "2000";
    public static void main(String[] args) {
        Test2 test2 = new Test2();
        try {
            test2.login("123","2000");
        }
        catch (UserNameError userNameError){
            userNameError.printStackTrace();
        }
        catch (PasswdError passwdError){
            passwdError.printStackTrace();
        }
    }
    public void login(String username,String passwd)throws UserNameError,PasswdError{
        if(!username.equals(Test2.passwd)){
            throw new UserNameError("用户名错误");
        }
        if(!passwd.equals(Test2.passwd)){
            throw new PasswdError("密码错误");
        }
        System.out.println("登录成功!");
    }
}

自定义异常通常会继承自 Exception 或者 RuntimeException
继承自 Exception 的异常默认是受查异常
继承自 RuntimeException 的异常默认是非受查异常


The end

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

zhanglf6699

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值