The Java™ Tutorials——(1)Essential Classes——Exception

一丶异常(Exception)

1.什么是异常

定义:异常是一个事件,它在程序执行过程中发生,会破环正常的程序指令流。

​ 当一个错误在方法内发生的时候,此方法会创建一个对象并将其递交给运行时系统。此对象我们称之为异常对象,它包含了错误信息,错误类型,还有出错时程序的状态。创建异常对象并将它递交给运行时系统的过程,我们称之为抛出异常(throwing an exception)

​ 在方法抛出异常后,运行时系统会尝试着找到一些信息并处理它。这些处理异常的信息可能是一些已经被调用并且调用后到达发生错误地方的方法集合。这些 方法集合 ,我们称之为 调用栈(call stack)

																/*
使用电脑版看下面的文字"示意图",更好哦。			
			
----------------------------- 
|Method where error occurred|←←↖
|-------------------------- |	↑		
								↑  Method Call
-----------------------------   ↑
|Method without an exception|→→↗
|		  handler           |
|-------------------------- |←←↖	
						        ↑  Method Call
-----------------------------   ↑
|Method  with an  exception |→→↗
|		  handler           |
|---------------------------|←←↖
		               		    ↑  Method Call
		main →→→→→→→→→→→→→→→→→→↗   

	
	      The call stack.	                                      */

​ 运行时系统会在调用栈中搜寻一个函数,此函数包含能够处理异常的代码块。这段代码块,我们称之为异常处理器(an exception handler)。

​ 搜寻的顺序,如上图的话,是从上层到底层。也就是说,从发生错误的地方开始搜寻,按照方法调用的逆序来搜索。当合适的处理器被找到后,运行时系统会传递异常给此异常处理器。异常处理器合适被用来处理异常的条件是:运行时系统需要处理的异常类型匹配异常处理器能够处理的异常类型。

