本篇重点

1.异常概念与体系结构

2.异常的处理方式

3.异常的处理流程

4.自定义异常类

1.异常概念与体系结构

1.1异常的概念

在生活中,我们身体经常会因为各种原因而出现毛病,当身体出现问题时,我们需要去医院看病,保护我们的身体,而程序也和人一样,一个好的程序员不仅需要拥有编程能力,更要有寻找漏洞,解决在编程时出现的各种问题,只有在不断的调试,解决问题,我们才能不断进步!

在java中,将程序执行时发生的不正常行为称为异常,比如我们在写代码时经常遇见的:算术异常,数组越界,数据格式不对······

(1)算术异常

因为0无法做除数,所以会报错

(2)数组越界

当我们访问数组时,超出数组的长度,就会造成数组越界

(3)空指针异常

在java中,空指针(null)是无法被访问的,当强行访问null时,会报错

同时,根据上面可以知道,不同的异常类型会有不同的类来进行描述

1.2异常的体系结构

在写代码的时候我们会犯不同的错误,为了更好的整理和归类,java内部维护了一个异常的体系结构

下图是异常的体系结构图

上图我们可知:

1.Throwable是异常体系的顶层类,它有两个非常重要的字类:ErrorException

2.Error指的是Java虚拟机无法解决的严重问题,比如:JVM的内部错误,资源耗尽等,例如:StackOverflowError和OutOfMemoryError,一旦发生回力乏术。

3.Exception:异常完成后程序员可以通过代码进行处理,使程序继续执行,比如:感冒,发烧,我们平时所说的异常就是指它

1.3异常的分类

异常可能在编译时发生,也可能在运行时发生,根据发生的时机不同,我们把异常分为:编译时异常/受查异常和运行时异常/非受查异常。

注意:编译时出现的语法性错误不能称为异常,例如:句子末尾忘记添加分号。这是在“编译期”出现的错误。

2.异常的处理

2.1防御式编程

在操作之前就做充分的检查,叫事前防御型

int []arr={1,4,5};
int n=0;
if(n<0){
//处理数组越界异常
return;
}

这样做的缺点很明显,将正常的代码和处理异常的代码杂糅在一起,代码的整体较混乱,同时也影响读者的阅读。

事后认错型:先操作,出现问题再解决

try{
int []arr={1,4,5};
int n=0;
}catch(n<0){
//处理数组越界异常
}

优势:正常流程和处理异常流程分开,程序员更关注正常流程,代码更清晰,是代码中主要用的核心思想。

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

2.2异常的抛出

可借助关键字throw抛出一个指定的异常对象,将信息告知给调用者

throw new xxxException("异常的原因");

重点:

1.throw必须写在方法体内部;

2.抛出的对象必须是RunTimeException或者Exception的子类对象

3.如果抛出的是RunTimeException或者RunTimeException的子类,则可以不用处理,直接交给JVM来处理

4.如果抛出的是编译时异常,用户必须处理,否则无法通过编译

5.异常一旦抛出,后面的代码就不会再执行

2.3异常的捕获

异常的捕获通常有两种方式:异常声明throws 和 try-catch捕获异常

异常声明throws

处在方法声明是参数列表的后面,当方法中抛出异常时,用户不想处理异常就可以通过借助throws将异常抛给方法的调用者来处理提醒方法的调用者处理异常

语法格式

修饰符 返回值类型 方法名 (参数列表) throws异常类型1,异常类型2 ...{ }

注意:

1 throws必须跟在方法参数列表的后面;

2 声明的异常必须是Exception及其子类;

3 如果方法抛出多个异常,throws必须跟多个异常,之间用逗号隔开,如果抛出的异常具有父子关系,那只需要声明父类就好了;

4 抛出异常时,调用者必须对该异常进行处理或者选择继续使用throws抛出。

​
public class ThrowsExample {
    // 1. 定义一个方法,声明可能抛出算术异常(ArithmeticException)
    // throws 关键字后跟上异常类型,表明该方法不处理这个异常,交给调用者处理

    public static int divide(int a, int b) throws ArithmeticException {
        // 除数为0时,会自动抛出ArithmeticException
        return a / b;
    }

    public static void main(String[] args) throws ArithmeticException{
        // 2. 调用声明了throws的方法,必须处理异常(try-catch),否则编译报错
        
            int result1 = divide(10, 2);
            System.out.println("10/2 = " + result1);
           
            int result2 = divide(10, 0);
            System.out.println("10/0 = " + result2);
            
        }

​

