异常处理
异常的基本概念
Java的基本理念是“结构不佳的代码不能运行。”
“我们的目标是:没有BUG!”
当我们开始写代码时,异常和错误就伴随着我们。为了写出逻辑正确的、健壮的系统,程序员们绞尽脑汁,让代码变得得尽善尽美。但是即便是这样,在系统的运行过程中仍然会遇到一些问题。程序员们感到很苦恼,因为很多问题不是靠代码能够避免的,比如:客户输入数据的格式,读取文件是否存在,网络是否始终保持通畅等等。
在讲异常处理之前,首先我们要明确一个概念:什么是异常。Exception这个词有“我对此感到意外”的意思,因此在有些材料中,也将其称作“例外”。在Java语言中,==将程序执行中发生的不正常情况称为“异常”(语法错误和逻辑错误不是异常)。==
异常是不可避免的,异常的处理机制可以确保我们程序的健壮性,提高系统可用率。因此我们通过学习异常处理,可以在我们的项目中受益明显。
当程序执行中发生的不正常情况时,我们可以认为是异常情形。异常情形是指阻止当前方法或者作用域继续执行的问题。 当出现异常情形,也就是在当前的环境下程序无法正常运行下去,也就是说程序已经无法来正确解决问题了时,就是告诉你:这里可能会或者已经产生了错误,您的程序出现了不正常的情况,可能会导致程序失败!这时程序所就会从当前环境中跳出,并抛出异常。
总的来说异常处理机制就相当于我们保护一段代码的 “底线” ,当程序发生异常时,它强制终止程序运行,记录异常信息并将这些信息反馈给我们,由我们来确定是否处理异常,并且将程序“恢复”到某个已知的安全状态。
异常的体系结构
==Throwable是java语言中所有错误和异常的父类。== Throwable作为异常处理家族的教父,手下有两名干将:Error和Exception。下面放出家族的成员图谱:
Java程序在执行过程中所发生的异常事件可分为两类:
- Error: Java虚拟机无法解决的严重问题。如:JVM系统内部错误、资源耗尽等严重情况。一般不编写针对性的代码进行处理。
-
Exception: 其它因编程错误或偶然的外在因素导致的一般性问题,可以使用针对性的代码进行处理 。例如:
空指针访问
试图读取不存在的文件
网络连接中断
针对于以上两种错误,一般有两种解决方法:
一是遇到错误就终止程序的运行。也就是出现错误以后什么都不做,自我放弃等待崩溃。
另一种方法是由程序员在编写程序时,就考虑到错误的检测、错误消息的提示,以及错误的处理。
很显然,我们应该使用第二种方法来处理异常。
异常有按照发生时间分为为编译时异常和运行时异常:
我们编写的叫.java文件,通过javac命令生成字节码文件.class(==这个过程成为编译==),字节码文件再通过java命令来运行,得到一个结果(==这个过程称为运行==)。
1.==运行时异常==(程序运行起来以后才发现的错误)
是指编译器不要求强制处置的异常。一般是指编程时的逻辑错误,是程序员应该积极避免其出现的异常。java.lang.RuntimeException类
及它的子类都是运行时异常。
对于这类异常,可以不作处理,因为这类异常很普遍,若全处理可能会对程序的可读性和运行效率产生影响。
2.==编译时异常==(我们在IDE中写代码时,在代码前面打红叉,也就是编译时不通过程序)
是指编译器要求必须处置的异常。即程序在运行时由于外界因素造成的一般性异常。编译器要求java程序必须捕获或声明所有编译时异常。
对于这类异常,如果程序不处理,可能会带来意想不到的结果。
处理异常的最佳时期就是在程序编译时进行处理。但有的错误只有在运行时才会发生。比如:除数为0,数组下标越界等。那么哪些是编译时异常?哪些是运行时异常呢?我们在族谱里找一下:==RuntimeException是运行时异常,其他的都是编译时异常==。
常见的运行时异常和编译时异常
运行时出现的异常:
- 数组下标越界的异常:ArrayIndexOutOfBoundsException
public void text1(){
int[] i = nerw int[10];
Sysout.out.println(i[10]);
//显然越界了
}
- 算术异常:ArithmeticException
public void text2() {
int i = 10;
Sysout.out.println(i / 0);
}
- 类型转换异常:ClassCastException
public void test3(){
Object obj = new Date();
String str = (String)obj;
//Date类型不能转化为String类型
}
- 空指针异常:NullPointerException
public void main test4(){
Person p = new Person();
p = null;
Sysout.out.println(p.toString());
}
编译时异常:
public void test5(){
FileInputStream fis = new FileInputStream(new File("Hello.txt"));
int b;
while((b = fis.read()) != -1){
System.out.println((char)b);
}
fis.close;
}
以上代码在编译时就会出现异常(红叉,红色波浪线),即使Hello.txt文件存在。
这里为什么会出现编译时异常呢?因为在程序编译的时候,是不知道这个文件到底存不存在的,只有在执行以后才能知道。因此要在编译时,显式的进行处理。 其中一种处理方法如下:
public void test5() throws Exception{
FileInputStream fis = new FileInputStream(new File("Hello.txt"));
int b;
while((b = fis.read()) != -1){
System.out.println((char)b);
}
fis.close;
}
异常的处理
Java采用了非常完美的异常处理机制,将异常处理的程序代码集中在一起,与正常的程序代码分开,使得程序简洁,并易于维护。
Java提供的是异常处理的抓抛模型。或者从顺序上说是“抛抓模型”:
1. 抛: ==一旦执行过程中出现异常,就会抛出一个异常类的对象。(自动抛 vs 手动抛(throw new 异常类名))==
当我们执行代码时,一旦出现异常,就会在异常的代码处生成一个异常类型的对象,并将此对象抛出。这个过程称为抛出(throw)异常。一旦抛出此异常类的对象,那么程序就终止执行。
异常对象有两种生成方式生成:一种是自动抛(由虚拟机自动生成),另一种是手动抛(由开发人员手动创建利用throw关键字)
如果一个方法内抛出异常,==该异常对象会被抛给调用者方法中处理==。如果异常没有在调用者方法中处理,它继续被抛给这个调用方法的上层方法。 这个过程将一直继续下去,直到异常被处理。如果一个异常回到main()方法,并且main()也不处理,则程序运行终止。
2. 抓: ==异常的处理,有两种方式(①try-catch-finally
②throws
+ 异常的类型)==
抓住上一步抛出来的异常类的对象,抓的过程就是异常处理的方式。 Java提供了两种方式来处理一个异常类的对象。 下面分别介绍两种异常处理方式:
处理方式一:try-catch
try-catch
是最实用的主动异常处理方法,在异常中try
块包含着可能出现异常的代码块,catch
块捕获异常后对异常进行处理。
try{
...... //可能产生异常的代码
}
catch( ExceptionName1 e ){
...... //当产生ExceptionName1型异常时的处置措施
}
catch( ExceptionName2 e ){
...... //当产生ExceptionName2型异常时的处置措施
}
finally{
...... //无论是否发生异常,都无条件执行的语句
}
try
块:
通过在方法中设置一个特殊的块来捕获异常,在这个块里“尝试”各种可能产生异常的方法调用,因此称为try
块。
异常处理catch
:
每个被捕获的异常需要在异常处理程序中得到相应的处理。异常处理程序紧跟在try
之后,以关键字catch
表示。当异常被抛出时,异常处理机制将负责搜寻参数与异常类型相匹配的第一个处理程序。然后进入到catch
中执行。一旦catch
语句执行结束,则处理程序的查找过程结束。
注:
-
try
内声明的变量,类似于局部变量,出了tyr{}
语句,就不能被调用了。 -
catch
和finally
都是可选的。 -
catch
语句内部是对异常对象的处理:getMessage();
printStackTrace();
- 可以有多个
catch
语句,try
中抛出的异常类对象从上往下去匹配catch
中的异常类的类型,一旦满足就执行catch
中的代码,执行完就跳出气候的多条catch
语句。 - 如果异常处理了,那么其后面的代码继续执行。
- 对于运行时一场来说,可以不显式的进行处理。(因为比较常见)
对于编译时异常来说,必须显式的进行处理。(如果不处理,运行不了) -
finally
后是一定要执行的代码,不管try
中、catch
中是否有异常未被处理。 -
try-catch
是可以嵌套的
处理方法二:throws
声明抛出异常是Java中处理异常的第二种方式,==即在方法的声明处,显式地通过throws
关键字抛出该异常对象的类型==。但是这种方式是一种逃避式的方式,只是将异常的对象逐层向上抛给方法的调用者(直至main函数),最终还是要通过try-catch函数进行处理的。
如果一个方法(中的语句执行时)可能生成某种异常,但是并不能确定如何处理这种异常,则此方法应显示地声明抛出异常,表明该方法将不对这些异常进行处理,而由该方法的调用者负责处理。
在方法声明中用throws语句可以声明抛出异常的列表,throws后面的异常类型可以是方法中产生的异常类型,也可以是它的父类。
import java.io.*;
public class TestException{
public static void main(String[] args){
TestException t = new TestException();
//在调用方法时,还是要通过try-catch来处理
try{
t.readFile();
}catch(IOException e){ }
}
public void readFile() throws IOException {
//显式的通过throws抛出一下异常的父类IOException,将其抛给该方法的调用者,即main中
FileInputStream in=new FileInputStream("myfile.txt");
int b;
b = in.read();
while(b!= -1) {
System.out.print((char)b);
b = in.read();
}
in.close();
}
创建自定义异常
Java确实给我们提供了非常多的异常,但是异常体系是不可能预见所有的希望加以报告的错误,所以Java允许我们自定义异常来表现程序中可能会遇到的特定问题,总之就是一句话:我们不必拘泥于Java中已有的异常类型。
Java自定义异常的使用要经历如下四个步骤:
- 定义一个类继承Throwable或其子类。
- 添加构造方法(当然也可以不用添加,使用默认构造方法)。
- 在某个方法类抛出该异常。
- 捕捉该异常。
现在我们要看下面一个例子:
package char12;
//自定义一个异常类:MyException
class MyException extends Exception {
public MyException() {}
public MyException(String msg) {
super(msg);
}
}
public class FullConstructors {
//创建两个方法,每个方法手动抛出一个异常
public static void f() throws MyException {
System.out.println("Throwing MyException from f()");
throw new MyException();
}
public static void g() throws MyException {
System.out.println("Throwing MyException from g()");
throw new MyException("Originated in g()");
}
//在main方法中处理异常
public static void main(String[] args) {
try {
f();
}catch (MyException e) {
e.printStackTrace();
}
try {
g();
} catch(MyException e) {
e.printStackTrace(System.out);
}
}
}
/*OutPut:
char12.MyException
Throwing MyException from f()
at char12.FullConstructors.f(FullConstructors.java:15)
Throwing MyException from g()
char12.MyException: Originated in g()
at char12.FullConstructors.main(FullConstructors.java:24)
at char12.FullConstructors.g(FullConstructors.java:19)
at char12.FullConstructors.main(FullConstructors.java:29)
*///:~
下面进行代码分析:
首先自定义一个异常类:MyException,其中包含了两个构造器。对于第二个构造器使用了super关键字,其作用是明确的调用了其父类的构造器,它接受一个字符串作为参数。
在方法f()中,手动抛出了一个异常throw new MyException();
,并且在声明时,通过throws
关键字将异常抛给去其方法的调用者处理。
在方法g()中,同样手动抛出了一个异常throw new MyException("Originated in g()");
,这个异常与第二个构造器想匹配,其作用相当于返回字符串参数。
方法将异常抛给main()方法通过try-catch
进行处理,在异常处理程序中,调用了在Throwable
类声明的printStackTrace()
方法。printStackTrace()
方法的意思是:在命令行打印异常信息在程序中出错的位置及原因,信息被输出到标准错误流,如果是printStackTrace(Sysout.out);
则信息被发送到了System.out,并自动被捕获和显式在输出中。
捕获所有异常
有时候我们要面对多个异常,如果怕我们嫌一个一个地来捕获异常太麻烦,我们可以只写一个异常处理程序来捕获所有异常。通过捕获异常类型的父类Exception
可以做到只写一个异常处理程序来捕获所有类型的异常。
catch(Exception e) {
Sysout.out.println("Caught an exception");
}
我们最好把它放在处理程序列表的末尾,以防它抢在其他处理程序之前先把异常捕获了。
但是由于我们用了具体异常类的父类,所以它不会包含太多具体的信息,因此我们通过继承异常类家族教父Throwable
的方法,来为我们提供一些具体的信息。
Exception可以调用它从其基类Throwable继承的方法:
-
String getMessage()
和String getLocalizedMessage()
方法用来获取详细信息。 -
String toString()
返回对Throwable
的简单描述,如果有详细信息的话,也会包括在内。 -
void printStackTrace()
void printStackTrace(PrintStream)
和void printStackTrace(java.io.PrintWriter)
打印Throwable
和Throwable
的调用栈轨迹。 -
Throwable fillInStackTrace()
用于在Throwable
对象的内部记录栈帧的当前状态。在程序重新抛出错误或异常时很有用。 -
printStackTrace()
方法所提供的信息可以通过etStackTrace()
方法来直接访问。这个方法返回一个由轨迹栈中的元素所构成的数组。元素0是栈顶元素,并且是调用序列中的最后一个方法调用(这个Throwable
被创建和抛出之处)。数组中的最后一个元素和栈底是调用序列中的第一个方法调用。
栈轨迹
首先提出,栈轨迹是什么?是方法调用轨迹。
printStackTrace()
方法所提供的信息可以通过getStackTrace()
方法来直接访问,该方法返回一个由栈轨迹元素所构成的数组,每个元素表示栈中的一帧,元素0也是栈顶元素,是最后调用的方法(Throwable被创建和抛出之处),最后一个元素是栈底,是调用序列的第一个方法调用。
重新被抛出异常
在我们使用Exception不活了所有异常之后,我们已经得到了当前异常对象的引用,可以将异常重新抛出。
catch(Exception e){
Systyem.out.println(“An exception was thrown”);
throw e;
}
重抛异常会把异常抛给上一级环境中的异常处理程序,同一个try块的后续catch子句将被忽略。此外,异常对象的所有信息都得以保持,所以高一级环境中捕获此异常的
处理程序可以从这个异常对象中得到所有信息。
如果只是把当前异常对象重新抛出,那么printStackTrace()方法显示的将是原来的异常的抛出点的调用堆栈信息,而并非重新抛出点的信息。要想更新这个信息,可以调用fillInStackTrace()方法,这将返回一个Throwable对象,它是通过把当前调用栈信息填入原来那个异常对象建立的,如下:
package com.exceptions;
public class Rethrowing {
public static void f() throws Exception{
System.out.println("originating the exception in f()");
throw new Exception("throw from f()");
}
public static void g() throws Exception{
try{
f();
}catch(Exception e){
System.out.println("Inside g(), e.printStackTrace()");
e.printStackTrace(System.out);
throw e;
}
}
public static void h() throws Exception{
try{
f();
}catch(Exception e){
System.out.println("Inside h(), e.printStackTrace()");
e.printStackTrace(System.out);
throw (Exception)e.fillInStackTrace();
}
}
public static void main(String[] args) {
// TODO Auto-generated method stub
try{
g();
}catch(Exception e){
System.out.println("main: printStackTrace()");
e.printStackTrace(System.out);
}
try{
h();
}catch(Exception e){
System.out.println("main: printStackTrace()");
e.printStackTrace(System.out);
}
}
}
/*OutPut:
originating the exception in f()
Inside g(), e.printStackTrace()
java.lang.Exception: throw from f()
at com.exceptions.Rethrowing.f(Rethrowing.java:8)
at com.exceptions.Rethrowing.g(Rethrowing.java:13)
at com.exceptions.Rethrowing.main(Rethrowing.java:33)
main: printStackTrace()
java.lang.Exception: throw from f()
at com.exceptions.Rethrowing.f(Rethrowing.java:8)
at com.exceptions.Rethrowing.g(Rethrowing.java:13)
at com.exceptions.Rethrowing.main(Rethrowing.java:33)
originating the exception in f()
Inside h(), e.printStackTrace()
java.lang.Exception: throw from f()
at com.exceptions.Rethrowing.f(Rethrowing.java:8)
at com.exceptions.Rethrowing.h(Rethrowing.java:23)
at com.exceptions.Rethrowing.main(Rethrowing.java:39)
main: printStackTrace()
java.lang.Exception: throw from f()
at com.exceptions.Rethrowing.h(Rethrowing.java:27)
at com.exceptions.Rethrowing.main(Rethrowing.java:39)
*///:~
异常链
当你捕获一个异常,然后抛出另一个类型的异常时,并且每个异常都是由另一个异常引起的时,依此类推,就形成了异常链。并且我们通常想保留着原来异常的信息。
更具体的说,每遇到一个异常信息,我们都需要进行try…catch,如果出现多个异常,我们就需要使用异常链了。我们通过含有cause参数的构造器来保存原来异常的信息。所有的Throwable的子类在构造器中都可以 ==接受== 一个cause对象作为参数。这个cause就用来表示原始异常,这样通过把原始异常传递给新的异常,使得即使在当前位置创建并抛出了新的异常,也能通过这个异常链追踪到异常最初发生的位置。
Cause 可以通过两种方式与 throwable 关联起来:
- 通过一个含有cause参数的构造器:
Throwable的子类中,只有三个基本异常类Error,Exception,RuntimeException提供了可以 ==传递== cause参数的构造器,而对于那些希望将 cause 与其关联起来的新 throwable 类,应该提供带有 cause 的构造方法,并委托(可能间接)给一个带有 cause 的 Throwable 构造方法。例如:
try {
lowLevelOp();
} catch (LowLevelException le) {
throw new HighLevelException(le); // Chaining-aware constructor
}
- 通过 initCause(Throwable) 方法。要将其它类型的异常链连接起来,应该使用initCause()而不是构造器。
public Throwable initCause(Throwable cause); 将这个异常的cause初始化为指定值。(该 Cause 是导致抛出该异常的异常。) 因为 initCause 方法是公共的,它允许 cause 与任何 throwable 相关联,甚至包括“遗留 throwable”,它的实现提前将异常链机制的附件应用到 Throwable。例如:
try {
lowLevelOp();
} catch (LowLevelException le) {
throw (HighLevelException)
new HighLevelException().initCause(le); // 传统构造器
}
下面我们展示一个例子:
Analysis the code:
f()方法中,在读文件的时候会抛出文件不存在的异常,但是在catch中我们把它封装成了一个MyException,并手动抛出带有cause的构造器MyException("文件没有找到--01",e);其中e就是cause,用来保存异常信息。
g()方法中包含了f()方法,即MyException,但是在catch中我们把它封装成了另一个MyException,并手动抛出带有cause的构造器MyException("文件没有找到--02",e);并抛出。
那么在输出结构中,是一层一层的显式异常链的。首先是最外层的02,cause by 01,cause byFileNotFoundException,这就是异常链
public class Test {
public void f() throws MyException{
try {
FileReader reader = new FileReader("G:\\myfile\\struts.txt");
Scanner in = new Scanner(reader);
System.out.println(in.next());
} catch (FileNotFoundException e) {
//e 保存异常信息
throw new MyException("文件没有找到--01",e);
}
}
public void g() throws MyException{
try {
f();
} catch (MyException e) {
//e 保存异常信息
throw new MyException("文件没有找到--02",e);
}
}
public static void main(String[] args) {
Test t = new Test();
try {
t.g();
} catch (MyException e) {
e.printStackTrace();
}
}
}
/*OutPut
com.test9.MyException: 文件没有找到--02
at com.test9.Test.g(Test.java:31)
at com.test9.Test.main(Test.java:38)
Caused by: com.test9.MyException: 文件没有找到--01
at com.test9.Test.f(Test.java:22)
at com.test9.Test.g(Test.java:28)
... 1 more
Caused by: java.io.FileNotFoundException: G:\myfile\struts.txt (系统找不到指定的路径。)
at java.io.FileInputStream.open(Native Method)
at java.io.FileInputStream.<init>(FileInputStream.java:106)
at java.io.FileInputStream.<init>(FileInputStream.java:66)
at java.io.FileReader.<init>(FileReader.java:41)
at com.test9.Test.f(Test.java:17)
... 2 more
*///:~
如果说我们在上述代码中稍加改动:在程序中,去掉e,也就是:throw new MyException("文件没有找到--02");那么异常信息就保存不了,这是因为我们实际上没有使用带有cause参数的构造器,构不成异常链,那么异常信息自然也就保存不了。
下面我们再来看一下在《Java编程思想》中关于异常链的晦涩难懂的例子:
Analysis the code:
DynamicFields对象包含一个Object-Object对的数组,第一个Object是一个字段指示符(a String),另一个是字段值值(value),值可以是除了基本类型之外的任何类型。
- 当创建一个DynamicFields对象时,需要估算一下会用到多少个域。
- 当调用setField()的时候,它先检查是否已经存在,如果存在就师徒通过标识修改已有字段值;如果不存在就创建一个新的字段,然后把值放进去。
- 如果没有空间了,就创建一个新的对象,新对象的长度比旧对象长1,把旧对象的内容复制过去。
- 如果试着放入一个null值,它会抛出一个DynamicFieldsException异常,使用initCause()传入一个NullPointerException当做参数cause,然后抛出DynamicFieldsException异常。
- setField()中会调用getField()把此位置的旧值取出,,getField()有可能抛出NoSuchFieldException,此时 NoSuchFieldException被转换成RuntimeException,此刻使用了后者的带cause构造函数,如果客户调用getField(),那么客户就负责处理。
package char12;
/**
* Created by japson on 6/21/2017.
*/
//自定义了一个异常类
class DynamicFieldsException extends Exception {
}
public class DynamicFields {
private Object[][] fields; //成员数据是一个Objct类型的二维数组Fields
/**
* 一个含参的构造函数,其中fields是一个N行2列的二维数组,在创建之后为其赋值null
* @param initialSize
*/
public DynamicFields(int initialSize) {
fields = new Object[initialSize][2];
for (int i = 0; i < initialSize; i++)
fields[i] = new Object[] { null, null };
}
/**
* 该方法的作用是以规定的方式输出对象
* 重写toString方法,输出对象的时候会采用其特定的格式
*append(String str)是StringBuilder()中的一个方法,其作用是连接一个字符串到末尾
* @return
*/
public String toString() {
StringBuilder result = new StringBuilder();
for (Object[] obj : fields) {
result.append(obj[0]);
result.append(": ");
result.append(obj[1]);
result.append("\n");
}
return result.toString();
}
/**
* 该方法用来比较id是否已经存在
* 其作用是判断传递进来的参数id是否与该数组中每行的第一个元素相等,若相等,则返回行号,否则返回-1
* @param id
* @return
*/
private int hasField(String id) {
for (int i = 0; i < fields.length; i++)
if (id.equals(fields[i][0]))
return i;
return -1;
}
/**
* 该方法的作用是获得字段号,并向其调用者getField()抛出一个异常
* 若传进的id不存在,则手动抛出一个异常;若id存在,则返回其行号
* @param id
* @return
* @throws NoSuchFieldException
*/
private int getFieldNumber(String id) throws NoSuchFieldException {
int fieldNum = hasField(id);
if (fieldNum == -1)
throw new NoSuchFieldException();
return fieldNum;
}
/**
* 该方法返回该id的行号;如果空间不够了就创建,然后递归的调用该方法直到返回行号
* 如果每行的第一个元素为空,则将参数id赋值给这个元素,并返回行号跳出该方法
* 如果空间不够了,就新建一个增加一行的新数组,并将旧的赋值给新的,并将空出的一行设为null
* 其返回值是对该方法的递归调用
* @param id
* @return
*/
private int makeField(String id) {
for (int i = 0; i < fields.length; i++)
if (fields[i][0] == null) {
fields[i][0] = id;
return i;
}
// No empty fields. Add one:
Object[][] tmp = new Object[fields.length + 1][2];
for (int i = 0; i < fields.length; i++)
tmp[i] = fields[i];
for (int i = fields.length; i < tmp.length; i++)
tmp[i] = new Object[] { null, null };
fields = tmp;
// Recursive call with expanded fields:
return makeField(id);
}
/**
* 该方法返回参数id所在行的第二列元素,即返回id所对应的域
* 捕获了getFieldNumber(id)抛出的异常,并继续向其调用者抛出
* @param id
* @return
* @throws NoSuchFieldException
*/
public Object getField(String id) throws NoSuchFieldException {
return fields[getFieldNumber(id)][1];
}
/**
* 该方法的作用是设置字段
* 如果设置域为null,它会创建一个DynamicFieldsException异常,用initCause()
* 传入一个NullPointerException作为cause,然后抛出DynamicFieldsException异常。
* 若域不为空,则通过 hasField(id)来判断id是否存在,如果不存在就创建新的字段
* 如果存在,先将result设置初始化为空,则将该位置的value取出赋值给result,将新的value放在这个位置,返回result
* 这个过程可能会抛出NoSuchFieldException,但是这个函数抛出的是DynamicFieldsException,
* 因此可以使用接受cause参数的构造器把NoSuchFieldException异常转换为RuntimeException异常
* @param id
* @param value
* @return
* @throws DynamicFieldsException
*/
public Object setField(String id, Object value) throws DynamicFieldsException {
if (value == null) {
// 许多异常类不提供带cause参数的构造器,因此要使用Throwable的子类中的initCause()方法
DynamicFieldsException dfe = new DynamicFieldsException(); //新建一个异常类的对象
dfe.initCause(new NullPointerException()); //该对象调用initCause()方法,其方法传递一个空指针异常
throw dfe; //抛出一个
}
int fieldNumber = hasField(id);
if (fieldNumber == -1)
fieldNumber = makeField(id);
Object result = null;
try {
result = getField(id); // Get old value
} catch (NoSuchFieldException e) {
// Use constructor that takes "cause":
throw new RuntimeException(e);
}
fields[fieldNumber][1] = value;
return result;
}
public static void main(String[] args) {
DynamicFields df = new DynamicFields(3);
System.out.println(df);
try {
df.setField("d", "A value for d");
df.setField("number", 47);
df.setField("number2", 48);
System.out.println(df);
df.setField("d", "A new value for d");
df.setField("number3", 11);
System.out.println("df: " + df);
System.out.println("df.getField(\"d\") : " + df.getField("d"));
Object field = df.setField("d", null); // Exception
} catch(NoSuchFieldException e) {
e.printStackTrace(System.out);
} catch (DynamicFieldsException e) {
e.printStackTrace(System.out);
}
}
}
/* OutPut:
null: null
null: null
null: null
d: A value for d
number: 47
number2: 48
df: d: A new value for d
number: 47
number2: 48
number3: 11
df.getField("d") : A new value for d
char11.DynamicFieldsException
at char11.DynamicFields.setField(DynamicFields.java:119)
at char11.DynamicFields.main(DynamicFields.java:153)
Caused by: java.lang.NullPointerException
at char11.DynamicFields.setField(DynamicFields.java:120)
... 1 more
*///:~
异常使用指南
应该在下列情况下使用异常:
- 在恰当的级别处理问题。(在知道该如何处理的情况下才捕获异常。)
- 解决问题并且重新调用产生异常的方法。
- 进行少许修补,然后绕过异常发生的地方继续执行。
- 用别的数据进行计算,一代替方法预计会返回的值。
- 把当前运行环境下能做的事情尽量做完,然后把相同的异常重新抛到更高层。
- 把当前运行环境下能做的事情尽量做完,然后把不同的异常抛到更高层。
- 终止程序。
- 进行简化。(可以用e.fillInStackTrace())
- 让类库和程序更安全。
异常的使用误区:
下面通过一个例子还说明总结异常的使用误区(以下部分出自:http://blog.csdn.net/chenssy/article/details/17651971):
OutputStreamWriter out = null;
java.sql.Connection conn = null;
try { //问题1
Statement stat = conn.createStatement();
ResultSet rs = stat.executeQuery("select *from user");
while (rs.next()){
out.println("name:" + rs.getString("name") + "sex:"
+ rs.getString("sex"));
}
conn.close(); //问题2
out.close();
}
catch (Exception ex){ //问题3
ex.printStackTrace(); //问题4
}
问题1:
这个try块中包含了太多的信息。这是我们为了偷懒而养成的代码坏习惯。有些人喜欢将一大块的代码全部包含在一个try块里面,因为这样省事,反正有异常它就会抛出,而不愿意花时间来分析这个大代码块有那几块会产生异常,产生什么类型的异常,反正就是一篓子全部搞定。这就想我们出去旅游将所有的东西全部装进一个箱子里面,而不是分类来装,虽不知装进去容易,找出来难啊!!!所有对于一个异常块,我们应该仔细分清楚每块的抛出异常,因为一个大代码块有太多的地方会出现异常了。
结论1:
尽可能的减小try块!!!
问题2:
在try块中异常改变了程序运行流程。如果该程序发生了异常那么conn.close(); out.close();是不可能执行得到的,这样势必会导致资源不能释放掉。所以如果程序用到了文件、Socket、JDBC连接之类的资源,即使遇到了异常,我们也要确保能够正确释放占用的资源。这里finally就有用武之地了:不管是否出现了异常,finally总是有机会运行的,所以finally用于释放资源是再适合不过了。
结论2:
保证所有资源都被正确释放。充分运用finally关键词。
问题3:
在catch中捕获Exception理。使用这样代码的人都有这样一个心理,一个catch解决所有异常,这样是可以,但是不推荐!首先我们需要明白catch块所表示是它预期会出现何种异常,并且需要做何种处理。上面的程序实例,可能需要抛出两个异常信息,SQLException和IOException。所以一个catch处理两个截然不同的Exception明显的不合适。如果用两个catch,一个处理SQLException、一个处理IOException就好多了。
结论3:
catch语句应当尽量指定具体的异常类型,而不应该指定涵盖范围太广的Exception类。 不要一个Exception试图处理所有可能出现的异常。
问题4:
这里涉及到了两个问题:
一是捕获了异常不做处理:即所谓的丢弃异常。出现异常时,程序希望我们能够对其做出处理,仅是使用ex.printStackTrace()来追踪栈信息,并不算是对异常进行了妥善的处理。因此我们可以用以下的方式对异常进行“妥善”的处理:
1、处理异常。对所发生的的异常进行一番处理,如修正错误、提醒。
2、重新抛出异常。如果认为现在不适合处理该异常,那么何以向上抛出。
3、封装异常。即使用异常链,对异常信息进行分类,然后进行封装处理。
二是异常信息不够明确:在出现异常后,我们最好能够提供一些文字信息,例如当前正在执行的类、方法和其他状态信息,包括以一种更适合阅读的方式整理和组织printStackTrace提供的信息。
结论4:
捕获了异常,就要对它进行适当的处理。不要捕获异常之后又把它丢弃。
结论5:
在异常处理模块中提供适量的错误原因信息,组织错误信息使其易于理解和阅读。
结论6:
不要在finally块中处理返回值。
结论7:
不要在构造函数中抛出异常。
5个关键字总结异常
抛出异常:
throw:异常的生成阶段,手动抛出异常对象。
捕获异常:
try:执行可能产生的异常的代码。
catch:捕获异常:
finally:总会被执行。
声明异常:
throws:异常的处理方式,声明方法可能要抛出各种异常类给其调用对象。
网友评论