重拾Java基础知识:异常

前言

Java 的基本理念是“结构不佳的代码不能运行,要想创建健壮的系统,它的每一个构件都必须是健壮的”。

改进的错误恢复机制是提高代码健壮性的最强有力的方式。错误恢复在我们所编写的每一个程序中都是基本的要素,发现错误的理想时机是在编译阶段,也就是在你试图运行程序之前。然而,编译期间并不能找出所有的错误,余下的问题必须在运行期间解决。这就需要错误源能通过某种方式,把适当的信息传递给某个接收者——该接收者将知道如何正确处理这个问题。
Java 中的异常处理的目的在于通过使用少于目前数量的代码来简化大型、可靠的程序的生成,使得构件能够与客户端代码可靠地沟通问题。通过这种方式可以使你更加确信:你的应用中没有未处理的错误。异常的相关知识学起来并非艰涩难懂,并且它属于那种可以使你的项目受益明显、立竿见影的特性之一。

异常概念

C 以及其他早期语言常常具有多种错误处理模式,这些模式往往建立在约定俗成的基础之上,而并不属于语言的一部分。通常会返回某个特殊值或者设置某个标志,并且假定接收者将对这个返回值或标志进行检查,以判定是否发生了错误。这种做法由来已久,对异常处理的实现可以追溯到 20 世纪 60 年代的操作系统,甚至于 BASIC 语言中的“on error goto”语句。而 C++ 的异常处理机制基于 Ada,Java 中的异常处理机制则建立在 C++ 的基础之上(尽管看上去更像 Object Pascal)。
异常往往能降低错误处理代码的复杂度。如果不使用异常,那么就必须检查特定的错误,并在程序中的许多地方去处理它。而如果使用异常,那就不必在方法调用处进行检查,因为异常机制将保证能够捕获这个错误。理想情况下,只需在一个地方处理错误,与以前的错误处理方法相比,异常机制使代码的阅读、编写和调试工作更加井井有条。

基本异常

异常情形(exceptional condition)是指阻止当前方法或作用域继续执行的问题。普通问题是指,在当前环境下能得到足够的信息,总能处理这个错误。从当前环境跳出,并且把问题提交给上一级环境,这就是抛出异常时所发生的事情。
当抛出异常后,有几件事会随之发生。首先,同 Java 中其他对象的创建一样,将使用 new 在堆上创建异常对象。然后,当前的执行路径(它不能继续下去了)被终止,并且从当前环境中弹出对异常对象的引用。此时,异常处理机制接管程序,并开始寻找一个恰当的地方来继续执行程序。这个恰当的地方就是异常处理程序,它的任务是将程序从错误状态中恢复,以使程序能要么换一种方式运行,要么继续运行下去。如下代码所示:

    public static void main(String[] args) {
        Integer i = null;
        if(i == null){
            throw new NullPointerException("Integer i is null");
        }
    }

幸运的是,这不必由你亲自来做,它属于 Java 的标准运行时检测的一部分。如果对 null 引用进行调用,Java 会自动抛出 NullPointerException 异常,所以上述代码是多余的,尽管你也许想要执行其他的检查以确保 NullPointerException 不会出现。异常使得我们可以将每件事都当作一个事务来考虑,而异常可以看护着这些事务的底线“…事务的基本保障是我们所需的在分布式计算中的异常处理。异常允许我们(如果没有其他手段)强制程序停止运行,并告诉我们出现了什么问题,或者(理想状态下)强制程序处理问题,并返回到稳定状态。

异常参数

与使用 Java 中的其他对象一样,我们总是用 new堆上创建异常对象,这也伴随着存储空间的分配和构造器的调用。所有标准异常类都有两个构造器:一个是无参构造器;另一个是接受字符串作为参数,以便能把相关信息放入异常对象的构造器:

throw new NullPointerException("Integer i is null");

