一、概念

使用异常能降低处理错误代码的复杂程度,并且将错误在一个地方进行处理,于是将“描述在正常行为过程中做过什么事”的代码和“出了问题怎么办”的代码相分离

二、基本异常

异常情形指的是当前环境下没有足够的信息来让我们解决这个问题,比如当除数为0发生的时候,我们不知道除数为零代表着什么,(比如在算淘宝购物花销所占百分比的时候,你发现你这个月根本没花钱,总数是零),并不知道该如何处理这个异常。因此就要抛出异常
抛出异常后,会在堆上new出一个新的异常对象,然后当前执行路径终止,并弹出这个异常对象的引用,然后异常处理机制接管程序抓住这个异常,进行异常处理。
抛出异常的时候就像这样。

throw new NullPointerException()

1.异常参数

异常也是对象,也有他自己的构造器,当在堆上new出一个异常对象的时候,他也可以执行不同的对象构造器。标准异常类都有两个构造器:一个是默认构造器;另一个是接受字符串参数,比如:

throw new NullPointerException("t=null");

从效果上看,将这个异常给throw了,就像是从方法中“返回”一样,另外还能用抛出异常的方式从当前作用域退出。
能够抛出任意类型的Throwable对象,他是异常类型的根类。

三、捕获异常

首先要理解监控区域的概念,他是一段可能产生异常的代码,后面跟着处理这些可能出现的异常的代码。

1.try块

如果在方法内部抛出了异常,那么这个方法将在抛出异常的时候结束,如果不希望方法直接结束,可以在方法内设置一个块来“尝试”各种可能产生异常的方法。

try{
//code
}

2.异常处理程序

抛出的异常必须在异常处理程序中得到处理。异常处理程序跟随在try块后

try{
}catch(Type1 id1){
//handle exceptions of type1
}catch(Type2 id2){
}

当在try块中出现异常后,异常被抛出,异常处理程序将负责搜寻与这个异常参数类型匹配的第一个异常处理程序,然后进行异常处理,一旦catch结束,则异常处理程序的查找过程结束。

3.终止与恢复

异常处理有两种模型,Java支持终止模型,一旦异常被抛出,表明错误无法挽回,无法退回来继续执行之前出错的代码。
另一种叫做恢复模型,指的是异常处理程序的工作是修正错误然后重新尝试调用出问题的方法,并认为第二次能成功。
clipboard.png

四、创建自定义异常

要自己定义异常类,必须从已有的异常类继承,最好选择意思相近的异常类继承。

package tij.exception;

public class Test {
    void f() throws SimpleException {
        System.out.println("Throw SimpleException from f()");
        throw new SimpleException();
    }
    public static void main(String[] args) {
        Test t = new Test();
        try {
            t.f();
        } catch (SimpleException e) {
            System.out.println("Caught it");
        }
    }
}
class SimpleException extends Exception {}

对于异常来说,最重要的部分就是类名。
这个例子的结果被打印到了控制台上,也可以通过写入System.err将错误发送给标准错误流。通常这比把错误输出到System.out要好,因为System.out也许会被重定向。

package tij.exception;

public class Test {
    static void f() throws MyException {
        System.out.println("Throw MyException from f()");
        throw new MyException();
    }
    static void g() throws MyException {
        System.out.println("Throw MyException from g()");
        throw new MyException();
    }
    public static void main(String[] args) {
        try {
            f();
        } catch (MyException e) {
            e.printStackTrace(System.out);
        }
        try {
            g();
        } catch (MyException e) {
            e.printStackTrace();
        }

    }
}

class MyException extends Exception {
    public MyException() {}
    public MyException(String msg) {
        super(msg);
    }
}

在异常处理程序中,调用了在Throwable类(Exception也是从他继承的)的printStackTrace方法,它将打印“从方法调用处知道异常抛出处的方法调用序列”,在上例中,如果信息被发送到了System.out,则将信息显示在输出中,如果使用默认版本e.printStackTrace则将输出到标准错误流。
clipboard.png

看,上下两个颜色不一样

1.异常与记录日志

可以使用java.util.logging将输出记录到日志中。

package tij.exception;

import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.logging.Logger;

public class Test {
    public static void main(String[] args) {
        try {
            throw new LoggingException();
        } catch (LoggingException e) {
            System.err.println("Caught " + e);
        }
        try {
            throw new LoggingException();
        } catch (LoggingException e) {
            System.err.println("Caught " + e);
        }
    }
}

