Java异常学习

什么是异常

异常本质上是程序上的错误,这个错误包括程序逻辑错误和系统错误。错误在程序中又有运行时异常和编译时异常。

编译异常在编译期间出错有编译器帮助错,然而运行期间的错误编译器则鞭长莫及了,并且运行期间的错误往往是难以预料的,但是如果不处理这些异常则会直接导致程序崩溃。因此在运行期间出现错误应该如何来补救昵?Java提供了异常机制来处理这些错误。

异常分类

在Java中的异常被当做对象来处理,根类为java.lang.Throwable类,在Java中定义了很多异常类(如OutOfMemoryErrorNullPointerExceptionIndexOutOfBoundsException等),这些异常类分为两大类:ErrorException

Error

Error是无法处理的异常,比如OutOfMemoryError,一般发生这种异常,JVM会自动终止程序,因此编写程序时不需要关心这类异常。

Exception

Exception,是经常见到的一些异常情况,比如NullPointerException,这些异常是可以处理的。

Exception类的异常包括checked exceptionunchecked exceptionunchecked exception也称运行时异常RuntimeException,当然这里的运行时异常并不是前面所说的运行期间的异常,只是Java中用运行时异常这个术语来表示,Exception类的异常都是在运行期间发生的)。

  1. unchecked exception(非检查异常),也称运行时异常(RuntimeException),比如常见的NullPointerExceptionIndexOutOfBoundsException。对于运行时异常,java编译器不要求必须进行异常捕获处理或者抛出声明,由程序员自行决定。

  2. checked exception(检查异常),也称非运行时异常(运行时异常以外的异常就是非运行时异常),java编译器强制程序员必须进行捕获处理,比如常见的IOExeptionSQLException。对于非运行时异常如果不进行捕获或者抛出声明处理,编译都不会通过。

异常类结构层次

在Java中,所有异常类的父类是Throwable类,Error类是error类型异常的父类,Exception类是exception类型异常的父类,RuntimeException类是所有运行时异常的父类,RuntimeException以外的并且继承Exception的类是非运行时异常。

  • 典型的RuntimeException包括NullPointerExceptionIndexOutOfBoundsExceptionIllegalArgumentException等。

  • 典型的非RuntimeException包括IOExceptionSQLException等。

Java中如何处理异常

在Java中如果需要处理异常,必须先对异常进行捕获,然后再对异常情况进行处理。

如何对可能发生异常的代码进行异常捕获和处理呢?使用trycatch关键字即可。

捕获异常

1
2
3
4
5
6
7
8
9
10
11
12
13
try {

File file = new File("d:/a.txt");

if(!file.exists())

file.createNewFile();

} catch (IOException e) {

// TODO: handle exception

}

在try中发生的异常,将交由catch进行处理。

抛出异常

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public class Main {

public static void main(String[] args) {

try {

createFile();

} catch (Exception e) {

// TODO: handle exception

}

}

public static void createFile() throws IOException{

File file = new File("d:/a.txt");

if(!file.exists())

file.createNewFile();

}

}

createFile()中并没有对异常进行处理,而是用throws关键字声明抛出异常,即告知这个方法的调用者此方法可能会抛出IOException。那么在main方法中调用createFile方法的时候,采用try...catch块进行了异常捕获处理。

throw关键字手动来抛出异常

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public class Main {

public static void main(String[] args) {

try {

int[] data = new int[]{1,2,3};

System.out.println(getDataByIndex(-1,data));

} catch (Exception e) {

System.out.println(e.getMessage());

}

}

public static int getDataByIndex(int index,int[] data) {

if(index<0||index>=data.length)

throw new ArrayIndexOutOfBoundsException("数组下标越界");

return data[index];

}

}

然后在catch块中进行捕获。

在Java中进行异常处理的话,对于可能会发生异常的代码,可以选择三种方法来进行异常处理:

1)对代码块用try..catch进行异常捕获处理;

2)在该代码的方法体外用throws进行抛出声明,告知此方法的调用者这段代码可能会出现这些异常,需要谨慎处理。此时有两种情况:

  • 如果声明抛出的异常是非运行时异常,此方法的调用者必须显示地用try..catch块进行捕获或者继续向上层抛出异常。

  • 如果声明抛出的异常是运行时异常,此方法的调用者可以选择地进行异常捕获处理。

3)在代码块用throw手动抛出一个异常对象,此时也有两种情况,跟2)中的类似:

  • 如果抛出的异常对象是非运行时异常,此方法的调用者必须显示地用try..catch块进行捕获或者继续向上层抛出异常。

  • 如果抛出的异常对象是运行时异常,此方法的调用者可以选择地进行异常捕获处理。

(如果最终将异常抛给main方法,则相当于交给jvm自动处理,此时jvm会简单地打印异常信息)

