发现错误的理想时机是在编译阶段,也就是程序在编码过程中发现错误,然而一些业务逻辑错误,编译器并不能一定会找到错误,余下的问题需要在程序运行期间解决,这就需要发生错误的地方能够准确的将错误信息传递给某个接收者,以便接收者知道如何正确的处理这个错误信息。
一、概念
二、基本异常
if(t==null){
throw new NullPointerException();
}
上面代码中,我们判断当前对象引用是否没有进行初始化,如果没有进行初始化,那么就创建一个NullPointerException()对象,然后使用throw关键字,将该对象的引用抛出。
三、捕获异常
try{
//---
}
在关键字try后包围的一部分代码块,称作try块,这样做比起之前说的要在每个会产生错误的地方进行判断要容易的多,并且代码的可读性大大增强,产生异常之后抛出的异常必须在某处得到处理,这个地点就是异常处理区,异常处理区紧跟着try块,使用catch关键字表示:
try{ //---}catch(Type1 arg1){ //---}catch(Type2 arg2){ //---每个catch语句看起来像是一个接收一个且仅接收一个的特殊参数类型的方法。可以在方法块内部处理这个参数,当然有的异常见名知意,因此可以不用处理参数,但并不可以省略。可以有多个catch块与try对应,用来捕获多种不同的异常。异常处理程序必须紧跟在try块之后,当异常被抛出之后,异常处理机制负责搜寻参数与异常类型相匹配的第一个处理程序。然后进入catch块中执行,此时认为异常得到了处理。一旦catch子句结束,则认为处理程序的查找过程结束。只有匹配的catch字句才会执行,在try块内部不同的方法可能会产生相同的异常,而你只需要提供一个针对此类型的异常处理程序。异常的处理理论上有两种模型,一种是终止模型,表示程序发生异常之后,已经无法恢复到程序正常执行的顺序上,程序被迫发生终止。另一种模型是恢复模型,表示异常发生时,我们要做的是处理错误,而不是抛出异常。目前终止模型已经基本取代了恢复模型,虽然这种恢复模型看起来很好,但是实际上使用起来并不容易四、创建自定义异常
Java中虽然提供了很多默认的异常类型,但是要想完全覆盖会发生的异常情况显然是不现实的,因此我们可以自定义异常来表示我们预期可能会出现的异常。自定义的形式也非常简单,只需要继承一个相似的异常类即可。建立一个新的异常类最贱的方法就是让编译器为你产生默认构造器,所以这几乎不需要多少代码。package com.chenxyt.java.practice;
class SimpleException extends Exception{}
public class InheritingException {
public void f() throws SimpleException{
System.out.println("Throw SimpleException from f");
throw new SimpleException();
}
public static void main(String[] args) {
InheritingException ite = new InheritingException();
try{
ite.f();
}catch(SimpleException e){
System.out.println("Caught it!");
}
}
}
运行结果:

当然我们还可以创建一个带有参数的构造器
package com.chenxyt.java.practice;
class MyException extends Exception{
public MyException(){
}
public MyException(String msg){
super(msg);
}
}
public class FullConstructors {
public static void f() throws MyException {
System.out.println("Throwing myException from f()");
throw new MyException();
}
public static void g() throws MyException{
System.out.println("Throwing myException from g()");
throw new MyException("Originated in g()");
}
public static void main(String[] args) {
try{
f();
}catch(MyException e){
e.printStackTrace();
}
try{
g();
}catch(MyException e){
e.printStackTrace(System.out);
}
}
}
运行结果:

五、异常说明
void f() throws TooBig,TooSmall,DivZero{};
代码必须与异常说明一致,比如上边的方法,如果我们在main()中调用,编译器会提示该方法会产生异常,要么使用try catch进行处理,要么使用throws关键字抛出这种异常。
六、捕获所有异常
catch(Exception e){
//---
}
Exception是与编程相关的所有异常的基类(还有其它的基类),所以它不会具有太多的信息。不过可以调用它从其基类Throwable继承的方法
package com.chenxyt.java.practice;
public class ExceptionMethods {
public static void main(String[] args) {
try{
throw new Exception("My Exception");
}catch(Exception e){
System.out.println("Caught Exception");
System.out.println("getMessage():" + e.getMessage());
System.out.println("getLocalizedMessage():" + e.getLocalizedMessage());
System.out.println("toString()" + e.toString());
System.out.println("printStackTrace():--==");
e.printStackTrace();
System.out.println("printStackTrace(System.out):--==");
e.printStackTrace(System.out);
}
}
}

package com.chenxyt.java.practice;
public class WhoCalled {
static void f(){
try{
throw new Exception();
}catch(Exception e){
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();
}
}
运行结果:

catch(Exception e){
System.out.println("caught an exception");
throw e;
}
重抛异常会把异常抛给上一级环境中的异常处理程序,同一个try块后边的catch块将被忽略。此外,异常对象的所有信息都会被保持,所以上一级环境的异常捕获程序可以获得 这个异常的所有信息。如果只是单纯的把这个异常抛出,那么printStackTrace()显示的将是原来这个异常的栈调用信息。如果想更新这个序列信息,可以使用fillInStackTrace()方法,这将返回一个Throwable对象,它是把当前调用栈的信息,填入到原来那个异常对象而建立的。如下:
package com.chenxyt.java.practice;
public class ReThrowing {
public static void f() throws Exception{
System.out.println("the exception in f()");
throw new Exception("exception from f()");
}
public static void g() throws Exception{
try{
f();
}catch(Exception e){
System.out.println("inside g e.printStackTrace");
e.printStackTrace();
throw e;
}
}
public static void h() throws Exception{
try{
f();
}catch(Exception e){
System.out.println("inside h e.printStackTrace");
e.printStackTrace();
throw (Exception)e.fillInStackTrace();
}
}
public static void main(String[] args) {
try{
g();
}catch(Exception e){
System.out.println("main:printStackTrace");
e.printStackTrace();
}
try{
h();
}catch(Exception e){
System.out.println("main:printStackTrace");
e.printStackTrace();
}
}
}
运行结果:

可以看出当使用了fillInStackTrace()方法之后,调用栈中的方法栈信息发生了变化。当然如果在捕获了第一种异常之后抛出了另一种异常,那么调用栈的信息也会发生变化,类似使用了fillInStackTrace()方法。但是这样做的话,原始的异常信息就消失了,有时候我们希望在抛出新的异常的时候能保留原来的异常信息,这被称作是异常链。在JDK1.4之前,程序员需要自己编写代码保存原来的异常信息,现在所有的Throwable子类都可以在构造函数中传入一个cause参数,这个参数就用来保存原始异常。这样就可以在当前位置创建并抛出新的异常,也可以通过这个异常链追踪到原始异常。
七、Java标准异常
八、使用finally进行清理
try{
//---
}catch(Exception e1){
//---
}catch(Exception e2){
//---
}finally{
//---
}
下面的示例用来证明finally字句不管异常是否抛出都能被执行:
package com.chenxyt.java.practice;
public class FinallyWorks {
static int count = 0;
public static void main(String[] args) {
while(true){
try{
if(count++ == 0){
throw new Exception();
}
System.out.println("No Exception");
}catch(Exception e){
System.out.println("Exception");
}finally{
System.out.println("In finally clause");
if(count == 2){
break;
}
}
}
}
}
可以看到不管异常是否被抛出,finally字句都执行了。这个程序告诉我们,当程序发生了异常之后不能正确的回到原来的执行顺序上,我们可以在finally块中处理一些必要做的事情。比如打开文件的操作,当文件操作发生异常的时候,我们要确保文件被正确的关闭。
九、异常的限制
package com.chenxyt.java.practice;
class BaseballException extends Exception{
}
class Foul extends BaseballException{
}
class Strike extends BaseballException{
}
abstract class Inning{
public Inning() throws BaseballException{
}
public void event() throws BaseballException{
}
public abstract void atBat() throws Strike,Foul;
public void walk(){
}
}
class StormException extends Exception{
}
class RainedOut extends StormException{
}
class PopFoul extends Foul{
}
interface Storm{
public void events() throws RainedOut;
public void rainHard() throws RainedOut;
}
public class StormyInning extends Inning implements Storm{
public StormyInning() throws BaseballException {
super();
// TODO Auto-generated constructor stub
}
@Override
public void rainHard() throws RainedOut {
// TODO Auto-generated method stub
}
@Override
public void atBat() throws Strike, Foul {
// TODO Auto-generated method stub
}
@Override
public void events() throws RainedOut {
// TODO Auto-generated method stub
}
//抽象类中的该方法没有抛出任何异常 所以覆盖重写的时候 也不能抛出异常
// public void walk() throws RainedOut{
//
// }
//抽象类中的该方法抛出了异常 重写的时候可以选择不抛出异常
// public void event(){
//
// }
//抽象类中的该方法抛出了BaseballException 所以可以抛出其异常的子异常
public void event() throws Foul{
}
}
如注释上所述,当继承或者实现一个类的时候,这个子类只能抛出基类中异常列表中的异常,或者其子异常,或者不抛出异常,但是不能抛出其它异常,或者抛出异常的基类异常。
十、构造器
package com.chenxyt.java.practice;
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 name) throws Exception{
try{
in = new BufferedReader(new FileReader(name));
}catch(FileNotFoundException e1){
//没有找到文件也就是文件没有打开 所以不需要清理
System.out.println("The file is not found");
throw e1;
}catch(Exception e2){
//所有其它的异常都发生在文件打开之后 因此需要进行清理工作
try{
in.close();
}catch(IOException e3){
//---
}
throw e2;
}finally{
//此处由于构造成功也会执行 所以不能进行文件关闭操作
}
}
}
package com.chenxyt.java.practice;
public class CleanUp {
public static void main(String[] args) {
try{
InputFile in = new InputFile("CleanUp.java");
try{
//
}catch(Exception e1){
//此处为文件操作异常
}finally{
//-- 此处清理操作关闭文件
}
}catch(Exception e2){
//--此处捕获构造异常
}
}
}
十一、异常匹配
package com.chenxyt.java.practice;
class Annyoance extends Exception{
}
class Sneeze extends Annyoance{
}
public class Human {
public static void main(String[] args) {
try{
throw new Sneeze();
}catch(Sneeze s){
System.out.println("Catch Sneeze");
}catch(Annyoance a){
System.out.println("Catch Annyoance1");
}
try{
throw new Sneeze();
}catch(Annyoance a){
System.out.println("Catch Annyoance2");
}
}
}

十二、总结
异常是Java编程中不可或缺的一部分,它将程序正常执行的路径与错误处理分开,减少了编码判断的冗余。使用try-catch的方式进行异常处理,同时我们也可以创建自己的异常,继承其它的异常类。一些RuntimeException无需我们自己捕捉,程序会自动捕捉。注意构造器内发生异常的情况,因为构造器往往是只是简单的构造了一个对象,对象还需要使用,所以在使用finally进行清理的时候需要注意这一点。因为finally域的代码块不管异常是否发生都会执行。处理异常的时候可以使用基类Throwable提供的方法,比如使用printStackTrace打印栈的调用顺序。