zoukankan      html  css  js  c++  java
  • Java 基础

    异常体系

    Error

    一般为底层的不可恢复的类,一般此类错误都比较严重,JVM将终止其运行的线程;

    • VirtualMachineError:虚拟机运行错误;
    • OutOfMemoryError:内存溢出;

    Exception

    程序本身可以捕获并且可以预处理的异常,例如捕获或者抛出;

    • CheckException
      受检查异常,编译阶段必须处理;
      编写异常类时直接继承Exception让它成为一个受检异常

    • RuntimeException
      运行时异常,可不用捕获,其实Exception都是受检异常,RuntimeException反而可以看成一种特例
      运行时异常场景

      1. 个人理解:不希望每个方法都处理异常,而是由最上层统一处理

    异常声明

    throws允许你对某个受检异常不进行处理,而是抛出去交给上层调用方处理

    • 受检异常
      受检异常只要抛出去了,必须声明throws,调用方也必须要处理(或者继续抛出)
    • 运行时异常
      运行时异常可以抛出时可以不用声明,声明了调用方也不一定要捕获

    栈轨迹

    错误的栈轨迹,栈顶元素是抛出异常的地方

    public class ExceptionStack {
        public static void main(String[] args) {
            h();  
        }
    
        private static void f() {
            try{
                throw new RuntimeException();
            } catch (Exception e) {
                for (StackTraceElement element : e.getStackTrace()) {
                    System.out.println(element.getMethodName());
                }
            }
        }
    
        private static void g() {
            f();
        }
    
        private static void h() {
            g();
        }
    }
    
    

    测试结果

    f
    g
    h
    main
    

    重新抛出原有异常

    catch(Exception e) {
        throw e;
    }
    

    堆栈信息不会记录新的调用点,重新抛出的地方不会成为栈顶元素
    即如果直接抛出原来的异常对象,错误的堆栈信息不会改变

    public class ExceptionStack2 {
        public static void main(String[] args) {
            h();
        }
    
        private static void f() {
            throw new RuntimeException();
        }
    
        private static void g() {
            try {
                f();
            } catch (Exception e) {
                System.out.println("g() : 重新抛出异常");
                throw e;
            }
        }
    
        private static void h() {
            try {
                g();
            } catch (Exception e) {
                for (StackTraceElement element : e.getStackTrace()) {
                    System.out.println(element.getMethodName());
                }
            }
        }
    }
    
    

    测试结果

    g() : 重新抛出异常
    f
    g
    h
    main
    

    重新构建异常

    如果重新构建新的异常,那么栈顶元素就是抛出的地方了,就跟正常抛出一样

    public static void main(String[] args) {
            h();
        }
    
        private static void f() {
            throw new RuntimeException();
        }
    
        private static void g() {
            try {
                f();
            } catch (Exception e) {
                System.out.println("g() : 重新构建异常抛出");
                throw new RuntimeException("g() : exception");
            }
        }
    
        private static void h() {
            try {
                g();
            } catch (Exception e) {
                for (StackTraceElement element : e.getStackTrace()) {
                    System.out.println(element.getMethodName());
                }
            }
        }
    

    测试结果
    从测试结果可以看出,g()这个方法抛出的异常不会保留f()方法调用它的轨迹了

    g() : 重新构建异常抛出
    g
    h
    main
    

    异常链

    上面提到重新构建异常抛出时无法得到完整的调用链了,那么我们如何得到上一个异常发生的信息呢?

    在我们重新抛出异常的时候希望保存原来的异常对象信息,JDK1.4之后我们就可以在构造时传入一个cause异常对象,保留上一个异常对象的信息

    在堆栈打印时会通过 Cause by 打印上一个异常对象的栈轨迹

    public static void main(String[] args) {
            try {
                h();
            } catch (Exception e){
                e.printStackTrace();
            }
        }
    
        private static void f() {
            throw new RuntimeException("f(): exception");
        }
    
        private static void g() {
            try {
                f();
            } catch (Exception e) {
                throw new RuntimeException("g(): exception",e);
            }
        }
    
        private static void h() {
            try {
                g();
            } catch (Exception e) {
                throw new RuntimeException("h(): exception",e);
            }
        }
    

    测试结果

    java.lang.RuntimeException: h(): exception
    	at example.exception.usage.demo2.ExceptionChain.h(ExceptionChain.java:28)
    	at example.exception.usage.demo2.ExceptionChain.main(ExceptionChain.java:6)
    Caused by: java.lang.RuntimeException: g(): exception
    	at example.exception.usage.demo2.ExceptionChain.g(ExceptionChain.java:20)
    	at example.exception.usage.demo2.ExceptionChain.h(ExceptionChain.java:26)
    	... 1 more
    Caused by: java.lang.RuntimeException: f(): exception
    	at example.exception.usage.demo2.ExceptionChain.f(ExceptionChain.java:13)
    	at example.exception.usage.demo2.ExceptionChain.g(ExceptionChain.java:18)
    	... 2 more
    

    initCause

    某些异常的构造函数中可能没有cause(Throwable)这种参数(例如FileNotFoundException),但是我们还可以用initCause来保存上一个异常对象
    这里仅作演示,RuntimeException是可以直接通过构造的方式保存异常链的

     private static void g() throws Exception {
            try {
                f();
            } catch (Exception e) {
                Exception exception = new RuntimeException("g(): exception");
                exception.initCause(e);
                throw exception;
            }
        }
    

    finally

    finallytry/try-catch后一定会执行的语句

    return 与 finally

    1. 就算try return了, finally也会执行
    public static void main(String[] args) {
            System.out.println("get(): " + get());
        }
    
        private static int get() {
            try {
                return 1;
            } finally {
                System.out.println("就算try return了, finally也会执行");
            }
        }
    

    测试结果

    就算try return了, finally也会执行
    get(): 2
    
    1. finally里面return
      会使用finallyreturn,所以最好别这么做
    public static void main(String[] args) {
            System.out.println("get(): " + retInfinally());
        }
    
        private static int retInfinally() {
            try {
                return 1;
            } finally {
                return 2;
            }
        }
    

    测试结果

    get(): 2
    

    finally丢失异常

    1. finally中抛出的异常会覆盖trycatch中抛出的异常
      你也许不会直接在finally中抛出异常,但你有可能在finally中调用其他方法,如果抛出了调用方法时抛出了异常,则会覆盖原有异常
    public static void main(String[] args) {
            System.out.println("throwInfinally(): " + throwInfinally());
        }
    
        static class ImportantException extends Exception {
        }
    
        private static int throwInfinally() {
            try {
                throw new ImportantException();
            } finally {
                throw new RuntimeException("finally中抛出:运行时异常");
            }
        }
    

    运行结果

    Exception in thread "main" java.lang.RuntimeException: finally:运行时异常
    	at example.exception.usage.demo3.FinallyTest2.throwInfinally(FinallyTest2.java:25)
    	at example.exception.usage.demo3.FinallyTest2.main(FinallyTest2.java:7)
    
    
    1. finally中进行return也会吞掉异常
      你可以发现即使我们抛出了一个受检异常也不会编译报错,因为我们在finally中return了
      public static void main(String[] args) {
           System.out.println("get(): " + retInfinally());
       }
       
       static class ImportantException extends Exception {
       }
      
       private static int retInfinally() {
           try {
               throw new ImportantException();
           } finally {
               return 2;
           }
       }
      

    运行结果

    get(): 2
    

    资源清理与构造器

    finally常被用来做资源清理,但是资源清理的前提是资源确实被初始化了,那如果资源初始化就失败了呢?那么finally中就不应该进行资源清理,比较好的方式是通过嵌套try语句,这里就直接用《OnJava8》中的demo了

    public class Cleanup {
        public static void main(String[] args) {
            try {
                InputFile in = new InputFile("Cleanup.java");
                try {
                    String s;
                    int i = 1;
                    while((s = in.getLine()) != null)
                        ; // Perform line-by-line processing here...
                } catch(Exception e) {
                    System.out.println("Caught Exception in main");
                    e.printStackTrace(System.out);
                } finally {
                    in.dispose();
                }
            } catch(Exception e) {
                System.out.println(
                        "InputFile construction failed");
            }
        }
    }
    

    try-with-resources

    概念

    从上面的例子中我们可以发现资源的初始化与释放操作手动编写比较复杂,所以在从Java7开始提供了 try-with-resources

    基本原理就是通过实现AutoCloseable接口,完成自己的资源释放操作,所以只要是实现了这个接口的类我们都可以通过try-with-resources在try语句中进行资源的初始化

    基本用法

    这里还是直接使用《OnJava8》中的例子:

     public static void main(String[] args) {
            try(
                    InputStream in = new FileInputStream(
                            new File("TryWithResources.java"))
            ) {
                int contents = in.read();
                // Process contents
            } catch(IOException e) {
                // Handle the error
            }
        }
    

    资源声明顺序

    需要构建多个资源对象时可以用分号分割,注意顺序,后声明资源的会先关闭

    异常与继承

    这个就是我们常常背的重写要求之一了,即重写的方法不能抛出比父类范围更广的异常
    这样做的意义就是保证接口的通用性:

    1. 子类可以不抛出异常,但是调用方针对基类处理了异常那也没问题
    2. 子类抛出基类的异常或者范围更小的异常,调用方针对基类做的异常处理仍然生效

    异常匹配

    被第一个匹配的catch捕获,例如说你不能先catch一个Exception,再catch它的子类,编译器会报错,因为后面的catch语句永远匹配不到

    try {
                // do something
            } catch (Exception e){
                e.printStackTrace();
            } catch (RuntimeException e){  // 这样是不行滴
    
            }
    

    异常使用指南

    这里直接贴一下《OnJava8》中的异常指南

    应该在下列情况下使用异常:

    1. 尽可能使用 try-with-resource。
    2. 在恰当的级别处理问题。(在知道该如何处理的情况下才捕获异常。)
    3. 解决问题并且重新调用产生异常的方法。
    4. 进行少许修补,然后绕过异常发生的地方继续执行。
    5. 用别的数据进行计算,以代替方法预计会返回的值。
    6. 把当前运行环境下能做的事情尽量做完,然后把相同的异常重抛到更高层。
    7. 把当前运行环境下能做的事情尽量做完,然后把不同的异常抛到更高层。
    8. 终止程序。
    9. 进行简化。(如果你的异常模式使问题变得太复杂,那用起来会非常痛苦也很烦人。)
    10. 让类库和程序更安全。(这既是在为调试做短期投资,也是在为程序的健壮性做长期投资。

    ------ 《OnJava8》

    异常日志

    异常日志一版记录哪些信息?

    1. 异常类型
    2. 异常信息
    3. 业务参数
    4. 异常时间
    5. 异常位置

    首先就是看下异常时间,即什么时候发生的异常,然后通过异常类型可以判断是哪一块有问题,通过异常信息和业务参数进一步确定异常,如果还解决不了,那就得通过异常位置看代码

    最后

    关于异常处理其实还有很多内容值得学习和研究,例如:
    Java中处理异常的一些场景,什么时候应该抛出原有异常,什么时候应该重新构建新的异常?
    其他语言是怎么处理异常的?

    极客上有几篇专栏是与异常处理相关的,等看完那几篇文章研究一下之后再来进行记录

    异常处理:别让自己在出问题的时候变为瞎子

    程序中的错误处理:错误返回码和异常捕捉

  • 相关阅读:
    第一周作业
    第0次作业
    IOS和Android解析newDate()不兼容问题
    行 1: <%@ Application Codebehind="Global.asax.cs" Inherits="FuceFP.Web.Global" Language="C#" %>
    The edge module has not been pre-compiled for node.js version v6.11.2. You must build a custom version of edge.node. Please refer to https://github.com/tjanczuk/edge for building instructions.
    无法确定条件表达式的类型,因为Datetime和<null>之间没有隐式转换
    VS Android Cordova 环境搭建 Java SDK/AVD Manager 闪退
    机器学习系列(3) 决策树
    python系列(2) 函数
    MYSQL系列(4) 窗口函数解决连续几天登录的问题
  • 原文地址:https://www.cnblogs.com/bax-life/p/14698622.html
Copyright © 2011-2022 走看看