一个方法所能捕捉的异常,一定是Java代码在某处所抛出的异常。简单地说,异常总是先被抛出,后被捕捉的。

理解try,catch,finally,throws,throw五个关键字

try,catch,finally

try关键字用来包围可能会出现异常的逻辑代码,它单独无法使用,必须配合catch或者finally使用。Java编译器允许的组合使用形式只有以下三种形式:

try...catch...;

try....finally......;

try....catch...finally...

当然catch块可以有多个,注意try块只能有一个,finally块是可选的(但是最多只能有一个finally块)。

三个块执行的顺序为try—>catch—>finally
try...catch...finally执行顺序

当然如果没有发生异常,则catch块不会执行。但是finally块无论在什么情况下都是会执行的(这点要非常注意,因此部分情况下,都会将释放资源的操作放在finally块中进行)。

在有多个catch块的时候,是按照catch块的先后顺序进行匹配的,一旦异常类型被一个catch块匹配,则不会与后面的catch块进行匹配。

在使用try..catch..finally块的时候,注意千万不要在finally块中使用return,因为finally中的return会覆盖已有的返回值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
public String openFile() {

try {

FileInputStream inputStream = new FileInputStream("d:/a.txt");

int ch = inputStream.read();

System.out.println("aaa");

return "step1";

} catch (FileNotFoundException e) {

System.out.println("file not found");

return "step2";

}catch (IOException e) {

System.out.println("io exception");

return "step3";

}finally{

System.out.println("finally block");

return "finally";

}
}

这里finally中的返回值回吧catch里面的返回值给覆盖,也会把try中的return覆盖掉。从调试中可以看出来,try中就算有return,也会执行finally中。

总结:

try 块:用于捕获异常。其后可接零个或多个catch块,如果没有catch块,则必须跟一个finally块。

catch 块:用于处理try捕获到的异常。

finally 块:无论是否捕获或处理异常,finally块里的语句都会被执行。当在try块或catch块中遇到return语句时,finally语句块将在方法返回之前被执行。在以下4种特殊情况下,finally块不会被执行:

1)在finally语句块中发生了异常。

2)在前面的代码中用了System.exit()退出程序。

3)程序所在的线程死亡。

4)关闭CPU。

throws和thow关键字

  1. throws出现在方法的声明中,表示该方法可能会抛出的异常,然后交给上层调用它的方法程序处理,允许throws后面跟着多个异常类型;

  2. throw一般会用于程序出现某种逻辑时程序员主动抛出某种特定类型的异常。throw只会出现在方法体中,当方法在执行过程中遇到异常情况时,将异常信息封装为异常对象,然后throw出去。throw关键字的一个非常重要的作用就是异常类型的转换

throws表示出现异常的一种可能性,并不一定会发生这些异常;throw则是抛出了异常,执行throw则一定抛出了某种异常对象。两者都是消极处理异常的方式(这里的消极并不是说这种方式不好),只是抛出或者可能抛出异常,但是不会由方法去处理异常,真正的处理异常由此方法的上层调用处理。

异常处理建议

  1. 只在必要使用异常的地方才使用异常,不要用异常去控制程序的流程。
    谨慎地使用异常:异常捕获的代价非常高昂,异常使用过多会严重影响程序的性能。如果在程序中能够用if语句和Boolean变量来进行逻辑判断,那么尽量减少异常的使用,从而避免不必要的异常捕获和处理。

  2. 切忌使用空catch块
    在捕获了异常之后什么都不做,相当于忽略了这个异常。千万不要使用空的catch块,空的catch块意味着你在程序中隐藏了错误和异常,并且很可能导致程序出现不可控的执行结果。如果你非常肯定捕获到的异常不会以任何方式对程序造成影响,最好用Log日志将该异常进行记录,以便日后方便更新和维护。

  3. 检查异常和非检查异常的选择

    一旦决定抛出异常,就要决定抛出什么异常。这里面的主要问题就是抛出检查异常还是非检查异常。

    检查异常导致了太多的try…catch代码,可能有很多检查异常对开发人员来说是无法合理地进行处理的,比如SQLException,而开发人员却不得不去进行try…catch,这样就会导致经常出现这样一种情况:逻辑代码只有很少的几行,而进行异常捕获和处理的代码却有很多行。这样不仅导致逻辑代码阅读起来晦涩难懂,而且降低了程序的性能。

    建议尽量避免检查异常的使用,如果确实该异常情况的出现很普遍,需要提醒调用者注意处理的话,就使用检查异常;否则使用非检查异常。

    一般情况下,尽量将检查异常转变为非检查异常交给上层处理。

  4. 注意catch块的顺序,不要把上层类的异常放在最前面的catch块。

  5. 不要将提供给用户看的信息放在异常信息里。

  6. 避免多次在日志信息中记录同一个异常

    只在异常最开始发生的地方进行日志信息记录。很多情况下异常都是层层向上跑出的,如果在每次向上抛出的时候,都Log到日志系统中,则会导致无从查找异常发生的根源。

  7. 异常处理尽量放在高层进行

    尽量将异常统一抛给上层调用者,由上层调用者统一之时如何进行处理。如果在每个出现异常的地方都直接进行处理,会导致程序异常处理流程混乱,不利于后期维护和异常错误排查。由上层统一进行处理会使得整个程序的流程清晰易懂。

  8. 在finally中释放资源

    如果有使用文件读取、网络操作以及数据库操作等,记得在finally中释放资源。这样不仅会使得程序占用更少的资源,也会避免不必要的由于资源未释放而发生的异常情况。

  9. 避免过大的try块,不要把不会出现异常的代码放到try块里面,尽量保持一个try块对应一个或多个异常。

  10. 细化异常的类型,不要不管什么类型的异常都写成Excetpion。

  11. catch块尽量保持一个块捕获一类异常,不要忽略捕获的异常,捕获到后要么处理,要么转译,要么重新抛出新类型的异常。

  12. 不要把自己能处理的异常抛给别人。

  13. 不要用try…catch参与控制程序流程,异常控制的根本目的是处理程序的非正常情况。