class LoggingException extends Exception {
    private static Logger logger = Logger.getLogger("LoggingExcetpion");
    public LoggingException() {
        StringWriter trace = new StringWriter();
        printStackTrace(new PrintWriter(trace));
        logger.severe(trace.toString());
    }

}

LoggingException首先创建了一个Logger对象,这个对象会将其输出发送到System.err。如果为了产生日志记录信息,现在我们想把栈轨迹记录下来。而printStackTrace不会产生字符串,因此采用了带有PrintWriter参数的printStackTrace方法,这个方法会将栈轨迹的字符串信息传入到PrintWriter中,然后将栈轨迹信息穿进trace,然后运用severe方法向Logger写入信息。(其实书上没说这个是我猜的)
StringWriter我以前也没有见过,于是查了查api,用了一下发现挺好玩

一个字符流,可以用其回收在字符串缓冲区中的输出来构造字符串。

然后试了试这玩意有啥用

public class Test {
    public static void main(String[] args) {
        StringWriter str=new StringWriter();
        PrintWriter pw=new PrintWriter(str);
        pw.print("abc");
        System.out.println(str.toString());
    }
}

看起来这个StringWriter就是用来收集各种缓冲区里的字符串的。上面的代码也就好解释了。
好回到原来的问题,更常见的情形是,我们需要捕捉与记录其他人编写的异常,因此可以在异常处理程序中生成日志信息

package tij.exception;

import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.logging.Logger;

public class Test {
    private static Logger logger = Logger.getLogger("LoggingException");
    static void logException(Exception e) {
        StringWriter trace = new StringWriter();
        e.printStackTrace(new PrintWriter(trace));
        logger.severe(trace.toString());
    }
    public static void main(String[] args) {
        try {
            throw new NullPointerException();
        } catch (NullPointerException e) {
            logException(e);
        }
    }
}

还可以进一步自定义异常,比如加入额外的构造器和成员

package tij.exception;

public class Test {
    static void f() throws MyException{
        System.out.println("Throwing MyException from f()");
        throw new MyException();
    }
    static void g() throws MyException{
        System.out.println("Throwing MyException from g()");
        throw new MyException("Originated in g()",47);
    }
    
    public static void main(String[] args) {
        try{
            f();
        }catch(MyException e){
            e.printStackTrace();
        }
        try{
            g();
        }catch(MyException e){
            e.printStackTrace();
        }
    }
}

class MyException extends Exception{
    private int x;
    public MyException(){}
    public MyException(String msg){
        super(msg);
    }
    public MyException(String msg,int x){
        super(msg);
        this.x=x;
    }
    public int val(){
        return x;
    }
    public String getMessage(){
        return "Detail Message: "+x+" "+super.getMessage();
    }
    
}

五、异常说明

大段文字,没啥可说
clipboard.png

六、捕获所有异常

一个简单的

catch(Exception e)

可以捕获所有类型的异常,因为Exception是所有与编程相关的异常的父类,但最好把他放在处理列表的末尾。
Exception作为父类自然不会有太多具体信息,但他可以调用从Throwable继承下来的方法比如

  • String getMessaget()
    获取异常详细信息
  • String getLocalizedMessage
    用本地语言表示的异常信息
  • void printStackTrace()
    打印调用栈轨迹,调用栈显示了“发你带到异常抛出地点”的方法调用序列,这个方法输出到标准错误流。
  • void printStackTrace(PrintWriter)
    可以选择输出的流
  • void printStackTrace(PrintStream)
    可以选择输出的流
  • void fillinStackTrace(PrintStream)
    用于在Throwable对象的内部记录栈帧的当前状态

1.栈轨迹

clipboard.png

package tij.exception;

import java.util.Arrays;

public class Test {
    static void f() throws Exception{
        System.out.println("Throwing Exception from f()");
        throw new Exception();
    }
    
    public static void main(String[] args) {
        try{
            f();
        }catch(Exception e){
            System.out.println(Arrays.asList(e.getStackTrace()));
            e.printStackTrace();
        }
    }
}

看起来栈中的一帧指的是一次方法调用啊

2.重新抛出异常

当前异常处理程序里也可以重新抛出异常

catch(MyException e){
            throw e;
        }

