Skip to main content
Jkyo Chen Blog

第十四章 异常·断言·日志和调试

异常·断言·日志和调试 #

处理错误 #

异常分类 #

声明已检查异常 #

如何抛出异常 #

String readData(Scanner in) throws EOFException {
	...
	while(...) {
		if(!in.hasNext()) {
			if(n < len) throw new EOFException();
			//EOFException e = new EOFException;
			//if(n < len) throw e;
		}
		...
	}
	return s;
}

// EOFException 类还有一个含有一个字符串型参数的构造器
String gripe = "Content-length: " + len ", Received: " + n;
throw new EOFException(gripe);

创建异常类 #

API java.lang.Throwable 1.0
	Throwable()
	//构造一个新的Throwable对象,这个对象没有详细的描述信息
	Throwable(String message)
	//构造一个新的Throwable对象,这个对象带有特定的详细描述信息。习惯上,所有派生的异常类都支持一个默认的构造器和一个带有详细描述信息的构造器。
	String getMessage()
	//获得Throwable对象的详细描述信息

捕获异常 #

Java try { code more code more code }catch(Exception e) { handler for this type }

捕获多个异常 #

再次抛出异常与异常链 #

finally子句 #

带资源的try语句 #

分析堆栈跟踪元素 #

API java.lang.Throwable 1.0
	Throwable(Throwable cause) 1.4
	Throwable(String message, Throwable cause) 1.4
	//用给定的“原因”构造一个Throwable对象
	Throwable initCause(Throwable cause) 1.4
	//将这个对象设置为“原因”。如果这个对象已经设置为“原因”,则抛出一个异常。
	//返回this引用
	Throwable getCause() 1.4
	//获得设置为这个对象的“原因”的异常对象。如果没有设置“原因”,则返回null
	StackTraceElement[] getStackTrace() 1.4
	//获得构造这个对象时调用堆栈的跟踪。
	void addSuppressed(Throwable t) 7
	//为这个异常增加一个“抑制”异常。这出现在带资源的try语句中,其中t是close方法抛出的一个异常。
	Throwable[] getSuppressed() 7
	//得到这个异常的所有“抑制”异常,一般来说,这些是带资源的try语句中close方法抛出的异常。

	java.lang.Exception 1.0
	Exception(Throwable cause) 1.4
	Exception(String message, Throwable cause)
	//给定的“原因”构造一个RuntimeException对象

	java.lang.StackTraceElement 1.4
	String getFileName()
	//返回这个元素运行时对应的源文件名,如果这个信息不存在,则返回null
	int getLineNumber()
	//返回这个元素运行时对应的源文件行数。如果这个信息不存在,则返回-1
	String getClassName()
	//返回这个元素运行时对应的类的全名
	String getMethodName()
	//返回这个元素运行时对应的方法名。构造器名是;静态初始化名是。这里无法区分同名的重载方法
	boolean isNativeMethod()
	//如果这个元素运行时在一个本地方法中,则返回true
	String toString()
	//如果存在的话,返回一个包含类名,方法名,文件名和行数的格式化字符串。

使用异常机制的技巧 #

  1. 异常处理不能代替简单的测试

    • 与执行简单的测试相比,捕获异常所花费的时间大大超过了前者,因此使用的异常的基本规则是:只在异常情况下使用异常机制。
  2. 不要过分地细化异常(例子:使用多个try-catch)

    • 将整个任务包装在一个try语句块中,这样出现一个操作问题时,整个任务都可以取消。
    try {
    	for(i = 0; i < 100; i++) {
    		n =s.pop();
    		out.writeInt(n);
    	}
    }catch(IOException e) {
    	//problem writing to file
    }catch(EmptyStackException e) {
    	//stack was empty
    }
  3. 利用异常层次结构

    • 不要只抛出RuntimeException异常。应该寻找更加适当的子类和创建自己的异常类。
    • 不要只捕获Thowable异常,否则就会使程序代码更难读,更难维护。
    • 考虑已检查异常与未检查异常的区别。已检查异常本就很庞大,不要为逻辑错误抛出这些异常。
    • 将一种异常转换成另一种更加适合的异常时不要犹豫。
  4. 不要压制异常(在Java中,往往强烈的倾向于关闭异常)

  5. 在检测错误的时候,“苛刻”要比放任更好。

    • 在出错的地方抛出一个EmptyStackException异常要比在后面抛出一个NullPointerException异常更好。
  6. 不要羞于传递异常(传递异常要比捕获这些异常更好)

    public void readStuff(String filename) throws IOException {
    	InputStream in = new FileInputStream(filename);
    }
    //让高层次的方法通知用户发生错误,或者放弃不成功的命令更加适宜。
    • 5, 6可以归纳为“早抛出,晚捕获”

