资讯专栏INFORMATION COLUMN

java:异常处理

LuDongWei / 2702人阅读

摘要:异常处理机制异常与异常类的继承体系在程序中,当程序出现意外情况时,系统会自动生成一个来通知程序,从异常发生出逐渐向外传播,如果没有人来处理该异常,就会交给来处理,对异常的处理方法是,打印异常跟踪栈信息,并中止程序的执行。

1 为什么要处理异常?

异常机制可以使程序的异常处理代码与正常业务代码分离,保证程序代码的健壮性。
在设计程序的时候,好的程序需要尽可能处理已知的可能产生的错误,但是事实上并不可能考虑到所有可能产生异常的情况,同时,众多的类似if-else的错误处理代码可能会带来很多的代码冗杂,错误处理与业务代码混杂的情况,带来阅读和维护的难度。因此我们需要合适的异常处理机制。
java的异常机制依靠于try,catch,finally,throw,throws关键字,其中try块中通常放置可能引发异常的代码,catch后对应异常类型和响应的异常处理代码块,finally块在java异常机制中总是会被执行,通常用于回收try块中打开的物理资源,throw用于抛出一个实际的异常(异常的实例),throws主要在方法签名中使用,用于声明该方法可能会抛出的异常,方便或者提醒方法的使用者来捕获并处理异常。
2 异常处理机制

2.1 异常与异常类的继承体系

在java程序中,当程序出现意外情况时,系统会自动生成一个Exception来通知程序,从异常发生出逐渐向外传播,如果没有人来处理该异常,就会交给jvm来处理,jvm对异常的处理方法是,打印异常跟踪栈信息,并中止程序的执行。
java提供了丰富的异常类体系,所有的异常类都继承自Throwable父类

java异常处理体系.jpg
注意:
Error错误:一般与虚拟机相关,如系统崩溃,动态链接错误等,这种错误无法恢复也无法捕获,所以应用程序一般不能处理这些错误,也不应该试图去捕获这类对象
我们主要处理的Exception类,该类在java中分为两类,一种是Checked异常,一种是Runtime异常。Checked异常是java特有的,在java设计哲学中,Checked异常被认为是可以被处理或者修复的异常,所以java程序必须显式处理Checked异常,当我们使用或者出现Checked异常类的时候,程序中要么显式try- catch捕获该异常并修复,要么显式声明抛出该异常,否则程序无法通过编译。(Checked异常某种程度上降低了开发生产率和代码执行率,在java领域是一个备受争论的问题)
常见的运行时异常:如上图所示,这里列举几个比较常见的。

public class test{
    public static void main(String[] args){
        try{
            int a = Integer.parseInt(args[0]);
            int b = Integer.parseInt(args[1));
            int c = a/b;
            System.out.println("您输出的结果是"+c);
        }
        catch(IndexOutOfBoundsException ie){
            System.out.println("数组越界,输入的参数不够");
        }
        catch(NumberFormatException ne){
            System.out.println("数字格式异常:程序只能接收整数参数");
        }
        catch(ArithmeticException ae){
            System.out.println("算术法异常");
        }
        catch(Exception e){
            System.out.println("出现异常");
        }
        
        Date d = null;
        try{
            System.out.println(d.after(new Date()));
        }
        // 试图调用一个null对象的方法或者实例变量时 出现的异常
        catch(NullPointerException ne){
            System.out.println("指向异常");
        }
    }
}

程序中一般将Exception放在最后,先捕获小异常(子类异常),再捕获大异常。如果顺序颠倒,还会出现编译错误。
2.2 try ... catch异常捕获

try ... catch 是最常见的异常捕获语句,try后与{ }配对使用,其中是业务实现代码,如果try块中出现问题或者异常,系统自动生成一个异常对象,该异常对象提交到Java运行环境,java运行环境收到异常对象后,会寻找能够处理该异常的catch块,如果找到,就将异常对象交给该catch块处理,如果没有找到,就终止程序运行。

从异常捕获过程中,我们可以看到程序要慎重考虑可能出现异常的代码块,否则这段程序是不够健壮的,一个经常出现崩溃或被终止的程序,是灾难性的。

catch块中如何处理异常:

