通过异常处理错误(2):创建自定义异常、异常说明

 一、创建自定义异常

    不必拘泥于java中已有的异常类型。java提供的异常体系不可能预见所有的希望加以报告的错误,所以可以自定义异常类来表示程序中可能会遇到的特定问题。

    要自己定义异常类,必须从已有的异常类继承,最好是选择意思相近的异常类继承(不过这样的异常并不容易找)。建立新的异常类型最简单的方法就是让编译器为你产生默认构造器,所以这几乎不用写多少代码:

class SimpleException extends Exception {
}

public class InheritingExceptions {
	public void f() throws SimpleException {
		System.out.println("Throw SimpleException from f()");
		throw new SimpleException();
	}

	public static void main(String[] args) {
		InheritingExceptions sed = new InheritingExceptions();
		try {
			sed.f();
		} catch (SimpleException e) {
			System.out.println("捕捉异常");
		}
	}
}

    编译器创建了默认构造器,它将自动调用基类的默认构造器。本例中不会得到像SimpleException(String)这样的构造器,这种构造器也不实用。你将看到,对异常来说,最重要的部分就是类名,所以本例中建立的异常类大多数情况下就已经够用了。

    本例的结果被打印到了控制台上,但是,你也许想通过写入System.err而将错误发送给标准错误流。通常这比把错误信息输出到System.out要好,因为System.out也许会被重定向。如果把结果送到System.err,它就不会随System.out一起被重定向,这样更容易被用户注意。

    也可以为异常类定义一个接受字符串参数的构造器:

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(System.out);
		}
		try {
			g();
		} catch (MyException e) {
			e.printStackTrace(System.out);
		}
	}
}

    新增的代码不长:两个构造器定义了MyException类型对象的创建方式。对于第二个构造器,使用super关键字明确调用了其基类构造器,它接受一个字符串作为参数。

    在异常处理程序中,调用了在Throwable类声明(Exception即从此类继承)的printStackTrace()方法。就像从输出中看到的,它将打印“从方法调用处直到异常抛出处”的方法调用序列。这里,信息被发送到了System.out,并自动地被捕获和显示在输出中。但是,如果调用默认版本:e.printStackTrace();则信息将被输出到标准错误流。

二、异常与记录日志

    你可能还想使用java.util.logging工具将输出记录到日志中。这里所使用的基本的日志记录功能还是相当简单易懂的:

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

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());
	}
}

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

    静态的Logger.getLogger()方法创建了一个String参数相关联的Logger对象(通常与错误相关的包名和类名),这个Logger对象会将其输出发送到System.err。向Logger写入的最简单方式就是直接调用与日志记录消息的级别相关联的方法,这里使用的是servere()。为了产生日志记录消息,我们欲获取异常抛出处的栈轨迹,但是printStackTrace()不会默认地产生字符串。为了获取字符串,我们需要使用重载的printStackTrace()方法,它接受一个java.io.PrintWriter对象作为参数。如果我们将一个java.io.StringWriter对象传递给这个PrintWriter的构造器,那么通过调用toString()方法,就可以将输出抽取为一个String。

    尽管由于LoggingException将所有记录日志的基础设施都构建在异常自身中,使得它所使用的方式非常方便,并因此不需要客户端程序员的干预就可以自动运行,但是更常见的情形是我们需要捕获和记录其他人编写的异常,因此我们必须在异常处理程序中生成日志消息:

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

public class LoggingException2 {
	private static Logger logger = Logger.getLogger("LoggingException2");

	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);
		}
	}
}

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

class MyException2 extends Exception {
	private int x;

	public MyException2() {
	}

	public MyException2(String msg) {
		super(msg);
	}

	public MyException2(String msg, int x) {
		super(msg);
		this.x = x;
	}

	public int val() {
		return x;
	}

	public String getMessage() {
		return "Detail Message: " + x + " " + super.getMessage();
	}
}

public class ExtraFeatures {
	public static void f() throws MyException2 {
		System.out.println("Throwing MyException2 from f()");
		throw new MyException2();
	}

	public static void g() throws MyException2 {
		System.out.println("Throwing MyException2 from g()");
		throw new MyException2("Originated in g()");
	}

	public static void h() throws MyException2 {
		System.out.println("Throwing MyException2 from h()");
		throw new MyException2("Originated in h()", 47);
	}

	public static void main(String[] args) {
		try {
			f();
		} catch (MyException2 e) {
			e.printStackTrace(System.out);
		}
		try {
			g();
		} catch (MyException2 e) {
			e.printStackTrace(System.out);
		}
		try {
			h();
		} catch (MyException2 e) {
			e.printStackTrace(System.out);
			System.out.println("e.val() = " + e.val());
		}
	}
}

    新的异常添加了字段x以及设定x值的构造器和读取数据的方法。此外,还覆盖了Throwable.getMessage()方法,以生成更详细的信息。对于异常类来说,getMessage()方法有点类似于toString()方法。

    既然异常也是对象的一种,所以可以继续修改这个异常类,以得到更强的功能。但要记住,使用程序包的客户端程序员可能仅仅只是查看一下抛出的异常类型,其他的就不管了(大多数java库里的异常都是这么用的),所以对异常所添加的其他功能也许根本用不上。

三、异常说明

    java鼓励人们把方法可能会抛出的异常告知使用此方法的客户端程序员。这是种优雅的做法,它使得调用者能确切知道写什么样的代码可以捕获所潜在的异常。当然,如果提供了源代码,客户端程序员可以在源代码中查找throw语句来获知相关信息,然而程序库通常并不与源代码一起发布。为了预防这样的问题,java提供了相应的语法(并强制使用这个语法),使你能以礼貌的方式告知客户端程序员某个方法可能会抛出的异常类型,然后客户端程序员就可以进行相应的处理。这就是异常说明,它属于方法声明的一部分,紧跟在形式参数列表之后。

    异常说明使用了附加的关键字throws,后面接一个所有潜在异常类型的列表,所以方法定义可能看起来像这样:

void f() throws TooBig, TooSmall, DivZero{ /* ... */}

    但是要是这样void f(){ /* ... */ },就表示此方法不会抛出任何异常(除了从RuntimeException继承的异常,它们可以在没有异常说明的情况下被抛出,这些将在后面进行讨论)。

    代码必须与异常说明保持一致。如果方法里的代码产生了异常却没有进行处理,编译器会发现这个问题并提醒你:要么处理这个异常,要么就在异常说明中表明此方法将产生异常。通过这种自顶向下强制执行的异常说明机制,java在编译时就可以保证一定水平的异常正确性。

    不过还是有个能“作弊”的地方:可以声明方法将抛出异常,实际上却不抛出。编译器相信了这个声明,并强制此方法的用户像真的抛出异常那样使用这个方法。这样做的好处是,为异常先占个位子,以后就可以抛出这种异常而不用修改已有的代码。在定义抽象基类和接口时这种能力很重要,这样派生类或接口实现就能够抛出这些预先声明的异常。

    这种在编译时被强制检查的异常称为被检查的异常。

如果本文对您有很大的帮助,还请点赞关注一下。

发布了100 篇原创文章 · 获赞 2 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/qq_40298351/article/details/104456031