Java编程思想 第十二章 通过异常处理错误


发生错误的理想时机是编译阶段,但编译期间并不能找出所有的错误,余下的问题必须在运行期间解决。这就需要错误源能通过某种方式,把适当的信息传递给某个接收者——该接受者知道如何正确处理这个问题。

12.1 概念

  • C以及早期语言的错误处理模式建立在约定俗成的基础之上(返回某个特殊值),而不属于语言的一部分。但是程序员往往不会去检查错误。
  • 解决办法是用强制规定的形式来消除处理过程中随心所欲的因素。C++的异常处理机制来源于Ada,Java建立在C++基础上。

12.2 基本异常

当抛出异常时,会使用new在堆上创建异常对象。当前路径被终止,执行异常处理程序

// 抛出异常
int a = 1;
try {
	if (a == 1) {
		throw new NullPointerException();
	}
	System.out.println("不会执行");  // 当前路径被终止,执行异常处理程序
} catch (NullPointerException e) {
	System.out.println("被捕捉");
}

12.2.1 异常参数

在堆上创建异常对象伴随着存储空间的分配和构造器的调用。标准异常类有:默认构造器接受字符串作为参数的构造器

int a = 1;
try {
	if (a == 1) {
		throw new NullPointerException("a == 1");
	}
	System.out.println("不会执行");  // 当前路径被终止,执行异常处理程序
} catch (NullPointerException e) {
	System.out.println("被捕捉");
}

关键字throw能够抛出任意类型的Throwable对象(异常类型的根类)。

所有的异常的父类是Exception,所有异常和错误的父类是Throwable。
在这里插入图片描述

12.3 捕获异常

12.3.1 try块——监控区域(guarded region)

如果在方法内部抛出了异常,这个方法会在抛出异常后结束。如果不希望方法结束,可以将可能产生异常的代码放到try块中。

try {
	// 
}

12.3.2 异常处理程序

针对捕获的异常类型准备相应的处理程序。

try {
	throw new NullPointerException("t = null");
} catch (NullPointerException n) {
	System.out.println("异常被处理");
}

12.3.3 终止与恢复

异常处理模型有两种基本模型:
(1)终止模型(Java支持)
异常一旦被抛出就不会回去继续执行
(2)恢复模型
可以重新尝试调用。如果想用Java实现恢复的行为,可以把try放在while中。

public class BBB {
	public static void main(String[] args) {
		int a =10;
		while (true) {
			try {
				if (a == 10) {
					throw new NullPointerException();
				}
				break;
			} catch (NullPointerException n) {
				a = 100;
			}
		}
	}
}

12.4 创建自定义异常

要自己定义异常类,必须从已有的异常类继承(使用默认构造器)。

class NewException extends Exception {
}

public class CCC {
    static void f1() throws NewException {
        throw new NewException();
    }

    public static void main(String[] args) {
        try {
            CCC.f1();
        } catch (NewException n) {
            System.out.println("异常被捕获");
        }
    }
}

定义字符串构造器,使用Throwable类的printStackTrace()方法将“从方法调用处直到异常抛出处”的方法调用序列输出到System.out,默认输出到标准错误流System.err。

class NewException extends Exception {
    NewException() {}
    NewException(String mes) { super(mes); }
}

public class CCC {
    private static void f1(String s1) throws NewException {
        throw new NewException(s1);
    }

    public static void main(String[] args) {
        try {
            CCC.f1("bbbbb");
        } catch (NewException n) {
            n.printStackTrace(System.out);  // 标准输出流
            n.printStackTrace();   // 标准错误流
            log.error("错误:{}", n.getMessage(), n);  // slf4j
        }
    }
}

12.4.1 异常与记录日志

将异常的调用序列转成字符串传给日志框架
package com.fei.hanhanhanhan;

import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import org.apache.log4j.Logger;

class LoggingException extends Exception{
    public LoggingException() {
    }

    public LoggingException(String a) {
        super(a);
    }
}

class BBB {
    static void f() throws LoggingException {
        throw new LoggingException();
    }
}

public class AAA {
    private static Logger logger1 = Logger.getLogger(AAA.class);

    public static void main(String[] args) throws LoggingException, IOException {
        try {
            BBB.f();
        } catch (LoggingException l) {
            StringWriter trace = new StringWriter();
            l.printStackTrace(new PrintWriter(trace));
            logger1.warn("" + trace.toString());
        }
    }
}
封装成logException函数
package com.fei.hanhanhanhan;