使用断言 #

启用和禁用断言 #

使用断言完成参数检查 #

为文档假设使用断言 #

if(i % 3 == 0)
	...
else if(i % 3 ==1)
	...
else (i % 3 == 2)

//使用断言会更好一些
assert i > 0
if(i % 3 == 0)
	...
else if(i % 3 == 2)
	...
else {
	assert i% 3 ==2;
	...
}
API java.lang.ClassLoader 1.0
	void setDefaultAssertionStatus(boolean b) 1.4
	//对于通过类加载器加载的所有类来说,如果没有显式地说明类或包的断言状态,就启用或禁用断言
	void setClassAssertionStatus(String className, boolean b) 1.4
	//对于给定的类和它的内部类,启用或禁用断言。
	void setPackageAssertionStatus(String packageName, boolean b) 1.4
	//对于给定包和其子包中的所有类,启用或禁用断言。
	void clearAssertionStatus() 1.4
	//移去所有类和包的显式断言状态设置,并禁用所有通过这个类加载器加载的类的断言。

记录日志 #

日志API #

###基本日志

//日志系统管理着一个名为Logger.global的默认日志记录器,可以用System.out替换它
Logger.getGlobal().info("File->Open menu item selected");

//在默认情况下,这条记录会显示出如下所示的内容:
//May 10, 2013 10:12:15 PM LoggingImageViewer fileOpen
//INFO: File->Open menu item selected
//自动包含时间,调用的类名和方法名。

//在相应的地方(如:main开始)调用下面这句,将会取消所有的日志
Logger.getGlobal().setLevel(Level.OFF);
//在修正bug7184195之前,还需要调用下面这句,来激活全局日志记录器
Logger.getGlobal().setLevel(Level.INFO);

高级日志 #

修改日志管理器配置 #

本地化 #

过滤器 #

格式化器 #

日志记录说明 #

