Java异常学习笔记(一)

什么是异常?

异常的英文单词是exception,字面翻译就是“意外、例外”的意思,也就是非正常情况。事实上,异常本质上是程序上的错误,包括程序逻辑错误和系统错误。比如使用空的引用、数组下标越界、内存溢出错误等,这些都是意外的情况,背离我们程序本身的意图。错误在我们编写程序的过程中会经常发生,包括编译期间和运行期间的错误,在编译期间出现的错误有编译器帮助我们一起修正,然而运行期间的错误便不是编译器力所能及了,并且运行期间的错误往往是难以预料的。假若程序在运行期间出现了错误,如果置之不理,程序便会终止或直接导致系统崩溃,显然这不是我们希望看到的结果。因此,如何对运行期间出现的错误进行处理和补救呢?Java提供了异常机制来进行处理,通过异常机制来处理程序运行期间出现的错误。通过异常机制,我们可以更好地提升程序的健壮性。

异常的种类


在java中,所有的非正常情况都继承Throwable。而Error和Exception是他的两个子类,各自又含有一系列的子类,对应一系列的错误和异常。这里可以看出java设计的特性与原则。

  • Error(错误):是程序无法处理的,指与虚拟机相关的问题(比如:系统崩溃、虚拟机错误)。这些错误无法恢复或者捕获
  • Exception(异常):是程序本身可处理的。而异常又分为两大类:Checked异常和Runtime异常(运行时异常)。

注意:异常和错误的区别:异常能被程序本身可以处理,错误是无法处理。

其中Exception异常又可分为

  • RuntimeException异常
  • 非RuntimeException异常(也叫Checked异常、可查异常)

Checked异常(可查异常)是指可以在编译阶段内处理的异常,java认为必须显示处理的异常。

在Exception中除了RuntimeException,其他的异常都属于可查Checked异常,都必须要显式地处理。

  • RuntimeException异常:都是RuntimeException类及其子类异常,如NullPointerException(空指针异常)、IndexOutOfBoundsException(下标越界异常)等,这些异常是不检查异常,程序中可以选择捕获处理,也可以不处理。这些异常一般是由程序逻辑错误引起的,程序应该从逻辑角度尽可能避免这类异常的发生。 运行时异常的特点是Java编译器不会检查它,也就是说,当程序中可能出现这类异常,即使没有用try-catch语句捕获它,也没有用throws子句声明抛出它,也会编译通过。
  • 非RuntimeException异常 :是RuntimeException以外的异常,类型上都属于Exception类及其子类。从程序语法角度讲是必须进行处理的异常,如果不处理,程序就不能编译通过。如IOException、SQLException等以及用户自定义的Exception异常,一般情况下不自定义检查异常。

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

举个例子

try {
  File file = new File("d:/a.txt");
  if(!file.exists())
    file.createNewFile(); //必须显式地处理
} catch (IOException e) {
  // TODO: handle exception
}

像上面这种IOException异常就必须显式地处理,否则无法编译通过。

int i = 1/0; //ArithmeticException异常
int[] a = new int[1];
System.out.println(a[2]); //ArrayIndexOutOfBoundsException异常

上面这种【RuntimeException异常】就不需要显式地处理。

使用Checked异常至少存在如下两大不便之处

  • 对于程序中的Checked异常,java要求必须显式捕获并处理该异常,或者显式声明抛出该异常。这样就增加了编程复杂度
  • 如果在方法中显式声明抛出Checked异常,将会导致方法签名与异常耦合,如果该方法是重写父类的方法,则该方法抛出的异常还会受到被重写方法所抛出异常的限制

Runtime异常则更加灵活,Runtime异常无须显式声明抛出,如果程序需要捕捉Runtime异常,也可以使用try...catch块来实现

在大部分时候推荐使用Runtime异常,而不使用Checked异常。尤其当程序需要自行抛出异常时,使用Runtime异常更加简洁。当使用Runtime异常时,程序无须在方法中声明抛出Checked异常,一旦发生了自定义错误,程序只管抛出Runtime异常即可

常见的 CheckedException(非RuntimeException异常) 有(必须显式地进行处理):

  • NoSuchMethodException(方法未找到抛出的异常)
  • ClassCastException(类型转换异常类)
  • NumberFormatException(字符串转换为数字抛出的异常)
  • IOException(操作输入流和输出流时可能出现的异常)

常见的 RuntimeException 有(不要求显式地处理):

  • ArrayIndexOutOfBoundsException(数组索引越界异常)
  • NullPointerException(空指针异常)
  • ClassNotFoundException(找不到类异常)
  • IllegalArgumentException(非法参数异常)
  • SecurityException(安全性异常)

异常处理机制

