您的位置: 首页 > 生活常识 >

程序员必须掌握的异常处理技巧和原则

748次浏览     发布时间:2024-03-19 19:19:16    

Java中的异常机制是指:当程序在运行过程中遇到意外情况时会自动抛出一个Exception对象来通知程序,程序收到这个异常通知后可以采取各种处理措施,这种机制能使程序更加健壮,可读性更高。本文就来讲讲异常处理的相关知识。


异常分类#

Java中的异常分为RuntimeException和CheckedException。

  • RuntimeException:程序运行过程中出现错误,才会被检查的异常。例如:类型错误转换,数组下标访问越界,空指针异常、找不到指定类等等。
  • CheckedException:来自于Exception且非运行时异常都是检查异常,编译器会强制检查并通过try-catch块来对其捕获,或者在方法头声明抛出该异常,交给调用者处理。常见的checked异常有FileNotFoundException和InterruptedException等。

Error和Exception的区别#

在谈到Exception时,经常会涉及到Error。Error和Exception存在如下的区别:

Error是指系统中的错误,程序员是不能改变和处理的,是在程序运行时出现的错误,只能通过修改程序才能修正。Java中的Error一般是指与虚拟机相关的问题,如系统崩溃,虚拟机错误,内存空间不足,方法调用栈溢出等。对于这类错误的导致的应用程序中断,仅靠程序本身无法恢复和和预防,遇到这样的错误,建议让程序终止,调整代码或者虚拟机参数再重新启动程序;

Exception(异常)是程序可以处理的。遇到这类异常,程序员应该尽可能捕获处理异常,使程序恢复运行,而不应该随意终止异常。实在不知道如何处理就向上抛出该异常留给调用者处理。

总结下:异常(Exception)是一种非程序原因的操作失败(Failure),而错误(Error)则意味着程序有缺陷(Bug)。

异常处理的原则#

1. 能处理的异常尽早处理
对于能明确知道要怎么处理的异常要第一时间处理掉。对于不知道要怎么处理的异常,要么直接向上抛出,要么转换成RuntimeException再向上抛出,让调用者处理。

2. 具体明确原则
尽量不要用Exception捕获抛出所有异常。抛出异常时需要针对具体问题来抛出异常,抛出的异常要足够具体详细;在捕获异常时需要对捕获的异常进行细分,这时会有多个catch语句块,这几个catch块中间泛化程度越低的异常需要越放在前面捕获,泛化程度高的异常捕获放在后面,这样的好处是如果出现异常可以近可能得明确异常的具体类型是什么。

抛出时:

Copy
public FileInputStream(File file) throws FileNotFoundException {
    String name = (file != null ? file.getPath() : null);
    SecurityManager security = System.getSecurityManager();
    if (security != null) {
        security.checkRead(name);
    }
    //根据不同的情况抛出不同的异常
    if (name == null) {
        throw new NullPointerException();
    }
    if (file.isInvalid()) {
        throw new FileNotFoundException("Invalid file path");
    }
    ...
}

捕获时:

Copypublic void foo1(String fileName){
    File file = new File(fileName);
    InputStream in = null;
    try {
        in = new FileInputStream(file);
        Integer num = in.read();
        in.close();
    } catch (FileNotFoundException e) {
        e.printStackTrace();
    } catch (IOException e){
        e.printStackTrace();
    } catch(Exception e){
        e.printStackTrace();
    }
}

3. 系统需要有统一异常处理机制
系统需要有自己的一套统一异常处理的机制,如果系统使用Spring等框架的话可以非常简单地引入统一异常处理框架。另外在统一异常处理时一定要打出异常堆栈,不然的话问题可能就无从查起了。

4. 一些其他注意点

  • try语句块内要分清稳定代码和非稳定代码,对于稳定的不会出现异常的代码不要放到try语句块中;
  • catch捕获的异常一定要处理,吃掉异常不处理的话将是灭顶之灾;
  • finally中不要使用return语句,因为finally语句块最后一定会执行,这里的return语句会覆盖之前的return语句。

如何自定义异常#

在复杂业务环境下,java自带的异常可能满足不了我们业务的需求, 这个时候我们可以自定义异常来进行对业务异常的处理。

