> 文档中心 > Java异常浅析一二

Java异常浅析一二

文章目录

  • Java异常
    • 1. 异常类层次图
      • 1.1 Throwable
      • 1.2 Error
      • 1.3 Exception
        • 1.3.1 RuntimeException 运行时异常
        • 1.3.2 CheckedException 可检查的异常
    • 2. 异常处理关键字
      • 2.1 throws
      • 2.2 throw
    • 3 try catch finally
      • **3.1 try-catch**
      • **3.2 try-catch-finally**
      • 3.3 **try-finally**
      • 3.4 **try-with-resource**
    • 4. try catch finally 与 return
    • 5. 异常处理原则
      • 5.1 只针对不正常的情况才使用异常
      • 5.2 清理工作回收资源放在finally块中
      • 5.3 优先捕获最具体的异常
      • 5.4 不要捕获Throwable
      • 5.5 不要忽略异常
      • 5.6 包装异常后抛出
      • 5.7 不要使用异常控制程序的流程
      • 5.8 不要在finally块中使用return
      • 5.9 尽可能使用标准异常
    • 6.常见异常种类
      • 6.1 RuntimeException
      • 6.2 IOException
      • 6.3 其他
    • 7.JVM处理异常的机制
      • 7.1 异常表
      • 7.2 异常处理极其耗时
  • 总结

Java异常

Java异常都是对象,是Throwable子类的实例,描述了出现在一段编码中的 错误条件。当条件生成时,错误将引发异常。

1. 异常类层次图

Throwable 是 Java 语言中所有错误与异常的超类。 Throwable 包含两个子类:Error(错误)和 Exception(异常),它们通常用于指示发生了异常情况。 Throwable 包含了其线程创建时线程执行堆栈的快照,它提供了 printStackTrace() 等接口用于获取堆栈跟踪数据等信息。
在这里插入图片描述

1.1 Throwable

Throwable 是 Java 语言中所有错误与异常的超类。

1.2 Error

Error 类及其子类:程序中无法处理的错误,表示运行应用程序中出现了严重的错误。一般表示代码运行时 JVM 出现问题。通常有 Virtual MachineError(虚拟机运行错误)、NoClassDefFoundError(类定义错误)等。比如 OutOfMemoryError:内存不足错误;StackOverflowError:栈溢出错误。此类错误发生时,JVM 将终止线程。

1.3 Exception

Exception 这种异常又分为两类:运行时异常和编译时异常。

1.3.1 RuntimeException 运行时异常

运行时异常的特点是Java编译器不会检查它,也会编译通过。
NullPointerException(空指针异常)、IndexOutOfBoundsException(下标越界异常)等,这些异常是不检查异常,程序中可以选择捕获处理,也可以不处理。这些异常一般是由程序逻辑错误引起的,程序应该从逻辑角度尽可能避免这类异常的发生。

1.3.2 CheckedException 可检查的异常

其他的Exception类及其子类都属于可查异常。这种异常的特点是Java编译器会检查它,也就是说,当程序中可能出现这类异常,要么用try-catch语句捕获它,要么用throws子句声明抛出它,否则编译不会通过。

2. 异常处理关键字

2.1 throws

用在方法签名中,用于声明该方法可能抛出的异常。当方法的调用者无力处理该异常的时候,应该抛出异常交由上级调用者处理。

若是父类的方法没有声明异常,则子类继承方法后,也不能声明异常。
若方法中存在检查异常,如果不对其捕获,那必须在方法头中显式声明该异常。