处理方式:

  1. 捕获异常:明确如何处理该异常,就应该捕获它,在对应的catch中修复 
  2. 抛出异常:不知道如何处理该异常,应在定义该方法时声明抛出该异常。

1.捕获异常

在Java中如果需要处理异常,必须先对异常进行捕获,然后再对异常情况进行处理。如何对可能发生异常的代码进行异常捕获和处理呢?使用try和catch关键字即可,如下面一段代码所示:

try {
    //do something
} catch (Exception e) {
    // TODO: handle exception
}

  被try块包围的代码说明这段代码可能会发生异常,一旦发生异常,异常便会被catch捕获到,然后需要在catch块中进行异常处理。

1.1finally

finally主要要理解三点

  1. finally主要用来回收资源
  2. finally什么情况下不会执行
  3. finally中不要使用return或throw语句

1.1.1finally回收资源

无论是否捕获或处理异常,finally 块里的语句都会被执行,甚至在异常没有被当前的异常处理程序捕获的情况下,异常处理机制也会在跳到更高一层的异常处理程序之前,执行 finally 子句,当涉及 break 及 continue 语句的时候,finally 子句也会得到执行。

有些时候,程序在try块里打开了一些物理资源(例如数据库连接、网络连接和磁盘文件等),这些物理资源都必须显式回收

Java垃圾回收机制不会回收任何物理资源,垃圾回收机制只能回收堆内存中对象所占用的内存

如果try块的某条语句引起了异常,该语句后面的其他语句通常不会获得执行的机会,这将导致位于该语句之后的资源回收语句得不到执行。如果在catch块里进行资源回收,但catch块完全有可能得不到执行,这将导致不能及时回收这些物理资源

为了保证一定能回收try块中打开的物理资源,异常处理机制提供了finally块。不管try块中的代码是否出现异常,也不管哪一个catch块被执行,甚至在try块或catch块中执行了return语句,finally块总会被执行

1.1.2finally不执行的条件

try块、catch块中调用了退出虚拟机的方法

比如

public static void main(String[] args) {
    try {
        int i = 2/0;
        System.out.println("try");
    } catch (Exception e) {
        System.out.println("catch");
        System.exit(0);
    } finally {
        System.out.println("finally");
    }
}
执行结果:

1.1.3finally中不要使用return或throw

在通常情况下,不要在finally块中使用如return或throw等导致方法终止的语句,一旦在finally块中使用了return或throw语句,将会导致try块、catch块中的return、throw语句失效。

因为finally总是会执行,所以会把try\catch里面的return、throw覆盖掉。

所以尽量避免在finally里使用return或throw等导致方法终止的语句,否则可能出现一些很奇怪的情况

2.抛出异常

 一个方法不处理这个异常,而是调用层次向上传递,谁调用这个方法,这个异常就由谁来处理。

抛出异常有两种方式

  1. throws:函数声明处添加
  2. throw:方法内添加

  在Java中还提供了另一种异常处理方式即抛出异常,顾名思义,也就是说一旦发生异常,我把这个异常抛出去,让调用者去进行处理,自己不进行具体的处理,此时需要用到throw或throws关键字。 

2.1throws

使用throws声明抛出异常的思路是,当前方法不知道如何处理这种类型的异常,该异常应该由上一级调用者处理;如果main方法也不知道如何处理这种类型的异常,也可以使用throws声明抛出异常,该异常交给JVM处理。JVM对异常处理的方法是打印异常的跟踪栈信息,并中止程序运行,这就是前面程序在遇到异常后自动结束的原因

throws声明抛出只能在方法签名中进行使用,throws可以声明抛出多个异常类,多个异常类之间以逗号隔开

throws Exception1, Exception2...

一旦使用throws语句声明抛出该异常,程序就无须使用try...catch块来捕获该异常了。程序声明不处理IOException异常,将该异常交给上一级处理

  下面看一个示例:

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块进行了异常捕获处理。

2.2throw

当程序出现错误时,系统会自动抛出异常;Java允许程序自行抛出异常,自行抛出异常使用throw语句来完成。

throw语句可以单独使用,throw语句抛出的不是异常类,而是一个异常实例,而且每次只能抛出一个异常实例。throw语句的语法格式如下:

throw ExceptionInsantance;

当Java运行时接收到开发者自行抛出的异常时,同样会中止当前的执行流,跳到该异常对应的catch块,由该catch块来处理该异常。也就是说,不管是系统自动抛出的异常,还是程序员手动抛出的异常,Java运行时环境对异常的处理没有任何差别。

当然还可以采用throw关键字手动来抛出异常对象。下面看一个例子:

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];
    }
}

小结:抛出的异常,最终还是要被调用者处理

猜你喜欢

转载自blog.csdn.net/CrankZ/article/details/80889264