一个try块之后可能存在多个catch块,java运行时与catch块()内的异常类进行比较,判断该异常是否 instanceof 该类,如果属于该类,就将该异常对象传给catch块内的异常形参,catch块后可以对该异常进行处理,获取相关异常的详细信息等。注意系统生成的异常实例对象是相对具体的子类异常对象,而进入一个catch块后就不会再进入下一个catch块,所以这也是我们尽量将小异常放在大异常前面的原因。
常见操作:输出出现异常提示信息
访问异常信息,打印异常信息

getMessage();// 返回详细描述字符串 
printStackTrace();// 打印异常跟踪栈信息到标准错误窗口
printStackTrace(PrintStream s);// 跟踪栈信息输出到指定输出流
getStackTrace();// 返回跟踪栈信息

采用别的替选数据或方案,或者提示用户重新操作,或者重新抛出异常,进行异常转译,重新包装,交给上层调用者来对该异常进行处理。
多异常捕获:(java 7 提供的新特性)

public class test{
    public static void main(String[] args){
        try{
            int a = Integer.parseInt(args[0]);
            int b = Integer.parseInt(args[1));
            int c = a/b;
            System.out.println("您输出的结果是"+c);
        }
        catch(IndexOutOfBoundsException ie | NumberFormatException ne
             |ArithmeticException ae){
            System.out.println("程序发生上述异常的某一种");
            // 此时ie ,ne, ae都是默认 final修饰的变量 不能再重新赋值
        }
        catch(Exception e){
            System.out.println("出现异常");
            // 此时可以赋值
            e = new RuntimeException("new error");
        }
        
    }
}

2.3 finally 一定会执行的finally模块

通常try块里打开了一些物理资源,(比如磁盘文件,数据库连接等),这些需要保证回收,所以我们通常在finally块中进行回收。
举个例子

public class test{
    public static void main(String[] args){
        FileInputStream fis = null;
        try{
            fis = new FileInputStream("a.txt");
        }
        catch(IOException e){
            System.out.println(e.getMessage);
            
            //如果执行return,程序会先跳到finally块执行,执行完之后再回来执行return语句
            return;
            // 如果这里是System.exit(0),因为是退出虚拟机
            // 所以finally块没办法执行
        }
        finally{
            if(fis!= null){
                try{
                    fis.close();
                }
                catch(IOException e){
                    e.printStackTrace();
                }
            }
        }
    }
}

注意:尽量不要在finally块中使用return或者throw语句,因为一旦在finally中执行,程序就不会再跳回原来try或者catch块中执行原本应该执行的return和throw语句了,程序自己就结束了,这可能会带来一些麻烦的错误。
为了方便物理资源的关闭,java7 提供了一种新的语法,增强了try的功能,可以在try后面跟一对圆括号,来声明初始化物理资源,try执行完毕后会自动关闭资源。

public class AutoClose{
    public static void main(String[] args)
    throws IOException
    {
        // ()内的资源类必须实现AutoCloseable 或者Closeable接口中的close()方法
        try(BufferedReader br  = new BufferedReader(
        new FileReader("auto.java")))
        {
            System.out.println(br.readLine());
        }
        
    }
}

2.4 throws 关键字:声明抛出异常

throws声明抛出异常,在方法签名中使用,上面的AtuoClose就是其使用的例子。它可以声明抛出多个类,多个类之间用“,”隔开。

首先要理解我们为什么要声明抛出异常:当某个方法中程序的执行可能会出现异常,但是该方法并不知道如何处理异常,或者我们想把这个异常交给上层方法调用者来处理或者修复,那我们给该方法加上关键字throws 异常,以声明该方法可能会出现的异常。自然,加了throws关键字之后,该方法我们就无需再用try—catch来捕获异常了,因为这已经不是我们这个方法需要操心的事情了。

注意使用throws声明异常的时候,涉及到子类对父类的方法重写时,子类声明的异常类型应该是父类方法声明的异常类型的子类或者相同类。

