浅谈Java中的异常

在理想的世界中,用户输入数据的格式永远是正确的,选择打开的文件也一定存在,代码永远不会出bug。然而,在现实世界中却充满了不良的数据和有问题的代码,现在我们在这篇博客中讨论一下这个问题。

在Java中,将程序执行过程中发生的不正常行为称为异常的,比如我们之前遇到过的算术异常(0做除数),数组越界异常,空指针异常。而在Java中不同类型的异常都有与其对应的类来进行描述。

1.异常的体系结构

在java中,异常对象都继承与Throwable类的一个类实例,而且如果java中内置的异常类不能满足需求,用户还可以创建自己的异常类。上图就是java异常层次的示意图

在上图中,我们看到所有的异常都是由Throwable继承而来,但在下一层立即分解成两个分支:Error和Exception

1.1Error

Error类层次结构描述了java运行时系统的内部错误和资源耗尽错误,你的应用程序不应该抛出这种类型的对象,如果出现了这样的内部错误,除了通知用户,并尽力终止程序之外几乎无能为力。

1.2Exception

在Java中要重点关注Exception层次结构。这个层次结构。该层次结构又分解为两个分支:一个分支派生于RuntimeException;另一个分支包含其他异常。一般规则是:由编程错误导致的异常属于RuntimeException。如果程序本身没有问题,但由于像IO错误这类问题导致的异常属于其他异常。

1.2.1派生于RuntimeException类的异常

1.错误的强制类型转换;

2.数组访问越界;

3.访问null指针;

1.2.2非派生于RuntimeException类的异常

1.试图超越文件末尾继续读取数据;

2.试图打开一个不存在的文件;

3试图根据给定的字符串查找class对象,而这个字符串表示的类并不存在;

2.异常的分类

java将派生于Error类或RuntimeException类的所有异常称为非检查型异常,所有的其他异常称为检查型异常。

3.异常的抛出与捕获

3.1异常的抛出(throw关键字的使用)

在编写程序时,如果程序中出现错误,此时就需要将错误的信息告知给调用者,比如:参数检测。

在java中,可以借助throw关键字抛出一个指定的异常对象,将错误信息告知给调用者,具体语法:

throw new xxxException;

接下来我们给出一个数组越界异常的抛出:

public class Test {
    public static int getExceotion (int [] array,int index){
        if (index < 0 || index >= array.length){
            throw new ArrayIndexOutOfBoundsException("数组下标越界");
            //抛出数组下标越界异常
        }
        return index;
    }
    public static void main(String[] args) {
        int [] array = {1,2,3};
        getExceotion(array,3);
    }
}

运行结果为:

我们在使用throw关键字时需要注意:

1.throw必须在方法内部

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

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

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

5.异常一旦抛出后面的代码将不会执行。 

3.2异常的捕获

异常的捕获,也就是异常的具体处理方式,主要由两种:异常声明throws以及try—catch捕获处理。

3.2.1异常声明throws

处在方法声明时参数列表之后,当方法中抛出编译时异常,用户不想处理该异常,此时就可以将异常抛给方法的调用者来处理。就是说当前的方法不处理异常,提醒方法的调用者处理异常。

语法格式:

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

}

比较上述两段代码中,我们为了使用clone方法在方法的调用者后声明了CloneNotSupportException这个异常而我们并不想处理这个异常,这就体现了throws的作用

3.2.2try—catch捕获并处理(try,catch,finally关键字)

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

语法格式:

public class Test {
    try{
        1
        //将可能出现异常的代码放在这里
        2
    }catch(要捕获的异常类型 e){
        3
        //如果try中的代码抛出异常了,此处catch捕获时异常类型与try中抛出的异常类型一致时,或者是try中抛出异常的基类
        // 时,就会被捕获
        //对异常就可以正常处理,处理完成后,跳出try—catch结构,继续执行后续代码
    }finally{
        4
        //此处的代码一定会被执行到
    }
    5
}

这个程序存在以下三种执行顺序:

1.代码没有抛出异常。在这种情况下,程序首先执行try语句块中的全部代码,然后执行finally子句中的代码。随后,继续执行finally子句之后的第一条语句。也就是说,执行的顺序是1—2—4—5;

2.代码抛出一个异常且在一个catch子句中捕获。在这种情况下,程序将执行try语句块中的所有代码,直到抛出异常为止。此时,将跳过try语句块中的剩余代码,转去执行与该异常匹配的catch子句中的代码,最后执行finally子句中的代码。

  如果catch子句没有抛出异常,程序将执行finally子句之后的第一条语句。这种情况下代码的执行顺序是:1-3-4-5;

反之,如果catch子句抛出了异常,异常将被抛回到这个方法的调用者。执行顺序则应该是1-3-4;注意这里的3只是执行到抛出异常为止。

3.代码抛出了一个异常,但没有任何catch子句捕获这个异常。在这种情况下,程序将执行try语句块中的所有语句,直到抛出异常为止。此时,将跳过try语句块中的剩余代码,然后执行finally子句中的语句,并将异常抛回给这个方法的调用者。此时代码的执行顺序只是1-4

总结一下就是无论在try语句块中是否遇到异常,finally子句中的语句都会执行,当然,如果真的遇到一个异常,这个异常将会被重新抛出,并且必须由另一个catch子句捕获。

特别要注意的是:如果异常之间具有父子关系,一定是子类异常在前catch父类异常在后catch否则语法错误。

4.创建异常类

我们的代码可能会遇到任何标准异常类都无法描述清楚的问题。在这种情况下,创建自己的异常类就显得非常重要。我们需要的只是定义一个派生于Exception的类,或者派生于Exception的某个子类。自定义的这个类应该包含两个构造器,一个是默认的构造器,另一个是包含详细描述信息的构造器。

下面我们给出一个简单的用户登录代码:

import java.util.Scanner;

public class Test
{
    private String userName = "123";
    private String password = "456";
    class UserNameError extends Exception{
        public UserNameError(String message){
            super(message);
        }
    }
    class PasswordError extends Exception{
        public PasswordError(String message){
            super(message);
        }
    }
    public void login (String userName,String password) throws UserNameError, PasswordError {
        Test test = new Test();
        if(!test.userName.equals(userName)){
            throw new UserNameError("用户名错误");
        }
        if (!test.password.equals(password)){
            throw new PasswordError("密码错误");
        }
        System.out.println("登陆成功");
    }
    public static void main(String[] args) {
        try {
            Test test = new Test();
            System.out.println("请输入用户名");
            Scanner scanner = new Scanner(System.in);
            String username = scanner.next();
            System.out.println("请输入密码");
            String password = scanner.next();
            test.login(username,password);
        }catch (UserNameError userNameError){
            userNameError.printStackTrace();
        }catch (PasswordError passwordError){
            passwordError.printStackTrace();
        }
    }
}

  • 4
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值