异常
1、对于异常情况,Java使用一种称为异常处理(exception handing)的错误捕获机制处理。
2、用户期望在出现错误是,程序能够采用一些理智的行为,如果由于出现错误而使得某些操作没有完成,程序应该:
·返回到一种安全的状态并能够让用户执行一些其他的命令
·允许用户保存所有操作的结果,并以适当的方式终止程序。
3、在Java中,如果某个方法不能够采用正常的途径完成它的任务,就可以通过另外一个路径退出方法。在这种情况下,方法并不返回任何值,而是抛出(throw)一个封装了错误信息的对象。
4、在Java程序设计语言中,异常对象都是派生与Throwable类的一个实例。Java异常层次结构如下:
5、Error类层次结构描述了Java运行时系统的内部错误和资源耗尽错误。应用程序不应该抛出这种类型的对象。
6、在设计Java程序时,需要关注Exception层次结构。这个层次结构有分解为两个分支:一分支派生与RuntimeException;另一分支包含其他异常。划分两个分支的规则是,由程序错误导致的异常属于RuntimeException;而程序本身没有问题,但由于像I/O错误这类问题导致的异常属于其他异常。
7、派生于RuntimeException的异常包含下面几种情况:
·错误的类型转换
·数据访问越界
·访问空指针。
不是派生于RuntimeException的异常包括:
·试图在文件尾部后面读取数据
·试图打开一个不存在的文件。
·试图根据给定的字符串查找Class对象,而这个字符串表示的类不存在。
8、“如果出现RuntimeException异常,那么就一定是你的问题”。
9、Java语言规范将派生于Error类和RuntimeException类的所有异常称为未检查(unchecked)异常,所有其他的异常称为已检查(checked)异常。
10、在遇到下面4中情况时应该抛出异常:
·调用一个抛出已检查异常的方法。
·程序运行过程中发现错误,并且利用throw语句抛出一个已检查异常
·程序出现错误。
·Java虚拟机和运行时库出现的内部错误。
11、对于那些可能被他人使用的Java方法,应该根据异常规范在方法的首部声明这个方法可能抛出异常。
class myAnimation {
...
public Image loadImage(String s)
throws IOException {
...
}
}
12、如果一个方法可能抛出多个已检查异常,那么就必须在方法的首部列出所有的异常类。每个异常类之间用逗号隔开:
class myAnimation {
...
public Image loadImage(String s)
throws FileNotFoundException, EOFException {
...
}
}
13、但是,不需要声明Java的内部错误,即从Error继承的错误。任何程序代码都有抛出那些异常的潜能,而我们对其没有任何控制能力。同样也不应该声明从RuntimeException集成的那些未检查的异常。
14、一个方法必须声明所有可能抛出的已检查异常,而未检查异常要么不可控制,要么就应该避免发生。
15、如果在子类中覆盖了一个超类的方法,这类方法中声明的已检查异常不能比超类中声明的异常更通用。
16、如果超类方法中没有抛出任何已检查异常,子类不能抛出任何已检查异常。
17、使用throw关键字抛出异常:
throw new EOFException();
18、在程序中可能会原道任何标准异常类都没有能够充分地描述清楚的问题,此时需要创建自己的异常类,创建的异常类必须派生与IOException或其子类。
19、习惯上,定义的异常类应该包含2个构造器,一个是默认的构造器,另一个是带有详细描述信息的构造器。
20、要想捕获一个异常,必须设置try/catch语句块,格式如下:
try {
code
more code
...
} catch (ExceptionType e) {
handler for this type;
}
21、如果try语句块中的任何代码抛出了一个在catch字句中说明的异常类,那么:
① 程序将跳过try语句块的其余代码。
② 程序将执行catch字句中的处理器代码。
22、编译器将严格地执行throws说明符。如果调用了一个抛出一个已检查异常的方法,就必须对它进行处理,或者将它继续进行传递。
23、通常,应该捕获那些知道如何处理的异常,而将那些不知道怎样处理的异常继续进行传递。
24、如果编写一个覆盖超类的方法,而这个方法有没有抛出异常,那么这个方法就必须捕获方法代码中出现的每一个已检查异常。不允许再来在throws方法说明符中出现超过超类方法所列出的异常类范围。
25、在一个try语句块中可以不过多个异常类型,并对不同类型的异常做出不同的处理。可以按照下列方式为每个异常类型使用一个单独的catch子句:
try {
code that might throw exceptions;
...
] catch (FileNotFoundException e) {
emergency action for mising files.
} catch (UnknownHostException e) {
emergency action for unknown hosts;
} catch (IOException e) {
emergency action for all other I/O problems.
}
26、异常对象可能包含与异常本省有关的信息。想要获得对象的更多信息,可以试着使用e.getMessage(),或者使用e.getClass().getName()获得异常对象的实际类型。
27、在JavaSE 7 中,同一个catch子句可以捕获多个异常类型:
try {
code that might throw exceptions;
...
] catch (FileNotFoundException | UnknownHostException e) {
emergency action for mising files.
} catch (IOException e) {
emergency action for all other I/O problems.
}
只有当捕获异常类型彼此之间不存在子类关系时才需要这个特性。
28、可以在catch语句中再抛出一个异常,这样做的目的是改变异常的类型。
29、可以使用一种“包装”技术使得用户可以在抛出子系统中的高级异常,同时不丢失原始异常的细节:
try {
access database
} catch (SQLException e) {
Throwable se = new ServletException(“database error”);
se.initCause(e);
throw se;
}
如果一个方法中发生了一个已检查异常,而不允许抛出它,那么包装技术就十分有用,我们可以捕获这个已检查异常,并将它包装成一个运行时异常。
30、不管是否有异常被捕获,finally字句中的代码都会被执行。
31、try语句可以只有finally子句,而没有catch子句。
32、建议独立使用try/catch和try/finally语句块,这样可以提高代码的清晰度:
try {
try {
code that might throw exceptions;
} finally {
in.close();
}
} catch (IOException e) {
show error message;
}
这样的设计方式不仅清楚,而且还具有一个功能,就是将会报告finally子句中出现的错误。
33、当finally字句中包含return语句时,将会出现一种意想不到的结果,因为在方法返回前,finally字句的内容将被执行,如果finally字句中也有一个return语句,这个返回值会覆盖原始的返回值。
34、如果finally语句块中的代码抛出了异常,这将会覆盖原始的异常。如要抛出原来的异常,代码会变得极其繁琐:
Exception ex = null;
try {
try {
code that might throw exceptions;
} catch (Exception e) {
ex = e;
throw e;
}
} finally {
try {
in.close();
} catch (exception e) {
if(ex == null) throw e;
}
}
35、对于实现了AutoCloseable接口的资源类,Java SE 7 位其提供了一个很有用的快捷方式:
try (Resource res = ...) {
work with res;
}
try块退出时,会自动调用res.close().
36、调用Throwable类中的printStackTrace方法可以查看堆栈跟踪的文本描述信息:
Throwable t = new Throwable();
ByteArrayOutputStream out = new ByteArrayOutputStream();
t.printStackTrace(out);
String description = out.toString();
37、一种更灵活的方法是使用getStackTrace()方法,它会得到StackTraceElement对象的一个数组,可以在程序中分析:
Throwable t = new Throwable();
StackTraceElement[] frames = t.getStackTrace();
for(StackTrace element frame : frames) {
analyze frame;
}
38、常用方法:
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)
用给定的“原因”构造一个异常对象。
java.lang.RuntimeException 1.0
·RuntimeException(Throwable cause) 1.4
·RuntimeException(String message, Throwable cause) 1.4
用给定的原因,构造一个RuntimeException对象。
java.lang.StackTraceElement 1.4
·String getFileName()
返回这个元素运行时对应的源文件名,如果这个信息不存在,则返回null。
·String getLineNumber()
返回这个元素运行时对应的源文件行数,如果这个信息不存在,则返回-1。
·String getClassName()
返回这个元素运行时对应的类的全名。
·String getMethodName()
返回这个元素运行时对应的方法名。构造器名是<int>;静态初始化名是<clinit>。这里无法区分同名的重载方法。
·boolean isNativeMethod()
如果这个元素运行时在一个本地方法中,则返回true。
·String toString()
如果存在的话,返回一个包含类名、方法名、文件名和行数的格式化字符串。
39、使用异常机制的技巧:
① 异常处理不能代替简单的测试。(只在异常情况下使用异常机制)
② 不要过分地细化异常。
③ 利用异常层次结构
④ 不要压制异常(强烈地倾向关闭异常)
public Image loadImage(String s) {
try {
code that threatens to throw checked exceptions;
} catch (Exception e) {}
}
⑤ 在检测错误时,“苛刻”要比放任更好。
⑥ 不要羞于传递异常。(早抛出,晚捕获)
断言
1、断言机制允许在测试期间向代码中插入一些检查语句。当代码发布时,这些出入的检测语句会被自动地移走。
2、使用assert关键字使用断言,格式如下:
assert <条件>
或:
assert <条件> <表达式>
这两种形式都会对条件进行检测,如果结果为false,则抛出一个AssertionError异常,在第二种形式中,表达式将被传入AssertionError的构造器,并转换成一个消息字符串。
3、默认情况下,断言被禁用。可以在运行程序时用-enableassertions或-ea选项启用它:
java -enableassertions MyApp
4、也可以在某各类或某个包中使用断言:
java -ea:MyClass -ea:com.mycompany.mylib... MyApp
5、使用-dissableassertions或-da选项禁用断言。
6、使用断言的时机:
·断言失败是致命的、不可恢复的错误。
·断言检查只用于开发和测试阶段(这种做法有时候被戏称为“在靠近海岸时穿上救生衣,但在海中央时就把救生衣抛掉吧”)
7、常用方法:
·void setDefaultAssertionStatus(boolean b) 1.4
对于通过类加载器加载的所有类来说,如果没有显式地说明类或包的断言状态,就启用或禁用断言。
·void setClassAssertionSatus(String className, boolean b) 1.4
对于给定的类和它的内部类,启用或禁用断言。
·void setPackageAssertionStatus(String packageName, boolean b) 1.4
对于给定包和其子包中的所有类,启用或禁用断言。
·void clearAssertionStatus() 1.4
移去所有类和包的显式断言设置,并金童所有通过这个类加载器加载的类的断言。
日志
1、日志记录API的优点:
·可以很容易地取消全部日志记录,或者仅仅取消某个级别的日志,而且打开和关闭这个操作也很容易。
·可以简单地禁止日志记录的输出,因此,将这些日志代码留在程序中的开销很小。
·日志记录可以被定向到不同的处理器,用于在控制台总显示,用于存储在文件中等。
·日志记录器和处理器都可以对记录进行过滤。过滤器可以根据过滤实现器指定的标准丢弃那些无用的记录项。
·日志记录可以采用不同的方式格式化,例如纯文本或XML。
·应用程序可以使用多个日志记录器,它们使用类似包名这种具有层次结构的名字,例如:com.mycompany.myapp
·在默认情况下,日志系统的配置由配置文件控制。如果需要的话,应用程序可以替换这个配置。
2、日志系统管理着一个名为Logger.global的默认日志记录器,可以用System.out来替换它,并通过info方法记录日志信息。
Logger.getGlobal().info(“File->Open menu item selected”);
3、在专业的应用程序中,不要讲所有的日志记录到一个全局日志记录器中,应该自定义日志记录器。
private static final Logger myLogger =
Logger.getLogger(“com.mycompany.myapp);
4、7个日志记录级别:
·SEVERE
·WARNING
·INFO
·CONFIG
·FINE
·FINER
·FINEST
默认情况下,只记录前三个级别。也可以设置其它的级别,例如:
logger.setLevel(Level.FINE);
或者使用Level.ALL开启所有级别的记录,很实用Level.OFF关闭所有级别的记录。
5、跟踪执行流的方法:
void entering(String className, String methodName);
void entering(String className, String methodName, Object param);
void entering(String className, String methodName,
Object[] params);
void exiting(String className, String methodName);
void exiting(String className, String methodName, Object result);
这些调用将生成FINER级别和以字符串“ENTRY”和“RETURN”开始的日志记录。
6、提供日志记录中包含的异常描述的方法:
void throwing(String className, String methodName, Throwable t);
void log(Level l, String message, Throwable t);
7、可以通过编辑配置文件来修改日志系统的各种属性。在默认情况下,配置文件存在于:
e/lib/logging.properties
要想使用另一个配置文件,就要将java.util.logging.config.file特性设置为配置文件的存储日志,并采用下面的命令启动应用程序:
java -Djava.util.config.file=configFile MainClass
8、要想修改默认的日志记录级别,就需要编辑配置文件,并修改一下命令行:
.level=INFO
可以通过添加一下内容来指定自己的日志记录级别:
com.mycompany.myapp.level=FINE
也就是说,在日志记录后面添加后缀.level
9、要想在控制台上看到FINE级别的消息,就需要进行下列设置:
java.util.logging.ConsoleHandler.level=FINE
10、在请求日志记录器时,可以指定一个资源包:
Logger logger =
Logger.getLogger(loggerName, “com.mycampany.logmessages);
11、在默认情况下,日志记录器将记录发送到ConsoleHandler中,并由它输出到System.err流中。被别是,日志记录器还会将记录发送到父处理器中.
12、与日志记录器一样,处理器也有日志记录级别。对于一个包被记录的日志记录,它的日志记录级别必须高于日志记录器和处理器的阈值。日志管理器配置文件设置的默认控制台处理器的日志记录级别为:
java.util.logging.ConsoleHandler.level=INFO
13、可以绕过配置文件,安装自己的处理器:
Logger logger = Logger.getLogger(“com.mycompany.myapp”);
logger.setLevel(Level.FINE);
logger.setUseParentHandler(false);
Handler handler = new ConsoleHandler();
handler.setLevel(Level.FINE);
logger.addHandler(handler);
14、日志API提供了两个很有用的处理器,一个是FileHandler;另一个是SocketHandler可以将记录分别发送到文件以及特定的主机端口。
15、直接将记录发送到默认的文件处理器:
FileHandler handler = new FileHandler();
logger.addHandler(handler);
16、如果多个应用程序使用同一个日志文件,就应该开启append标志。另外,应该在文件名模式中使用%u,以便每个应用程序创建日志的唯一副本。
17、开启文件循环功能也是一个不错的主意。日志记录文件以myapp.log.0,myapp.log.1,myapp.log.2,这种循环的形式出现。只要文件大小超出了限制,醉酒的文件就会被删除,其他的文件将重新命名,同时创建一个新文件,其编号为0。
18、文件处理器配置参数:
配置属性 | 描 述 | 默 认 |
java.util.logging.FileHandler.level | 处理器级别 | Level.ALL |
java.util.logging.FileHandler.append | 控制处理器应该追加到一个已经存在的文件的尾部;还是应该为每个运行的程序打开一个新文件 | false |
java.util.logging.FileHandler.limit | 在打开另一个文件之前允许写入一个文件的近似最大字节数(0表示无限制) | |
java.util.logging.FileHandler.pattern | 日志文件名的模式 | %hjava%u.log |
java.util.logging.FileHandler.count | 在循环序列中的日志记录数量 | 1(不循环) |
java.util.logging.FileHandler.filter | 使用的过滤器类 | 没有使用过滤器 |
java.util.logging.FileHandler.encoding | 使用字符编码 | 平台的编码 |
java.util.logging.FileHandler.formatter | 记录格式器 | java.util.logging.XMLFormatter |
19、日志记录文件模式变量:
变 量 | 描 述 |
%h | 系统属性user.home的值。 |
%t | 系统临时目录。 |
%u | 用于解决冲突的唯一性编号。 |
%g | 为循环日志记录生成的数值。(当使用循环功能且模式不包括%g时,使用后缀%g)。 |
%% | %字符。 |
20、可以通过扩展Handler类或StreamHandler类来自定义处理器。
21、在默认情况下,过滤器根据日志记录的级别进行过滤。每个日志记录器和处理器都可以有一个可选的过滤器来完成附加的过滤。另外,可以通过实现Filter接口并定义下列方法来自定义过滤器:
boolean isLoggable(LogRecord record);
22、使用setFilter方法将一个过滤器安装到日志记录器或处理器中。
23、ConsoleHandler和FileHandler类可以生成文本和XML格式的日志记录。但是也可以自定义格式。这需要扩展Formatter类并覆盖下面的这个方法:
String format(LogRecord record)
24、日志记录基本操作:
① 为一个简单的应用程序,选择一个日志记录器,并把日志记录器命名为与主应用程序包一样的名字,例如:com.mycompany.myprog,这是一种很好的编程习惯,另外可以通过下列方法得到日志记录器。
Logger logger = Logger.getLogger(“com.mycompany.myprog”);
为了方便起见,可能希望利用一些日志操作量下面的静态域添加到类中:
private static final Logger logger =
logger.getLogger(“com.mycompany.myporg”);
② 默认的日志配置将级别等于或高于INFO级别的所有校级记录到控制台。用户可以覆盖默认的配置文件。但是正如前面所述、改变配置需要做相当多的工作。因此,最好在应用程序中安装一个更加适宜的默认配置。
③ 现在,可以记录自己想要的内容了。但需要牢记:所有级别为INFO、WARNING和SEVERE的消息都将显示到控制台上。因此,最好只将对程序用户有意义的消息设置为这几个级别。将程序员想要的日志记录,设定为FINE是一个很好的选择。
25、常用方法:
java.util.logging.Logger 1.4:
·Logger getLogger(String loggerName);
·Logger getLogger(String loggerName, String 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 ();
获得和设置这个日志记录器的父日志记录器
·Handler[] getHandlers();
获得这个日志记录器的所有处理器
·void addHandler(Handler h);
·void removeHandler(Handler h);
增加或删除这个日志记录器中的一个处理器。
·boolean getUseParentHandlers();
·void setUseParentHandlers(boolean b);
获得和设置“use parent handler”属性。如果这个属性是true, 则日志记录器会将全部的日志记录发个它的父处理器。
·Filter getFilter();
·Filter setFilter(Filter f);
获得和设置这个日志记录器的过滤器
java.util.logging.Handler 1.4:
·abstract void publish(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.lang.ConsoleHandler 1.4:
·ConsoleHandler()
构造一个新的控制台处理器。
java.util.longging.FileHandler 1.4:
·FileHandler(String pattern);
·FileHandler(String pattern ,boolean append);
·FileHandler(String pattern, int limit, int count);
·FileHandler(String pattern, int limit, int count,
boolean append);
构造一个文件处理器。
参数:
pattern 构造日志文件名的模式。
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 getMills();
获得创建时间。以毫秒为单位(从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(Header h);
·String getTail(Header h);
返回应该出现在包含日志记录的文档的开头和结尾的字符串。超类Formatter顶一个了这些方法,它们只返回空字符串。如果必要的话,可以对它们进行覆盖。
·String formatMessage(LogRecord record);
返回经过本地化和格式化后的日志记录的消息内容。
调试技巧和建议
1、可以用下面的方法打印或记录任意变量的值:
Sytem.out.println(“x=” + x);
或
Logger.getGlobal().info(“x=” + x);
如果x是一个数值,则会被转换成等奖的字符串。如果x是一个对象,那么Java就会调用这个对象的toString方法。想要获得隐式参数对象的状态,就可以打印this对象的状态。
Logger.getGlobal().info(“this=” + this);
2、一个不太为人所知但却非常有效的技巧是在每一个类中放置一个main方法。这样就可以对每一个类进行单元测试。
public class myClass {
methods and fields
...
public static void main(String[] args) {
test code;
}
}
利用这种技巧,只需要创建少量的对象,调用所有的方法,并检测每个方法是否能够正确地运行就可以了。灵位,可以为每个类保留一个main方法,然后分别为每个文件调用java虚拟机运行测试。Java虚拟机值调用启动类的main方法。
3、JUnit是一个非常常见的单元测试框架,利用它可以很容易地组织几套测试用例。只要修改类,就需要运行测试。在发现bug时,还需要补充一些其他的测试用例。
4、日志代理(logging proxy)是一个子类的对象,它可以窃取方法调用,并进行日志记录,然后调用超类中的方法。例如,如果在调用一个面板的setBackground方法时出现了问题,就可以按照下面的方式,以匿名子类实例的形式创建一个代理对象:
Random generator = new Random() {
public double nextDouble() {
double result = super.nextDouble();
Logger.getGlobal().info(“nextDouble: “ + result);
return result;
}
}
当调用nextDouble方法时,就会产生一个日志消息。要想知道谁调用了这个方法,就要生成一个堆栈跟踪。
5、利用Throwable类提供的printStackTrace方法,可以从任何一个异常对象中获得堆栈情况。下面的代码将捕获任何异常,打印异常对象和堆栈跟踪,然后重新抛出异常,以便能够找到相应的处理器。
try {
...
} catch (Throwable t) {
t.printStackTrace();
throw t;
}
不一定要通过捕获异常才能生成堆栈跟踪。只要在代码的任何位置插入下面这条语句就可以获得堆栈跟踪:
Thread.dumpStack();
6、一般来说,堆栈跟踪显示在System.err上。也可以利用printStackTrace(PrintWriter s)方法将它发送到一个文件中。另外,如果想记录或显示堆栈跟踪,就可以采用下面的方式,将它捕获到一个字符串中。
ByteArrayOutputStream out = new ByteArrayOutputStream();
new Throwable().printStackTrace(out);
String description = out.toString();
7、通常,将一个程序中的错误信息保存到一个文件中是非常有用的。然而,错误信息被发送到System.err中,而不是System.out中。因此,不能通过运行下面的语句获得它们:
java MyProgram > errors.txt;
而是采用下面的方式捕获错误刘:
java MyProgram 2> errors.txt
要想在同一个文件中同时捕获System.err和System.out,需要使用下面这条命令:
java MyProgram >& errors.txt
这条命令将工作在Windows shell中。
8、让非捕获异常的堆栈跟踪出现在System.err中并不是一个很理想的方法。如果在客户端偶然看到这些信息,则会感到迷惑,并且在需要的时候也无法实现诊断目的。比较好的方式是将这些内容记录到一个文件中。可以调用静态的Thread.setDefaultUncaughtExceptionHandler方法改变非捕获异常的处理器:
Thread.setDefaultUncaughtExceptionHandler(
new Thread.UncaughtExceptionHandler() {
public void uncaughtException(Thread t,
Throwable th) {
save information in log file.
}
}
});
9、要观察类的加载过程,可以使用-verbose标识启动Java虚拟机。这样就可以看到如下所示的输出结果:
[Opened /usr/local/jdk5.0/jre/lib/rt.jar]
[Opened /usr/local/jdk5.0/jre/lib/jsse.jar]
[Opened /usr/local/jdk5.0/jre/lib/jce.jar]
[Opened /usr/local/jdk5.0/jre/lib/charsets.jar]
[Loaded java.lang.Object from shared objects file]
[Loaded java.io.Serializable from shared objects file]
[Loaded java.lang.Comparable from shared object file]
[Loaded java.lang.CharSequence from shared objects file]
...
有时候,这种方法有助于诊断由于类路径引发的问题。
10、Xlint选项告诉编译器对一些普遍容易出现的代码问题进行检查。例如如果使用下面这条命令编译:
javac -Xlint:fallthrough
当switch语句中缺少break语句时,编译器就会报告(术语“lint”最初用来描述一种定位C程序中潜在问题的工具、现在通常用于描述查找可以但不违背语法规则的代码问题的工具。)
11、Java虚拟机增加了对Java应用程序进行监控和管理的支持。它允许利用虚拟机中的代理专职跟踪内存消耗、线程使用、类加载等情况。这个功能对于先像应用程序服务器这样大型的、长时间运行的Java程序来说特别重要。下面是一个能够展示这种功能的例子:JDK加载了一个称为jconsole的图形工具,可以用于显示虚拟机性能的统计结果。在UNIX/Linux环境下,运行ps实用工具,在Windows环境下,使用任务管理器。然后运行jconsole程序:
jconsole processID
12、可以使用jmap使用工具获得一个堆的转储,其中显式了堆中的每个对象。使用命令如下:
jmap -dump:format=b, file=dumpFileName processID
jhat dumpFileName
然后,通过浏览器进入localhost:7000,将会运行一个网络应用程序,借此探查存储对象时堆的内容。
13、如果使用-Xprof标志运行Java虚拟机,就会运行一个基本的剖析器来跟踪那些代码中经常被调用的方法。剖析信息将发送给System.out。输出结果中还会显示那些方法是由即时编译器编译的。
GUI程序排错技巧
1、如果查看Swing窗口,想知道设计者如何将所有这些组件排列得如此整齐,可以监视器内容。按下Ctrl+Shift+F1,会按照层次结构打开所有组件的信息。
2、如果定义了自己定制的Swing组件,但它看起来不能正确显示,对此有一个很好的工具:Swing图形调试工具(Swing graphics debugger)。即使你并没有编写自己的组件类,能看到组件的内容如何绘制也很有意义,而且很有趣。要对一个Swing组件进行表示,可以使用JComponent类的setDebugGraphicsOptions方法。有以下可用选项:
DebugGraphics.FLASHOPTION 绘制各线段、矩形和文本之前,用红色闪烁显示。
DebugGraphics.LOG_OPTION 为各个绘制操作分别打印一个消息。
DebugGraphics.BUFFERED_OPTION 显示在屏幕外缓冲区上完成的操作。
DebugGraphics.NONE_OPTION 关闭图形调试。
我们发现,要让闪烁选项工作,不许禁用“双重缓冲”(Swing采用这种策略来缓解更新窗口时的屏幕抖动现象)。要打开闪烁选项,可以使用一下“魔咒”:
RepaintManager.currentManager(getRootPane()).
setDoubleBufferingEnable(false);
((JComponent) getContentPane()).
setDebugGraphicsOptions(DebugGraphics.FLASH_OPTION);
只需要将这几行代码放在框架构造函数的最后。程序运行时,你会看到窗格缓慢地填充。或者,如果要完成更多本地化调试,只需要对单个组件调用setDebugGraphicsOptions。要控制闪烁,可以设置持续时间、次数和闪烁颜色。
3、如果想得到GUI应用中生成的各个AWT时间记录,可以在发出事件的各个组件中安装一个监听器。借助反射的强大能力,可以很容易地自动实现这一点。
4、Robot类可以将案件和点击鼠标的是假你发送给AWT程序,并能够对用户界面自动地检测。
5、要想获得Robot对象,首先要得到一个GraphicsDevice对象。通过下面的一系列调用获得默认的屏幕设备:
GraphicEnvironment environment =
GraphicsEnvironment.getLocalGraphicsEnvironment();
GraphicsDevice screen =
environment.getDefaultScreenDevice();
然后构造一个Robot对象:
Robot robot = new Robot(screen);
要想发送一个按键的事件,就要告诉robot模拟按下键和释放建的动作:
robot.keyPress(KeyEvent.VK_TAB);
robot.keyRelease(KeyEvent.VK_TAB);
要想发送一个点击鼠标的事件,首先要模拟移动鼠标的时间,然后在模拟按下和释放鼠标的事件:
robot.mouseMove(x, y);
// x and y are absolute screen pixel coordinates.
robot.mousePress(InputEvent.BUTTON1_MASK);
robot.mouseRelease(InputEvent.BUTTON1_MASK);
这里的思路是:模拟键盘和鼠标的输入,然后进行屏幕快照,以便查看应用程序是否实施了预期的操作。可以调用createScreenCapture方法实现捕捉屏幕的操作:
Rectangle rect = new Rectangle(x, y, width, height);
BufferedImage image = robot.createScreenCapture(rect);
矩形坐标使用的也是绝对像素坐标。
最后,通常希望在上面的各条命令之间加上一个短暂的演示,以保证程序能够捕获到各个时间。设置延迟的方法是:调用delay方法,并传递一个以毫秒为单位的延迟时间,例如:
robot.delay(1000); // delay by 1000 milliseconds
6、Robot类自身并不适用与进行用户界面的测试。实际上,它是用于构建基本测试工具基础构件。一个专业的测试工具应该具有捕获、存储和再现用户交互场景的功能,并能够确定组件在名目中的位置,以及调整鼠标点击位置。
7、常用方法:
java.awt.GraphicsEnvironment 1.2:
·static GraphicsEnvironment getLocalGraphicsEnvironment();
返回本地图形环境。
·GraphicsDevice getDefaultScreenDevice();
返回默认的屏幕设备。需要注意的是,使用多态监视器的计算机,每一个屏幕有一个图形设备。通过调用getScreenDevices方法可以得到一个保存所有屏幕的数组。
java.awt.Robot 1.3:
·Robot(GraphicsDevice device);
构造一个能够与给定设备交互的Robot对象。
·void keyPress(int key);
·void keyRelease(int key);
模拟按下或释放按键。
参数:key 键码。
·void mouseMove(int x, int y);
模拟移动鼠标。
参数:x, y 用绝对像素坐标表示的鼠标位置。
·void mousePress(int eventMask);
·void mouseRelease(int eventMask);
模拟按下或释放鼠标键。
参数:eventMask 描述鼠标键事件的掩码。
·void delay(int milliseconds);
根据给的你给毫秒数延迟robot。
·BufferedImage createScreenCapture(Rectangle rect);
截取屏幕的一部分。
参数:rect 用绝对像素坐标表示的所截取的矩形。