当异常处理器被搜索到并选中的时候,我们称之为捕获到了异常( catch the exception。如果运行时系统在调用栈中找不到合适的异常处理器,那么运行时系统就会终止,结果就是程序终止/结束。​

																	   /*		
使用电脑版看下面的文字"示意图",更好哦。	
			
                    ----------------------------- 
  Throws exception  |Method where error occurred|→→↘
                    |---------------------------|	↓  looking for 	
                                                    ↓  appropriate
                    -----------------------------   ↓  handler
 Forward exception  |Method without an exception|←←↙
                    |		  handler           |
                    |---------------------------|→→↘	
                                                    ↓  looking for 	
                                                    ↓  appropriate
                    -----------------------------   ↓  handler
 Catches some other |Method  with an  exception |←←↙
     exception      |		  handler           |
                    |---------------------------|
                    
                              main                                    
                                                                   
         Searching the call stack for the exception handler.                                                                                                                                    												 */

2.异常的要求(The Catch or Specify Requirement)

​ Java语言遵守The Catch or Specify Requirement,也就是说,可能抛出异常的代码必须符合下面列出的其中一种情况。

  • The Catch or Specify Requirement:

    • try语句来捕获异常。try语句必须要提供异常处理。

    • 声明方法时,指定它能够抛出异常。throws 语句。

    下面所示的三种异常类型,只有checked exceptions受The Catch or Specify Requirement约束

​ 有三种异常类型:checked exceptions,Error,RuntimeException所有的异常类型都是checked exceptions,除了那些用Error,RuntimeException或者它们的子类标示的异常外。Error,RuntimeException合称***unchecked exceptions***.

checked exception好的程序,应该能够预见这些异常,并且从能够从此异常中恢复。比如说java.io.FileReader类,我们传入文件名来调用构造函数创建此对象时,若文件名对应的文件本身并不存在,那么它会抛出java.io.FileNotFoundException异常。好的程序应该能够捕获此异常并提醒用户输入正确的文件名。

errorError及其子类程序外部的异常,程序并不能够预见或者从中恢复。例如,程序正常打开文件,正想要读取文件时,由于硬件或者系统的故障,导致程序并不能够读取文件。此时会抛出java.io.IOError错误。我们编写的程序可能会选择捕获此异常,从而提醒用户出现了此问题。但是程序打印调用栈并退出也是有意义的。

runtime exceptionRuntimeException及其子类程序中的异常,程序并不能够预见或者从中恢复。一般此类错误暗示着程序有bug,像逻辑错误,不合适的API调用。再拿java.io.FileReader类作为例子,假设我们传入构造器的文件名为null,那么构造器会抛出NullPointerException,程序捕获此异常对排除程序bug有帮助

​ 实践过程中,我们必须要捕获或者抛出checked exception,可以选择捕获或者抛出unchecked exception。如果不捕获或者抛出checked exception,编译时会出错。 看下面第3小姐的代码演示:

3.捕获并处理异常

​ 这里的第3节我们以下列源代码为一个基本类,在此基础上对其添加语句。

import java.io.*;
import java.util.List;
import java.util.ArrayList;

public class ListOfNumbers {

    private List<Integer> list;
    private static final int SIZE = 10;

    public ListOfNumbers () {
        list = new ArrayList<Integer>(SIZE);
        for (int i = 0; i < SIZE; i++) {
            list.add(new Integer(i));
        }
    }

    public void writeList() {
		// The FileWriter constructor throws IOException, which must be caught.
        PrintWriter out = new PrintWriter(new FileWriter("OutFile.txt"));
        for (int i = 0; i < SIZE; i++) {
            out.println("Value at: " + i + " = " + list.get(i));
        }
        out.close();
    }
}

​ 上面的代码片段,我们来分析分析:

// 必须捕获或者声明抛出IOException异常,否则会报错
PrintWriter out = new PrintWriter(new FileWriter("OutFile.txt"));

// get方法可能会抛出IndexOutOfBoundsException指针越界异常
// 它是RuntimeException,unchecked exception,我们可以选择不抛出异常。
int i = list.get(i);

// 现在我们查看下面的代码。
for (int i = -1; i <= SIZE; i++) {
    try{
        System.out.println("Value at: " + i + " = " + list.get(i));
    } catch (IndexOutOfBoundsException e){
        System.out.printf("i=%d数组越界了!!%n", i);
        e.printStackTrace();
    }
}

/* 程序输出为:

i=-1数组越界了!!
java.lang.ArrayIndexOutOfBoundsException: -1
	at java.util.ArrayList.elementData(ArrayList.java:422)
	at java.util.ArrayList.get(ArrayList.java:435)
	at basisLang.essentialclasses.ListOfNumbers.writeList(ListOfNumbers.java:27)
	at basisLang.essentialclasses.ListOfNumbers.main(ListOfNumbers.java:37)
Value at: 0 = 0
Value at: 1 = 1
Value at: 2 = 2
Value at: 3 = 3
Value at: 4 = 4
Value at: 5 = 5
Value at: 6 = 6
Value at: 7 = 7
Value at: 8 = 8
Value at: 9 = 9
i=10数组越界了!!
java.lang.IndexOutOfBoundsException: Index: 10, Size: 10
	at java.util.ArrayList.rangeCheck(ArrayList.java:657)
	at java.util.ArrayList.get(ArrayList.java:433)
	at basisLang.essentialclasses.ListOfNumbers.writeList(ListOfNumbers.java:27)
	at basisLang.essentialclasses.ListOfNumbers.main(ListOfNumbers.java:37)      */

3.1 try语句

try {
    //可能会抛出异常的,一行或者多行代码
}
catch //try后面必须要跟catch
  and
finally blocks . . .

3.2 catch语句

​ 基本语法:

try {
	//code
} catch (ExceptionType name) {

} catch (ExceptionType name) {

}

​ 下面改写上面的程序代码

// ExceptionType必须是一个继承自Throwable类的类
public class Throwable extends Object implements Serializable

// 我们对writeList函数进行改造,能够得到下面的代码
public void writeList() {
	int i = -1;
    try {
    	PrintWriter out = new PrintWriter(new FileWriter("OutFile.txt"));
        for (; i <= SIZE; i++) 
        	System.out.println("Value at: " + i + " = " + list.get(i));
    } catch (IndexOutOfBoundsException e){
    	System.out.printf("i=%d数组越界了!!%n", i);
    	e.printStackTrace();
    } catch (IOException e){
    	out.println("IOException!!");
       	e.printStackTrace();
    }
}

​ 考虑一个异常处理器处理多种类型的异常。在这种情况下,ex默认为final修饰

catch (IOException | SQLException ex) {
    logger.log(ex);
    throw ex;
}

3.3 finally语句

​ try-catch块存在的时候,finally总是会执行。

​ 但是,正当try或者catch语句被执行的时候,如果此时JVM关闭或者线程被中断/杀死,finally块是不会执行的。

​ finally能够有效地防止资源泄露,当然了,也可以使用try-with-resources语句来自动释放资源

finally {
    if (out != null) { 
        System.out.println("Closing PrintWriter");
        out.close(); 
    } else { 
        System.out.println("PrintWriter not open");
    } 
} 

3.4try-with-resources语句

try-with-resources语句能够确保每个资源能够在此语句的最后自动被关闭。

任何实现 java.lang.AutoCloseable接口的类(自然也包含了所有实现**java.io.Closeable**接口的类),都能够包含在try-with-resources中。

// java.lang.AutoCloseable
public interface AutoCloseable {
	void close() throws Exception;
}

// java.io.Closeable
public interface Closeable extends AutoCloseable {
    public void close() throws IOException;
}
static String readFirstLineFromFile(String path) throws IOException {
    try (BufferedReader br =
                   new BufferedReader(new FileReader(path))) {
        return br.readLine();
    }
}

​ 考虑我们之前的用法,下面的例子中,如果try,finally代码块都出现异常,那么此方法就会产生一个由finally代码块里面抛出的异常;而由try抛出的异常被抑制了。

​ 相对的,上面的readFirstLineFromFile代码,如果try-with-resources和try-block都产生异常,try-with-resources语句的异常将被抑制。被抑制的异常能够通过Throwable.getSuppressed()方法来获取。

Throwable[] getSuppressed()

static String readFirstLineFromFileWithFinallyBlock(String path)
                                                     throws IOException {
    BufferedReader br = new BufferedReader(new FileReader(path));
    try {
        return br.readLine();
    } finally {
        if (br != null) br.close();
    }
}

在try-with-resources语句中,catch,finally语句在资源被关闭后执行。

4.指定方法抛出的异常

​ 在某些情况下,我们希望抛出的异常交由调用栈的更上层来处理。

public void writeList() throws IOException, IndexOutOfBoundsException {
    //...
}

5.主动抛出异常

// 语法
throw someThrowableObject;

​ Java平台提供了大量的异常类,所有的异常类都是 Throwable 类的子类。

public class Throwable
	extends Object
	implements Serializable

​ 实际上,如果你是一个包开发者,你可能必须得去自己定义一套异常类来,让使用此包的用户区分错误到底是发生你开发的包,还是Java平台亦或者其它包。

​ 下面给出一个throw语句的例子

public Object pop() {
    Object obj;

    if (size == 0) {
        throw new EmptyStackException();
    }

    obj = objectAt(size - 1);
    setObjectAt(size - 1, null);
    size--;
    return obj;
}

​ 实际上,Throwable类及其子类的继承关系,我们可以通过如下图所示来理解:

															/*
使用电脑版看下面的文字"示意图",更好哦。	

														
    Object											
      ↑										
   Throwable	
   ↑       ↑
Error	   Exception		
           ↑       ↑
        . . .     RuntimeException
      ↗  ↑  ↖       ↗  ↑  ↖
     ..   ..  ..     ..   ..  ..
															
															*/

5.1 链式异常(Chained Exceptions)

应用程序中,我们常常通过抛出一个异常来回应一个异常处理。一个异常导致了另一个异常,我们称之为链式异常(Chained Exceptions

​ 下面是一些Throwable类中的构造器/方法,用来支持链式异常。下面的Throwable传参,都是导致当前异常的异常

Throwable getCause()
Throwable initCause(Throwable)
Throwable(String, Throwable)
Throwable(Throwable)

// 举个例子:
try {
	// ...
} catch (IOException e) {
    throw new SampleException("Other IOException", e);
}

堆栈轨迹(Stack Trace)。堆栈轨迹提供当前线程的执行历史信息,并列出异常发生时刻被调用的类名,方法名异常发生时,堆栈轨迹一般都是debug利器。

​ 下面代码展示如何使用getStackTrace()

try{
    // ...
}
catch (Exception cause) {
    StackTraceElement elements[] = cause.getStackTrace();
    for (int i = 0, n = elements.length; i < n; i++) {       
        System.err.println(elements[i].getFileName()//类名
            + ":" + elements[i].getLineNumber() //多少行
            + ">> "
            + elements[i].getMethodName() + "()");//方法名
    }
}

5.2 创建自己的异常类

啥时候创建自己的异常类?

  • 你需要的异常类,Java平台不提供。

  • 用户能够明显区分你的异常类和其它提供商提供的异常类,并且对用户挺有帮助的

  • 你的包是否独立?

    为了代码的可读性,添加``Exception到类的声明中,是一个挺好的实践。

Creating Exception Classes

6. Unchecked Exceptions的争议

两个问题:

  1. 为啥语言的设计者要强制一个方法必须要处理/向上抛出checked exceptions

  2. 在方法的API文档中,指出其可能抛出的异常;那么为啥不指出它可能抛出的运行时异常呢?

​ **一个指导:**如果异常能被合理地按预期从异常中恢复,那么我们认为它是checked exception。如果我们不能做任何事情来恢复异常,我们认为它是unchecked exception

Unchecked Exceptions — The Controversy

7.异常的优点

  • 将一般的业务性代码与错误处理代码分离
  • 将错误传给调用栈的更高层来处理(Propagating Errors Up the Call Stack)
  • 错误类型的分类与区分
// 优点1,错误处理与业务性代码分离

// 程序流程
readFile {
    open the file;
    determine its size;
    allocate that much memory;
    read the file into memory;
    close the file;
}

// 通过状态码来管理/处理错误
errorCodeType readFile {
    initialize errorCode = 0;
    
    open the file;
    if (theFileIsOpen) {
        determine the length of the file;
        if (gotTheFileLength) {
            allocate that much memory;
            if (gotEnoughMemory) {
                read the file into memory;
                if (readFailed) {
                    errorCode = -1;
                }
            } else {
                errorCode = -2;
            }
        } else {
            errorCode = -3;
        }
        close the file;
        if (theFileDidntClose && errorCode == 0) {
            errorCode = -4;
        } else {
            errorCode = errorCode and -4;
        }
    } else {
        errorCode = -5;
    }
    return errorCode;
}

// 通过异常来管理/处理错误
readFile {
    try {
        open the file;
        determine its size;
        allocate that much memory;
        read the file into memory;
        close the file;
    } catch (fileOpenFailed) { // -5
       doSomething;
    } catch (sizeDeterminationFailed) { // -3
        doSomething;
    } catch (memoryAllocationFailed) { // -2
        doSomething;
    } catch (readFailed) { // 0
        doSomething;
    } catch (fileCloseFailed) { -4
        doSomething;
    }
    // -1
}
// 优点2,错误传播
method1 {
    call method2;
}

method2 {
    call method3;
}

method3 {
    call readFile;
}

// 状态码
method1 {
    errorCodeType error;
    error = call method2;
    if (error)
        doErrorProcessing;
    else
        proceed;
}

errorCodeType method2 {
    errorCodeType error;
    error = call method3;
    if (error)
        return error;
    else
        proceed;
}

errorCodeType method3 {
    errorCodeType error;
    error = call readFile;
    if (error)
        return error;
    else
        proceed;
}

// 异常处理
method1 {
    try {
        call method2;
    } catch (exception e) {
        doErrorProcessing;
    }
}

method2 throws exception {
    call method3;
}

method3 throws exception {
    call readFile;
}
// 优点3. 异常类型分类

// File相关
catch (FileNotFoundException e) {
    ...
}

// I/O相关
catch (IOException e) {
    ...
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值