异常处理

概述

异常是程序在执行过程中发生不正常的情况。

Java程序的异常情况分为两类:

  • **Error:**Java虚拟机无法解决的严重问题。如JVM内部错误、资源耗尽等。一般不编写针对性代码处理。
  • **Exception:**其他由于编程出错或者偶发性的外在因素导致的一般性错误,则可以通过针对性的代码进行处理。如:空指针访问,试图读取不存在的文件,网络中断,数组越界等等情况。

对于异常,我们拥有两种办法:第一种是不处理,直接终止程序运行。另外一种就是在编写程序的过程中,将异常的处理方法写入代码中。

常见异常

运行时异常

  • NullPointerException 空指针

    1
    2
    int[] arr = null;
    System.out.println(arr[3]);
  • ArrayIndexOutOfBoundsException 数组越界

    1
    2
    int[] arr = new int[2];
    System.out.println(arr[2]);
  • ClassCastException 类型转换错误

    1
    2
    Object obj = new Date();
    String str = (String) obj;
  • NumberFormatException

    1
    2
    String str = "abc";
    int i = Integer.parseInt(str);
  • InputMismatchException

    1
    2
    3
    Scanner scanner = new Scanner(System.in);
    int score = scanner.nextInt();
    System.out.println(score);

    当你输入内容为非int类型时,就会报出错误。

  • ArithmeticException 算术异常

    1
    2
    3
    int a = 10;
    int b = 0;
    System.out.println(a / b);

编译时异常

  • IOException

    1
    2
    3
    4
    5
    6
    7
    8
    File file = new File("hello.txt");
    FileInputStream fis = new FileInputStream(file);
    int data = fis.read();
    while (data != -1) {
    System.out.print((char) data);
    data = fis.read();
    }
    fis.close();

    这种编译是无法通过的。

异常处理

在编写程序时候,我们往往需要if-else分支对可能出现错误的地方进行检测,但是过多的if-else分支会导致代码过于臃肿。异常处理机制可以将异常处理的代码集中在一起,与正常代码分开,减少过多if-else分支。

Java有两种异常处理的方式,分别是try-catch-finally方式与throws方式。try-catch-finally方式是自行解决,throws方式是上报的方式,让上面去解决。

Java的异常处理是一个“抓抛模型”:“抛”是指程序执行的过程中,如果出现异常,就会生成一个对应异常类的对象,并将此对象抛出,一旦抛出异常对象以后,其后代码将不再执行。“抓”是抓取到异常对象后,对异常的处理——try-catch-finally和throws。

try-catch-finally方式

结构:

1
2
3
4
5
6
7
8
9
10
try{
//可能出现异常的代码
}catch(异常类型1 变量1){
//处理异常类型1
}catch(异常类型2 变量2){
//处理异常类型2
}.....
finally{
//一定会执行的代码
}

说明:

使用try将可能出现异常代码包装起来,在执行的过程中一旦出现异常,就会生成一个对应异常类的对象,根据此对象的类型,在catch中进行匹配。进入catch后,进行异常的处理,处理完成后,就会跳出结构(在没有写finally情况下),继续执行后面代码。

在try-catch结构中申明的变量,在出了结构以后,就不可以再使用了。

使用try-catch处理异常时,可以使得编译时的错误延迟到运行时再出现。

catch可以写多个。finally是可选的,不一定要写。

try-catch基础例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
String str = "abc";
try{
int num = Integer.parseInt(str);
System.out.println("1");
}catch (NumberFormatException e) {
System.out.println("NumberFormatException");
}
System.out.println("2");
/*
* output:
* NumberFormatException
* 2
*/

由于System.out.println("1");是位于异常后面,因此在抛出异常后,发生异常之后的代码将不会执行。

throws方式

throws给我个人的感觉与抽象类有点相似,throws就是在这个方法里我先不处理,谁调用了这个方法就由谁进行处理。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public static void main(String[] args) {
try {
test();
} catch (IOException e) {
e.printStackTrace();
}
}

public static void test() throws IOException {
File file = new File("hello.txt");
FileInputStream fis = new FileInputStream(file);
int data = fis.read();
while (data != -1) {
System.out.print((char) data);
data = fis.read();
}
fis.close();
}

如何选择处理方式

  1. 如果父类中被重写的方法没有throws处理异常,则子类重写的方法如果出现异常,就只能使用try-catch-finally进行处理;
  2. 在执行的方法A中,如果需要先后调用几个具有递进关系的方法,则这几个方法使用throw的方式处理,而A方法使用try-catch-finally的方式处理;
  3. 注意:子类重写的方法抛出的异常类型不大于父类被重写方法抛出的异常类型。

手动抛出异常

异常对象可以通过跟一般对象一样,通过new生成异常对象。

通过throw关键词,我们可以将异常对象抛出。注意此处为throw,并非throws

1
2
3
4
5
6
7
public void register(int id) {
if(id > 0){
this.id = id;
}else{
throw new RuntimeException("id must be greater than 0");
}
}

自定义异常

下面是自定义的步骤

  1. 自定义异常类一般我们先会让我们自定义的类继承现有的异常体系(Exception、RuntimeException);
  2. 定义全局常量serialVersionUID(对类的唯一标识);
  3. 提供重载的构造器。
1
2
3
4
5
6
7
8
9
public class NewException extends RuntimeException {
private static final long serialVersionUID = 1L;
public NewException() {
super();
}
public NewException(String msg) {
super(msg);
}
}