import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import org.apache.log4j.Logger;

class LoggingException extends Exception{
    public LoggingException() {
    }

    public LoggingException(String a) {
        super(a);
    }
}

class BBB {
    static void f() throws LoggingException {
        throw new LoggingException("BBB发生异常");
    }
}

public class AAA {
    private static Logger logger1 = Logger.getLogger(AAA.class);

    static void logException(Exception e) {
        StringWriter trace = new StringWriter();
        e.printStackTrace(new PrintWriter(trace));
        logger1.warn(trace.toString());
    }

    public static void main(String[] args) throws LoggingException, IOException {
        try {
            BBB.f();
        } catch (LoggingException l) {
            logException(l);
        }
    }
}

12.5 方法的异常说明

异常声明:把方法可能会抛出的异常告诉使用此方法的客户端程序员。属于方法声明的一部分。

package com.fei.hanhanhanhan;
import java.io.IOException;
public class CCCC {
    public void f() throws IOException,ClassNotFoundException {}
}

12.6 捕获所有异常

Exception异常是异常类的基类,最好放在处理程序的末尾。

12.6.1 栈轨迹

getStackTrace()可以得到由栈轨迹组成的数组

package com.fei.hanhanhanhan;

import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import org.apache.log4j.Logger;

public class CCCC {
    private static Logger logger1 = Logger.getLogger(AAA.class);

    public static void main(String[] args) throws LoggingException, IOException {
        try {
            BBB.f();
        } catch (LoggingException l) {
            for (StackTraceElement  ste: l.getStackTrace()) {
                System.out.println(ste.getMethodName() + "       " +ste);
            }
        }
    }
}

12.6.2 重新抛出异常

使用fillInStackTrace()重置stacktrace

package com.fei.hanhanhanhan;

import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import org.apache.log4j.Logger;

class LoggingException extends Exception{
    public LoggingException() {
    }

    public LoggingException(String a) {
        super(a);
    }
}

class BBB {
    static void f() throws Exception {
        try {
            h();
        } catch (Exception e) {
            e.printStackTrace();
            throw (Exception)e.fillInStackTrace();
            // throw new Exception();
        }
    }

    static void h() throws LoggingException {
        throw new LoggingException("BBB发生异常");
    }
}

public class AAA {
    private static Logger logger1 = Logger.getLogger(AAA.class);

    static void logException(Exception e) {
        StringWriter trace = new StringWriter();
        e.printStackTrace(new PrintWriter(trace));
        logger1.info(trace.toString());
    }

    public static void main(String[] args) throws LoggingException, IOException {
        try {
            BBB.f();
        } catch (Exception l) {
            logException(l);
        }
    }
}

12.6.3 异常链

用initCause()将原始的异常传递给新的异常,使得通过这个异常链可以追踪到最初发生的位置。

package com.fei.hanhanhanhan;

import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import org.apache.log4j.Logger;

import javax.lang.model.element.VariableElement;

class LoggingException extends Throwable{
    public LoggingException() {
    }

    public LoggingException(String a) {
        super(a);
    }
}

class BBB {
    static void f() throws Exception {
        try {
            h();
        } catch (LoggingException e) {
            e.printStackTrace();
            System.out.println("====================");
            Exception exception = new Exception();
            exception.initCause(e);
            throw exception;
        }
    }

    static void h() throws LoggingException {
        throw new LoggingException("BBB发生异常");
    }
}

public class AAA {
    private static Logger logger1 = Logger.getLogger(AAA.class);

    static void logException(Exception e) {
        StringWriter trace = new StringWriter();
        e.printStackTrace(new PrintWriter(trace));
        logger1.info(trace.toString());
    }

    public static void main(String[] args) throws LoggingException, IOException {
        try {
            BBB.f();
        } catch (Exception l) {
            logException(l);
        }
    }
}

12.7 Java标准异常

Throwable被用来表示任何可以作为异常被抛出的类。
Error用来表示编译时和系统错误(不用关心)
Exception(可以被抛出的基本类型)——程序员关心的基类型

12.7.1 RuntimeException

对null进行引用会抛出NullPointerException。运行时的异常会被虚拟机抛出,它们都是从RuntimeException继承的,不需要异常声明。

12.8 使用finally进行清理

12.8.1 finally用来做什么

当要把除内存之外的资源(打开的文件或网络连接、外部世界的某个开关)恢复到它们的初始状态时,就要用到finally子句。

当异常在catch中被再次抛出前会执行finally子句。