Throwable类中的常用方法

catch关键字后面括号中的Exception类型的参数e。Exception就是try代码块传递给catch代码块的变量类型,e就是变量名。catch代码块中语句”e.getMessage();”用于输出错误性质。通常异常处理常用3个函数来获取异常的有关信息:

  • getCause():返回抛出异常的原因。如果 cause 不存在或未知,则返回 null。

  • getMeage():返回异常的消息信息。

  • printStackTrace():对象的堆栈跟踪输出至错误输出流,作为字段 System.err 的值。

有时为了简单会忽略掉catch语句后的代码,这样try-catch语句就成了一种摆设,一旦程序在运行过程中出现了异常,就会忽略处理异常,而错误发生的原因很难查找。

Java常见异常

runtimeException子类

  1. java.lang.ArrayIndexOutOfBoundsException
    数组索引越界异常。当对数组的索引值为负数或大于等于数组大小时抛出。
  2. java.lang.ArithmeticException
    算术条件异常。譬如:整数除零等。
  3. java.lang.NullPointerException
    空指针异常。当应用试图在要求使用对象的地方使用了null时,抛出该异常。譬如:调用null对象的实例方法、访问null对象的属性、计算null对象的长度、使用throw语句抛出null等等
  4. java.lang.ClassNotFoundException
    找不到类异常。当应用试图根据字符串形式的类名构造类,而在遍历CLASSPAH之后找不到对应名称的class文件时,抛出该异常。
  5. java.lang.NegativeArraySizeException 数组长度为负异常
  1. java.lang.ArrayStoreException 数组中包含不兼容的值抛出的异常
  1. java.lang.SecurityException 安全性异常
  1. java.lang.IllegalArgumentException 非法参数异常

IOException

IOException:操作输入流和输出流时可能出现的异常。

EOFException:文件已结束异常

FileNotFoundException:文件未找到异常

其他异常

ClassCastException:类型转换异常类

ArrayStoreException:数组中包含不兼容的值抛出的异常

SQLException:操作数据库异常类

NoSuchFieldException:字段未找到异常

NoSuchMethodException:方法未找到抛出的异常

NumberFormatException:字符串转换为数字抛出的异常

StringIndexOutOfBoundsException:字符串索引超出范围抛出的异常

IllegalAccessException:不允许访问某类异常

InstantiationException:当应用程序试图使用Class类中的newInstance()方法创建一个类的实例,而指定的类对象无法被实例化时,抛出该异常

自定义异常

使用Java内置的异常类可以描述在编程时出现的大部分异常情况。除此之外,用户还可以自定义异常。用户自定义异常类,只需继承Exception类即可。

在程序中使用自定义异常类,大体可分为以下几个步骤。
(1)创建自定义异常类。
(2)在方法中通过throw关键字抛出异常对象。
(3)如果在当前抛出异常的方法中处理异常,可以使用try-catch语句捕获并处理;否则在方法的声明处通过throws关键字指明要抛出给方法调用者的异常,继续进行下一步操作。
(4)在出现异常方法的调用者中捕获并处理异常。

本文标题:Java异常学习

文章作者:王洪博

发布时间:2018年03月13日 - 10:03

最后更新:2019年09月23日 - 03:09

原始链接:http://whb1990.github.io/posts/e60072fb.html

▄︻┻═┳一如果你喜欢这篇文章,请点击下方"打赏"按钮请我喝杯 ☕
0%