Java编程基础:异常处理

David发表在天码营

异常

异常定义了程序中遇到的非致命的错误,比如如程序要打开一个不存的文件、网络连接中断、除零操作、操作数越界、装载一个不存在的类等情况。

先来看看下面的程序代码:

package com.tianmaying;

public class HelloWorld {    
    public static void main(String[] args) {
        int x = 5 / 0;
        System.out.println(x);
    }
}

编译运行上面的程序,将出现如下错误:

Exception in thread "main" java.lang.ArithmeticException: / by zero
    at com.tianmaying.HelloWorld.main(HelloWorld.java:5)

上面的程序运行的结果报告发生了算术异常(ArithMethicException),应用执行提前结束,这种情况就是我们所说的异常。

try/catch语句

Java异常强制我们去考虑程序的强健性和安全性,其在设计时就考虑到这些问题,提出了一套异常处理的解决方案。将上面的程序代码进行如下修改:

package com.tianmaying;

public class HelloWorld {    
    public static void main(String[] args) {
        try {
            int x = 5 / 0;
            System.out.println(x);
        } catch (Exception e) {
            e.printStackTrace();
        }
        System.out.println("program is still running here!");
    }
}

程序运行结果如下:

java.lang.ArithmeticException: / by zero
    at com.tianmaying.HelloWorld.main(HelloWorld.java:6)
program is still running here!

我们将可能出现异常的代码通过try/catch代码进行了处理,当异常发生时,系统能够继续运行,而没有意外终止。

try代码块中的语句发生了异常,程序就会跳转到catch代码块中执行,执行完catch代码块中的程序代码后,系统会继续执行catch代码块之后的代码,try代码块中发生异常语句后的代码则不会再执行。比如如程序中的System.out.println(x);不会再被执行。

异常发生时,系统会将代码行号,异常类别等信息封装到一个对象中,并将这个对象传递给catch代码块,catch代码块是以下面的格式出现的。

catch(Exception e) {
    e.printStackTrace();
}

catch关键字后跟有一个用括号括起来的Exception类型的参数e,这跟我们经常用到的如何定义一个函数接收的参数格式是一样的。

括号中的Exception就是try代码块传递给catch代码块的变量类型,e就是变量名,所以我们也可以将e改用成别的名称(如ex),如下所示:

catch(Exception ex) {
    ex.printStackTrace();
}

每个try语句必须有一个或多个catch语句对应,try代码块与catch代码块及finally代码块之间不能有其他语句。

throws关键字

我们修改一下代码,将进行除法运算的代码提取到一个成员方法中:

package com.tianmaying;

public class HelloWorld {    

    private static void fun() {
        int x = 5 / 0;
        System.out.println(x);
    }

    public static void main(String[] args) {
        try {
            fun();
        } catch (Exception e) {
            e.printStackTrace();
        }
        System.out.println("program is still running here!");
    }
}

这种情况下,我们面对的是一个fun()函数,我们如何知道需要给这个函数调用添加try/catch处理呢? 或者需要调用别人提供的方法时,我们如何知道是否需要进行异常处理呢?

在Java中,这个问题是交给被调用的方法的实现者来解决的。在这个例子中,定义fun()方法的时候,我们在方法参数列表后面增加一个throws关键字,然后增加这个方法可能抛出的异常,这种情况下调用者就必须使用try/catch进行处理了,否则编译将无法通过。

因此,我们将代码改为:

package com.tianmaying;

public class HelloWorld {    

    private static void foo() throws Exception {
        int x = 5 / 0;
        System.out.println(x);
    }

    public static void main(String[] args) {
        try {
            foo();
        } catch (Exception e) {
            e.printStackTrace();
        }
        System.out.println("program is still running here!");
    }
}

如果此时把try/catch语句删除的话,如下代码将会报编译错误。

package com.tianmaying;

public class HelloWorld {    

    ...

    public static void main(String[] args) {
        fun();
        System.out.println("program is still running here!");
    }
}

如果一个方法中的语句执行时可能生成某种异常,但是并不能确定如何处理,则此方法应声明抛出异常,表明该方法将不对这些异常进行处理,而由该方法的调用者负责处理。也就是如果程序中的异常没有用try/catch捕捉异常以及处理异常的代码,我们可以在程序代码所在的函数(方法)声明后用throws声明该函数要抛出异常,将该异常抛出到该函数的调用函数中。

自定义异常与Throw关键字

Exception类是java.lang.Throwable类的子类。在实际应用中,我们一般是使用Exception的子类来描述特定的异常的。Exception类是所有异常类的父类,Java语言为我们提供了许多Exception类的子类,分别对应不同的异常类型,例如:

  • ArithmeticException(在算术运算中发生的异常,如除以零)
  • NullPointerException(变量还没有指向一个对象,就引用这个对象的成员)
  • ArrayIndexOutOfBoundsException(访问数组对象中不存在的元素)

