Java编程思想读书笔记-6

来源:互联网 发布:淘宝旺旺网页版 编辑:程序博客网 时间:2024/06/11 17:44

第12章 通过异常处理错误

Java异常类结构图


 在 Java 中,所有的异常都有一个共同的祖先 Throwable(可抛出)。Throwable 指定代码中可用异常传播机制通过 Java 应用程序传输的任何问题的共性。
       Throwable: 有两个重要的子类:Exception(异常)和 Error(错误),二者都是 Java 异常处理的重要子类,各自都包含大量子类。

       Error(错误):是程序无法处理的错误,表示运行应用程序中较严重问题。大多数错误与代码编写者执行的操作无关,而表示代码运行时 JVM(Java 虚拟机)出现的问题。例如,Java虚拟机运行错误(Virtual MachineError),当 JVM 不再有继续执行操作所需的内存资源时,将出现 OutOfMemoryError。这些异常发生时,Java虚拟机(JVM)一般会选择线程终止。

。这些错误表示故障发生于虚拟机自身、或者发生在虚拟机试图执行应用时,如Java虚拟机运行错误(Virtual MachineError)、类定义错误(NoClassDefFoundError)等。这些错误是不可查的,因为它们在应用程序的控制和处理能力之 外,而且绝大多数是程序运行时不允许出现的状况。对于设计合理的应用程序来说,即使确实发生了错误,本质上也不应该试图去处理它所引起的异常状况。在 Java中,错误通过Error的子类描述。

       Exception(异常):是程序本身可以处理的异常。

       Exception 类有一个重要的子类 RuntimeException。RuntimeException 类及其子类表示“JVM 常用操作”引发的错误。例如,若试图使用空值对象引用、除数为零或数组越界,则分别引发运行时异常(NullPointerException、ArithmeticException)和 ArrayIndexOutOfBoundException。

   注意:异常和错误的区别:异常能被程序本身可以处理,错误是无法处理。

通常,Java的异常(包括Exception和Error)分为可查的异常(checked exceptions)和不可查的异常(unchecked exceptions)
      可查异常(编译器要求必须处置的异常):正确的程序在运行中,很容易出现的、情理可容的异常状况可查异常虽然是异常状况,但在一定程度上它的发生是可以预计的,而且一旦发生这种异常状况,就必须采取某种方式进行处理。

      除了RuntimeException及其子类以外,其他的Exception类及其子类都属于可查异常。这种异常的特点是Java编译器会检查它,也就是说,当程序中可能出现这类异常,要么用try-catch语句捕获它,要么用throws子句声明抛出它,否则编译不会通过。

     不可查异常(编译器不要求强制处置的异常):包括运行时异常(RuntimeException与其子类)和错误(Error)。

     Exception 这种异常分两大类运行时异常和非运行时异常(编译异常)。程序中应当尽可能去处理这些异常。

 运行时异常:都是RuntimeException类及其子类异常,如NullPointerException(空指针异常)、IndexOutOfBoundsException(下标越界异常)等,这些异常是不检查异常,程序中可以选择捕获处理,也可以不处理。这些异常一般是由程序逻辑错误引起的,程序应该从逻辑角度尽可能避免这类异常的发生。

      运行时异常的特点是Java编译器不会检查它,也就是说,当程序中可能出现这类异常,即使没有用try-catch语句捕获它,也没有用throws子句声明抛出它,也会编译通过。
       非运行时异常 (编译异常):是RuntimeException以外的异常,类型上都属于Exception类及其子类。从程序语法角度讲是必须进行处理的异常,如果不处理,程序就不能编译通过。如IOException、SQLException等以及用户自定义的Exception异常,一般情况下不自定义检查异常

12.6 捕获所有异常

12.6.1 栈轨迹

printStackTree()方法所提供的信息可以通过getStackTrace()方法来直接访问,这个方法将返回一个由栈轨迹中的元素构成的数组,其中每一个元素都表示栈中的一帧。