Copypublic class MyException extends RuntimeException {

    private static final long serialVersionUID = 6958499248468627021L;
   
    private String errorCode;
    
    private String errorMsg;

    public MyException(String errorCode,String errorMsg){
        super(errorMsg);
        this.errorCode = errorCode;
        this.errorMsg = errorMsg;
    }

    public MyException(String errorCode,String errorMsg,Throwable throwable){
        super(errorCode,throwable);
        this.errorCode = errorCode;
        this.errorMsg = errorMsg;
    }

    public String getErrorCode() {
        return errorCode;
    }

    public void setErrorCode(String errorCode) {
        this.errorCode = errorCode;
    }

    public String getErrorMsg() {
        return errorMsg;
    }

    public void setErrorMsg(String errorMsg) {
        this.errorMsg = errorMsg;
    }
}

在使用MyException时,最好定义一个枚举类用来枚举错误代码和错误详情。

return和finally的执行顺序#

在Java中,异常处理主要由try、catch、throw、throws和finally5个关键字处理。Java程序中如果出现了异常,而且没有被try-catch块捕获的话,系统会把异常一直往上层抛,直到遇到处理代码。如果一直没有处理块,就抛到最上层,如果是多线程就由Thread.run()抛出,如果是单线程就被main()抛出。抛出之后,如果是子线程,这个子线程就退出了。如果是主程序抛出的异常,那么这整个程序也就退出了。

一个比较常见的异常处理流程如下:

Copy    try {
        //step1
        System.out.println("try...");
        throw new RuntimeException("异常1...");
    }catch (Exception e){
        //step2
        System.out.println("catch。。。");
    }finally {
        //step3
        System.out.println("finally。。。");
    }
    //step4
    System.out.println("end...");

上述的代码中由于抛出的异常被顺利catch住了,所以当前线程不会结束,程序会继续往下执行,step4这步代码会被打印出来。

Copy    try {
        System.out.println("try...");
        throw new RuntimeException("异常1...");
    }catch (Exception e){
        System.out.println("catch。。。");
        throw new RuntimeException("异常2...");
    }finally {
        System.out.println("finally。。。");
    }
    System.out.println("end...");

上面的代码中,由于catch块中又抛出了一个异常,而这个异常没有相应的catch块处理,所以系统会向上抛这个异常,最后的打印语句也就的不到执行。

try、catch、finally、throw和throws使用归纳

  • try、catch和finally都不能单独使用,只能是try-catch、try-finally或者try-catch-finally。
  • try语句块监控代码,出现异常就停止执行下面的代码,然后将异常移交给catch语句块来处理,catch块执行完之后代码还会继续往下执行
  • finally语句块中的代码一定会被执行,常用于回收资源 。
  • throws:声明一个异常,告知方法调用者。
  • throw :抛出一个异常,至于该异常被捕获还是继续抛出都与它无关。

还有一个比较重要的是return和finally的执行关系,可以参考下这篇博客

异常链#

在平时的开发中,常常会在捕获一个异常后抛出另外一个自定义异常,并且希望把异常原始信息保存下来,这被称为异常链。我们在自定义异常时,只要提供一个接收throwable参数的构造函数即可:

Copypublic MyException(String errorCode,String errorMsg,Throwable cause){
    super(errorCode,cause);
    this.errorCode = errorCode;
    this.errorMsg = errorMsg;
}

try-with-resources#

我们知道,在Java编程过程中,如果打开了外部资源(文件、数据库连接、网络连接等),我们必须在这些外部资源使用完毕后,手动关闭它们。因为外部资源不由JVM管理,无法享用JVM的垃圾回收机制,如果我们不在编程时确保在正确的时机关闭外部资源,就会导致外部资源泄露,紧接着就会出现文件被异常占用,数据库连接过多导致连接池溢出等诸多很严重的问题。

  1. 传统的资源关闭方式
Copy//这种方式关闭资源,代码显得比较臃肿
public static void main(String[] args) {
    FileInputStream inputStream = null;
    try {
        inputStream = new FileInputStream(new File("test"));
        System.out.println(inputStream.read());
    } catch (IOException e) {
        throw new RuntimeException(e.getMessage(), e);
    } finally {
        if (inputStream != null) {
            try {
                inputStream.close();
            } catch (IOException e) {
                throw new RuntimeException(e.getMessage(), e);
            }
        }
    }
}
  1. try-with-resources方式关闭资源