使用Java内置的异常类可以描述在编程时出现的大部分异常情况。除此之外,用户还可以自定义异常。用户自定义异常类,只需继承Exception类即可。

在程序中使用自定义异常类,大体可分为以下几个步骤:

  1. 创建自定义异常类
  2. 在方法中通过throw关键字抛出异常对象1.如果在当前抛出异常的方法中处理异常,可以使用try-catch语句捕获并处理;否则在方法的声明处通过throws关键字指明要抛出给方法调用者的异常,继续进行下一步操作1.在出现异常方法的调用者中捕获并处理异常

比如下面的代码定义了已定义异常:

package com.tianmaying;

public class BlogAppException extends Exception {

    private static final long serialVersionUID = 1L;

    private String command;// 可以给自定义异常增加成员变量,用以保存额外的异常信息

    public BlogAppException(String command) {
        this.command = command;
    }

    public String toString(){
        return "Exception happened when executing command " + command;
    }

}

Java是通过throw关键字抛出异常对象,其语法格式是:

throw 异常对象;

我们来增加一个可以抛出这个异常的方法,并且在main方法中进行调用:

package com.tianmaying;

public class HelloWorld {    

    private static void bar() throws BlogAppException {
        System.out.println("let's assume BlogAppException happened when executing `create` command");
        // 为了演示,这里我们假设执行create命令时,抛出了异常
        throw new BlogAppException("create");
    }

    private static void foo() throws ArithmeticException {
        int x = 5 / 0;
        System.out.println(x);
    }

    public static void main(String[] args) {
        try {
            foo();
            bar();
        } catch (Exception e) {
            e.printStackTrace();
        }
        System.out.println("program is still running here!");
    }
}

catch多个异常

Java中一个方法是可以被声明成抛出多个异常的。下面再来看看调用程序应该如何对多个异常进行处理。

有时针对不同的异常我们需要进行不同的处理,比如现在HelloWorld类的main方法中可能同时面对BlogAppExceptionArithmeticExceptioncatch (Exception e)语句不能区分到底是发生了哪个异常。

Java中可以使用一个try后面跟着多个catch来捕捉多个异常,每一个catch可以处理一个不同的异常类型。为了分别处理不同的异常情况,我们可以将代码进行如下修改:

package com.tianmaying;

public class HelloWorld {

    ...

    public static void main(String[] args) {
        try {
            foo();
            bar();
        } catch (BlogAppException e) {
            e.printStackTrace();
        } catch (ArithmeticException e) {
            e.printStackTrace();
        } catch (Exception e) {
            e.printStackTrace();
        }

        System.out.println("program is still running here!");
    }
}

如果运行时出现异常,异常处理的流程如下:

  • 首先看抛出异常是否是BlogAppException类型的异常,如果匹配则执行对应的语句块
  • 否则看抛出异常是否是ArithmeticException类型的异常,如果匹配则执行对应的语句块
  • 否则执行Exception异常对应的语句块

所以各种catch代码块的放置顺序非常重要。如果使用catch(Exception e)语句,那么它不能放在其他catch语句的前面,否则后面的catch永远得不到执行,因为Exception是所有异常的父类,可以匹配任何异常。在上面的例子中,由于只可能发生BlogAppExceptionArithmeticException,所以最后catch(Exception e)其实是不需要的。

关于异常,在继承中还要注意两点:

  • 一个方法被覆盖时,覆盖它的方法必须扔出相同的异常或异常的子类。
  • 如果父类抛出多个异常,那么重写(覆盖)方法必须扔出那些异常的一个子集,也就是说,不能扔出新的异常。

finally关键字

try/catch语句后,我们还可以有个finally语句,finally语句中的代码块不管异常是否被捕获总是要被执行的。我们将上面的程序作如下修改,来看看finally语句的用法与作用。

package com.tianmaying;

public class HelloWorld {

    ...

    public static void main(String[] args) {
        try {
            foo();
            bar();
        } catch (BlogAppException e) {
            e.printStackTrace();
            return;
        } catch (ArithmeticException e) {
            e.printStackTrace();
            return; // 即使这里return了,依然会执行finally中的语句
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            System.out.println("programe will run `finally` code block!");
        }

        System.out.println("program is still running here!");
    }
}

finally还有一个特殊之处在于,即使try代码块和catch代码块中使用了return语句退出当前方法或break跳出某个循环 ,相关的finally代码块都要执行。finally中的代码块不能被执行的唯一情况是:在被保护代码块中执行了System.exit(0)


更多文章请访问天码营网站



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值