public static void method() throws IOException, FileNotFoundException{

2.2 throw

用于抛出异常。因为Java的大部分方法要么已经处理异常,要么已声明异常。所以一般都是捕获异常或者再往上抛,不需要手工throw。

if(value == 0) { throw new ArithmeticException("参数不能为0"); //抛出一个运行时异常    }

throw的目的常常是为了改变异常的类型。多用于在多系统集成时,异常类型可能有多种,可以用统一的异常类型向外暴露,不需暴露太多内部异常细节。

catch (IOException e) { MyException ex = new MyException("read file failed."); ex.initCause(e); throw ex;    }

异常可以自定义

public class MyException extends Exception {    public MyException(){ }    public MyException(String msg){ super(msg);    }}

3 try catch finally

try , 监听代码,在try语句块之内,当try语句块内发生异常时,异常就被抛出。 catch, 用于捕获异常。catch用来捕获try语句块中发生的异常。 finally , finally语句块总是会被执行。它主要用于回收在try块里打开的资源。只有finally块执行完成之后才会回来执行try或者catch块中的return或者throw语句;如果finally中使用了return或者throw等终止方法的语句,则就不会跳回执行,直接停止。
常用的组合方法有

3.1 try-catch

可以捕获多个异常,但要注意异常类的层次,要先捕获子类异常。

static void readFile(String fileName) {    try { // dosomething()    } catch (FileNotFoundException | UnknownHostException e) { // handle FileNotFoundException    } catch (IOException e){ // handle IOException    }}

3.2 try-catch-finally

如果没有异常发生,代码执行顺序是:try代码块==>finally代码块;
如果异常发生,但是catch的异常不包含此类异常,则finally语句块里的语句还是会被执行,但finally语句块后的语句不会被执行。
结构如下:

try {//执行程序代码,可能会出现异常   } catch(Exception e) {//捕获异常并处理   } finally {    //必执行的代码}

3.3 try-finally

try-finally可用在不需要捕获异常的代码,可以保证资源在使用后被关闭。例如IO流中执行完相应操作后,关闭相应资源;使用Lock对象保证线程同步,通过finally可以保证锁会被释放;数据库连接代码时,关闭连接操作等等。如:

//以Lock加锁为例,演示try-finallyReentrantLock lock = new ReentrantLock();try {    //需要加锁的代码} finally {    lock.unlock(); //保证锁一定被释放}

3.4 try-with-resource

try-with-resource是Java 7中引入的,提供了更优雅的方式来实现资源的自动释放,自动释放的资源需要是实现了 AutoCloseable 接口的类

private  static void tryWithResourceTest(){    try (Scanner scanner = new Scanner(new FileInputStream("c:/abc"),"UTF-8")){ // code    } catch (IOException e){ // handle exception    }}

查看源码Scanner实现了Closeable 接口。

public final class Scanner implements Iterator<String>, Closeable {  // ...}

4. try catch finally 与 return

先说结论:
如果try里没有出错,优先finally的return。
如果在try里出错,还是优先finally的return。如果没有finally块,才是catch块里return。

如果在finally里变更值的指向,对于基本类型和引用类型都不会生效。
但如果调用可变对象的更改方法,则会生效。

所以,最好不要在finally块里加上return或者对变量重新赋值或设置,因为比较容易弄混淆,导致代码不可读。

public class Test01 {    static int m1(){ try{     int a =1/0;     return 1; } catch (Exception e) {     return 2; }    }    static int m2(){ try{     int a =1/0;     return 1; } catch (Exception e){     return 2; } finally {     return 3; }    }    static int m3(){ try{     return 1; } finally {     return 2; }    }    static int m4(){ int result = 0; try{     return result; } finally {     result = 1; }    }    static StringBuilder m5(){ StringBuilder result = new StringBuilder("ab"); try{     return result; } finally {     result.append("aaaa"); }    }    static StringBuilder m6(){ StringBuilder result = new StringBuilder("ab"); try{     return result; } finally {     result = new StringBuilder("bbb"); }    }    public static void main(String[] args) { System.out.println("Test01.m1():"+Test01.m1()); System.out.println("Test01.m2():"+Test01.m2()); System.out.println("Test01.m3():"+Test01.m3()); System.out.println("Test01.m4():"+Test01.m4()); System.out.println("Test01.m5():"+Test01.m5()); System.out.println("Test01.m6():"+Test01.m6());    }}

运行结果:

Test01.m1():2Test01.m2():3Test01.m3():2Test01.m4():0Test01.m5():abaaaaTest01.m6():ab

5. 异常处理原则

5.1 只针对不正常的情况才使用异常

比如文本转换字符串时,不应该使用异常来实现,代价昂贵。

5.2 清理工作回收资源放在finally块中

finally 代码块总是会被执行,你可以确保你清理了所有打开的资源。或者使用try-with-resource 语法

5.3 优先捕获最具体的异常

总是优先捕获最具体的异常类,并将不太具体的 catch 块添加到列表的末尾。

5.4 不要捕获Throwable

如果catch Throwable ,它不仅会捕获所有异常,也将捕获所有的错误。对于JVM 错误,应该由应用程序处理。

5.5 不要忽略异常

捕获异常后不应当不做任何处理。

5.6 包装异常后抛出

捕获标准异常并包装为自定义异常是一个很常见的做法。
throw new MyBusinessException("error message.", e);

5.7 不要使用异常控制程序的流程

本应该使用if语句进行条件判断的情况下,你却使用异常处理,这是非常不好的习惯

5.8 不要在finally块中使用return

try块中的return语句执行成功后,并不马上返回,而是继续执行finally块中的语句,如果此处存在return语句,则在此直接返回,无情丢弃掉try块中的返回点。

5.9 尽可能使用标准异常

在语义符合的情况下,尽量重用java标准异常。

IllegalArgumentException 参数的值不合适
IllegalStateException 参数的状态不合适
NullPointerException 在null被禁止的情况下参数值为null
IndexOutOfBoundsException 下标越界
ConcurrentModificationException 在禁止并发修改的情况下,对象检测到并发修改
UnsupportedOperationException 对象不支持客户请求的方法

6.常见异常种类

6.1 RuntimeException

java.lang.ArrayIndexOutOfBoundsException 数组索引越界异常。
java.lang.ArithmeticException 算术条件异常
java.lang.NullPointerException 空指针异常。
java.lang.ClassNotFoundException 找不到类异常。当应用试图遍历CLASSPAH之后找不到对应名称的class文件时,抛出该异常。 java.lang.NegativeArraySizeException 数组长度为负异常
java.lang.ArrayStoreException 数组中包含不兼容的值抛出的异常
java.lang.SecurityException 安全性异常
java.lang.IllegalArgumentException 非法参数异常

6.2 IOException

IOException IOException:操作输入流和输出流时可能出现的异常。
EOFException 文件已结束异常
FileNotFoundException 文件未找到异常

6.3 其他

ClassCastException 类型转换异常类
ArrayStoreException 数组中包含不兼容的值抛出的异常
SQLException 操作数据库异常类
NoSuchFieldException 字段未找到异常
NoSuchMethodException 方法未找到抛出的异常
NumberFormatException 字符串转换为数字抛出的异常
StringIndexOutOfBoundsException 字符串索引超出范围抛出的异常
IllegalAccessException 不允许访问某类异常
InstantiationException 当应用程序试图使用Class类中的newInstance()方法创建一个类的实例,而指定的类对象无法被实例化时.

7.JVM处理异常的机制

7.1 异常表

JVM处理异常的机制,在字节码层面使用了异常表。异常表中包含了一个或多个异常处理者(Exception Handler)的信息,这些信息包含如下 from 可能发生异常的起始点 to 可能发生异常的结束点 target 上述from和to之前发生异常后的异常处理者的位置 type 异常处理者处理的异常的类信息

7.2 异常处理极其耗时

建立一个异常对象,是建立一个普通Object耗时的约20倍(实际上差距会比这个数字更大一些,因为循环也占用了时间,追求精确的读者可以再测一下空循环的耗时然后在对比前减掉这部分),而抛出、接住一个异常对象,所花费时间大约是建立异常对象的4倍。

总结

合理的处理异常,是程序健壮性的保证。

java基础 系列在github上有一个开源项目,主要是本系列博客的demo代码。https://github.com/forestnlp/javabasic
如果您对软件开发、机器学习、深度学习有兴趣请关注本博客,将持续推出Java、软件架构、深度学习相关专栏。
您的支持是对我最大的鼓励。