JAVA异常 笔记

异常、断言和日志

——笔记内容参考《Core JAVA Volume》 和 《Effective JAVA》

Java异常

在理想世界里,用户输入的数据格式永远都是正确的,选择打开的文件也一定存在,代码中永远不会出现bug。@Core Java Volume

  1. 处理异常
    Core Java Volume中指出:

    用户期望在出现错误的时候,程序能够采取合理的行为。如果因为错误某些操作无法完成,程序应该:

    • 返回到一种安全的状态,并能够让用户执行其他命令。
    • 或者允许用户保存所有工作的成果,并以妥善的方式终止程序。

    如果一个方法无法通过正常的途径完成他的任务,那么还可以通过另外一种方式退出方法。
    在这种情况下,方法将会通过throw 抛出一个异常(封装了错误信息的对象),这个方法将不会返回任何正常值,而且也不会从调用这个方法的地方继续执行。而是跳转到能够处理这种异常的异常处理器(Exception Handler,即Catch{})。

  2. 异常分类

    所有的异常类均派生与 Throwable,从Throwable 类 分化出了 Error 和 Exception ,又从Exception分化出了RunTimeException和其它异常

    java规范将所有的异常分为了两类,检查型异常和非检查型异常。其中UncheckedException(非检查型异常)包括:Error 和 RunTimeException。CheckedException(受检异常)包括:从Exception派生出来的异常中除了RunTimeException的所有其他异常都是受检异常。

    • (Error描述了java RunTimeSystem的内部错误和资源耗尽错误,你的应用程序中不该抛出这样的异常,如果产生了内部错误,除了通知用户,并尽力妥善地终止程序之外,你将无能为力。)

    • (RunTimeException是指由编程错误导致的异常,简单来说就是你自己代码写的有问题从而导致的那些异常。像数组下标越界异常和空指针异常,这些都可以通过提前做一些判断规避掉的异常就是RuntimeException。)

      你可能有个问题,那岂不是所有的异常都是RunTimeException?并不是,比如:打开一个文件,而文件不存在导致的异常,即使你在打开前对它是否存在进行了判断,但它可能在你刚判断完就被删掉了,这样仍然会出现文件不存在异常。文件的是否存在,并不仅仅取决于你的代码,而取决于外部环境

    总结:

    • 如何区分受检异常和未受检异常?

      所有的Error和RunTimeException都属于未受检异常,除此之外的异常都属于受检异常。

    • 如何区分受检异常和RuntimeException?

      所有因代码编程错误导致的异常都属于RunTimeException,如果代码逻辑正确,仍然发生的异常就是受检异常(主要取决于环境,而不取决于或不仅仅取决于你的代码。)

    • Exception拓展出两条分支:RunTimeException和OtherException,受检异常就是这里的“其它”异常。

  3. 声明检查型异常(非检查型异常不会由你抛出,所以不用也不要在方法首部声明将会抛出非检查型异常)

    非检查型异常不该被声明也可以这样理解:

    • 首先,Error是java运行系统内部发生的错误或资源耗尽错误,无法预测
    • 其次,RunTimeException是你自己代码的问题。你知道自己的代码有问题,为什么不想着怎么修改你代码里的错误,而是要告诉编译器我的代码有问题,诶我就是不改,就是玩。???

    CoreJava中指出:

    在方法首部声明检查型异常的道理很简单:一个方法不仅需要告诉编译器它将返回什么类型的值,也需要告诉编译器有可能发生什么样的错误。

    如:Public FileInputStream(String name) throws FileNotFoundException

    这是一个FileInputStream构造器。

    总结:

    • 哪些异常需要在方法首部声明?

      检查型异常

    • 什么情况下需要声明?

      • 调用了一个抛出检查型异常的方法(如FileInputStream构造器)
      • 检测到一个错误,并用throw语句抛出了一个检查型异常。(怎么检测到的,用try{})
    • 如果一个方法可能抛出多个检查型异常呢?

      那就在方法首部列出所有的检查型异常类,用逗号隔开。

      如:public Image LoadImage(String s) throws FileNotFoundException, EOFException

    • 如果没有声明所有可能抛出的检查型异常呢?

      编译器会给你发出一个错误消息。

    • 如果超类中的某个方法声明抛出了检查型异常,而子类又覆盖了这个方法,那子类该如何声明呢?

      如果子类方法中要抛出这个异常,子类方法声明的检查型异常就要比超类方法中声明的异常更具体,更详细,不能比超类中的更加通用。当然子类方法中也可以不抛出异常(在方法中用try{…}catch{…}捕获并处理掉),那就不需要声明了。

    • 如果子类覆盖父类中的方法并没有声明可能抛出异常,那子类中的这个方法就也不能抛出任何检查型异常,但如果子类中的确可能产生异常该怎么办呢?

      用try/catch捕获它并处理掉。

  4. 如何抛出异常

    步骤很简单:

    1. 找到一个合适的异常类
    2. 创建这个类的一个对象
    3. 抛出这个对象

    💎:一旦抛出这个对象,这个方法就不会返回到调用者。(也就是说,不必操心设置一个错误码,或默认返回值)

    💎:Java中只能抛出Throwable类型的对象,而C++中,可以抛出任何类型的值。

  5. 创建异常类

    什么情况下需要创建一个异常类呢,就是所有的标准异常类都无法描述你遇到的错误,这时候你就可以定义你自己的异常类了。

    但,这很没有必要。(根据Effective Java 第72条:优先使用标准的异常,代码重用的原则同样适用于异常类)

    💎重用标准异常类有三个好处:

    • 使API更易于学习,因为它与程序员已经习惯的用法一致
    • 对于用到这些API的程序而言,它们的可读性更好,因为不会出现程序员不太熟悉的异常。
    • 异常类越少,意味着内存占用就越小,装载这些类的开销就越小。
  6. 异常的捕获与处理 try/catch块

    💎:CoreJava中指出: 一般经验,捕获那些你知道怎么处理的异常,继续传播那些你不知道怎么处理的异常。最好的选择是什么也不做,而是将异常传递给调用者。如果方法抛出了异常,还是让调用者去想该怎么处理吧。(但也不可以无限向高层传播,因为如果一个错误信息过于底层,其他人可能还要去看你的底层代码,这很不友好。)

    try/catch执行过程

    1. 如果try语句块中的任何一个地方发生了一个异常,将会跳过try语句块中的剩下代码。
    2. 程序将继续执行catch子句中的代码。
    3. 如果try子句中没有发生任何异常,将跳过catch子句。
    4. 如果方法中任何地方抛出了catch子句中没有声明的异常类型,那么这个方法将会立即退出(希望方法调用者为它提供了catch子句)

    在一个try语句块中可以捕获多个异常,同理在一个catch子块中如果多个异常的处理方式一样那就可以放在同一个catch子块中。

    //🌰
    try
    {
    	code that may throw exceptions.
    }
    catch(FileNotFoundException | UnknownHostException e)
    {
    	emergency action for missing files and unknown hosts.
    }
    catch(IOException e)
    {
    	emergency action for I/O problems.
    }
    
  7. 再次抛出异常&异常链

    再次抛出异常,即包装技术,可以抛出高层异常,而非非常底层的异常。常常会遇到这种情况,程序抛出了一个异常信息,上面显示了很多异常,查看异常位置,这些异常又与我的代码没有直接关系,找了半天终于找到关于我代码的异常信息。此时,你可以包装异常,来直接获得高层异常。

    或者像CoreJava中说的那样:你如果开发了一个子系统供其他程序员使用,其它程序员使用的时候这个子系统抛出了异常,但这个错误信息包含了子系统的很多细节原因。这对其他程序员来说很不友好,其他程序员可能只想知道是不是子系统出了问题,但并不想知道发生这个错误的细节原因。

    //🌰ServletException就是这样一个异常类型的例子。执行一个servlet代码可能不想知道发生错误的细节原因,但希望明确地知道servlet是否有问题。
    try
    {
    	access the database
    }
    catch(SQLException orginal)
    {
     var e = new ServletException("database error");
     e.initCause(orginal);
     throw e;
    }
    //捕获到异常时,可以使用下边语句获取原始异常对象:
    Throwable orginal = caughtException.getCause();
    //这样可以在子系统中抛出高层异常,而且不会丢失原始异常的细节。
    //💎注意Effective Java 第73条:虽然异常包装与不加选择地从底层传递异常的做法相比有所改进,但是也不能滥用它。如有可能,处理来自底层异常最好的做法是,在调用底层方法之前确保他们会成功执行,从而避免他们抛出异常。有时候,可以在给底层方法传递参数之前,检测更高层方法的参数的有效性,从而避免底层抛出异常。
    
  8. finally的体 主要用于清理资源

    注意: 当 try 语句和 finally 语句中都有 return 语句时,在方法返回之前,finally 语句的内容将被执行,并且 finally 语句的返回值将会覆盖原始的返回值。如下:

    public class Test {
        public static int f(int value) {
            try {
                return value * value;
            } finally {
                if (value == 2) {
                    return 0;
                }
            }
        }
    }
    //如果调用 `f(2)`,返回值将是 0,因为 finally 语句的返回值覆盖了 try 语句块的返回值。
    

    💎注意:因此不要把该表控制流的语句(return, throw , break , continue )放入finally子句中。

  9. try-with-resources(带资源的try语句)

    //资源一般都实现了AutoCloseable接口。
    try(Resource res = ...)
    {
    work with res
    }
    //try块退出时,会自动调用res.close()。
    //try块中可以指定多个资源。
    //try-with-resources语句自身也可以有catch子句,甚至还可以有一个finally子句。这些字句会在关闭资源后执行。
    

。。。断言。。。日志。。。后续补充

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值