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

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

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语法更优雅的关闭资源,消除板式代码。


相关文章

《上古卷轴OL:暗影盛宴》地下城游戏包与U47更新现已登陆PC/Mac平台

《上古卷轴OL:暗影盛宴》地下城游戏包以及免费的U47更新基础版游戏补丁现已登陆PC/Mac平台,并已包含在《上古卷轴OL》2025年内容通行证中。玩家可以从即日起探索两处全新的4人地下城“黑宝石铸造厂”与“纳吉-卡尔迪什”。《暗影盛宴》与U47更新将于9月3日登陆Xbox和PlayStation主
2025-08-21 00:41:22

《妖怪金手指》流血队终极攻略:高爆发持续输出阵容搭配

在《妖怪金手指》中,流血队以其强大的持续输出能力和高频攻击机制成为PVE和PVP的热门选择。该阵容以盘古为核心,搭配控制、辅助与守门角色,能在战斗中快速叠加流血层数,造成巨额伤害。本攻略将详细解析最佳阵容搭配、技能升级顺序、遗物选择及实战技巧,助你轻松驾驭流血队!一、核心阵容推荐1. 标准流血队(均
2025-08-20 16:23:21

三国群英传3:都骂吕布三姓家奴,却在游戏里享受做义子的感觉!

“义子”这个词,本来只是一个指代身份和关系的形容词,却因为吕布两拜义父又两杀义父的行为而在后世里成为了一个贬义词。尽管人们都喷吕布是个三姓家奴,却都在当年的《三国群英传》3代游戏里非常享受做义子的感觉,因为接盘实在是太舒服了。当年我们要是选择董卓阵营进行游戏的话,等到时间推进到一定的程度,就会触发貂
2025-08-19 04:49:37

策略模拟类游戏《万神之城》的玩法特色一览

《万神之城》(Citadelum)是一款以古罗马为背景的城市建设与策略模拟游戏,玩家将扮演一位领导者,从零开始建造并发展自己的定居点,逐步扩张势力,最终打造一座宏伟的城市。以下是游戏的核心玩法介绍:1. 城市建设与资源管理资源收集:玩家需要从森林、采石场等地方收集木材、石材等资源,同时通过农业生产(
2025-08-19 01:37:13

金铲铲之战萌新入门宝典:从棋盘小白到策略高手

踏入《金铲铲之战》这片充满策略博弈的自走棋天地,就像开启了一场未知的冒险。每一局对战都是智慧与运气的双重考验,对初来乍到的玩家而言,想要在棋盘上崭露头角,掌握基础玩法与实用技巧是必经之路。接下来,这份详尽攻略将带你一步步揭开游戏的奥秘,在激烈的对战中脱颖而出。一、基础玩法全解读(一)游戏模式选择排位
2025-08-18 15:43:05

📶 2025年手机必装的联机生存神作,4G也能和队友笑疯在末日!

“当丧尸潮淹到脚脖子,我还在语音里狂笑:队友把我家当厕所了!”开放世界组:手机里塞进一个宇宙,兄弟一起浪《明日之后》——末日版“基建狂魔”60人同服盖房子,队友盖别墅你盖茅厕?南极地图冻成狗,抱团烤火还被队友偷走烤红薯!跨服拍卖行赚金条,零氪党靠卖“丧尸香水”(尸潮掉落的变异腺体)发家致富,莱文市尸
2025-08-16 23:57:28

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