JAVA学习之路Java学习笔记

异常处理

2017-07-17  本文已影响298人  Japson

异常处理

异常的基本概念

Java的基本理念是“结构不佳的代码不能运行。”

“我们的目标是:没有BUG!”

当我们开始写代码时,异常和错误就伴随着我们。为了写出逻辑正确的、健壮的系统,程序员们绞尽脑汁,让代码变得得尽善尽美。但是即便是这样,在系统的运行过程中仍然会遇到一些问题。程序员们感到很苦恼,因为很多问题不是靠代码能够避免的,比如:客户输入数据的格式,读取文件是否存在,网络是否始终保持通畅等等。

在讲异常处理之前,首先我们要明确一个概念:什么是异常。Exception这个词有“我对此感到意外”的意思,因此在有些材料中,也将其称作“例外”。在Java语言中,==将程序执行中发生的不正常情况称为“异常”(语法错误和逻辑错误不是异常)。==

异常是不可避免的,异常的处理机制可以确保我们程序的健壮性,提高系统可用率。因此我们通过学习异常处理,可以在我们的项目中受益明显。

当程序执行中发生的不正常情况时,我们可以认为是异常情形。异常情形是指阻止当前方法或者作用域继续执行的问题当出现异常情形,也就是在当前的环境下程序无法正常运行下去,也就是说程序已经无法来正确解决问题了时,就是告诉你:这里可能会或者已经产生了错误,您的程序出现了不正常的情况,可能会导致程序失败!这时程序所就会从当前环境中跳出,并抛出异常。

总的来说异常处理机制就相当于我们保护一段代码的 “底线” ,当程序发生异常时,它强制终止程序运行,记录异常信息并将这些信息反馈给我们,由我们来确定是否处理异常,并且将程序“恢复”到某个已知的安全状态。

异常的体系结构

==Throwable是java语言中所有错误和异常的父类。== Throwable作为异常处理家族的教父,手下有两名干将:Error和Exception。下面放出家族的成员图谱:

image

Java程序在执行过程中所发生的异常事件可分为两类:

针对于以上两种错误,一般有两种解决方法:

一是遇到错误就终止程序的运行。也就是出现错误以后什么都不做,自我放弃等待崩溃。
  另一种方法是由程序员在编写程序时,就考虑到错误的检测、错误消息的提示,以及错误的处理。

很显然,我们应该使用第二种方法来处理异常。

异常有按照发生时间分为为编译时异常运行时异常

我们编写的叫.java文件,通过javac命令生成字节码文件.class(==这个过程成为编译==),字节码文件再通过java命令来运行,得到一个结果(==这个过程称为运行==)。

1.==运行时异常==程序运行起来以后才发现的错误

是指编译器不要求强制处置的异常一般是指编程时的逻辑错误,是程序员应该积极避免其出现的异常。java.lang.RuntimeException类及它的子类都是运行时异常
对于这类异常,可以不作处理,因为这类异常很普遍,若全处理可能会对程序的可读性和运行效率产生影响。

2.==编译时异常==我们在IDE中写代码时,在代码前面打红叉,也就是编译时不通过程序

是指编译器要求必须处置的异常。即程序在运行时由于外界因素造成的一般性异常。编译器要求java程序必须捕获或声明所有编译时异常
对于这类异常,如果程序不处理,可能会带来意想不到的结果。

处理异常的最佳时期就是在程序编译时进行处理。但有的错误只有在运行时才会发生。比如:除数为0,数组下标越界等。那么哪些是编译时异常?哪些是运行时异常呢?我们在族谱里找一下:==RuntimeException是运行时异常,其他的都是编译时异常==

常见的运行时异常和编译时异常

运行时出现的异常:

  1. 数组下标越界的异常:ArrayIndexOutOfBoundsException
public void text1(){
    int[] i = nerw int[10];
    Sysout.out.println(i[10]);
    //显然越界了
}
  1. 算术异常:ArithmeticException
public void text2() {
    int i = 10;
    Sysout.out.println(i / 0);
}
  1. 类型转换异常:ClassCastException
public void test3(){
    Object obj = new Date();
    String str = (String)obj;
    //Date类型不能转化为String类型
}
  1. 空指针异常: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-finallythrows + 异常的类型)==

抓住上一步抛出来的异常类的对象,抓的过程就是异常处理的方式。 Java提供了两种方式来处理一个异常类的对象。 下面分别介绍两种异常处理方式:

处理方式一:try-catch

try-catch是最实用的主动异常处理方法,在异常中try块包含着可能出现异常的代码块,catch块捕获异常后对异常进行处理。

