• Java的异常
  1. 计算机程序运行的过程中,总是会出现各种各样的错误。有一些错误是用户造成的,比如,希望用户输入一个int类型的年龄,但是用户的输入是abc。程序想要读写某个文件的内容,但是用户已经把它删除了。还有一些错误是随机出现,并且永远不可能避免的。比如:

    • 网络突然断了,连接不到远程服务器;
    • 内存耗尽,程序崩溃了;
    • 用户点“打印”,但根本没有打印机;
    • ……
  2. Java内置了一套异常处理机制,总是使用异常来表示错误。异常是一种class,因此它本身带有类型信息。异常可以在任何地方抛出,但只需要在上层捕获,这样就和方法调用分离了。

     1 try {
    2 String s = processFile(“C:\\test.txt”);
    3 // ok:
    4 } catch (FileNotFoundException e) {
    5 // file not found:
    6 } catch (SecurityException e) {
    7 // no read permission:
    8 } catch (IOException e) {
    9 // io error:
    10 } catch (Exception e) {
    11 // other error:
    12 }
  3. Java的异常是class,它的继承关系如下:
  4. 从继承关系可知:Throwable是异常体系的根,它继承自ObjectThrowable有两个体系:ErrorExceptionError表示严重的错误,程序对此一般无能为力。
  5. Exception则是运行时的错误,它可以被捕获并处理。
  6. Exception又分为两大类:

    1. RuntimeException以及它的子类;
    2. RuntimeException(包括IOExceptionReflectiveOperationException等等)
  7. Java规定:
    • 必须捕获的异常,包括Exception及其子类,但不包括RuntimeException及其子类,这种类型的异常称为Checked Exception。

    • 不需要捕获的异常,包括Error及其子类,RuntimeException及其子类。

  8. 编译器对RuntimeException及其子类不做强制捕获要求,不是指应用程序本身不应该捕获并处理RuntimeException。是否需要捕获,具体问题具体分析。
  9. 捕获异常

    • 捕获异常使用try...catch语句,把可能发生异常的代码放到try {...}中,然后使用catch捕获对应的Exception及其子类。

       1 import java.io.UnsupportedEncodingException;
      2 import java.util.Arrays;
      3
      4 public class Main {
      5 public static void main(String[] args) {
      6 byte[] bs = toGBK("中文");
      7 System.out.println(Arrays.toString(bs));
      8 }
      9
      10 static byte[] toGBK(String s) {
      11 try {
      12 // 用指定编码转换String为byte[]:
      13 return s.getBytes("GBK");
      14 } catch (UnsupportedEncodingException e) {
      15 // 如果系统不支持GBK编码,会捕获到UnsupportedEncodingException:
      16 System.out.println(e); // 打印异常信息
      17 return s.getBytes(); // 尝试使用用默认编码
      18 }
      19 }
      20 }
    • 如果我们不捕获UnsupportedEncodingException(去掉try catch),会出现编译失败的问题。编译器会报错,错误信息类似:unreported exception UnsupportedEncodingException; must be caught or declared to be thrown,并且准确地指出需要捕获的语句是return s.getBytes("GBK");。意思是说,像UnsupportedEncodingException这样的Checked Exception,必须被捕获。
    • 这是因为String.getBytes(String)方法定义是(如下),在方法定义的时候,使用throws Xxx表示该方法可能抛出的异常类型(Runtime及其子类除外)。调用方在调用的时候,必须强制捕获这些异常,否则编译器会报错。
      public byte[] getBytes(String charsetName) throws UnsupportedEncodingException {
      ...
      }
    • toGBK()方法中,因为调用了String.getBytes(String)方法,就必须捕获UnsupportedEncodingException。我们也可以不捕获它,而是在方法定义处用throws表示toGBK()方法可能会抛出UnsupportedEncodingException,就可以让toGBK()方法通过编译器检查。
       1 import java.io.UnsupportedEncodingException;
      2 import java.util.Arrays;
      3
      4 public class Main {
      5 public static void main(String[] args) {
      6 byte[] bs = toGBK("中文");
      7 System.out.println(Arrays.toString(bs));
      8 }
      9
      10 static byte[] toGBK(String s) throws UnsupportedEncodingException {
      11 return s.getBytes("GBK");
      12 }
      13 }
    • 只要是方法声明的Checked Exception,不在调用层捕获,也必须在更高的调用层捕获。所有未捕获的异常,最终也必须在main()方法中捕获,不会出现漏写try的情况。这是由编译器保证的。main()方法也是最后捕获Exception的机会。
    • 如果是测试代码,上面的写法就略显麻烦。如果不想写任何try代码,可以直接把main()方法定义为throws Exception。因为main()方法声明了可能抛出Exception,也就声明了可能抛出所有的Exception,因此在内部就无需捕获了。代价就是一旦发生异常,程序会立刻退出。
    • toGBK()内部“消化”异常
      static byte[] toGBK(String s) {
      try {
      return s.getBytes("GBK");
      } catch (UnsupportedEncodingException e) {
      // 什么也不干
      }
      return null;
    • 这种捕获后不处理的方式是非常不好的,即使真的什么也做不了,也要先把异常记录下来:
      static byte[] toGBK(String s) {
      try {
      return s.getBytes("GBK");
      } catch (UnsupportedEncodingException e) {
      // 先记下来再说:
      e.printStackTrace();
      }
      return null;
    • 所有异常都可以调用printStackTrace()方法打印异常栈,这是一个简单有用的快速打印异常的方法。
  • 捕获异常
    1. 凡是可能抛出异常的语句,都可以用try ... catch捕获。把可能发生异常的语句放在try { ... }中,然后使用catch捕获对应的Exception及其子类。  
    2. 可以使用多个catch语句,每个catch分别捕获对应的Exception及其子类。JVM在捕获到异常后,会从上到下匹配catch语句,匹配到某个catch后,执行catch代码块,然后不再继续匹配。

      简单地说就是:多个catch语句只有一个能被执行。

    3. 存在多个catch的时候,catch的顺序非常重要:子类必须写在前面。
    4. 无论是否有异常发生,都希望执行一些语句,例如清理工作,可以把执行语句写若干遍:正常执行的放到try中,每个catch再写一遍。

       1 public static void main(String[] args) {
      2 try {
      3 process1();
      4 process2();
      5 process3();
      6 System.out.println("END");
      7 } catch (UnsupportedEncodingException e) {
      8 System.out.println("Bad encoding");
      9 System.out.println("END");
      10 } catch (IOException e) {
      11 System.out.println("IO error");
      12 System.out.println("END");
      13 }
      14 }
    5. Java的try ... catch机制还提供了finally语句,finally语句块保证有无错误都会执行。上述代码可以改写如下。
      public static void main(String[] args) {
      try {
      process1();
      process2();
      process3();
      } catch (UnsupportedEncodingException e) {
      System.out.println("Bad encoding");
      } catch (IOException e) {
      System.out.println("IO error");
      } finally {
      System.out.println("END");
      }
      }
    6. finally有几个特点:

      1. finally语句不是必须的,可写可不写;
      2. finally总是最后执行。
    7. 某些情况下,可以没有catch,只使用try ... finally结构。
      void process(String file) throws IOException {
      try {
      ...
      } finally {
      System.out.println("END");
      }
      }

      因为方法声明了可能抛出的异常,所以可以不写catch

  • 抛出异常
    1. 查看Integer.java源码可知,抛出异常的方法代码如下。

      public static int parseInt(String s, int radix) throws NumberFormatException {
      if (s == null) {
      throw new NumberFormatException("null");
      }
      ...
      }
    2. 查看Integer.java源码可知,抛出异常的方法代码如下当发生错误时,例如,用户输入了非法的字符,我们就可以抛出异常。如何抛出异常?参考Integer.parseInt()方法,抛出异常分两步:

      1. 创建某个Exception的实例;
      2. throw语句抛出。
        void process2(String s) {
        if (s==null) {
        NullPointerException e = new NullPointerException();
        throw e;
        }
        } 或: void process2(String s) {
        if (s==null) {
        throw new NullPointerException();
        }
        }
      3. 在代码中获取原始异常可以使用Throwable.getCause()方法。如果返回null,说明已经是“根异常”了。
      4. 捕获到异常并再次抛出时,一定要留住原始异常,否则很难定位第一案发现场!
    3. try或者catch语句块中抛出异常,不会影响finally的执行。JVM会先执行finally,然后抛出异常。
    4. 如果在执行finally语句时抛出异常,那么,finally抛出异常后,原来在catch中准备抛出的异常就“消失”了,因为只能抛出一个异常。没有被抛出的异常称为“被屏蔽”的异常(Suppressed Exception)。
      public class Main {
      public static void main(String[] args) {
      try {
      Integer.parseInt("abc");
      } catch (Exception e) {
      System.out.println("catched");
      throw new RuntimeException(e);
      } finally {
      System.out.println("finally");
      throw new IllegalArgumentException();
      }
      }
      } 输出: catched
      finally
      Exception in thread "main" java.lang.IllegalArgumentException
      at Main.main(Main.java:11)
    5. 在极少数的情况下,我们需要获知所有的异常。如何保存所有的异常信息?方法是先用origin变量保存原始异常,然后调用Throwable.addSuppressed(),把原始异常添加进来,最后在finally抛出。(通过Throwable.getSuppressed()可以获取所有的Suppressed Exception。绝大多数情况下,在finally中不要抛出异常。因此,通常不需要关心Suppressed Exception。)
       1 public class Main {
      2 public static void main(String[] args) throws Exception {
      3 Exception origin = null;
      4 try {
      5 System.out.println(Integer.parseInt("abc"));
      6 } catch (Exception e) {
      7 origin = e;
      8 throw e;
      9 } finally {
      10 Exception e = new IllegalArgumentException();
      11 if (origin != null) {
      12 e.addSuppressed(origin);
      13 }
      14 throw e;
      15 }
      16 }
      17 }
  • 自定义异常
    1. 在一个大型项目中,可以自定义新的异常类型,但是,保持一个合理的异常继承体系是非常重要的。一个常见的做法是自定义一个BaseException作为“根异常”,然后,派生出各种业务类型的异常。BaseException需要从一个适合的Exception派生,通常建议从RuntimeException派生。

      public class BaseException extends RuntimeException {
      }

      //其他业务类型的异常就可以从BaseException派生:
      public class UserNotFoundException extends BaseException {
      } public class LoginFailedException extends BaseException {
      }

      ...

      //自定义的BaseException应该提供多个构造方法
      public class BaseException extends RuntimeException {
      public BaseException() {
      super();
      } public BaseException(String message, Throwable cause) {
      super(message, cause);
      } public BaseException(String message) {
      super(message);
      } public BaseException(Throwable cause) {
      super(cause);
      }
      }
    2. 上述构造方法实际上都是原样照抄RuntimeException。这样,抛出异常的时候,就可以选择合适的构造方法。通过IDE可以根据父类快速生成子类的构造方法。

  • NullPointerException

    • NullPointerException即空指针异常,俗称NPE。如果一个对象为null,调用其方法或访问其字段就会产生NullPointerException,这个异常通常是由JVM抛出的。
    • 指针这个概念实际上源自C语言,Java语言中并无指针。我们定义的变量实际上是引用,Null Pointer更确切地说是Null Reference。
    • NullPointerException是一种代码逻辑错误,遇到NullPointerException,遵循原则是早暴露,早修复,严禁使用catch来隐藏这种编码错误。  
    • 成员变量在定义时初始化使用空字符串""而不是默认的null可避免很多NullPointerException,编写业务逻辑时,用空字符串""表示未填写比null安全得多。
    • 如果调用方一定要根据null判断,比如返回null表示文件不存在,那么考虑返回Optional<T>
      public Optional<String> readFromFile(String file) {
      if (!fileExist(file)) {
      return Optional.empty();
      }
      ...
      }

      这样调用方必须通过Optional.isPresent()判断是否有结果。

    • 定位NullPointerException

      •   从Java 14开始,如果产生了NullPointerException,JVM可以给出详细的信息告诉我们null对象到底是谁。
      • 这种增强的NullPointerException详细信息是Java 14新增的功能,但默认是关闭的,我们可以给JVM添加一个-XX:+ShowCodeDetailsInExceptionMessages参数启用它:

        java -XX:+ShowCodeDetailsInExceptionMessages Main.java
  • 使用断言

    • 断言(Assertion)是一种调试程序的方式。在Java中,使用assert关键字来实现断言。 

      public static void main(String[] args) {
      double x = Math.abs(-123.45);
      assert x >= 0;
      System.out.println(x);
      }

      语句assert x >= 0;即为断言,断言条件x >= 0预期为true。如果计算结果为false,则断言失败,抛出AssertionError

    • 使用assert语句时,还可以添加一个可选的断言消息。 
      assert x >= 0 : "x must >= 0";

      断言失败的时候,AssertionError会带上消息x must >= 0,更加便于调试。

  • 使用JDK Logging

    • Java标准库内置了日志包java.util.logging,可以直接用。

      import java.util.logging.Level;
      import java.util.logging.Logger; public class Hello {
      public static void main(String[] args) {
      Logger logger = Logger.getGlobal();
      logger.info("start process...");
      logger.warning("memory is running out...");
      logger.fine("ignored.");
      logger.severe("process will be terminated...");
      }
      }
    • 日志的输出可以设定级别。JDK的Logging定义了7个日志级别,从严重到普通:

      • SEVERE
      • WARNING
      • INFO
      • CONFIG
      • FINE
      • FINER
      • FINEST
    • 因为默认级别是INFO,因此,INFO级别以下的日志,不会被打印出来。使用日志级别的好处在于,调整级别,就可以屏蔽掉很多调试相关的日志输出。

  • Commons Logging(理解)

    • Commons Logging是一个第三方日志库,它是由Apache创建的日志模块。Commons Logging的特色是,它可以挂接不同的日志系统,并通过配置文件指定挂接的日志系统。默认情况下,Commons Loggin自动搜索并使用Log4j(Log4j是另一个流行的日志系统),如果没有找到Log4j,再使用JDK Logging。

    • 使用Commons Logging只需要和两个类打交道,并且只有两步:第一步,通过LogFactory获取Log类的实例; 第二步,使用Log实例的方法打日志。

      import org.apache.commons.logging.Log;
      import org.apache.commons.logging.LogFactory; public class Main {
      public static void main(String[] args) {
      Log log = LogFactory.getLog(Main.class);
      log.info("start...");
      log.warn("end.");
      }
      }
  • 使用Log4j

  • 使用SLF4J和Logback

Java学习_异常处理的更多相关文章

  1. Java学习之异常处理

    在 Java 中,所有的异常都有一个共同的祖先 Throwable(可抛出),Throwable 指定代码中可用异常传播机制通过 Java 应用程序传输的任何问题的共性.       Throwabl ...

  2. JAVA学习之 异常处理机制

    今天就来说说java的异常处理机制,异常处理不是第一接触,尤其是写过非常多c#的代码,基本都会写到异常处理的代码,事实上c#的异常处理与java的异常处理基本都是一样的,仅仅是在一些细节上不是非常一样 ...

  3. Java学习_面向对象编程

    抽象类 一个class定义了方法,但没有具体执行代码,这个方法就是抽象方法,抽象方法用abstract修饰.因为抽象类本身被设计成只能用于被继承,因此,抽象类可以强迫子类实现其定义的抽象方法,否则编译 ...

  4. Java学习_反射

    什么是反射? 反射就是Reflection,Java的反射是指程序在运行期可以拿到一个对象的所有信息. 反射是为了解决在运行期,对某个实例一无所知的情况下,如何调用其方法. JAVA反射机制是在运行状 ...

  5. Java学习_注解

    使用注解 注解是放在Java源码的类.方法.字段.参数前的一种特殊"注释". 1 // this is a component: 2 @Resource("hello&q ...

  6. Java学习_泛型

    什么是泛型. Java标准库提供的ArrayList内部就是一个Object[]数组,配合存储一个当前分配的长度,就可以充当"可变数组". public class ArrayLi ...

  7. Java学习_常见异常

    JAVA常见异常 Java.io.NullPointerException null 空的,不存在的 NullPointer 空指针 空指针异常,该异常出现在我们操作某个对象的属性或方法时,如果该对象 ...

  8. java学习_文件工具类

    工具类里面的方法全部都是静态的,调用的时候不需要实例化

  9. Java 学习(10):java 异常处理

    java 异常处理 异常发生的原因有很多,通常包含以下几大类: 用户输入了非法数据. 要打开的文件不存在. 网络通信时连接中断,或者JVM内存溢出. 三种类型的异常: 检查性异常: 最具代表的检查性异 ...

随机推荐

  1. 三. Vue组件化

    1. 认识组件化 1.1 什么是组件化 人面对复杂问题的处理方式 任何一个人处理信息的逻辑能力都是有限的,所以当面对一个非常复杂的问题时我们不太可能一次性搞定一大堆的内容. 但是我们人有一种天生的能力 ...

  2. Guava中EventBus分析

    EventBus 1. 什么是EventBus 总线(Bus)一般指计算机各种功能部件之间传送信息的公共通信干线,而EventBus则是事件源(publisher)向订阅方(subscriber)发送 ...

  3. 17.java设计模式之观察者模式

    基本需求: 气象站可以将每天测量到的温度,湿度,气压等等,以公告的形式发布出去(比如发布到自己的网站或第三方) 需要设计开放型API,便于其他第三方也能接入气象站获取数据 提供温度.气压和湿度的接口 ...

  4. this大全,还有谁??????!!!!!!!

    this在函数调用时创建,一般的对象没有this,全局window可以理解为一个函数,他有一个全局this JavaScript 语言之所以有this的设计,跟内存里面的数据结构有关系. 函数里thi ...

  5. 第2.2节 Python的语句

    上节已经介绍了极简的Python代码编写,已经用到了赋值语句,本节对Python的程序语句进行介绍. 一. 常用命令 在介绍Python语句之前,先介绍一下几个有用的Python命令. dir(模块名 ...

  6. 老猿Python重难点知识博文汇总

    老猿Python博文目录 专栏:使用PyQt开发图形界面Python应用 老猿Python博客地址 除了相关教程外,老猿在学习过程中还写了大量的学习随笔,内容比较杂,文章内容也参差不齐,为了方便,老猿 ...

  7. 问题:PyCharm调试方法Force run to cursor与run to cursor的区别

    Force run to cursor与run to cursor的差别是,后者在执行到光标的代码行前,如果有代码中设置了断点,会在该断点处暂停,等待进一步调试指令,而Force run to cur ...

  8. PyQt(Python+Qt)学习随笔:Designer(设计师)中部件属性编辑的cursor(光标样式)属性

    部件(又称为组件或控件)的cursor属性保存该部件的鼠标光标形状,当鼠标位于该部件上时就会呈现该属性设置的光标形状,对应类型为枚举类型Qt.CursorShape,可取值的范围可以在Qt文档官网:h ...

  9. 【Docker】 .Net Core 3.1 webapi 集成EF Code First,使用MySql进行业务操作 、配置swagger (三)

    系列目录: [Docker] CentOS7 安装 Docker 及其使用方法 ( 一 ) [Docker] 使用Docker 在阿里云 Centos7 部署 MySQL 和 Redis (二) [D ...

  10. js- 实现属性名的拼接 obj['name']

     obj.name---->obj[name] 这两种调用方式一样,使用obj.name内部转换成 obj['name'], 使用obj['name']更快. obj['name'] 里面必须是 ...