zoukankan      html  css  js  c++  java
  • 通过异常处理错误-1

    Java的基本理念是“ 结构不佳的代码不能运行”。
     
          发现错误的理想时机是在编译阶段,也就是在你试图运行程序之前。然而,编译期间并不能找出所有的错误,余下的问题必须在运行期间解决。这就需要错误源能通过某种方式,把适当的信息传递给某个接收者一该接收者将知道如何正确处理这个问题。
     
          改进的错误恢复机制是提供代码健壮性的最强有力的方式。错误恢复在我们所编写的每一个程序中都是基本的要素,但是在Java中它显得格外重要,因为Java的主要目标之-就是创建供他人使用的程序构件。要想创建健壮的系统,它的每一个构件 都必须是健壮的。Java使用异常来提供致的错误报告模型,使得构件能够与客户端代码可靠地沟通问题。
     
          Java中的异常处理的目的在于通过使用少于目前数量的代码来简化大型、可靠的程序的生成,并且通过这种方式可以使你更加自信:你的应用中没有未处理的错误。异常的相关知识学起来并非艰涩难懂,并且它属于那种可以使你的项目受益明显、立竿见影的特性之一。
     
          因为异常处理是Java中唯一正式的错误报告机制,并且通过编译器强制执行,所以不学习异常处理的话,书中也就只能写出那么些例子了。本章将向读者介绍如何编写正确的异常处理程序,并将展示当方法出问题的时候,如何产生自定义的异常。

    一,概念

    使用异常的好处是降低错误处理代码的复杂度,如果不使用异常,那么就必须检查特定的错误,并在程序中的许多地方处理。而如果使用异常那就不必在方法调用处进行检查,因为异常机制将保证能够捕捉这个错误,并且只需要在一个地方处理错误。这种方式不仅节省代码,而且把 /描述在正常执行过程中做什么事/的代码和/除了问题怎么办/的代码相分离。

    二,基本异常

    异常情形(exceptional condition)是指阻止当前方法或作用域继续执行的问题。把异常情形与普通问题相区分很重要,所谓的普通问题是指,在当前环境下能得到足够的信息,总能处理这个错误。而对于异常情形,就不能继续下去了,因为在当前环境下无法获得必要的信息来解决问题。你所能做的就是从当前环境跳出,并且把问题提交给上-级环境。这就是抛出异常时所发生的事情。
          除法就是一个简单的例子。除数有可能为0,所以先进行检查很有必要。但除数为0代表的究竟是什么意思呢?通过当前正在解决的问题环境,或许能知道该如何处理除数为0的情况。但如果这是-一个意料之外的值,你也不清楚该如何处理,那就要抛出异常,而不是顺着原来的路径继续执行下去。
          当抛出异常后,有几件事会随之发生。首先,同Java中其他对象的创建样,将使用new在堆上创建异常对象。然后,当前的执行路径(它不能继续下去了)被终止,并且从当前环境中弹出对异常对象的引用。此时,异常处理机制接管程序,并开始寻找一个恰当的地方来继续执行程序。这个恰当的地方就是异常处理程序,它的任务是将程序从错误状态中恢复,以使程序能要么换-一种方式运行,要么继续运行下去。
          举一个抛出异常的简单例子。对于对象引用t,传给你的时候可能尚未被初始化。所以在使用这个对象引用调用其方法之前,会先对引用进行检查。可以创建一个代表错误信息的对象,并且将它从当前环境中“抛出”,这样就把错误信息传播到了“更大”的环境中。这被称为抛出一个异常,看起来像这样:
    if(t==null){
      throw new NullPointerException();
    }
    这就抛出了异常,于是在当前环境下就不必再为这个问题操心了,它将在别的地方得到处理。具体是哪个“地方”后面很快就会介绍。
          异常使得我们可以将每件事都当作-一个事务来考虑,而异常可以看护着这些事务的底线....务的基本保障是我们1所需的在分布式计算中的异常处理。事务是计算机中的合同法,如果出了什么问题,我们只需要放弃整个计算。”。我们还可以将异常看作是一种内建的恢复(undo)系统,因为(在细心使用的情况下)我们在程序中可以拥有各种不同的恢复点。如果程序的某部分失败了,异常将“恢复”到程序中某个已知的稳定点上。
          异常最重要的方面之-一就是如果发生问题,它们将不允许程序沿着其正常的路径继续走下去。在C和C++这样的语言中,这可真是个问题,尤其是C,它没有任何办法可以强制程序在出现问题时停止在某条路径上运行下去,因此我们有可能会较长时间地忽略了问题,从而陷入了完全不恰当的状态中。异常允许我们( 如果没有其他手段)强制程序停止运行,并告诉我们出现了什么问题,或者(理想状态下)强制程序处理问题,并返回到稳定状态。
    异常参数:
          与使用Java中的其他对象一样,我们总是用new在堆上创建异常对象,这也伴随着存储空间的分配和构造器的调用。所有标准异常类都有两个构造器:一个是默认构造器;另一个是接受字符串作为参数,以便能把相关信息放入异常对象的构造器:
          throw new NullPointerException("t = null");
    不久读者将看到,要把这个字符串的内容提取出来可以有多种不同的方法。
          关键字throw将产生许多有趣的结果。在使用new创建了异常对象之后,此对象的引用将传给throw。尽管返回的异常对象其类型通常与方法设计的返回类型不同,但从效果上看,它就像是从方法‘返回” 的。可以简单地把异常处理看成一种不同的返 回机制,当然若过分 强调这种类比的话,就会有麻烦了。另外还能用抛出异常的方式从当前的作用域退出。在这两种情况下,将会返回一个异常对象,然后退出方法或作用域。
          抛出异常与方法正常返回值的相似之处到此为止。因为异常返回的“地点”与普通方法调用返回的“地点”完全不同。(异常将在一个恰当的异常处理程序中得到解决,它的位置可能离异常被抛出的地方很远,也可能会跨越方法调用栈的许多层次。)
          此外,能够抛出任意类型的Throwable对象,它是异常类型的根类。通常,对于不同类型的错误,要抛出相应的异常。错误信息可以保存在异常对象内部或者用异常类的名称来暗示。上一层环境通过这 些信息来决定如何处理异常。(通常,异常对象中仅有的信息就是异常类型,除此之外不包含任何有意义的内容。)

    三,捕获异常

    监控区域,是一段可能产生异常的代码,并且后面跟着处理这些异常的代码


          如果在方法内部抛出了异常(或者在方法内部调用的其他方法抛出了异常),这个方法将在抛出异常的过程中结束。要是不希望方法就此结束,可以在方法内设置-一个特殊的块来捕获异常。因为在这个块里‘尝试” 各种(可能产生异常的)方法调用,所以称为try块。它是跟在try关键字之后的普通程序块:
    1,try块
          try {
            Code that might generate exceptions
      }

          对于不支持异常处理的程序语言,要想仔细检查错误,就得在每个方法调用的前后加上设置和错误检查的代码,甚至在每次调用同一方法时也得这么做。有了异常处理机制,可以把所有动作都放在try块里,然后只需在一个地方就可以捕获所有异常。这意味着代码将更容易编写和阅读,因为完成任务的代码没有与错误检查的代码混在一起。
    2,异常处理程序

      抛出的异常必须在某处得到处理。这个“地点”就是异常处理程序,而且针对要捕获的异常,得准备相应的处理程序。异常处理程序紧跟在在try后,这个关键字是catch

          每个catch子句(异常处理程序)看起来就像是接收-一个 且仅接收-一个特殊类型的参数的方法。可以在处理程序的内部使用标识符(id1, id2等等),这与方法参数的使用很相似。有时可能用不到标识符,因为异常的类型已经给了你足够的信息来对异常进行处理,但标识符并不可以省略。

          异常处理程序必须紧跟在try块之后。当异常被抛出时,异常处理机制将负责搜寻参数与异常类型相匹配的第一个处理程序。然后进人catch子句执行,此时认为异常得到了处理。一旦catch子句结束,则处理程序的查找过程结束。注意,只有匹配的catch子句才能得到执行;这与switch语句不同,switch语 句需要在每一个case后面跟一个break, 以避免执行后续的case子句。

          注意在try块的内部,许多不同的方法调用可能会产生类型相同的异常,而你只需要提供一个针对此类型的异常处理程序。
    3,终止与恢复

          异常处理理论上有两种基本模型。Java支持终止模型(它是Java和C++ 所支持的模型)在这种模型中,将假设错误非常关键,以至于程序无法返回到异常发生的地方继续执行。一旦异常被抛出,就表明错误已无法挽回,也不能回来继续执行。

          另一种称为恢复模型。意思是异常处理程序的工作是修正错误,然后重新尝试调用出问题的方法,并认为第二次能成功。对于恢复模型,通常希望异常被处理之后能继续执行程序。如果想要用Java实现类似恢复的行为,那么在遇见错误时就不能抛出异常,而是调用方法来修正该错误。或者,把try块放在while循环里,这样就不断地进入try块,直到得到满意的结果。

          长久以来,尽管程序员们使用的操作系统支持恢复模型的异常处理,但他们最终还是转向使用类似“终止模型”的代码,并且忽略恢复行为。所以虽然恢复模型开始显得很吸引人,但不是很实用。其中的主要原因可能是它所导致的耦合:恢复性的处理程序需要了解异常抛出的地点,这势必要包含依赖于抛出位置的非通用性代码。这增加了代码编写和维护的困难,对于异常可能会从许多地方抛出的大型程序来说,更是如此。

    四,创建自定义异常

    不必拘泥于已有的异常类型,因为Java不可能预见所有的情况,所以需要自定义异常解决遇到的特定的问题

    要自己定义异常类,就必须从已有的异常类中继承,最好选择医生相近的异常类继承。建立新的异常类最简单的方法就是让编译器为你产生默认构造器,这几乎步涌多少代码

    例如  class miaoException extends RuntimeException { }

    int c = 0, b = 5;
            try {
                System.out.println(b / c);//首先像其他对象一样,在堆上创建异常对象
            } catch (ArithmeticException e) {
                System.err.println("出现错误");//将错误发送到标准错误流,这通常比把错误输出到out好,因为System.out可能会重定向
                System.out.println(e.getMessage());
                System.out.println(e.toString());
                e.printStackTrace(System.err);
            }


          编译器创建了默认构造器,它将自动调用基类的默认构造器。本例中不会得到像Simple-Exception(String)这样的构造器,这种构造器也不实用。你将看到,对异常来说,最重要的部分就是类名,所以本例中建立的异常类在大多数情况下已经够用了。

          本例的结果被打印到了控制台上,本书的输出显示系统正是在控制台上自动地捕获和测试这些结果的。但是,你也许想通过写入System.err而将错误发送给标准错误流。通常这比把错误信息输出到System.out要好,因为System.out也许会 被重定向。如果把结果送到System.err,它就不会随System.out- -起被重定向,这样更容易被用户注意。

          也可以为异常类定义一个接受字符串参数的构造器:

    class miaoException extends RuntimeException {////也可以直接继承Throwable
        public miaoException() {
            super();
        }
    
        public miaoException(String message) {
            super(message);
        }
    }

    异常说明


          Java鼓励人们把方法可能会抛出的异常告知使用此方法的客户端程序员。这是种优雅的做法,它使得调用者能确切知道写什么样的代码可以捕获所有潜在的异常。当然,如果提供了源代码,客户端程序员可以在源代码中查找throw语句来获知相关信息,然而程序库通常并不与源代码- -起发布。为了预防这样的问题,Java提供 了相应的语法(并强制使用这个语法),使你能以礼貌的方式告知客户端程序员某个方法可能会抛出的异常类型,然后客户端程序员就可以进行相应的处理。这就是异常说明,它属于方法声明的一部分,紧跟在形式参数列表之后。

          异常说明使用了附加的关键字throws,后面接一一个所有潜在异常类型的列表,所以方法定义可能看起来像这样:

          但是,要是这样写:  void f() throws MiaoException,ArithmeticException { ,,,,, 这样就表示这个方法不会抛出任何异常

          代码必须与异常说明保持-致。如果方法里的代码产生了异常却没有进行处理,编译器会发现这个问题并提醒你:要么处理这个异常,要么就在异常说明中表明此方法将产生异常。通过这种自顶向下强制执行的异常说明机制,Java在编译时就可以保证一定水平的异常正确性。

          不过还是有个能“作弊”的地方:可以声明方法将抛出异常,实际上却不抛出。编译器相信了这个声明,并强制此方法的用户像真的抛出异常那样使用这个方法。这样做的好处是,为异常先占个位子,以后就可以抛出这种异常而不用修改已有的代码。在定义抽象基类和接口时这种能力很重要,这样派生类或接口实现就能够抛出这些预先声明的异常。

          这种在编译时被强制检查的异常称为  被检查的异常。

    捕获所有异常

    可以只写一个异常处理程序来捕获所有类型的异常,通过捕获异常类型的基类Exception就可以做到这一点,如 catch ( Exception e ) { ,,,,},但是这个应该放在异常处理程序的末尾,以防止抢在其他处理程序之前捕获异常

    因为Exception是与编程有关的所有异常类的基类,所以它不会含有太多具体的信息,不过可以调用从其基类Throwable继承的方法:
    String getMessage
    String getLocalizedMessage
    用来获取详细信息,或用本地语言表示的详细信息。
    String toString
    返回对Throwable的简单描述,要是有详细信息的话,也会把它包含在内。

          void printStackTrace

          void printStackTrace(PrintStream)

          void printStackTrace(java.io.PrintWriter)

    打印Throwable和Throwable的调用栈轨迹。调用栈显示了“把你带到异常抛出地点”的方法调用序列。其中第一个版本输出到标准错误,后两个版本允许选择要输出的流(在后面,你将学习这两种流的不同之处)。  

          Throwable fllInStackTrace

    用于在Throwable对象的内部记录栈帧的当前状态。这在程序重新抛出错误或异常(很快就会讲到)时很有用。

          此外,也可以使用Throwable从其基类Object (也是所有类的基类)继承的方法。对于异常来说,getClass(也许是个 很好用的方法,它将返回一个表示此对象类型的对象。然后可以使用getName(方法查询这个Class对象包含包信息的名称,或者使用只产生类名称的getSimpleName0方法。

     栈轨迹

          printstackirace方法所提供的信息可以通过getStackTrace方法来直接访问,这个方法将返回一个由栈轨迹中的元素所构成的数组,其中每一个元素都表示栈中的一桢。元素0是栈顶元素,并且是调用序列中的最后一个方法调用(这个Throwable被创建和抛出之处)。数组中的最后一个元素和栈底是调用序列中的第-一个 方法调用。下面的程序是一个简单的演示示例:
    public class ToException {
        
        public static void f() throws Exception{
            try {
                throw new Exception();
            }catch(Exception e) {
                
            }
        }
        
        public static void g() throws Exception{
            f();
        }
        public static void h() throws Exception{
            g();
        }
        public static void main(String[] args) throws MiaoException,Exception {
            f();
            System.out.println("---------------------------------------");
            g();
            System.out.println("---------------------------------------");
            h();
            
        }
    }

    这里,我们只打印了方法名,但实际上可以打印整个StackTraceElement,它包含所有其他附件的信息。

    重新抛出异常


          有时希望把刚捕获的异常重新抛出,尤其是在使用Exception捕获所有异常的时候。既然已经得到了对当前异常对象的引用,可以直接把它重新抛出:

          catch(Exception e) {

          System. out. println("An exception Was thrown");throw e;}

          重抛异常会把异常抛给上一级环境中的异常处理程序,同一个try块的后续catch子句将被忽略。此外,异常对象的所有信息都得以保持,所以高-级环境中捕获此异常的处理程序可以从这个异常对象中得到所有信息。

          如果只是把当前异常对象重新抛出,那么printStackTrace0方法显示的将是原来异常抛出点的调用栈信息,而并非重新抛出点的信息。要想更新这个信息,可以调用fllInStackTrace0方法,这将返回一个Throwable对象,它是通过把当前调用栈信息填入原来那个异常对象而建立的,就像这样:
    public class Rethrowing {
    
        public static void f() throws Exception {
            System.out.println("f()");
            throw new Exception("from f");
        }
    
        public static void g() throws Exception {
            try {
                f();
            } catch (Exception e) {
                System.out.println("g()");
                e.printStackTrace(System.out);
                throw e;
            }
        }
    
        public static void h() throws Exception {
            try {
                f();
            } catch (Exception e) {
                System.out.println("h()");
                e.printStackTrace(System.out);
                throw (Exception) e.fillInStackTrace();//新的异常发生地
            }
        }
    
        public static void main(String[] args) {
            
            try {
                g();
            } catch (Exception e) {
                System.out.println("main g()");
                e.printStackTrace(System.out);//显示原来异常抛出点的调用栈信息
            }
            
            System.out.println("---------------------");
            
            try {
                h();
            } catch (Exception e) {
                System.out.println("main h()");
                e.printStackTrace(System.out);//显示新的异常抛出点的调用栈信息
            }
        }
    }

     调用fillInStackTrace的那一行就是新的异常发生地了;

    有可能在捕获异常之后又抛出另一种异常,得到的效果类似于fillInStackTrace,有关原来异常发生点的信息会丢失,剩下的是新抛出点的信息

     永远不必为清理前一个异常对象而担心,或者说为异常对象的处理而担心,他们都是用new在堆上建立的对象,所以垃圾回收期会自动将他们回收

    异常链

          常常会想要在捕获一个异常后抛出另一一个异常,并且希望把原始异常的信息保存下来,这被称为异常链。在JDK 1.4以前,程序员必须自己编写代码来保存原始异常的信息。现在所有Throwable的子类在构造器中都可以接受-一个cauise (因 由)对象作为参数。这个cause就用来表示原始异常,这样通过把原始异常传递给新的异常,使得即使在当前位置创建并抛出了新的异常,也能通过这个异常链追踪到异常最初发生的位置。

          有趣的是,在Throwable的子类中,只有三种基本的异常类提供了带cause参数的构造器。它们是Error (用于Java虚拟机报告系统错误)、Exception以及 RuntimeException。如果要把其他类型的异常链接起来,应该使用initCause0方法而不是构造器。

          下面的例子能让你在运行时动态地向DynamicFields对象添加字段:

    class DynamicFieldsException extends Exception {
    }
    
    public class DynamicFields {
        private Object[][] fields;
    
        public DynamicFields(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("
    ");
            }
            return result.toString();
        }
    
        private int hasField(String id) {
            for (int i = 0; i < fields.length; i++) {
                if (id.equals(fields[i][0]))
                    return i;
            }
            return -1;
        }
    
        private int getFieldNumber(String id) throws NoSuchFieldException {
            int fieldNum = hasField(id);
            if (fieldNum == -1) {
                throw new NoSuchFieldException();
            }
            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;
                }
            }
            //No empty fields .Addone
            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;
            //Resource call with expanded fields
            return makeField(id);
        }
    
        public Object getField(String id) throws NoSuchFieldException {
            return fields[getFieldNumber(id)][1];
        }
    
        public Object setField(String id, Object value) throws DynamicFieldsException {
            if (value == null) {
                DynamicFieldsException def = new DynamicFieldsException();
                def.initCause(new NullPointerException());//
                throw def;
            }
            int fieldNumber = hasField(id);
            if (fieldNumber == -1) {
                fieldNumber = makeField(id);
            }
            Object result = null;
            try {
                result = getField(id);// get old value
            } catch (NoSuchFieldException e) {
                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("getField:"+df.getField("d"));
                Object field=df.setField("d", null);
            }catch(NoSuchFieldException e) {
                e.printStackTrace(System.out);
            }catch(DynamicFieldsException e) {
                e.printStackTrace(System.out);
            }
        }
    }


          每个DynamicFields对象都含有一一个数组,其元素是“成对的对象”。第个 对象表示字段标识符(一个字符串),第二个表示字段值,值的类型可以是除基本类型外的任意类型。当创建对象的时候,要合理估计一下需要多少字段。当调用setField0方法的时候,它将试图通过标识修改已有字段值,否则就建一个新的字段,并把值放入。如果空间不够了,将建立一个更长的数组,并把原来数组的元素复制进去。如果你试图为字段设置一个空值,将抛出一个DynamicFieldsException异常,它是通过使用initCause0方法把NullPointerException对象插入而建立的。

          至于返回值,setField()将用getField(方法把此位置的旧值取出,这个操作可能会抛出NoSuchFieldException异常。如果客户端程序员调用了getField0方法,那么他就有责任处理这个可能抛出的NoSuchFieldException异常,但如果异常是从setField0方法里抛出的,这种情况将被视为编程错误,所以就使用接受cause参数的构造器把NoSuchFieldException异常转换为RuntimeException异常
    你会注意到,toString0方 法使用了一个StringBuilder来创建其结果。在第13章中你将会了解到更多的关于StringBuilder的知识,但是只要你编写设计循环的toString0方法,通常都会想使用它,就像本例一样。










  • 相关阅读:
    大端小端
    浅谈协程
    boost总结之any
    boost总结之variant
    STL总结之functor
    zabbix设置多个收件人
    zabbix设置发送消息的时间
    zabbix利用微信报警
    Windos无法验证文件数组签名
    zabbix基础安装
  • 原文地址:https://www.cnblogs.com/QianYue111/p/10268817.html
Copyright © 2011-2022 走看看