如果要想把当前的异常对象重新抛出,那再调用printStackTrace方法的时候将是原来异常抛出点的调用栈信息,没有重新抛出点的信息,要想更新这个信息,可以调用fillInStackTrace方法,这将返回一个Throwable对象,它是通过把当前调用栈信息填入原来那个异常对象而建立的。

package tij.exception;

public class Test {
    static void f() throws Exception {
        System.out.println("originating the exception from f()");
        throw new Exception();
    }
    static void g() throws Exception {
        try {
            f();
        } catch (Exception e) {
            System.out.println("Inside g().e.printStackTrace");
            e.printStackTrace(System.out);
            throw e;
        }
    }
    
    static void h() throws Exception{
        try{
            f();
        }catch(Exception e){
            System.out.println("Inside h().e.printStackTrace");
            e.printStackTrace(System.out);
            throw (Exception)e.fillInStackTrace();
        }
    }

    public static void main(String[] args) {
        try {
            g();
        } catch (Exception e) {
            System.out.println("main:printStackTrace()");
            e.printStackTrace(System.out);
        }
        try {
            h();
        } catch (Exception e) {
            System.out.println("main:printStackTrace()");
            e.printStackTrace(System.out);
        }
        
    }
}

对比输出结果,发现在主程序中的针对h的catch块中打印栈轨迹的时候,发现她只有两行,因为他捕捉到的异常其实是(Exception)e.fillInStackTrace(),这其实是一个新返回的异常,它只记录了自己这个位置的栈信息,因为他是一个新的异常。
恩要注意重新抛出的异常和原来的异常到底是啥关系,很可能就没啥关系的

3.异常链

在捕获一个异常后抛出另一个异常,并希望吧原是一场的信息保留下来,这被称为异常链。所有Throwable的子类的构造器可以接受一个cause对象作为参数,cause表示原始异常对象。

clipboard.png

package tij.exception;

public class Test {

    public static void main(String[] args) {
        DynamicFields df = new DynamicFields(3);
        System.out.println(df);
        try {
            df.setField("d", "a value of d");
            df.setField("killer47", 47);
            df.setField("fatkiller48", 48);
            System.out.println(df);
            df.setField("d", "a new value of d");
            df.setField("thinkiller", 11);
            System.out.println("df:" + df);
            System.out.println("df.getField(\"d\")" + df.getField("d"));
            Object field = df.setField("d", null);
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        } catch (DynamicFieldException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
}

class DynamicFieldException extends Exception {}
class DynamicFields {
    private Object[][] fields;
    public DynamicFields(int initialSize) {
        this.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[] object : fields) {
            result.append(object[0] + ": " + object[1] + "\n");
        }
        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;
            }
        }
        // 如果空间满了,那就在造一个空间
        Object[][] temp = new Object[fields.length + 1][2];
        for (int i = 0; i < fields.length; i++) {
            temp[i] = fields[i];
        }
        temp[fields.length] = new Object[]{null, null};
        fields = temp;
        return makeField(id);
    }

    public Object getField(String id) throws NoSuchFieldException {
        return fields[getFieldNumber(id)][1];
    }

    public Object setField(String id, Object value)
            throws DynamicFieldException {
        if (value == null) {
            DynamicFieldException dfe = new DynamicFieldException();
            dfe.initCause(new NullPointerException());
            throw dfe;
        }
        int fieldNumber = hasField(id);
        if (fieldNumber == -1) {
            fieldNumber = makeField(id);
        }
        Object result = null;
        try {
            result = getField(id);
        } catch (NoSuchFieldException e) {
            throw new RuntimeException(e);
        }
        fields[fieldNumber][1] = value;
        return result;
    }
}

其实他就是完成了一个类似于map的数据结构,在

Object field = df.setField("d", null);

这段代码中,尝试插入一个value为null的对儿,他抛出了一个DynamicFieldException异常,这个异常是由于NullPointerException引起的,在结果中可以看到,虽然抛出的是DynamicFieldException,但NullPointerException也被记录了下来

七、Java标准异常

Throwable对象可以分为两类:Error用来表示编译时和系统错误;Exception是可以被抛出的基本类型。

1.特例:RuntimeException

运行时异常发生的时候会自动被虚拟机抛出不一定要在异常说明中将它们列出来。
但如果不人工捕获这种异常,他会穿越所有执行路径直达main方法。

package tij.exception;

public class Test {

