Java编程思想读书笔记——通过异常处理错误(一)

第十二章 通过异常处理错误

12.1 概念
12.2 基本异常

异常情形(exceptional condition):阻止当前方法或作用域继续执行的问题。
普通问题:在当前环境下能得到足够的信息,总能处理的问题。

当程序遇到异常情形而不知如何处理时,就需要抛出异常。在抛出异常时,首先会在堆中创建异常对象,之后终止当前的执行路径,并从当前环境中弹出异常对象的引用。之后交由异常处理程序进行处理。

12.2.1 异常参数

标准异常类都有两个构造器,一个是默认构造器,另外一个是接受字符串作为参数,以便能把相关信息放入异常对象的构造器。

抛出异常使用throw关键字。

Throwable是所有异常类型的根类。

12.3 捕获异常

监控区域(guarded region):一段可能产生异常的代码。其后跟着处理这些异常的代码。

12.3.1 try块
try {
    // Code that might generate exceptions
}
12.3.2 异常处理程序

异常处理程序紧跟在try块后,使用关键字catch来捕获相应的异常:

try {
    // Code that might generate exceptions
} catch(Type1 id1) {
    // Handle exceptions of Type1
} catch(Type2 id2) {
    // Handle exceptions of Type1
} catch(Type3 id3) {
    // Handle exceptions of Type1
}

异常处理理论的两种模型:终止模型和恢复模型

终止模型:在这种模型中,将假设错误非常关键,以至于程序无法返回到异常发生的地方继续执行。一旦异常被抛出,就表明错误已经无法挽回,也不能回来继续执行。
恢复模型:异常处理程序的工作是修正错误,然后重新尝试调用出问题的方法,并认为第二次能成功。在Java中,可以在遇到错误时,调用相应的方法修正错误,或者把try块放入while循环中,不断进入try块,直到错误被修正。

一般使用的都是终止模型,因为恢复模型会导致耦合,它需要了解抛出异常的地点,不利于代码的编写和维护。

12.4 创建自定义异常

自定义异常类必须从继承已有的异常类。

12.4.1 异常与记录日志

记录自定义异常信息

package com.mzm.chapter12;

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

/**
 * 使用Logger对象记录自定义异常信息
 * 
 */
public class LoggingExceptions {

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

        try{
            throw new LoggingException();
        }catch (Exception e){
            System.err.println("Caught " + e);
        }
    }
}

class LoggingException extends Exception{

    private static Logger logger = Logger.getLogger("LoggingException");

    public LoggingException(){
        StringWriter trace = new StringWriter();
        printStackTrace(new PrintWriter(trace));
        logger.severe(trace.toString());
    }
}

记录已有异常信息

package com.mzm.chapter12;

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

/**
 * 使用Logger对象记录已有异常类信息
 * 
 */
public class LoggingExceptions2 {

    private static Logger logger = Logger.getLogger("LoggingExceptions2");

    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 (Exception e) {
            logException(e);
        }

    }
}
12.5 异常说明

throws关键字,紧跟在方法参数之后,表明该方法可能会抛出的异常。throws关键字后接的异常都是编译时异常,如果在方法体内产生了以异常,则该方法的异常说明中必须包含抛出的这类异常,否则编译不通过。

12.6 捕获所有异常

可以使用catch(Exception e)来捕获所有异常,但最好放置在处理程序的末尾。

Exception的方法:

String getMessage():获取异常的详细信息;
String getLocalizedMessage():同上,只是使用本地语言;

void printStackTrace():使用标准错误流打印Throwable和Throwable的调用栈轨迹;
void printStackTrace(PrintStream):与上面类似,只是使用打印流操作;
void printStackTrace(java.io.PrintWriter):同上;

Throwable fillInStackTrace():用于在Throwable对象的内部记录栈帧的当前状态。
12.6.1 栈轨迹

printStackTrace()方法所提供的信息可以通过getStackTrace()方法直接访问,这个方法将返回一个由栈轨迹中的元素所构成的数组,其中每一个元素都表示栈中的一帧。元素0是栈顶元素,并且是调用序列中的最后一个方法调用(该Throwable被创建和抛出之处)。数组中的最后一个元素和栈底则是调用序列中的第一个方法调用。

package com.mzm.chapter12;

/**
 * 调用栈
 * 
 */
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();
    }
}
12.6.2 重新抛出异常

可以在捕获异常后,在处理程序中将其重新抛出,交由高一级环境处理。

但是printStackTrace()方法仍然显示的是原来的异常的抛出点,使用fillInStackTrace()可以改变异常的抛出点。

package com.mzm.chapter12;

/**
 * 异常抛出点的修改
 * 
 */
public class Rethrowing {

    public static void f() throws Exception {
        System.out.println("originating the exception in f()");
        throw new Exception("throw 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 {
            g();
        } catch (Exception e) {
            System.out.println("Inside h(),e.printStackTrace()");
            e.printStackTrace();
            //使用fillInStackTrace()方法改变异常的抛出点
            throw (Exception) e.fillInStackTrace();
        }
    }

    public static void main(String[] args){
        try{
            g();
        }catch (Exception e){
            System.out.println("main: printStackTrace()");
            //异常的抛出点仍然是f()
            e.printStackTrace(System.out);
        }

        System.out.println("---------------------------------------------");

        try{
            h();
        }catch (Exception e){
            System.out.println("main: printStackTrace()");
            //异常的抛出点已经改为了h()
            e.printStackTrace(System.out);
        }
    }
}