如果throws 声明的是checked异常,根据checked异常的规定,我们不能对该异常视而不见,因为我们必须处理该异常,所以当拿到一个声明了可能会发生checked异常的方法时,在调用该方法时,要么放在try块中来显式捕捉该异常,要么放在另外一个带throws声明异常的方法中。
所以使用checked异常时,要特别注意处理它的问题,还会带来方法重写的限制性,因此大部分时候推荐使用Runtime异常。

public class ThrowTest{
    public static void main(String[] args)
    throws Exception
    {
        test();//test 声明会产生checked 异常 因此main函数也需要声明异常
        // 或者在try - catch 中捕获该异常
    }
    
    public void test() throws IOException{
        FileInputStream fis = new FileInputStream("a.text");
    }

}

2.5 throw关键字 :抛出异常

java允许程序自行抛出异常,通常系统帮助我们检查是否发生一些普遍定义的异常,但是有些异常可能不是普遍定义的,只是与我们业务不符,所以我们可以自行抛出异常,也可以自行抛出一些我们自定义的异常,而抛出异常的行为与系统抛出异常的行为一定程度上是等价的,后续处理方式也是一样的,在这里我们使用throw关键字。

throw语句可以多带带使用,注意它抛出的是一个异常实例。

try{
    // do something...
    throw new Exception("hhh 我是新异常");  // 异常实例
}
catch{
    System.out.println("出现异常");
    continue;
}

当我们自行抛出的异常是checked异常的时候,该throw语句要么是在如上面例子中的try块中,显示捕获,要么是在一个已经用throws声明会出现异常的方法中,而如果我们抛出的是runtime异常,那么情况就很简单了,它无需在try块中,也不需要将对应的方法用throws声明,如果我们想要处理,就捕获处理它,不管是在它自身方法体内,或者是对应方法者,也可以不去理会,当然我们自己抛出的异常,通常情况下是要处理的,不然抛出去之后不管最后只能中断程序运行了,只不过抛出是runtime异常时,在编译时没有那么严格。

自定义异常类:前面说了,系统会抛出一些普遍意义的异常,那么我们也就没必要再自己操心throw了,通常throw的是自定义的异常类。

自定义异常类都应该继承Exception类,或者Exception下的子类如runtime异常
定义异常类的时候需要提供两个构造器,一个无参构造器,一个带一个字符串参数的构造器,这串字符串实际上是getMessage() 时返回的异常对象的详细描述信息。

public class myException extends Exception{
    public myException(){};
    public myException(String msg){
        super(msg);
    }
}

catch中throw(抛出)异常:有时候我们在本方法中捕捉了异常,我们只能处理异常的一部分,我们还需要别的方法来处理或者我们想把产生了异常的这个信息告诉调用者,这个时候我们通常捕捉了异常后会在catch块中抛出我们想抛出的异常
在企业级应用中,通常对异常处理分为两部分:应用后台打印或者通过日志记录异常发生时详细情况(异常跟踪栈)和向使用者传达某种提示。
java 7 中增强了throw语句:java 7 编译器执行更细致检查,检查抛出的异常的具体类型。

public class Test{
    private double initprice = 30.0;
    // 自定义的异常 
    // 方法中抛出了异常 所以此处要声明异常
    public void bid(String bidprice) throws AuctionException{
        double d =0.0;
        try{
            d = Double.parseDouble(bidprice);
        }
        catch(Exception e){
            e.printStackTrace();
            throw new AuctionException("新异常");
            
            //这里也可以抛出e 但是我们通常会抛出我们包装过后的异常
            // 用来向方法调用者或者上层提示某种信息 而不会直接暴露异常的原因
            // throw e; 
            // 如果这里throw e 在java7 以前不会做细致检查,throws声明那里必须声明为Exception
            // 但是java7 之后可以声明为更细致的异常子类型
            
        }
    }
    public static void main(String[] args){
        Test test = new Test();
        try{
            test.bid("df");
        }
        catch(AuctionException ae){
            System.err.println(ae.getMessage);
        }
    }
}

异常链:异常转译与异常链
3. 异常处理的一些基本规则

3.1 异常跟踪栈

异常是从里向外传播,可以很方便跟踪异常的发生情况,可以用来调试程序,但在发布的程序中,应该避免使用,而是将异常进行适当的处理。
3.2 不要过度使用