try{
    ......  //可能产生异常的代码
}
catch( ExceptionName1 e ){
    ......  //当产生ExceptionName1型异常时的处置措施
}
catch( ExceptionName2 e ){
    ......  //当产生ExceptionName2型异常时的处置措施
}  
finally{
    ......  //无论是否发生异常,都无条件执行的语句
}  

try块:

通过在方法中设置一个特殊的块来捕获异常,在这个块里“尝试”各种可能产生异常的方法调用,因此称为try块。

异常处理catch

每个被捕获的异常需要在异常处理程序中得到相应的处理。异常处理程序紧跟在try之后,以关键字catch表示。当异常被抛出时,异常处理机制将负责搜寻参数与异常类型相匹配的第一个处理程序。然后进入到catch中执行。一旦catch语句执行结束,则处理程序的查找过程结束。

注:

  1. try内声明的变量,类似于局部变量,出了tyr{}语句,就不能被调用了。
  2. catchfinally都是可选的。
  3. catch语句内部是对异常对象的处理:getMessage(); printStackTrace();
  4. 可以有多个catch语句,try中抛出的异常类对象从上往下去匹配catch中的异常类的类型,一旦满足就执行catch中的代码,执行完就跳出气候的多条catch语句。
  5. 如果异常处理了,那么其后面的代码继续执行。
  6. 对于运行时一场来说,可以不显式的进行处理。(因为比较常见)
    对于编译时异常来说,必须显式的进行处理。(如果不处理,运行不了)
  7. finally后是一定要执行的代码,不管try中、catch中是否有异常未被处理。
  8. 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自定义异常的使用要经历如下四个步骤:

  1. 定义一个类继承Throwable或其子类。
  2. 添加构造方法(当然也可以不用添加,使用默认构造方法)。
  3. 在某个方法类抛出该异常。
  4. 捕捉该异常。

现在我们要看下面一个例子:

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继承的方法:

  1. String getMessage()String getLocalizedMessage()方法用来获取详细信息。
  2. String toString() 返回对Throwable的简单描述,如果有详细信息的话,也会包括在内。
  3. void printStackTrace() void printStackTrace(PrintStream)void printStackTrace(java.io.PrintWriter)
    打印ThrowableThrowable的调用栈轨迹。
  4. Throwable fillInStackTrace()用于在Throwable对象的内部记录栈帧的当前状态。在程序重新抛出错误或异常时很有用。
  5. 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 关联起来:

  1. 通过一个含有cause参数的构造器:
    Throwable的子类中,只有三个基本异常类Error,Exception,RuntimeException提供了可以 ==传递== cause参数的构造器,而对于那些希望将 cause 与其关联起来的新 throwable 类,应该提供带有 cause 的构造方法,并委托(可能间接)给一个带有 cause 的 Throwable 构造方法。例如:
try {
         lowLevelOp();
     } catch (LowLevelException le) {
         throw new HighLevelException(le);  // Chaining-aware constructor
     }

  1. 通过 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),值可以是除了基本类型之外的任何类型。

  1. 当创建一个DynamicFields对象时,需要估算一下会用到多少个域。
  2. 当调用setField()的时候,它先检查是否已经存在,如果存在就师徒通过标识修改已有字段值;如果不存在就创建一个新的字段,然后把值放进去。
  3. 如果没有空间了,就创建一个新的对象,新对象的长度比旧对象长1,把旧对象的内容复制过去。
  4. 如果试着放入一个null值,它会抛出一个DynamicFieldsException异常,使用initCause()传入一个NullPointerException当做参数cause,然后抛出DynamicFieldsException异常。
  5. 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
*///:~

异常使用指南

应该在下列情况下使用异常:

  1. 在恰当的级别处理问题。(在知道该如何处理的情况下才捕获异常。)
  2. 解决问题并且重新调用产生异常的方法。
  3. 进行少许修补,然后绕过异常发生的地方继续执行。
  4. 用别的数据进行计算,一代替方法预计会返回的值。
  5. 把当前运行环境下能做的事情尽量做完,然后把相同的异常重新抛到更高层。
  6. 把当前运行环境下能做的事情尽量做完,然后把不同的异常抛到更高层。
  7. 终止程序。
  8. 进行简化。(可以用e.fillInStackTrace())
  9. 让类库和程序更安全。

异常的使用误区:

下面通过一个例子还说明总结异常的使用误区(以下部分出自: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:异常的处理方式,声明方法可能要抛出各种异常类给其调用对象。

上一篇下一篇

猜你喜欢

热点阅读