public class C {
    public static void main(String[] args) {
        try {
        	try {
        		System.out.println("1");
        		throw new Exception();
        	} catch (Exception e) {
        		System.out.println("2");
        		throw e;
        	} finally {
        		System.out.println("3");
        	}
        } catch (Exception l) {
        	System.out.println("4");
        } finally {
        	System.out.println("5");
        }
    }
}

12.8.2 在return中使用finally

public class D {
	public static void f(int i) {
		try {
			System.out.println("1");if(i == 1) return;
			System.out.println("2");if(i == 2) return;
			System.out.println("3");if(i == 3) return;
			System.out.println("end");return;
        } finally {
        	System.out.println("clean");
        }
	}

    public static void main(String[] args) {
        for (int i = 1; i < 4; i++) {
        	f(i);
        }
    }
}

12.8.3 缺憾:异常丢失

finally语句中存在异常

前一个异常还没被处理就抛出下一个异常,Java中未修复这个问题

public class C {
    public static void main(String[] args) {
        try {
        	try {
        		throw new NewException();
        	} finally () {
        		throw new NewException2();
        	} 
        } catch (Exception e) {
			System.out.println(e);  // 只打印出NewException2,NewException被替换了
        }
    }
}
从finally子句返回
public class C {
    public static void main(String[] args) {
        try {
        	throw new NewException();
        } finally {
			return;
		}
    }
}

12.9 异常的限制(在继承和覆盖过程中,导出类的异常说明变小)

  • 覆盖方法时只能抛出基类的异常说明里列出的那些异常。基类有异常说明,导出类可以没有异常说明或者是基类的子集;基类无异常说明,导出类也不能有异常说明。
class A  {
    public void f() throws NewException {   // NewException
    	System.out.println("aaa");
    }
}

interface i1  {
    public void f() throws NewException,NewException3;   // NewException,NewException3
}

public class B extends c1 implements i1 {
	public void f() throws NewException {   // NewException 必须在基类的列表中
		System.out.println("bbb");
	}

    public static void main(String[] args) throws NewException {
    	B b = new B();
    	b.f();
    }
}
  • 异常限制对构造器不起作用。基类的构造器会在导出类中被调用,因此导出类的异常说明必须包含基类的异常说明
class C1  {
    public C1() throws NewException {   // NewException
    	System.out.println("ccc");
    }
}

public class B extends C1 {
	public B() throws NewException,NewException3 {   // NewException 必须在基类的列表中
		System.out.println("bbb");
	}

    public static void main(String[] args) throws NewException,NewException3  {
    	B b = new B();
    }
}
  • 因为在导出类中对基类构造器的构造必须首先被执行,所以基类构造器的异常是无法被捕获
  • 如果对导出类向上转型为基类,需要对基类的异常做说明。

12.10 构造器(确保所有东西都能被清理)

如果在构造阶段发生异常,最安全的方式是使用嵌套的try子句:

package com.fei.hanhanhanhanh;

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 fname) throws Exception {
        try {
            in = new BufferedReader(new FileReader(fname));
        } catch (FileNotFoundException e) {   // 文件没找到,不需要关闭
            System.out.println("file not found");
            throw e;
        } catch (Exception e) {
            try {
                in.close();
            } catch (IOException e2){
                System.out.println("not close");
            }
            throw e;
        }
    }
    public String getLine() {
        String s;
        try {
            s = in.readLine();
        } catch (IOException e) {
            throw new RuntimeException();
        }
        return s;
    }

    public void dispose() {
        try {
            in.close();
        } catch (IOException e2) {
            throw new RuntimeException();
        }
    }
}

所有需要关闭的资源都应该使用try-finally,在finally中将资源关闭

package com.fei.hanhanhanhanh;

public class AAA {
    public static void main(String[] args) {
        try {
            InputFile inputFile = new InputFile("./src/com/fei/hanhanhanhanh/aaa.txt");
            try {
                String s;
                String s1;
                while ((s = inputFile.getLine())!=null) {    // 赋值表达式的返回值是所赋的值
                    if ("aaa".equals(s)) {
                        System.out.println(s);
                    }
                }
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                inputFile.dispose();
            }
        } catch (Exception e) {
            System.out.println("构造异常");
        }

    }
}

12.11 异常匹配

派生类的对象也可以匹配其基类的处理程序。

12.12 其他可选方式

被检查的异常”使得我们在还准备好处理异常的时候被迫加上catch子句,吞食了异常。通过打印栈轨迹修补这个问题。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值