关键字 throw 将产生许多有趣的结果。在使用 new 创建了异常对象之后,此对象的引用将传给 throw。尽管异常对象的类型通常与方法设计的返回类型不同,但从效果上看,它就像是从方法“返回”的。可以简单地把异常处理看成一种不同的返回机制。另外还能用抛出异常的方式从当前的作用域退出。在这两种情况下,将会返回一个异常对象,然后退出方法或作用域。抛出异常与方法正常返回的相似之处到此为止。因为异常返回的“地点”与普通方法调用返回的“地点”完全不同。(异常将在一个恰当的异常处理程序中得到解决,它的位置可能离异常被抛出的地方很远,也可能会跨越方法调用栈的许多层级。)此外,能够抛出任意类型的 Throwable 对象,它是异常类型的根类。通常,对于不同类型的错误,要抛出相应的异常。错误信息可以保存在异常对象内部或者用异常类的名称来暗示。上一层环境通过这些信息来决定如何处理异常。(通常,唯一的信息只有异常的类型名,而在异常对象内部没有任何有意义的信息。)

异常捕获

要明白异常是如何被捕获的,必须首先理解监控区域(guarded region)的概念。它是一段可能产生异常的代码,并且后面跟着处理这些异常的代码。

try 语句块

存放可能产生异常的代码,如果在方法内部抛出了异常(或者在方法内部调用的其他方法抛出了异常),这个方法将在抛出异常的过程中结束。要是不希望方法就此结束,可以在方法内设置一个特殊的块来捕获异常。

try {
    // Code that might generate exceptions
}

有了异常处理机制,可以把所有动作都放在 try 块里,然后只需在一个地方就可以捕获所有异常,你的代码将更容易编写和阅读。

catch语句块

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

    public static void main(String[] args) {
        try{
            // Code that might generate exceptions
        }catch (NullPointerException e){// Handle exceptions type
            
        }catch (IndexOutOfBoundsException e){// Handle exceptions type
            
        }catch (RuntimeException e){// Handle exceptions type
            
        }
    }

当异常被抛出时,异常处理机制将负责搜寻参数与异常类型相匹配的第一个处理程序。然后进入 catch 子句执行,此时认为异常得到了处理。一旦 catch 子句结束,则处理程序的查找过程结束。

捕获所有异常

可以只写一个异常处理程序来捕获所有类型的异常。通过捕获异常类型的基类 Exception,就可以做到这一点(事实上还有其他的基类,但 Exception 是所有异常类的基类):

        try {
            // Code that might generate exceptions
        }catch(Exception e) {
            System.out.println("Caught an exception");
        }
多重捕获

如果有一组具有相同基类的异常,你想使用同一方式进行捕获,那你直接 catch 它们的基类型。但是,如果这些异常没有共同的基类型,在 Java 7 之前,你必须为每一个类型编写一个 catch

public class OneException extends Exception {
}
public class TwoException extends Exception {
}
public class ThreeException extends Exception {
}
public class Test {
    void x() throws OneException, TwoException, ThreeException{};
    void a(){
        try {
            x();
        }catch(OneException e) {
            e.printStackTrace();
        } catch (TwoException e) {
            e.printStackTrace();
        } catch (ThreeException e) {
            e.printStackTrace();
        }
    }
}

通过 Java 7 的多重捕获机制,你可以使用“或”将不同类型的异常组合起来,只需要一行 catch 语句:

public class Test {

    void x() throws OneException, TwoException, ThreeException{};
    void a(){
        try {
            x();
        }catch(OneException | TwoException |ThreeException e) {
            e.printStackTrace();
        }
    }
}
重新抛出异常

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

    public static void main(String[] args) {
        try {
            try{
                x();
            }catch (OneException e){
                throw new TwoException();
            }
        }catch(TwoException e) {
            e.printStackTrace();
        }
    }

永远不必为清理前一个异常对象而担心,或者说为异常对象的清理而担心。它们都是用 new 在堆上创建的对象,所以垃圾回收器会自动把它们清理掉。

终止与恢复

异常处理理论上有两种基本模型。Java 支持终止模型(它是 JavaC++ 所支持的模型)。在这种模型中,将假设错误非常严重,以至于程序无法返回到异常发生的地方继续执行。一旦异常被抛出,就表明错误已无法挽回,也不能回来继续执行;另一种称为恢复模型。意思是异常处理程序的工作是修正错误,然后重新尝试调用出问题的方法,并认为第二次能成功。
在过去,使用支持恢复模型异常处理的操作系统的程序员们最终还是转向使用类似“终止模型”的代码,其中的主要原因可能是它所导致的耦合:恢复性的处理程序需要了解异常抛出的地点,这势必要包含依赖于抛出位置的非通用性代码。这增加了代码编写和维护的困难,对于异常可能会从许多地方抛出的大型程序来说,更是如此。

