异常(Exceptions)
java
编程语言使用异常来处理错误(error
)和其他的异常事件。本节课描述何时、如何使用异常。
概览
什么是异常?
执行一个程序时,打断正常的指令流程的事件就是异常。
The Catch or Specify Requirement
本节涵盖如何捕获(Catch
)或解决异常。讨论包含try
,Catch
和finally
块,以及异常链(chain
)和logging
。
如何抛出异常
本章节包括throw
语句和Throwable
类以及其子类。
try-with-resource
语句
本章节描述try-with-resource
语句,即一个声明一个或多个资源的try
语句。资源是一个必须在程序结束之后关闭的对象。语句try-with-resources
保证在语句结束后每个资源都会被释放掉。
Unchecked Exception
- 争议
RuntimeException
的子类阐述了UncheckedException
的正确与错误用法。
异常的优势
与传统的错误管理技术相比,使用异常来管理错误占据更多的优势。
什么是异常
异常是特殊事件(exceptional event
)的简称。异常是在程序执行过程中所发生的打断程序指令流程的事件。
当错误发生在方法的内部,方法会创建一个对象并把它交给系统(runtime system
)。这个对象叫做异常对象exception object
,它包含关于错误的信息。包括错误发生时程序的状态和类型。创建一个异常对象并将它递交给系统的过程称之为抛出一个异常(throwing an exception
)。
方法抛出一个异常以后,运行时系统尝试着找到某种东西来处理它。可能处理异常的东西的集合是有序的方法列表,它们被调用以进入错误发生的方法内部。有序的方法列表被称为调用栈(call stack
)。
运行时系统搜索调用栈以找出可以处理异常拥有代码块的方法。这个代码块被称为异常处理器(exception Handler
)。搜素开始的时机是错误发生的时候,搜索的顺序是调用栈的反顺序。当合适的处理器被发现,运行时系统会递交异常给该处理器。当被抛出的异常对象的类型与可处理异常的处理器类型想匹配时,我们认为该异常处理器是合适的。
异常处理器被选择的过程被称为捕获异常。如果运行时系统在调用栈搜索了一遍没有找到合适的异常处理器,如下图所示,则运行时系统停止。
The Catch or Specify Requirement
有效的java
编程语言代码必须遵循Catch
或者Specity requirement
。这意味着代码中抛出的异常必须以以下两种方式之一结束:
try
语句捕获异常。try
必须为异常提供一个处理器。- 一种可以指定它抛出异常的方法。方法必须提供
throws
语句以列出异常。
代码如果没有遵循Catch
或者Specity requirement
,则编译不通过。
并非所有的异常都被 Catch
或者Specity requirement
约束住。为了了解为什么,我们需要学习三个基本的异常分类,仅其中之一需要遵循这种约束。
三种类型的异常
第一种异常类型被称为受查异常(checked exception
)。一个编写良好的程序应该预知特殊的异常,并从中恢复。例如,以下程序在正常情况下用户应该输入一个存在的文件名称,但是如果文件不存在,好的程序应该捕获这个异常并通知用户这种错误。
//在一个实际的文件上创建一个链接connection
public FileInputStream(File file) throws FileNotFoundException {
String name = (file != null ? file.getPath() : null);
SecurityManager security = System.getSecurityManager();
if (security != null) {
security.checkRead(name);
}
if (name == null) {
throw new NullPointerException();
}
if (file.isInvalid()) {
throw new FileNotFoundException("Invalid file path");
}
fd = new FileDescriptor();
fd.incrementAndGetUseCount();
this.path = name;
open(name);
}
受查异常是遵循Catch
或者Specity requirement
约束的。除了Error
和RuntimeException
以及它们的子类,所有的异常都是受查异常。
第二种异常是Error
。这种异常条件是程序外部引起的,程序通常不能从中预知并恢复。例如程序打开读取一个文件,但因硬件故障或者系统故障导致文件不可读。
第三种异常是运行时异常runtime exception
。这些异常条件是程序内部的,程序通常可以从中预知并恢复。这种异常通常表现为程序bugs,例如逻辑错误或者使用不恰当的API
。例如前面所举例的程序接收用户输入的文件名,然后打开读取里面的内容,这时,如果用户传入的文件名是null
,则程序抛出空指针异常。
运行时异常不遵循Catch
或者Specity requirement
约束,它是Runtiem exception
和它的子类。
Errors
和runtime exception
合称为非受查异常。
绕过Catch or Specify
有些编程人员认为Catch or Specify
在异常体系中是一个严重的缺陷,应当用非受查异常替代受查异常。通常情况下,这使不建议的。
捕获并处理异常
本章节讨论如何使用三种异常处理组件—try,catch,finally
块—来编写异常处理器。然后介绍java SE 1.7
引入的try-with-resources
。try-with-resources
尤其适合Closeable
资源,例如Stream
。
// Note: This class will not compile yet.
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++) {
// The get(int) method throws IndexOutOfBoundsException, which must be caught.
out.println("Value at: " + i + " = " + list.get(i));
}
out.close();
}
}
new FileWriter("OutFile.txt")
是调用了构造函数。如果文件不存在,则会抛出一个IOException
。list.get(i)
可能会抛出一个IndexOutOfBoundsException
,如果其index
值小于零或者大于SIZE
时会抛出异常。
编译会出错因为IOException
是受查异常,而IndexOutOfBoundsException
是非受查异常。
try
语句
构造异常处理器的第一个步骤是在可能抛出异常的代码块上用try
语句包围住。如下所示:
try {
code
}
catch and finally blocks . . .
code
所包含的代码可能会抛出一个或者几个异常。
你可以为每一行可能抛出异常的代码用try
语句包裹起来,也可以用一个try
语句将所有可能抛出异常的语句全部包裹起来,如下所示:
private List<Integer> list;
private static final int SIZE = 10;
public void writeList() {
PrintWriter out = null;
try {
System.out.println("Entered try statement");
out = new PrintWriter(new FileWriter("OutFile.txt"));
for (int i = 0; i < SIZE; i++) {
out.println("Value at: " + i + " = " + list.get(i));
}
}
catch and finally blocks . . .
}
Catch
语句
你可以直接在try
块后面用一个或多个catch
块来和异常处理器关联起来。在try
块之后,catch
块钱不能有任何代码:
try {
} catch (ExceptionType name) {
} catch (ExceptionType name) {
}
每一个catch
块是一个异常处理器,它所处理的异常类型是由参数(ExceptionType
)所决定。ExceptionType
必须是Throwable
类或其子类的名称。
catch
块包含的代码会在异常处理器调用时执行。当所抛出的异常类型ExceptionType
第一次与调用栈内的相匹配,运行时系统便会执行该异常处理器。如果所抛出的异常对象可以正确赋值给异常处理器参数,则系统认为它们的异常类型相匹配。
以下是两个异常处理器(exception handler
):
try {
} catch (IndexOutOfBoundsException e) {
System.err.println("IndexOutOfBoundsException: " + e.getMessage());
} catch (IOException e) {
System.err.println("Caught IOException: " + e.getMessage());
}
异常处理器不仅可以打印错误信息或者停止程序的执行,它还可以进行错误恢复,提示用户做出决定,或者使用异常链将错误传播到更高一层的处理器。
用一个异常处理器捕获一种或者多种类型的异常
在java SE 7
及其之后,一个catch
块可以处理一种或者多种类型的异常。这种特性可以减少代码的冗余以及减少过度捕获更加宽泛的异常。
在catch
语句中,指定可以处理的异常类型,并用垂直短线来分割
catch (IOException|SQLException ex) {
logger.log(ex);
throw ex;
}
注意如果一个catch
块处理多种异常类型,catch
参数暗示着final
。在这个例子中,ex
是final
的,是故不能赋予它新的值。
finally
块
finally
块在try
语句退出时总会执行。这保证了即使预料之外的异常发生了finally
块仍可执行。但finally
块的作用不仅局限于此,它防止因程序员的疏忽大意使用return,continue,break
而绕过清理代码。即使没有异常发生,将清理代码放到finally
块内总会是一种好的实践。
注意:当正在执行try或者catch
块时,JVM退出finally
块可能不会被执行。同理当线程被打断也是这样的。
writeList
方法有3种方式可以跳出try
块:
new FileWriter
语句执行失败并抛出IOException
.list.get(i)
执行失败,抛出IndexOutOfBoundsExceptions
。- 整个代码执行成功,
try
块正常退出。
运行时系统总会执行finally
内的语句而不管try
语句内发生什么,所以finally
块内是执行清理的最佳地方。
finally {
if (out != null) {
System.out.println("Closing PrintWriter");
out.close();
} else {
System.out.println("PrintWriter not open");
}
}
finally
块是一个防止资源泄漏的一个重要的工具。当关闭一个文件或者回收资源,将代码放到finally
块内保证资源总是被回收。
The try-with-resources Statement
try-with-resources
语句是一个try
语句,它声明了一个或者多个资源。资源是一个对象,它必须在程序执行完后关闭掉。try-with-resources
语句保证每一个资源会在语句的最后被关闭掉。任何实现java.lang.AutoCloseable
接口以及实现java.io.Closeable
的对象都可以被当做一种资源。
以下的例子从文件中读取第一行文字。它用了BufferedReader
的实例从文件中读取数据。BufferedReader
是一种资源,因此程序结束后需要关掉。
static String readFirstLineFromFile(String path) throws IOException {
try (BufferedReader br =
new BufferedReader(new FileReader(path))) {
return br.readLine();
}
}
在这个例子中,声明在try-with-resources
语句内的是BufferedReader
。声明的语句是在try
之后小括号内的。类BufferedReader
在java SE 7
及其之后实现了接口java.lang.AutoCloseable
。因为BufferedReader
是声明在try-with-resources
语句内的,所以无论try
语句发生什么(执行成功或者抛出异常),该资源都会被关闭掉。
在java SE 7
之前,你可以使用一个finally
块来保证资源是关闭的,无论try
是执行成功还是抛出异常。以下用是用finally
块来关闭资源而不是使用try-with-resources
语句。
static String readFirstLineFromFileWithFinallyBlock(String path)
throws IOException {
BufferedReader br = new BufferedReader(new FileReader(path));
try {
return br.readLine();
} finally {
if (br != null) br.close();
}
}
然而,在该例子中,如果方法readLine
和close
都抛出异常,则方法readFirstLineFromFileWithFinallyBlock
只会抛出finally
块内的异常,而try
块内的异常被屏蔽掉了(supressed
)。同理try
块和try-with-resources
同时抛出异常,则只会抛出try
块内的异常。
你可能在try-with-resources
语句内声明一种或者多种资源,如下所示:
java.nio.charset.Charset charset =
java.nio.charset.StandardCharsets.US_ASCII;
java.nio.file.Path outputFilePath =
java.nio.file.Paths.get(outputFileName);
// Open zip file and create output file with
// try-with-resources statement
try (
java.util.zip.ZipFile zf =
new java.util.zip.ZipFile(zipFileName);
java.io.BufferedWriter writer =
java.nio.file.Files.newBufferedWriter(outputFilePath, charset)
) {
// Enumerate each entry
for (java.util.Enumeration entries =
zf.entries(); entries.hasMoreElements();) {
// Get the entry name and write it to the output file
String newLine = System.getProperty("line.separator");
String zipEntryName =
((java.util.zip.ZipEntry)entries.nextElement()).getName() +
newLine;
writer.write(zipEntryName, 0, zipEntryName.length());
}
}
}
在本例子中,try-with-resources
内声明的两个资源用分号(;
)分隔开,无论程序执行成功与否,BufferedWriter
和ZipFile
对象的close
方法会依次被调用,其方向是资源创建顺序的反顺序。
下面的例子使用try-with-resources
语句关闭一个java.sql.Statement
对象
public static void viewTable(Connection con) throws SQLException {
String query = "select COF_NAME, SUP_ID, PRICE, SALES, TOTAL from COFFEES";
try (Statement stmt = con.createStatement()) {
ResultSet rs = stmt.executeQuery(query);
while (rs.next()) {
String coffeeName = rs.getString("COF_NAME");
int supplierID = rs.getInt("SUP_ID");
float price = rs.getFloat("PRICE");
int sales = rs.getInt("SALES");
int total = rs.getInt("TOTAL");
System.out.println(coffeeName + ", " + supplierID + ", " +
price + ", " + sales + ", " + total);
}
} catch (SQLException e) {
JDBCTutorialUtilities.printSQLException(e);
}
}
注意:try-with-resources
语句也可以有catch
和finally
块,但是它们是在资源关闭之后才调用的。
被压制的异常(Suppressed Exceptions
)
异常可以被try-with-resources
块内代码所抛出。try
块和try-with-resources
同时抛出异常,则只会抛出try
块内的异常,而try-with-resources
抛出的异常被忽略(Suppressed
)了。
实现AutoCloseable
和Closeable
的类
接口Closeable
接口继承自AutoCloseable
。Closeable
内的close
方法抛出IOException
而AutoCloseable
内的close
方法抛出Exception
,一般来讲AutoCloseable
的子类可以重写close
方法来抛出具体的异常,如IOException
或者干脆不抛出异常。
汇集一起(Put It All Together
)
public void writeList() {
PrintWriter out = null;
try {
System.out.println("Entering" + " try statement");
out = new PrintWriter(new FileWriter("OutFile.txt"));
for (int i = 0; i < SIZE; i++) {
out.println("Value at: " + i + " = " + list.get(i));
}
} catch (IndexOutOfBoundsException e) {
System.err.println("Caught IndexOutOfBoundsException: "
+ e.getMessage());
} catch (IOException e) {
System.err.println("Caught IOException: " + e.getMessage());
} finally {
if (out != null) {
System.out.println("Closing PrintWriter");
out.close();
}
else {
System.out.println("PrintWriter not open");
}
}
}
//output
Entering try statement
Caught IOException: OutFile.txt
PrintWriter not open
上面代码中,有三种方式可以退出try
块,以下是其中之二:
try
块内代码抛出异常。即new FileWriter
语句或者list.get(i)
抛出异常。- 代码执行正常。
第一种情况:抛出异常
new FileWriter
抛出异常,比如程序不能创建指定文件。
当抛出IOException
后,运行时系统停止执行try
语句,方法调用不能执行完。运行系统开始在方法调用栈顶端依次往下找合适的异常处理器。在本例子中,寻找的过程为:
FileWriter
构造器在方法调用栈的顶端,但是该构造器没有合适的异常处理器,于是找到下个方法。writerList
方法,有两个异常处理器IOException
,IndexOutOfBoundsException
。- 运行时系统按顺序判断所抛出异常与异常处理器是否匹配,先找到的是
IndexOutOfBoundsException
,不匹配,往下继续找。 IOException
正好匹配。运行时系统停止搜索,然后执行catch
块。- 异常处理器执行完毕后,运行时系统将控制递交给
finally
块。然后执行finally
块内代码,而不管上面Catch
块的异常。因FileWriter
没有被打开,所以并不会调用close
方法。 finally
块被执行完毕好后,程序继续执行finally
块后的第一条语句。
public void writeList() {
// PrintWriter out = null;
// try {
// System.out.println("Entering" + " try statement");
// out = new PrintWriter(new FileWriter("OutFile.txt"));
for (int i = 0; i < SIZE; i++) {
out.println("Value at: " + i + " = " + list.get(i));
}
} catch (IndexOutOfBoundsException e) {
System.err.println("Caught IndexOutOfBoundsException: "
+ e.getMessage());
// } catch (IOException e) {
// System.err.println("Caught IOException: " + e.getMessage());
// } finally {
// if (out != null) {
System.out.println("Closing PrintWriter");
out.close();
}
// else {
// System.out.println("PrintWriter not open");
// }
}
}
上面注释掉的代码是情况一所执行的代码情况。
情况二:try
块正常退出
执行情况如下代码注释处
public void writeList() {
// PrintWriter out = null;
// try {
// System.out.println("Entering try statement");
// out = new PrintWriter(new FileWriter("OutFile.txt"));
// for (int i = 0; i < SIZE; i++)
// out.println("Value at: " + i + " = " + list.get(i));
}
// catch (IndexOutOfBoundsException e) {
// System.err.println("Caught IndexOutOfBoundsException: "
// + e.getMessage());
//
// } catch (IOException e) {
// System.err.println("Caught IOException: " + e.getMessage());
// } finally {
// if (out != null) {
// System.out.println("Closing PrintWriter");
// out.close();
// }
else {
System.out.println("PrintWriter not open");
}
// }
}
指定方法所抛出的异常
上一节课阐述了如果编写异常处理器。有时,仅仅捕捉代码块内所发生的异常是合适的,但是更好的办法是让更上一层的调用栈来处理异常。例如,如果你把ListOfNumber
类作为包的一部分给其他人用,那可能就无法满足一部分用户的需求(即每个用户处理异常的方式不一样,你在底层直接给解决掉了,那其它人只能重写方法了)。
可以如下例子所示把异常抛给上一层调用栈处理
public void writeList() throws IOException, IndexOutOfBoundsException {
由于list.get(i)
所抛出的异常IndexOutOfBoundsException
是非受查异常,所以可以简写如下
public void writeList() throws IOException{
如何抛出异常
在你捕获异常之前,某些代码必须要抛出一个异常。任何代码可以抛出异常,如你编写的代码,java
平台提供的代码等。无论是什么抛出异常,总会用到throw
语句来抛出。
正如你所注意到的那样,java
平台提供了很多异常类,所有的类都是Throwable
的子孙类,并允许程序对抛出的异常种类加以区分。
throw
语句
所有的方法使用throw
抛出异常。throw
语句需要一个参数:一个throwable
的对象。
throw someThrowableObject;
你可以抛出任意继承自throwable
接口的异常类,如
public Object pop() {
Object obj;
if (size == 0) {
throw new EmptyStackException();
}
obj = objectAt(size - 1);
setObjectAt(size - 1, null);
size--;
return obj;
}
Throwable
类及其子类
如上图所示throwable
有两直接的子孙类Error
和Exception
。
Error
类
发生Java虚拟机中的动态链接失败或其他硬故障时,虚拟机就会抛出Error
。
Exception
类
大多数程序抛出和所捕获的异常都是派生自Exception
类。异常表明一个问题的产生,但是不是一个严重的系统问题。
RuntimeException
表明错误地使用API
。
异常链Chained Exceptions
一个应用程序总会用抛出异常的方式来回应另一个异常。即第一个异常引发了第二个异常。知道何时一个异常引发另一个异常有时候非常有用处。异常链帮助软件开发者做这样的事情。
Throwable
内的方法和构造器支持异常链:
Throwable getCause()
Throwable initCause(Throwable)
Throwable(String, Throwable)
Throwable(Throwable)
下面的例子展示了如何使用一个异常链
try {
} catch (IOException e) {
throw new SampleException("Other IOException", e);
}
IOException
被抛出后,利用原始的异常对象为参数创建出一个新的异常SampleException
,因此异常链被跑到更高层次的异常处理器。
访问堆栈跟踪信息
假设现在更高层次的异常处理器想要以自己的格式导出堆栈跟踪信息。
定义:当异常发生时了,堆栈跟踪提供当前线程执行类和方法名称的列表信息,它是一种非常有用的调试工具。
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() + "()");
}
}
Logging API
把所要打印输出的信息放到文件中。
try {
Handler handler = new FileHandler("OutFile.log");
Logger.getLogger("").addHandler(handler);
} catch (IOException e) {
Logger logger = Logger.getLogger("package.name");
StackTraceElement elements[] = e.getStackTrace();
for (int i = 0, n = elements.length; i < n; i++) {
logger.log(Level.WARNING, elements[i].getMethodName());
}
}
创建异常类
当面对选择抛出何种类型的异常时,你可以自己编写亦可以使用别人写好的—java
平台提供了很多异常类可供选择。如果以下的任何一个问题的答案是yes
的话,那么就编写自己的异常类,否则你应该使用已存在的异常类。
- 你所需要的类型不存在与
java
平台内? - 如果用户能够将您的异常与其他供应商编写的区分开来,它会对用户有所帮助吗?
- 你的代码抛出多于一个相关的异常么?
- 如果你使用其他人提供的异常,那用户可以访问这些异常么?类似的问题是,你所编写的包是独立存在的还是自给自足的?
一个栗子
假设你编写一个链表类,该类包含以下方法:
objectAt(int n)
,返回列表中位置是n的对象,如果n<0
或者大于列表元素的个数那会抛出一个异常。firstObject()
,返回列表中的第一个元素。如果列表不包含任何元素则抛出异常。indexOf(Object o)
,返回指定对象在列表中的索引,假如入参不存在与列表中,则抛出异常。
该链表类可以抛出多种异常,如果只用一个异常处理器来捕获所有的异常会非常方便。
选择一个父类
任意一个Exception
的子类都能充当LinkedListException
的父类。然而这些子类不是与LinkedListException
无关就是太过具体,因此选择Exception
作为其父类比较合适。
非受查异常的争议
因为java
编程语言不要求方法捕获非受查异常(RuntimeException
, Error
, 以及它们的子类),因此编程者可能视图让编写的代码只抛出受查异常或者只抛出非受查异常。这两种捷径都允许程序员编写出编译通过或者不需要捕获异常的代码。这看似十分方便,但这种回避了Catch
或者specify
需求的捷径会给使用你编写类的人带来问题。
为何设计者决定强制方法指定所有未捕获的可以在其范围内抛出的异常?任何异常是方法的公共编程接口的一部分。调用方法时必须知道方法所能抛出的异常,这样使用者才能决定如何使用它们。
将所有的runtime
异常加入到方法的声明当中会降低程序的间接性。因此编译器不需要你去捕获或者指定运行时异常。
一般而言,不要简单的抛出一个RuntimeException
或者它的子类,因为你不想在为方法指定异常时受到打扰。
这是指南的底线:如果想让程序从异常中恢复,那么把该异常指定为受查异常;否则指定为非受查异常。
异常的优势
优势一:将错误处理代码和业务代码分离
假如要做以下的事情
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) {
doSomething;
} catch (sizeDeterminationFailed) {
doSomething;
} catch (memoryAllocationFailed) {
doSomething;
} catch (readFailed) {
doSomething;
} catch (fileCloseFailed) {
doSomething;
}
}
优势二:将异常传播到上一层调用栈
假如方法是嵌套调用的,如下所示
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
处处理异常即可,其他方法可以不管
method1 {
try {
call method2;
} catch (exception e) {
doErrorProcessing;
}
}
method2 throws exception {
call method3;
}
method3 throws exception {
call readFile;
}
优势三:可以对异常类型进行分组和区分
//没有子类的异常 可与其他异常加以区分
catch (FileNotFoundException e) {
...
}
//所有IO的异常,即异常分组
catch (IOException e) {
// Output goes to System.err.
e.printStackTrace();
// Send trace to stdout.
e.printStackTrace(System.out);
}
总结
程序可以使用异常来表明发生了错误。为了抛出一个异常,使用throw
语句并为它提供一个异常对象—Throwable
的子孙。
一个程序可以使用try catch finally
的组合来捕获处理异常。
错误对象所属的class
表明了所抛出异常对象的类别。