public class WhoCalled  {  static void f(){try {throw new Exception();} catch (Exception e) {// TODO Auto-generated catch block for(StackTraceElement ste: e.getStackTrace()){System.out.println(ste.getMethodName());}}}static void g(){f();}static void h(){g();}public static void main(String[] args){f();System.out.println("------------------------------");g();System.out.println("------------------------------");h();}}
运行结果:

fmain------------------------------fgmain------------------------------fghmain

12.6.3 异常链

JDK1.4以前,程序员必须为自己编写代码来保存原始异常的信息。现在所有Throwable的子类在构造器中可以接受一个cause对象作为参数。这个cause用来显示原始异常,这样通过把原始异常传递给新的异常,使得即使在当前位置创建并抛出了新的异常,也能通过这个异常链追踪到异常最初发生的位置。

在Throwable的子类中,只有三种基本类型提供了带cause参数的构造器,有Error,Exception以及RuntimeException,如果要把其他类型的异常链接起来,应该使用initCause()方法而不是构造器。

如例子:动态向DynamicFields对象添加字段

class DynamicFieldException extends Exception{ }class NoSuchFiledException extends Exception{ }public class DynamicField {   private Object[][] fields;public DynamicField(int initialSize){fields = new Object[initialSize][2];for(int i = 0; i < initialSize; i++){fields[i] = new Object[]{null, null};}}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();}private int hasField(String id){for(int i = 0; i < fields.length; ++i){if(id.equals(fields[i][0])){return 1;}}return -1;}private int getFieldNumber(String id) throws NoSuchFiledException{int fieldNum = hasField(id);if(fieldNum == -1){throw new NoSuchFiledException();}return fieldNum;}private int makeField(String id){for(int i = 0; i < fields.length; ++i){if(fields[i][0] == null){fields[i][0] = id;return i;}}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;return makeField(id);}public Object getField(String id) throws NoSuchFiledException{return fields[getFieldNumber(id)][1];}public Object setField(String id, Object value) throws DynamicFieldException{if(value == null){DynamicFieldException dfe = new DynamicFieldException();dfe.initCause(new NullPointerException());throw dfe;}int fieldNum = hasField(id);if(fieldNum == -1){fieldNum = makeField(id);}Object result = null;try {result = getField(id);} catch (NoSuchFiledException e) {// TODO Auto-generated catch blocke.printStackTrace();throw new RuntimeException();}fields[fieldNum][1] = value;return result;}public static void main(String[] args){DynamicField df = new DynamicField(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);} catch (DynamicFieldException e) {// TODO Auto-generated catch blocke.printStackTrace();} catch (NoSuchFiledException e) {// TODO Auto-generated catch blocke.printStackTrace();}}}
结果是:

null: nullnull: nullnull: nulld: A value for dnumber: 47number2: 48df: d: A value for dnumber: A new value for dnumber2: 48number3: 11df.getField("d"): A new value for dDynamicFieldExceptionat DynamicField.setField(DynamicField.java:67)at DynamicField.main(DynamicField.java:99)Caused by: java.lang.NullPointerExceptionat DynamicField.setField(DynamicField.java:68)... 1 more

12.8.2 在return中使用finally

public class MultiReturns {  public static void f(int i){System.out.println("Initialization that requires cleanup");try{System.out.println("Point 1");if(i == 1) return;System.out.println("Point 2");if(i == 2) return;System.out.println("Point 3");if(i == 3) return;System.out.println("End");return;}finally{System.out.println("Performing cleanup");}} public static void main(String[] args){ for(int i = 1; i < 3; ++i){ f(i); }}}
结果是:

Initialization that requires cleanupPoint 1Performing cleanupInitialization that requires cleanupPoint 1Point 2Performing cleanup
finally子句总会执行,所以在一个方法中可以有多个返回点,并且可以保证重要的清理工作仍会执行。

12.8.3 异常丢失

例如三个try catch  嵌套在一起 ..内部的2个try catch 就可以省略 catch ....直接 try finally ..

public class MyTest {public void open() throws Exception {throw new Exception() {public String toString() {return this.getClass().getName() + "CeryImmportException";};};}public void close() throws Exception {throw new Exception() {public String toString() {return this.getClass().getName() + "close Exception";};};}public void three() throws Exception {throw new Exception() {public String toString() {return this.getClass().getName() + "three";};};}public static void main(String[] agrs) {MyTest mt = new MyTest();try {try {try {mt.open();} finally {System.out.println("delete open");mt.close();}} finally {System.out.println("delete close");mt.three();}} catch (Exception ex) {ex.printStackTrace();}}}

12.10 构造器

import java.io.BufferedReader;import java.io.FileNotFoundException;import java.io.FileReader;import java.io.IOException;public class InputFile {private BufferedReader in;public InputFile(String fname) throws Exception{try {in = new BufferedReader(new FileReader(fname));} catch (FileNotFoundException e) {//文件还未打开,不需要closeSystem.out.println("Could not open " + fname);throw e;} catch(Exception e){try{in.close();}catch(IOException e2){System.out.println("in.close() unsucessful");}throw e;}finally{}}//返回下一行内容的字符串public String getLine(){String s;try {s= in.readLine();} catch (IOException e) {// TODO Auto-generated catch block throw new RuntimeException("readLine() failed");}return s;}public void dispose(){try {in.close();System.out.println("dispose() sucessful");} catch (IOException e) {// TODO Auto-generated catch block throw new RuntimeException("in.close() failed");}}}
public class Cleanup {public static void main(String[] args) { try {InputFile in = new InputFile("C:/Users/keke/Desktop/doc.txt");try{String s;int i = 1;while((s = in.getLine()) != null){}}catch(Exception e){System.out.println("Caught Exception in main");e.printStackTrace(System.out);}finally{</span></span>
<span style="font-size:18px;"><span style="font-size:18px;">//in创建失败,则不会调用in.dispose();}} catch (Exception e) {System.out.println("InputFile construction failed");}}}
对于构造阶段可能会抛出的异常,并且要求清理的类,最安全的方式是使用嵌套的try子句。基本规则是:在创建需要清理的对象之后,立即进入一个try-finally语句块。

import java.io.FileNotFoundException;import java.io.IOException;//生成不同类型的exception,这些异常被捕获并包装进了RuntimeException对象,成为运行时异常的"cause"class WrapCheckedException{void throwRuntimeException(int type){try{switch(type){case 0:throw new FileNotFoundException();case 1:throw new IOException();case 2:throw new RuntimeException("Where am I?");}}catch(Exception e){throw new RuntimeException(e);}}}class SomeOtherException extends Exception{}public class TurnOffChecking {public static void main(String[] args) { WrapCheckedException wce = new WrapCheckedException();wce.throwRuntimeException(3);for(int i = 0; i < 4; ++i){try{if(i < 3){wce.throwRuntimeException(i);}else{throw new SomeOtherException();}}catch(SomeOtherException e){System.out.println("SomeOtherException: " + e);}catch(RuntimeException re){try{throw re.getCause();}catch(FileNotFoundException e){System.out.println("FileNotFoundException: " + e);} catch(IOException e){System.out.println("IOException: " + e);} catch (Throwable e) { System.out.println("Throwable: " + e);}}}}}

12.13 异常使用指南

应该在下列情况使用异常

1).在恰当的级别处理问题(在知道如何处理的情况下,捕获异常)

2).解决问题并且重新调用产生异常的方法

3).进行少许修补,然后绕过异常发生的地方继续执行

4).用别的数据进行计算,以替代方法预计会返回的值

5).把当前运行环境下能做的事情尽量做完,然后把相同的异常重抛到更高层

6).把当前运行环境下能做的事情尽量做完,然后把不同的异常抛到更高层

7).终止程序

8).进行简化(如果你的异常模式使用问题变得太复杂,那么用起来会非常痛苦)

9).让类库和程序更加安全

异常处理

恢复模式:

使用异常处理可以是程序从错误中恢复