自定义异常

不必拘泥于 Java 中已有的异常类型。Java 提供的异常体系不可能预见所有的希望加以报告的错误,所以可以自己定义异常类来表示程序中可能会遇到的特定问题。

public class ThisException extends Exception {
    ThisException(String message){
        super(message);
    }
}

public class Test {
    public static void main(String[] args) {
        try {
            throw new ThisException("this exception");
        } catch (ThisException e) {
            //e.printStackTrace();标准错误流
            System.out.println(e.getMessage());//Output: this exception
        }
    }
}

super 关键字明确调用了其基类构造器,它接受一个字符串作为参数。还可以更进一步自定义异常,比如加入额外的构造器和成员:

public class ThisException extends Exception {
    private int code;

    ThisException(){

    }
    ThisException(String message){
        super(message);
    }

    ThisException(int code,String message){
        super(message);
        this.code=code;
    }

    public int getCode() {
        return code;
    }
}
public class Test {

    static void a(){
        try {
            throw new ThisException();
        } catch (ThisException e) {
            System.out.println("empty exception");//empty exception
        }
    }

    static void a1(){
        try {
            throw new ThisException("this exception");
        } catch (ThisException e) {
            System.out.println(e.getMessage());//Output: this exception
        }
    }

    static void a2(){
        try {
            throw new ThisException(0000,"this exception");
        } catch (ThisException e) {
            System.out.println(e.getMessage()+","+e.getCode());//Output: this exception,0
        }
    }



    public static void main(String[] args) {
        a();
        a1();
        a2();
    }
}

既然异常也是对象的一种,所以可以继续修改这个异常类,以得到更强的功能。但要记住,使用程序包的客户端程序员可能仅仅只是查看一下抛出的异常类型,其他的就不管了(大多数 Java 库里的异常都是这么用的),所以对异常所添加的其他功能也许根本用不上。

异常声明

Java 鼓励人们把方法可能会抛出的异常告知使用此方法的客户端程序员。这是种优雅的做法,它使得调用者能确切知道写什么样的代码可以捕获所有潜在的异常,这样就可以进行相应的处理。Java 提供了相应的语法(并强制使用这个语法),使你能以礼貌的方式告知客户端程序员某个方法可能会抛出的异常类型。这就是异常说明,它属于方法声明的一部分,紧跟在形式参数列表之后。

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

    public void a() throws IndexOutOfBoundsException, NullPointerException {

    }

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

    public void a(){

    }

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

Java标准异常

Throwable 这个 Java 类被用来表示任何可以作为异常被抛出的类。Throwable 对象可分为两种类型(指从 Throwable 继承而得到的类型):Error 用来表示编译时和系统错误(除特殊情况外,一般不用你关心);Exception 是可以被抛出的基本类型,在 Java 类库、用户方法以及运行时故障中都可能抛出 Exception 型异常。所以 Java 程序员关心的基类型通常是 Exception。要想对异常有全面的了解,最好去浏览一下 HTML 格式的 Java 文档(可以从 java.sun.com 下载)。为了对不同的异常有个感性的认识,这么做是值得的。但很快你就会发现,这些异常除了名称外其实都差不多。同时,Java 中异常的数目在持续增加,所以在书中简单罗列它们毫无意义。所使用的第三方类库也可能会有自己的异常。对异常来说,关键是理解概念以及如何使用。

异常的基本的概念是用名称代表发生的问题,并且异常的名称应该可以望文知意。异常并非全是在 java.lang 包里定义的;有些异常是用来支持其他像 util、net 和 io 这样的程序包,这些异常可以通过它们的完整名称或者从它们的父类中看出端倪。比如,所有的输入/输出异常都是从 java.io.IOException 继承而来的。

RuntimeException