异常的运行效率会差一些,因此如果不是那种不可预期的错误,应该避免使用异常,而是放在正常的业务判断或者处理逻辑中。
3.3 不要使用过于庞大的try块

为了更好的判断发生的异常的类型,应该将大块try块分割为多个可能出现异常的try段落。
3.4 避免catch-all

类似上面的理由,更好的区分度,更好的异常判断
3.5 不要忽略异常

避免catch块为空,或者仅仅打印出异常情况,我们还是要处理异常,比如绕过异常发生的地方,或者采用别的替选数据或方案,或者提示用户重新操作,或者重新抛出异常,进行异常转译,重新包装,交给上层调用者来对该异常进行处理。

欢迎加入学习交流群569772982,大家一起学习交流。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/67755.html

相关文章

  • 16.java异常处理

    摘要:不受检查异常为编译器不要求强制处理的异常,检查异常则是编译器要求必须处置的异常。潜在的异常处理器是异常发生时依次存留在调用栈中的方法的集合。当运行时系统遍历调用栈而未找到合适的异常处理器,则运行时系统终止。异常处理涉及到五个关键字,分别是。 概念 程序运行时,发生的不被期望的事件,它阻止了程序按照程序员的预期正常执行,这就是异常。 异常是程序中的一些错误,但并不是所有的错误都是异常,并...

    asce1885 评论0 收藏0
  • Java异常处理

    摘要:异常也就是指程序运行时发生错误,而异常处理就是对这些错误进行处理和控制。有两个重要的子类异常和错误,二者都是异常处理的重要子类,各自都包含大量子类。需要注意的是,一旦某个捕获到匹配的异常类型,将进入异常处理代码。 1,异常现象 程序错误分为三种:1,编译错误;2,运行时错误;3,逻辑错误。 编译错误是因为程序没有遵循语法规则,编译程序能够自己发现并且提示我们错误的原因和位置,这...

    CarlBenjamin 评论0 收藏0
  • Java异常处理

    摘要:可以被异常处理机制使用,是异常处理的核心。非检测异常,在编译时,不会提示和发现异常的存在,不强制要求程序员处理这样的异常。总体来说,语言的异常处理流程,从程序中获取异常信息。处理运行时异常,采用逻辑合理规避同时辅助处理。 目录 什么是Java异常? 当一个Exception在程序中发生的时候,JVM是怎么做的呢? 当我们编写程序的时候如何对待可能出现的异常呢? 正文 1. 什么是J...

    Fourierr 评论0 收藏0
  • Java 异常处理

    摘要:下面是异常处理机制的语法结构业务实现代码输入不合法如果执行块里业务逻辑代码时出现异常,系统自动生成一个异常对象,该对象被提交给运行时环境,这个过程被称为抛出异常。 Java的异常机制主要依赖于try、catch、finally、throw和throws五个关键字, try关键字后紧跟一个花括号括起来的代码块(花括号不可省略),简称try块,它里面放置可能引发异常的代码 catch后对...

    senntyou 评论0 收藏0
  • java异常处理机制的理解

    摘要:根据异常对象判断是否存在异常处理。否则,范围小的异常会因异常处理完成而无法处理。异常处理中使用作为异常的统一出口。 参考《第一行代码java》《java程序设计教程》java中程序的错误有语法错误、语义错误。如果是语法性错误,在编译时就可以检查出来并解决。语义错误是在程序运行时出现的,在编译时没有错误,但在运行时可能会出现错误导致程序退出,这些错误称为异常。在没有异常处理的情况下,也即...

    khs1994 评论0 收藏0
  • Java异常处理 10 个最佳实践

    摘要:为可恢复的错误使用检查型异常,为编程错误使用非检查型错误。检查型异常保证你对错误条件提供异常处理代码,这是一种从语言到强制你编写健壮的代码的一种方式,但同时会引入大量杂乱的代码并导致其不可读。在编程中选择检查型异常还是运行时异常。 异常处理是Java 开发中的一个重要部分。它是关乎每个应用的一个非功能性需求,是为了处理任何错误状况,比如资源不可访问,非法输入,空输入等等。Java提供了...

    Forelax 评论0 收藏0

发表评论

0条评论

LuDongWei

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<