API java.util.logging.Logger 1.4
	Logger getLogger(String loggerName)
	Logger gerLogger(String loggerName, String bundleName)
	//获得给定名字的日志记录器。如果这个日志记录器不存在,创建一个日志记录器
	//参数:loggerName 具有层次结构的日志记录器名。
	//		bundleName 用来查看本地消息的资源包名
	void severe(String message)
	void warning(String message)
	void info(String message)
	void config(String message)
	void fine(String message)
	void finer(String message)
	void finest(String message)
	//记录一个由方法名和给定消息指示级别的日志记录。
	void entering(String className, String methodName)
	void entering(String className, String methodName, Object param)
	void entering(String className, String methodName, Object[] param)
	void exiting(String className, String methodName)
	void exiting(String className, String methodName, Object result)
	//记录一个描述进入/退出方法的日志记录,其中应该包括给定参数和返回值
	void throwing(String className, String methodName, Throwable t)
	记录一个描述抛出给定异常对象的日志记录
	void log(Level level, String message)
	void log(Level level, String message, Object obj)
	void log(Level level, String message, Object[] objs)
	void log(Level level, String message, Throwable t)
	//记录一个给定级别和消息的日志记录,其中可以包括对象或者抛出对象。要想包括对象,消息中必须包含格式化占位符{0}, {1}等。
	void logp(Level level, String className, String methodName, String message)
	void logp(Level level, String className, String methodName, String message, Object obj)
	void logp(Level level, String className, String methodName, String message, Object[] objs)
	void logp(Level level, String className, String methodName, String message, Throwable t)
	//记录一个给定级别,准确的调用者信息和消息的日志记录,其中可以包括对象或可抛出对象
	void logrb(Level level, String className, String methodName, String bundleName, String message)
	void logrb(Level level, String className, String methodName, String bundleName, String message, Object obj)
	void logrb(Level level, String className, String methodName, String bundleName, String message, Object[] objs)
	void logrb(Level level, String className, String methodName, String bundleName, String message, Throwable t)
	//记录一个给定级别,准确的调用着信息,资源包名和消息的日志记录,其中可以包括对象或可抛出对象
	Level getLevel()
	void setLevel(Level l)
	//获得和设置这个日志记录器的级别
	Logger getParent()
	void setParent(Logger l)
	//获得和设置这个日志记录器的父日志记录器
	Handler[] getHandlers()
	//获得这个日志记录器的所有处理器
	void addHandler(Handler h)
	void removeHandler(Handler h)
	//增加或删除这个日志记录器中的一个处理器
	boolean getUseParentHandlers()
	void setUseParentHandlers(boolean b)
	//获得和设置“use parent handler”属性。如果这个属性是true,则日志记录器会将全部的日志记录器转发给他的父处理器
	Filter getFilter()
	void setFilter(Filter f)
	//获得和设置这个日志记录器的过滤器

	java.util.logging.Handler 1.4
	abstract void public(LogRecord record)
	//将日志记录发送到希望的目的地
	abstract void flush()
	//刷新所有已缓冲的数据
	abstract void close()
	//刷新所有已缓冲的数据,并释放所有相关的资源
	Filter getFilter()
	void setFilter(Filter f)
	//获得和设置这个处理器的过滤器
	Formatter getFormatter()
	void setFormatter(Formatter f)
	//获得和设置这个处理器的格式化器
	Level getLevel()
	void setLevel(Level l)
	//获得和设置这个处理器的级别

	java.util.logging.ConsoleHandler 1.4
	ConsoleHandler()
	//构造一个新的控制台处理器

	java.util.logging.FileHandler 1.4
	FilteHandler(String pattern)
	FilteHandler(String pattern, boolean append)
	FilteHandler(String pattern, int limit, int count)
	FilteHandler(String pattern, int limit, int count, boolean append)
	//构造一个文件处理器
	//参数:pattern		构造日志文件名的模式,参见表11-2列出的模式变量
	//		limit		在打开一个新的日志文件之前,日志文件可以包含的近似最大字节数
	//		count		循环序列的文件数量
	//		append		新构造的文件处理器对象应该追加在一个已存在的日志文件尾部,则为true

	java.util.logging.LogRecord 1.4
	Level getLevel()
	//获得这个日志记录的记录级别
	String getLoggerName()
	//获得正在记录这个日志记录的日志记录器的名字
	ResourceBundle getresourceBundle()
	String getresourceBundleName()
	//获得用于本地化消息的资源包或资源包的名字。如果没有获得,则返回null
	String getMessage()
	//获得本地化和格式化之前的原始消息
	Object[] getParameters()
	//获得参数对象。如果没有获得,则返回null
	Throwable getThrown()
	//获得被抛出的对象。如果不存在,则返回null
	String getSourceClassName()
	String getSourceMethodName()
	//获得记录这个日志记录的代码区域。这个信息有可能是由日志记录代码提供的,也有可能是自动从运行时堆栈推测出来的。如果日志记录代码提供的值有误,或者运行时代码由于被优化而无法推测出确切的位置,这两个方法的返回值就有可能不准确。
	long getMillis()
	//获得创建时间。以毫秒为单位(从1970年开始)
	long getSequenceNumber()
	//获得这个日志记录的唯一序列序号
	int getThreadID()
	//获得创建这个日志记录的线程的唯一ID,这些ID是由LogRecord类分配的,并且与其他线程的ID无关。

	java.util.logging.Filter 1.4
	boolean isLoggable(LogRecord record)
	//如果给定日志记录需要记录,则返回true

	java.util.logging.Formatter 1.4
	abstract String format(LogRecord record)
	//返回对日志记录格式化后得到的字符串
	String getHead(Handler h)
	String getTail(Handler h)
	//返回应该出现包含日志记录的文档的开头和借我的字符串。超类Formatter定义了这些方法,它们只返回空字符串。如果必要的话,可以对它们进行覆盖。
	String formatMessage(LogRecord record)
	//返回结果本地化和格式化后的日志记录的消息内容

调试技巧 #

GUI程序排错技巧 #

使用调试器 #