    static void f() {
        throw new RuntimeException("From f()");
    }
    static void g() {
        f();
    }
    public static void main(String[] args) {
        g();
    }
}

对于这种异常我们程序猿心里要有点B数,不处理的话出错了全崩了

八、使用finally进行清理

对于一些代码,无论try块中是否有异常抛出,他们都应该执行。这通常适用于内存回收之外的情况。可以运用finally语句。

package tij.exception;

public class Test {
    static int count = 0;
    public static void main(String[] args) {
        while (true) {
            try {
                if (count++ == 0)
                    throw new ThreeException();
                System.out.println("No Exception");
            } catch (ThreeException e) {
                System.out.println("ThreeException");
            } finally {
                System.out.println("in finally clause");
                if (count == 2)
                    break;
            }
        }
    }
}
class ThreeException extends Exception {}

1.finally用来做什么

clipboard.png
clipboard.png
(额= =Java内存回收机制和构析函数不是一个东西么?)

package tij.exception;

public class Test {
    private static Switch sw = new Switch();
    static void f() throws OnOffException1, OnOffException2 {}
    public static void main(String[] args) {
        try {
            sw.on();
            f();
        } catch (OnOffException1 e) {
            System.out.println("OnOffException1");
        } catch (OnOffException2 e) {
            System.out.println("OnOffException2");
        } finally {
            sw.off();
        }
    }
}
class OnOffException1 extends Exception {}
class OnOffException2 extends Exception {}

class Switch {
    private boolean state = false;
    boolean read() {
        return this.state;
    }
    void on() {
        this.state = true;
        System.out.println(this);
    }
    void off() {
        this.state = false;
        System.out.println(this);
    }
    public String toString() {
        return state ? "on" : "off";
    }
}

可以保证sw最后都是关闭的。

2.在return中使用finally

finally总会执行,所以一个方法中,可以从多个点返回

package tij.exception;

public class Test {
    static void f(int i) {
        try {
            System.out.println("Point 1");
            if (i == 1)
                return;
            System.out.println("Point 2");
            if (i == 2)
                return;
            System.out.println("Point 3");
            if (i == 3)
                return;
        } finally {
            System.out.println("Performing cleanup");
        }
    }
    public static void main(String[] args) {
        for (int i = 1; i <= 4; i++) {
            f(i);
        }
    }
}

3.异常缺失

异常有时候会被轻易地忽略。

package tij.exception;

public class Test {
    void f() throws VeryImportantException {
        throw new VeryImportantException();
    }
    void dispose() throws HoHumException {
        throw new HoHumException();
    }
    public static void main(String[] args) {
        try {
            Test t = new Test();
            try {
                t.f();
            } finally {
                t.dispose();
            }
        } catch (Exception e) {
            System.out.println(e);
        }
    }
}

class VeryImportantException extends Exception {
    public String toString() {
        return "A very important exception!";
    }
}
class HoHumException extends Exception {
    public String toString() {
        return "A trival exception";
    }
}

clipboard.png
还有一种更加容易丢失的异常

package tij.exception;

public class Test {
    @SuppressWarnings("finally")
    public static void main(String[] args) {
        try {
            throw new RuntimeException();
        } finally {
            return;
        }
    }
}

九、异常的限制

package tij.exception;