属于运行时异常的类型有很多,它们会自动被 java 虚拟机抛出。这些异常都是从 RuntimeException 类继承而来,所以既体现了继承的优点,使用起来也很方便。并且,也不再需要在异常说明中声明方法将抛出 RuntimeException 类型的异常(或者任何从 RuntimeException 继承的异常),它们也被称为“不受检查异常”。这种异常属于错误,将被自动捕获,就不用你亲自动手了。要是自己去检查 RuntimeException 的话,代码就显得太混乱了。不过尽管通常不用捕获 RuntimeException 异常,但还是可以在代码中抛出 RuntimeException 类型的异常。

  1. 无法预料的错误。比如从你控制范围之外传递进来的 null 引用。
  2. 作为程序员,应该在代码中进行检查的错误。(比如对于 ArrayIndexOutOfBoundsException,就得注意一下数组的大小了。)在一个地方发生的异常,常常会在另一个地方导致错误。
    在这些情况下使用异常很有好处,它们能给调试带来便利。

如果不捕获这种类型的异常会发生什么事呢?因为编译器没有在这个问题上对异常说明进行强制检查,RuntimeException 类型的异常也许会穿越所有的执行路径直达 main() 方法,而不会被捕获。要明白到底发生了什么,可以试试下面的例子:

public class Test {

    static void f() {
        throw new RuntimeException("From f()");
    }
    static void g() {
        f();
    }
    public static void main(String[] args) {
        g();
    }
    /** Output: 
     * Exception in thread "main" java.lang.RuntimeException: From f()
     * 	at com.study.test.Test.f(Test.java:18)
     * 	at com.study.test.Test.g(Test.java:21)
     * 	at com.study.test.Test.main(Test.java:24)
     */
}

如果 RuntimeException 没有被捕获而直达 main(),那么在程序退出前将调用异常的 printStackTrace() 方法。

不应把 Java 的异常处理机制当成是单一用途的工具。是的,它被设计用来处理一些烦人的运行时错误,这些错误往往是由代码控制能力之外的因素导致的;然而,它对于发现某些编译器无法检测到的编程错误,也是非常重要的。

使用 finally 进行清理

有一些代码片段,可能会希望无论 try 块中的异常是否抛出,它们都能得到执行。这通常适用于内存回收之外的情况(因为回收由垃圾回收器完成),为了达到这个效果,可以在异常处理程序后面加上 finally 子句。完整的异常处理程序看起来像这样:

public class Test {
    public static void main(String[] args) {
        try {
            throw new TwoException();
        } catch (TwoException e) {
            e.printStackTrace();
            return;
        } finally {
            System.out.println("run finally");
        }
        /** Output:
         * com.study.test.exception.TwoException
         * 	at com.study.test.Test.main(Test.java:19)
         * run finally
         */
    }
}

可以从输出中发现,无论异常是否被抛出,finally 子句总能被执行。对于没有垃圾回收和析构函数自动调用机制的语言来说,finally 非常重要。它能使程序员保证:无论 try 块里发生了什么,内存总能得到释放。但 Java 有垃圾回收机制,所以内存释放不再是问题。而且,Java 也没有析构函数可供调用。因为 finally 子句总是会执行,当涉及 breakcontinue 语句的时候,所以可以从一个方法内的多个点返回,仍然能保证重要的工作会执行

异常丢失

Java 的异常实现也有瑕疵。异常作为程序出错的标志,决不应该被忽略,但它还是有可能被轻易地忽略。如果运行这个程序,就会看到即使方法里抛出了异常,它也不会产生任何输出。

    public static void main(String[] args) {
        try {
            throw new RuntimeException();
        }finally{
        
        }
    }

C++ 把“前一个异常还没处理就抛出下一个异常”的情形看成是糟糕的编程错误。也许在 Java 的未来版本中会修正这个问题。

构造器

有一点很重要,即你要时刻询问自己“如果异常发生了,所有东西能被正确的清理吗?"尽管大多数情况下是非常安全的,但涉及构造器时,问题就出现了。如果在构造器内抛出了异常,这些清理行为也许就不能正常工作了。这意味着在编写构造器时要格外细心。