    while(true){

        try{

            //...

            break;

        }catch(){

        //...

        }

    }

    同时还可以定义变量在尝试n次之后从错误中退出。

受检异常

    编译时被强制检查的异常,如 void f() throws XXException

非受检异常

    均继承自RuntionException,它们会自动被java虚拟机抛出。这种异常属于错误,将被自动捕获,但可在代码中抛出RuntimeException。当程序产生非受检异常时程序会在退出前自动调用printStackTrace()方法。非受检异常可以被捕获

finally

   在异常没有被当前异常处理程序捕获的情况下,异常机制也会在跳至更高的异常处理之前执行finally (break、continue、return也是)

    在有finally的try中执行会抛出RuntimeException的语句时,即便程序产生了异常并退出,也会在退出前执行finally子句

    bug:当try中的异常尚未得到处理而在finally中再次抛出异常时,finally中的异常就会替代原有的异常,导致原有异常信息丢失,或者在finally中加上return也会出现这种情况

异常与方法的覆盖和重载

    override时子类的方法可以比父类抛出更少的异常(甚至不抛出),但不能多于。

    异常说明本身并不属于方法类型的一部分,因此不能基于异常说明重载方法。除了内存的清理外,所有的清理(如文件)都不会自动产生。所以必须告诉客户端程序员,这是他们的责任。

构造器异常

    对于在构造阶段可能会抛出异常,并且要求清理的类,最安全的方式是使用嵌套try语句。

    try{

        in = new FileInput("1.txt"); //FileInput会在构造器中抛出异常

        //文件操作

        }finally{

            in.close();

        }

    }

    这种做法有可能在构造阶段发生异常,此时in并没有指向文件,却调用了close方法。

    更好的做法:

    try{

        in = ..;

        try{

            //文件操作

        }finally{

            in.close();

        }

    }catch(){

        //文件构造阶段出错,不需要关闭文件

    }

派生类构造器不能捕获其基类构造器所抛出的异常

异常处理的原则:只有在知道如何处理的情况在才捕获异常。

异常处理的目标:把错误处理代码同错误发生的地点相分离。

处理异常的几种方式

    1.把异常传给控制台 main(String args[])throws XXException

        最简单而又不用写多少代码就能保护异常信息的方法。

    2.将"受检异常"转换为"非受检异常"

        当你不知道该如何去处理一个方法的异常,并且不希望吞掉异常或者打印无用信息时,可以使用异常链来抛出RuntimeException(throw new RuntimeException(e)),此时异常链会替你保存原始异常信息

       编译器不会强制你捕获RuntimeException,但你可以捕获

吞食则有害(harmful if swallowed)

"被检查的异常"强制程序员可能在没准备好的情况下被迫加上catch子句,就导致了吞食则有害的问题

try{//...to do something useful}catch(ObligatoryException e){}//Gulp
0 0