Copypublic static void main(String[] args) {
    try (FileInputStream inputStream = new FileInputStream(new File("test"))) {
        System.out.println(inputStream.read());
    } catch (IOException e) {
        throw new RuntimeException(e.getMessage(), e);
    }
}

将外部资源的句柄对象的创建放在try关键字后面的括号中,当这个try-catch代码块执行完毕后,Java会确保外部资源的close方法被调用。代码是不是瞬间简洁许多!当一个外部资源的句柄对象实现了AutoCloseable接口,JDK7中便可以利用try-with-resource语法更优雅的关闭资源,消除板式代码。


相关文章

《如龙8:无限财富》IGN 9分:惊艳神作,系列最佳游戏!

《如龙8:无限财富》于1月26日正式发售,IGN编辑特里斯坦·奥格尔维(TRISTAN OGILVIE)给出9分好评,本作在继承系列传统的同时,对战斗系统进行了重大改进优化,场景设定到夏威夷给游戏新鲜感,难度设计更加平滑,整体游戏内容充实,各种细微设计升级使得玩家可以全面投入其中,《如龙8》内容庞大
2025-04-11 08:56:37

如何解决玩黑神话悟空的高延迟问题,好办法推荐给你

如何解决玩黑神话悟空的高延迟问题,好办法推荐给你 《黑神话:悟空》是一款备受期待的动作角色扮演游戏,自8月20日正式发售以来,迅速吸引了大量玩家。游戏以其精美的画面、流畅的战斗体验以及深刻的故事背景赢得了广大玩家的喜爱。然而,很多玩家在游戏过程中也遇到了一个共同的问题——高延迟。这一问题不仅影响了游
2025-04-11 07:22:50

《修仙家族模拟器》修仙经营类游戏

大家好,今天给大家介绍这款修仙类游戏:玩家在游戏中扮演一位家族老祖,开创家族,占领小岛,引导门派走向繁荣。游戏有多个阶段,玩家可以体验到养成的乐趣,包括炼制丹药、提高仙力、学习阵法等。游戏具有丰富的剧情和较为自由的玩法,能让玩家充分发挥策略和经营能力,感受家族逐步壮大的过程。家族的发展进度充满了开放
2025-04-11 05:58:43

肉鸽游戏,永远滴神!个人向小众roguelite游戏体验

前言自打诞生伊始,rogue(下文简称肉鸽)这一游戏类型就吸引了全世界的目光。相对较高的难度、传统的RPG玩法、广泛的随机内容,是该分支下的作品得以经久不衰的主要原因。时代的车轮不停转动,个人电脑的数量和普及率也与日剧增。传统的肉鸽游戏,在诸如表现、口碑等方面都显得有些力不从心。正因如此,肉鸽的另一
2025-04-11 00:51:47

《召唤师对决》评测:诸神史诗的战斗

在这个手游井喷的时代,各种各样的手游层出不穷,其中以卡牌类的手游最为火热,卡牌的对弈不仅适合手机操作同时也具备着智力对抗,娱乐性竞技性都很强,《召唤师对决》就是这样一款史诗级的卡牌巨作,上手简单精通难,以古希腊神话为背景,凭借专业团队绘制的精美卡牌画面,宏大不失轻松的丰富剧情,新颖易懂的游戏玩法,巧
2025-04-11 00:00:22

元梦之星:萌新入园指南!快速上手,闯关不愁!

闯关玩法介绍《元梦之星》的核心玩法就是超级有趣的闯关挑战!小编觉得这简直太棒了~我们需要应对多个关卡,全力保护自己,争夺冠军的宝座!在闯关模式中,有三类不同的关卡等着我们,分别是竞速模式、个人积分模式和生存模式。每个模式都有独特的挑战和乐趣,让我们在游戏中充满刺激感!【关卡分类】在闯关模式中,有三类
2025-04-10 12:27:36

网站内容来自网络,如有侵权请联系我们,立即删除!
Copyright © 阔百科 琼ICP备2023010365号-4