public class A {
    public void close(){
        System.out.println("close resource");
    }
    public static void main(String[] args) {
        A a = new A();
        try {

        }finally {
            a.close();//Output: close resource
        }
    }
}

如果对象构造不会失败,就不需要任何 catch。如果具有可以失败的构造器,且需要清理的对象。为了正确处理这种情况,事情变得很棘手,因为对于每一个构造,都必须包含在其自己的 try-finally 语句块中,并且每一个对象构造必须都跟随一个 try-finally 语句块以确保清理。

public class A {
    A() throws RuntimeException {
    }

    public void close() {
        System.out.println("close resource");
    }

    public static void main(String[] args) {
        try {
            A a = new A();
            try {
                A a1 = new A();
            } finally {
                a.close();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

基本上,你应该仔细考虑所有的可能性,并确保正确处理每一种情况。

Try-With-Resources 用法

如下面的例子,你可能需要考虑:资源清理、需要在特定的时刻进行资源清理,比如你离开作用域的时候(在通常情况下意味着通过异常进行清理)。

    public static void main(String[] args) {
        FileInputStream fileInputStream = null;
        try {
            fileInputStream = new FileInputStream("D:\\mnt\\1.txt");
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            if(fileInputStream != null){
                try {
                    fileInputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

finally 子句有自己的 try 块时,感觉事情变得过于复杂。

幸运的是,Java 7 引入了 try-with-resources 语法,在 Java 7 之前,**try **总是后面跟着一个 {,但是现在可以跟一个带括号的定义 - 这里是我们创建的 FileInputStream 对象。括号内的部分称为资源规范头(resource specification header)。现在可用于整个 try 块的其余部分。更重要的是,无论你如何退出 try 块(正常或异常),都会执行前一个 finally 子句的等价物,但不会编写那些杂乱而棘手的代码。这是一项重要的改进。它可以非常清楚地简化上面的代码:

    public static void main(String[] args) {
        try (FileInputStream fileInputStream = new FileInputStream("D:\\mnt\\1.txt")) {
            int read = fileInputStream.read();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

资源规范头中可以包含多个定义,并且通过分号进行分割(最后一个分号是可选的)。规范头中定义的每个对象都会在 try 语句块运行结束之后调用 close() 方法。try-with-resources 里面的 try 语句块可以不包含 catch 或者 finally 语句而独立存在。在这里,IOExceptionmain() 方法抛出,所以这里并不需要在 try 后面跟着一个 catch 语句块。

Java 5 中的 Closeable 已经被修改,修改之后的接口继承了 AutoCloseable 接口。所以所有实现了 Closeable 接口的对象,都支持了 try-with-resources 特性。假设我们在资源规范头中定义了一个不是 AutoCloseable 的对象,会出现编译时错误。

public class C implements AutoCloseable {
    C(){
        String name = getClass().getSimpleName();
        System.out.println("create "+ name);
    }
    @Override
    public void close() throws Exception {
        String name = getClass().getSimpleName();
        System.out.println("close "+ name);
    }
}
public class A extends C {
}
public class B extends C {
}
public class D {
}
public class Test {
    public static void main(String[] args) {
        try (A a = new A();B b= new B()) {
            D d = new D();
        } catch (Exception e) {
            e.printStackTrace();
        }
        /** Output:
         * create A
         * create B
         * close B
         * close A
         */
    }
}

退出 try 块会调用两个对象的 close() 方法,并以与创建顺序相反的顺序关闭它们。再try代码块中的对象并不会被清除,因为它不是在资源规范头中创建的,所以它没有被保护。

如果其中一个构造函数抛出异常,如下所示:

public class C implements AutoCloseable {
    C(){
        String name = getClass().getSimpleName();
        System.out.println("create "+ name);
    }
    @Override
    public void close() throws Exception {
        String name = getClass().getSimpleName();
        System.out.println("close "+ name);
    }
}
public class A extends C {
}
public class B extends C {
    public B(){
        throw new RuntimeException();
    }
}
public class Test {
    public static void main(String[] args) {
        try (A a = new A();B b= new B()) {
        } catch (Exception e) {
            System.out.println("exception");
            e.printStackTrace();
        }
        /** Output:
         * create A
         * create B
         * close A
         * exception
         * java.lang.RuntimeException
         * 	at com.study.test.exception.B.<init>(B.java:5)
         * 	at com.study.test.Test.main(Test.java:18)
         */
    }
}

其中一个出现异常,就不会为这个对象调用关闭方法,如果有已创建的对象,就会先关闭后,再抛出异常,结束操作。

最后,让我们看一下抛出异常的 close() 方法:

public class C implements AutoCloseable {
    C(){
        String name = getClass().getSimpleName();
        System.out.println("create "+ name);
    }
    @Override
    public void close() throws Exception {
        String name = getClass().getSimpleName();
        System.out.println("close "+ name);
    }
}
public class A extends C {
}
public class B extends C {
    @Override
    public void close() throws Exception {
        super.close();
        throw new RuntimeException();
    }
}
public class Test {
    public static void main(String[] args) {
        try (A a = new A();B b= new B()) {
        } catch (Exception e) {
            System.out.println("exception");
            e.printStackTrace();
        }
        /** Output:
         * create A
         * create B
         * close B
         * close A
         * exception
         * java.lang.RuntimeException
         * 	at com.study.test.exception.B.close(B.java:7)
         * 	at com.study.test.Test.main(Test.java:19)
         */
    }
}

A、B对象都已创建,它们都以相反的顺序关闭,close() 抛出异常。当你想到它时,这就是你想要发生的事情,但是如果你必须自己编写所有这些逻辑,那么你可能会错过一些错误。想象一下所有代码都在那里,程序员没有考虑清理的所有含义,并且做错了。因此,应始终尽可能使用 try-with-resources。它有助于实现该功能,使得生成的代码更清晰,更易于理解。

异常匹配

抛出异常的时候,异常处理系统会按照代码的书写顺序找出“最近”的处理程序。找到匹配的处理程序之后,它就认为异常将得到处理,然后就不再继续查找。如果把捕获基类的 catch 子句放在最前面,以此想把派生类的异常全给“屏蔽”掉,你会发现编译错误。

public class OneException extends Exception {
}
public class TwoException extends OneException {
}
public class Test {


    public static void main(String[] args) {
        try{
            throw new TwoException();
        }catch (Exception e){
		
        }catch (OneException e){//Output: Exception 'com.study.test.exception.OneException' has already been caught

        }
    }
}

异常指南

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

  1. 尽可能使用 try-with-resource
  2. 在恰当的级别处理问题。(在知道该如何处理的情况下才捕获异常。)
  3. 解决问题并且重新调用产生异常的方法。
  4. 进行少许修补,然后绕过异常发生的地方继续执行。
  5. 用别的数据进行计算,以代替方法预计会返回的值。
  6. 把当前运行环境下能做的事情尽量做完,然后把相同的异常重抛到更高层。
  7. 把当前运行环境下能做的事情尽量做完,然后把不同的异常抛到更高层。
  8. 终止程序。
  9. 进行简化。(如果你的异常模式使问题变得太复杂,那用起来会非常痛苦也很烦人。)
  10. 让类库和程序更安全。(这既是在为调试做短期投资,也是在为程序的健壮性做长期投资。)

本章小结

异常处理系统就像一个活门(trap door),使你能放弃程序的正常执行序列。异常处理的一个重要原则是“只有在你知道如何处理的情况下才捕获异常"。通过允许一个处理程序去处理多个出错点,异常处理还使得错误处理代码的数量趋于减少。
异常处理起源于 PL/1Mesa 之类的系统中,后来又出现在 CLU、Smalltalk、Modula-3、Ada、Eiffel、C++、Python、Java 以及后 Java 语言 RubyC# 中。Java 的设计和 C++ 很相似,只是 Java 的设计者去掉了一些他们认为 **C++**设计得不好的东西。Java 无谓地发明了“被检查的异常”(很明显是受 C++ 异常说明的启发,以及受 C++ 程序员们一般对此无动于衷的事实的影响),但是,这还只是一次尝试,目前为止还没有别的语言采用这种做法。

好的程序设计语言能帮助程序员写出好程序,但无论哪种语言都避免不了程序员用它写出坏程序。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值