如果在捕捉异常后,在处理程序中抛出另一种异常,则异常抛出点也会改变,原来异常抛出点的信息会丢失。

package com.mzm.chapter12;


/**
 * 重新抛出另一种异常后,异常抛出点会发生改变
 */
public class RethrowNew {

    public static void f() throws OneException {
        System.out.println("originating the exception in f()");
        throw new OneException("throw from f()");
    }

    public static void main(String[] args){
        try{
            try{
                f();
            }catch (OneException e){
                System.out.println("Caught in inner try, e.printStackTrace()");
                e.printStackTrace(System.out);
                //异常抛出点为f()
                throw new TwoException("from inner try");
            }
        }catch (TwoException e){
            System.out.println("Caught in out try, e.printStackTrace()");
            //异常的抛出点已变为main
            e.printStackTrace(System.out);
        }
    }
}

class OneException extends Exception{
    public OneException(String s){
        super(s);
    }
}

class TwoException extends Exception{
    public TwoException(String s){
        super(s);
    }
}
12.6.3 异常链

在捕获一个异常后抛出另一个异常,并希望把原始异常的信息保存,这被称为异常链。

Throwable的子类在构造器中都可以接受一个cause对象最为参数,这个cause就表示原始异常,这样就可以把原始异常传递给新异常,使得即使在当前位置创建并抛出了新异常,也能通过异常链追踪到异常最初发生的位置。

注意,只有Error、Exception和RuntimeException三个Throwable的子类提供了带cause参数的构造器。如果要把其他类型的异常添加到异常链中来,应该使用initCause()方法。

package com.mzm.chapter12;

/**
 * Created by 蒙卓明 on 2017/10/21.
 */
public class DynamicFields {

    //内置二维数组,第一位表示字段标识符,第二位表示字段值(Object)
    private Object[][] fields;

    /**
     * 构造器,根据初始长度构造内置二维数组
     * @param initialSize 初始长度
     */
    public DynamicFields(int initialSize){
        fields = new Object[initialSize][];
        for(int i = 0; i < initialSize; i++){
            fields[i] = new Object[]{null, null};
        }
    }

    /**
     * 打印
     * @return 打印内容
     */
    public String toString(){
        StringBuilder result = new StringBuilder();
        for(Object[] obj : fields){
            result.append(obj[0]);
            result.append(": ");
            result.append(obj[1]);
            result.append("\n");
        }
        return result.toString();
    }

    /**
     * 判断内置二维数组是否包含指定的标识符
     * @param id 标识符
     * @return 包含则返回其在二维数组的位置,没有则返回-1
     */
    private int hasField(String id){
        for(int i = 0; i < fields.length; i++){
            if(id.equals(fields[i][0])){
                return i;
            }
        }
        return -1;
    }

    /**
     * 获取指定标识符
     * @param id 指定标识符
     * @return 标识符所在的位置
     * @throws NoSuchFieldException 没有该标识符时抛出异常
     */
    private int getFieldNumber(String id) throws NoSuchFieldException {
        int fieldNum = hasField(id);
        if(fieldNum == -1){
            throw new NoSuchFieldException();
        }
        return fieldNum;
    }

    /**
     * 向内置二维数组添加一个标识符
     * @param id
     * @return
     */
    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[][] tmp = new Object[fields.length + 1][2];
        //将旧二维数组的内容复制到新二维数组当中
        for(int i = 0; i < fields.length; i++){
            tmp[i] = fields[i];
        }
        //在增加的部分,先初始化二维数组
        for(int i = fields.length; i < tmp.length; i++){
            tmp[i] = new Object[]{null, null};
        }
        //替代
        fields = tmp;

        //递归返回
        return makeField(id);
    }

    /**
     * 获取指定标识符对应的内容
     * @param id
     * @return
     * @throws NoSuchFieldException
     */
    public Object getField(String id) throws NoSuchFieldException {
        return fields[getFieldNumber(id)][1];
    }

    /**
     * 更新指定标识符所对应的字段值
     * @param id 指定标识符
     * @param value 新字段值
     * @return
     * @throws DynamicFieldsException
     */
    public Object setField(String id, Object value) throws DynamicFieldsException {
        if(value == null){
            //新字段值为null,则抛自定义异常,注意这个自定义异常并没有提供带cause参数的构造器,要想实现异常转型,需使用initCause()方法指定
            DynamicFieldsException dfe = new DynamicFieldsException();
            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;
    }

    public static void main(String[] args){
        DynamicFields df = new DynamicFields(3);
        System.out.println(df);
        try{
            df.setField("d", "A value for d");
            df.setField("number", 47);
            df.setField("number2", 48);
            System.out.println(df);
            df.setField("d", "A new value for d");
            df.setField("number3", 11);
            System.out.println("df: " + df);
            System.out.println("df.getField(\"d\") : " + df.getField("d"));
            Object field = df.setField("d", null);
        } catch (DynamicFieldsException e) {
            e.printStackTrace(System.out);
        } catch (NoSuchFieldException e) {
            e.printStackTrace(System.out);
        }
    }
}

class DynamicFieldsException extends Exception{

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值