public class Test {
    public static void main(String[] args) {
        try {
            StormyInning si = new StormyInning();
            si.atBat();
        } catch (PopFoul e1) {
            // TODO Auto-generated catch block
            e1.printStackTrace();
        } catch (RainedOut e1) {
            // TODO Auto-generated catch block
            e1.printStackTrace();
        } catch (BaseballException e1) {
            // TODO Auto-generated catch block
            e1.printStackTrace();
        }

        try {
            Inning i = new StormyInning();
            i.atBat();
        } catch (RainedOut e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (Strike e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (Foul e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (BaseballException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
}

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 event() throws RainedOut;
    public void rainHard() throws RainedOut;
}

class StormyInning extends Inning implements Storm {
    // 对于构造方法来说,你可以添加新的抛出异常,但是你必须也得抛出父类构造方法所声明的异常
    public StormyInning() throws RainedOut, BaseballException {}

    public StormyInning(String s) throws Foul, BaseballException {}

    // 普通方法抛出的异常必须必须遵循父类,父类抛啥你抛啥,抛多了也不行,父类不抛你也不许抛,阿父真的很严格
    // public void walk() throws PopFoul{}

    // 可以看到接口和父类中有一个相同的方法event,他们抛出了不同的异常,前面说了继承方法不能多抛异常,所以即使是接口,也不能向父类中已经存在的方法添加新的抛出异常
    // public void event() throws RainedOut{}

    // 但rainHard只在接口中出现了,同样也不能多抛其他异常
    public void rainHard() throws RainedOut {}

    // 但庆幸的是即使父类或者接口的方法抛异常了,子类重写的方法可以不抛异常,就是说可以偷懒恩
    public void event() {}

    // 并且子类抛出的异常可以遵循继承原则,下面这个函数中相当于把Strike异常忽略了,然后抛出了Foul异常的子类PopFoul
    public void atBat() throws PopFoul {}
}

看书,书上这段写的超棒!

十、构造器

class InputFile {
    private BufferedReader in;
    InputFile(String fname) throws Exception {
        try {
            in = new BufferedReader(new FileReader(fname));
        } catch (FileNotFoundException e) {
            System.out.println("Could not open " + fname);
            // 这个文件并没有被成功的打开
            throw e;
        } catch (Exception e) {
            try {
                in.close();
            } catch (IOException e2) {
                System.out.println("in没有被成功关闭");
            }
            throw e;
        } finally {
            // 不要关闭这个文件
        }
    }

    String getLine() {
        String s;
        try {
            s = in.readLine();
        } catch (IOException e) {
            throw new RuntimeException("readLine() failed");
        }
        return s;
    }

    void dispose() {
        try {
            in.close();
            System.out.println("dispose() successful");
        } catch (IOException e2) {
            throw new RuntimeException("in.close() failed");
        }
    }
}

从中可以看出,如果in这个对象创建失败,他会抛出一个创建异常,并且它不需要关闭,因为他根本没有被成功创建出来;如果in这个对象如果创建成功了,但如果除了其他的岔子,这个in应该被关闭掉,这个对象的构造函数具备了这个功能。

public class Test {
    public static void main(String[] args) {
        try {
            InputFile in = new InputFile("src\\tij\\exception\\Test.java");
            String s;
            try {
                while ((s = in.getLine()) != null) {

                }
            } catch (Exception e) {
                System.out.println("caught Exception in main");
                e.printStackTrace(System.out);
            } finally {
                in.dispose();
            }
        } catch (Exception e) {
            System.out.println("InputFile construction failed");

        }
    }
}

由于InputFile in这个对象满足两个特征:1.构造的时候可能产生异常。2.用完之后需要被清理。因此上面的try-catch嵌套用法是最安全的。因为它保证了:1.如果创建失败,直接抛出异常,这个对象不需要也不应该执行关闭方法(因此不能傻了吧唧的都把close丢finally块中)。2.如果创建成功,那么应该保证这个对象在用完之后关闭掉。
clipboard.png

package tij.exception;

import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;

public class Test {
    public static void main(String[] args) {
        NeedsCleanup nc1=new NeedsCleanup();
        try{
            
        }finally{
            nc1.dispose();
        }
        
        NeedsCleanup nc2=new NeedsCleanup();
        NeedsCleanup nc3=new NeedsCleanup();
        try{
            
        }finally{
            nc3.dispose();
            nc2.dispose();
        }
        
        try {
            NeedsCleanup2 nc4=new NeedsCleanup2();
            try {
                NeedsCleanup2 nc5=new NeedsCleanup2();
                try{
                    
                }finally{
                    nc5.dispose();
                }
            } catch (ConstructionException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }finally{
                nc4.dispose();
            }
        } catch (ConstructionException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
            
        
    }
}

class NeedsCleanup{
    //我的构造不会出错
    private static long counter=1;
    private final long id=counter++;
    public void dispose(){
        System.out.println("NeedsCleanup "+id+" disposed");
    }
}

class ConstructionException extends Exception{}

class NeedsCleanup2 extends NeedsCleanup{
    public NeedsCleanup2() throws ConstructionException{
        
    }
}

nc123都不会出错,而nc45都可能出错的,上面的方法虽然麻烦,但是可行且可靠。

十一、异常匹配

从上到下,遵循继承

十二、其他可选方式

书上这段写的主要是思想方面的事,回来补,今天累= =马克

end


z_dominic
115 声望15 粉丝

你有freestyle吗?