文章目录
发生错误的理想时机是编译阶段,但编译期间并不能找出所有的错误,余下的问题必须在运行期间解决。这就需要错误源能通过某种方式,把适当的信息传递给某个接收者——该接受者知道如何正确处理这个问题。
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子句,吞食了异常。通过打印栈轨迹修补这个问题。