 try-catch 捕获并处理

上例中的throws并没有对异常进行处理,而是将异常报告给抛出异常方法的调用者,由调用者处理,如果要真正对异常进行处理,就需要try-catch。

语法:

try{

//将可能异常的代码放在这里

}catch(可能出现的异常类型 e){

//对异常进行处理

}[

//可能处理多个异常

catch(可能出现的异常类型 e){

//对异常进行处理

}

finally{

//此代码一定会被执行

}

]

//后续代码,如果上面的异常被正确处理,则这里的代码就会被执行,如果上面出现的异常没有被捕获到或者是处理不正确,这里的代码就不会被执行

注意:

1.try中的代码可能会报错也可能不会报错!

2.try内抛出异常之后的代码不在执行

3.如果try抛出的异常没有被catch捕捉到,就不会被处理 会继续外抛,直到jvm收到后中断程序

4.try中可能会抛出多个不同的异常对象,必须用catch来捕获一种或多种异常,多次捕获

5.由于Exception类是所有异常类的父类,可以用这个类型捕获所有异常,但是不建议使用

关于异常的处理方式:

异常的种类较多,要根据不同的业务处理

对于比较严重的问题(例如算钱),应该让程序崩溃,防止造成更严重的后果

对于不太严重的问题(日常业务),可以记录错误日志,并通过监视报警程序及时提示

对于可能恢复的问题(和网络有关),可以尝试就行重试

如果多个异常的处理方式相同

如果异常具有父子关系,一定是子类异常在前,父类异常在后,如果相反的话会出现语法错误;

finally

在写一些代码的时候,有一些代码无论是否出现异常都要执行,例如程序中打开的资源,最后必须进行收回,另外因为出现异常而可能无法执行一些代码,finally可以很好的解决这个问题

注意:finally的代码一定会被执行,与有无异常或异常是否正确被处理无关。

finally执行的时机是在方法返回之前,如果finally和try中同时有return语句,则会返回finally中的return语句,try中的语句不会被执行。因此我们一般不会在finally中写return

2.4异常的处理流程

关于“调用栈”

方法之间存在互相调用关系,这种关系我们可以用“调用栈”来描述,在JVM中有一块内存空间称为“虚拟机栈”专门存储方法之间的调用关系,当代码中出现异常的时候,我们可以使用e.printStackTrace();的方法查看异常代码的调用栈。

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

如果向上一直传递都没有合适的方法处理异常,最终会交给JVM处理,程序就会异常终止(与我们最开始没有使用try-catch时是一样的

2.5异常处理流程总结

1)程序先执行try中的代码;

2)如果try中的代码出现异常,就会结束try的代码,查看try中出现的异常与catch的异常是否匹配;

3)如果匹配,就会先执行catch的代码;

4)如果没有找到匹配的异常,就会向上传递交给上层调用者;

5)finally中的代码无论如何一定会被执行;

6)如果上层调用者也处理得了异常,就会一直向上传递;

7)一直到main方法也没有合适的代码处理异常,就会交给JVM处理,程序就会异常终止

3.自定义类异常(重要)

java中虽然设置了丰富的异常代码,但是实际开发中我们会遇到各种各样的异常,有可能遇到的代码异常没有被定义到,此时就需要我们维护符合实际情况的异常结构

具体方法:

1.自定义一个异常类,继承Exception 或者RunTimeException 

2.实现一个带String类型参数的构造方法,参数含义:出现异常的原因

例子:实现一个登录异常的代码

class UserNameException extends Exception{
    public UserNameException(String message){
        super(message);        
}

    }
class PasswordException extends Exception{
        public PasswordException(String message){
            super(message);
}

    }

自定义异常通常会继承自Exception(受查异常)或RuntimeException(非受查异常)

例子:写一个登录异常

不使用自定义异常类


class Logic{
    public String userName;
    public String userPassword;
    public void checkUserName(String userName,String userPassword) {
//判断登录的名字是否正确
        if (!this.userName.equals(userName)) {
            System.out.println("登录账户错误..");
        }
//判断登录密码是否正确
            if(!this.userPassword.equals(userPassword)){
                System.out.println("登录密码错误..");
            }
    }
    public String getUserName() {
        return userName;
    }

    public void setUserName(String userName) {
        this.userName = userName;
    }

    public String getUserPassword() {
        return userPassword;
    }

    public void setUserPassword(String userPassword) {
        this.userPassword = userPassword;
    }
}
public class Test {
    public static void main(String[] args) {
   Logic logic=new Logic();
   logic.setUserName("张三");
   logic.setUserPassword("123");
   logic.checkUserName("李四","456");

    }

}

名称和密码均错误,因此登录失败

使用自定义异常类

定义一个账户名称异常类


public class UserNameException extends RuntimeException{
    public UserNameException() {
    }
    public UserNameException(String message) {
        super(message);//显示异常原因
    }
}

定义一个账户密码异常类


public class UserPassWordException extends RuntimeException{
    public UserPassWordException() {
    }
    public UserPassWordException(String message) {
        super(message);//打印异常原因
    }
}
class Logic {
    public String userName;
    public String userPassword;
    public void checkUserName(String userName,String userPassword) {
        if(!this.userName.equals(userName)){
            throw new UserNameException("账户名称错误");
        }
        if(!this.userName.equals(userPassword)){
            throw new UserPassWordException("账户密码错误");
        }
        System.out.println("登录成功...");
    }
    public String getUserName() {
        return userName;
    }

    public void setUserName(String userName) {
        this.userName = userName;
    }

    public String getUserPassword() {
        return userPassword;
    }

    public void setUserPassword(String userPassword) {
        this.userPassword = userPassword;
    }
}
public class Test {
    public static void main(String[] args) {
        try{
            Logic logic=new Logic();
            logic.setUserPassword("123");
            logic.setUserName("张三");
            logic.checkUserName("张三","456");
        }catch(UserNameException e){
            e.printStackTrace();
        }catch (UserPassWordException e){
            e.printStackTrace();
        }
    }

}

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