异常
异常定义了程序中遇到的非致命的错误,比如如程序要打开一个不存的文件、网络连接中断、除零操作、操作数越界、装载一个不存在的类等情况。
先来看看下面的程序代码:
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类即可。
在程序中使用自定义异常类,大体可分为以下几个步骤:
- 创建自定义异常类
- 在方法中通过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
方法中可能同时面对BlogAppException
和ArithmeticException
,catch (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是所有异常的父类,可以匹配任何异常。在上面的例子中,由于只可能发生BlogAppException
和ArithmeticException
,所以最后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)
。
更多文章请访问天码营网站