Java-异常、断言、日志和调试
一.处理错误
二.捕获异常
三.使用异常机制的技巧
四.使用断言
- 断言机制:允许在测试期间在代码中插入一些检查语句,当代码发布时这些插入的检测语句将会被自动移走。
- 使用assert关键字断言,格式如下:
assert <条件> 或 assert <条件> <表达式>,如果判断结果为false,则抛出一个AssertionError,在第二种表达式中,表达式将被传入AssertionError的构造器,转换成一个消息字符串。 - 默认情况下,断言被禁用,需要通过运行时用
-enableassertions 或 -ea选项启用: java -enableassertions MyApp。 - 也可以在各个类或者某个包中使用断言:
java -ea:MyClass -ea:com.mycompany.mylib... MyApp。 - 使用-disableassertions或-da选项禁用断言
-
使用断言的时机:
- 断言失败是致命的,不可恢复的错误
- 断言检查只用于开发和测试阶段
7.常用方法:
void setDefaultAssertionStatus(boolean b) 对于通过类加载器加载的所有类来说,如果没有显式说明类或包的断言状态,就启用或者禁用断言void setClassAssertionStatus(String className, boolean b)对于给定的类或者它的内部类,启用或者禁用断言void setPackageAssertionStatus(String packageName, boolean b)对于给定包及其子包中的所有类,启用或者禁用断言void clearAsserionStatus()移除所有类和包的显式断言设置
五.记录日志
-
日志记录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);
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;
9.要想在控制台看到FINE级别的消息,就需要进行下列设置:java.util.logging.ConsoleHandler.level = FINE;
10.在请求日志记录器时,可以指定一个资源包:Logger logger = Logger.getLogger(loggerName,"com.mycompany.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.setUserParentHandler(false);
Handler handler = new ConsoleHandler();
handler.setLevel(Levle.FINE);
logger.addHandler(handler);
- 1
- 2
- 3
- 4
- 5
- 6
14.日志API提供了两个很有用的处理器,一个是FileHandler;另一个是SocketHandler,可以将日志记录发送到文件及指定的主机端口
15.直接将日志记录发送到默认的文件处理器:FileHandler fd = new FileJHandler();
logger.addHandler(fd);
16.如果多个应用程序使用同一个日志文件,就应该开启append标志,另外,应该在文件名模式中使用%u,让每个应用程序创建日志的唯一副本
17.开启文件循环功能是一个不错的主意,日志记录文件以myapp.log.0, myapp.log.1, myapp.log.2这种循环的形式出现。只要文件大小超出限制,最久的文件就会被删除,其他文件将会被重新命名,同时创建一个新文件,其编号为0
18.文件处理器配置参数:
19.日志文件模式变量:
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.myconpany.myprog"); - 默认的日志配置将级别等于或高于INFO级别的所有记录到控制台,用户可以覆盖默认的配置文件,但最好在应用程序中安装一个更加适宜的默认配置
- 所有级别为INFO,WARNING,SEVERE的消息都将显示到控制台上,因此,最好只将对程序用户有意义的消息设置为这几个级别,将程序员想要的日志级别设定为FINE是一个很好的选择
25.常用方法:
java.util.logging.Logger
//记录一个包含方法名和给定消息显示级别的日志记录
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[] params)
void exiting(String className, String methodName)
void exiting(String className, String methodName, Object result)
//记录一个描述抛出给定异常对象的日志记录
void throwing(String className, String method methodName, Throwable t)
//记录一个给定级别消息的日志记录,其中可以包括对象或者可抛出对象,要想包括对象,消息中必须包括格式化的占位符{0},{1}等
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)
//记录一个给定级别,准确的调用者信息和消息的日志记录,其中包括对象或可抛出对象
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, 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 level)
//获得和设置这个日志记录器的父类日志记录器
Logger.getParent()
void setParentLogger()
//获取这个日志记录器的所有处理器
Handler[] getHandlers()
//增加或者删除这个日志记录器中的一个处理器
void addHandler(Handler handler)
void removeHandler(Handler handler)
//获得和设置"use parent handler"属性,如果这个属性为true,则日志记录器会将全部的日志记录到它的父类处理器
boolean getUseParentHandlers()
void setUseParentHandlersI(boolean b)
//获得和设置这个日志记录器的过滤器
Filter getFilter()
Filter setFilter()
java.util.logging.Handler
//将日志记录发送到希望的目的地
abstract void publish(LogRecord record)
//刷新所有已缓冲的数据
abstract void flush()
//刷新所有已缓冲的所有数据并释放相关的资源
abstract void close()
//获得和设置这个处理器的过滤器
Filter getFilter()
void setFilter()
//获得和设置这个处理器的格式化器
Formatter getFormatter()
void setFormatter(Formatter f)
//获得和设置这个处理器的级别
Level getLevel()
void setLevel(Level level)
java.lang.ConsoleHandler
//构造一个新的控制台处理器
ConsoleHandler()
java.util.logging.FileHandler
/**
* 构造一个文件处理器
* @pattern 构造日志文件名的模式
* @limit 日志文件可以包含的近似最大字节数
* @count 循环序列的文件数量
* @append 新构造的文件处理器对象应该追加在一个已存在的日志文件尾部,则为true
*/
FileHandler(String pattern)
FileHandler(String pattern, boolean append)
FileHandler(String pattern, int limit, int count)
FileHandler(String pattern, int limit, int count, boolean append)
java.util.logging.LogRecord
//获得这个日志记录的记录级别
Level getLevel()
//获得正在记录这个日志记录的记录器的名字
String getLoggerName()
//获得用于本地化消息的资源包或资源包的名字,如果没有获得,则返回null
ResourceBundle getresourceBundle()
String getresourceBundleName()
//获得本地化和格式化之前的原始消息
String getMessage()
//获得参数对象, 如果没有获得,则返回null
Object[] getParameters()
//获得被抛出的对象, 如果不存在,则返回null
Throwable getThrown()
//获得记录这个日志记录的代码区域, 这个消息有可能是由日志记录代码提供的; 也有可能是自动从运行时堆栈推测出来的; 如果日志记录代码提供的值有误,或者运行至代码由于被优化而无法推测出确切位置,这两个方法的返回值就有可能不准确
String getSourceClassName()
String getSourceMethodName()
//获得创建时间, 以毫秒为单位(从1970年开始)
long getMills()
//获得这个日志记录的唯一序列序号
long getSequenceNumber()
//获得创建这个日志记录的线程的唯一ID, 这些ID是由LogRecord类分配的,而且与其他线程的ID无关
int getThreadID()
java.util.logging.Filter
//如果给定日志记录需要记录,则返回true
boolean isLoggable(LogRecord record)
java.util.logging.Formatter
//返回对日志记录格式化后得到的字符串
abstract string format(LogRecord record)
//返回应该出现在包含日志记录的文档的开头和结尾的字符串, 超类Formatter定义了这些方法,它们只返回空字符串。如果必要的话,可以对它们进行覆盖
String getHead(Header h)
String getTail(Header h)
//返回经过本地化和格式化后的日志记录的消息内容
String formatMessage(LogRecord record)
六.调试技巧
- 可以用下面的方法打印或记录任意变量的值:
System.out.ptintln("x= "+x); 或 Logger.getGlobal().info("x= "+x); 如果x是一个对象,JVM会调用对象的toString()方法,想要获得隐式参数对象的状态,就可以打印this对象的状态:Logger.getGlobal().info(“this = “+this);` - 可以利用在每一个类中加入一个main()方法这样就可以对每个类进行单元测试
- Junit是一个非常常见的单元测试框架,利用它可以很容易组织测试用例,只要修改类就需要运行测试,在发现bug后,还需要补充一些凄然的测试用例
- 日志代理(logging proxy)是一个子类的对象,它可以窃取方法调用,并进行日志记录,然后调用超类中的方法,例如如果调用一个面板的setBackground()方法出现了问题,就可以使用下面的方式以匿名子类实例的方式创建一个代理对象:
Random generator = new Random() {
public double nextDouble() {
double result = super.nextDouble();
Logger.getGlobal().info("nextDouble: "+result);
return result;
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
当调用nextDouble()方法时就会出现一个日志消息,要想知道谁调用了这个方法就要生成一个堆栈跟踪
5. 利用Throwable类提供的printStackTrace()方法可以从任何一个对象中获得堆栈信息:
try {
...
}catch(Throwable t) {
t.printStackTrace();
throw t;
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
上面的代码将捕获任何异常,打印异常对象和堆栈跟踪,然后重新抛出异常,以便能有合适的捕获处理
不一定要通过捕获异才能生成堆栈跟踪,只要在代码的任何位置插入`Thread.dumpStack();`就可以获得堆栈跟踪
- 1
- 2
6. 一般堆栈跟踪会显示在System.err上或通过printStackTrace(PrintWriter pw)发送到文件中,也可以捕获到字符串:“`
ByteArrayOutputStream bos = new ByteArrayOutputStream();
new Throwable().printStackTrace(bos);
String description = bos.toString();
7. 将程序中的错误信息保存到一个文件中是非常有用的,可通过如下方式将错误信息保存在txt文档中:
`捕获错误流: java Myprogram 2 > errors.txt;`
`捕获输出和错误流: java Myprogram >& errors.txt;(我在cmd调试中报错>&不可用)`
8. 让非捕获异常的堆栈出现在System.err流中会让客户感到迷惑,干扰诊断信息,可以调用静态的Thread.setDefaultUncaughtExceptionHandler()方法改变非捕获异常的处理器:
- 1
- 2
- 3
- 4
Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
public void uncaughtException(Thread t, Throwable t) {
save information in log file
}
});
- 1
- 2
- 3
- 4
- 5
9. 可以用-verbose标识启动虚拟机观察类的加载过程,可以观察到如下的输出结果,有助于诊断由于类路径引发的问题:
- 1
[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]
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
10. Xlint选项告诉编译器对一些普遍容易出现的问题代码进行检查,例如如果使用下面这条命令编译:`javac -Xlint:fallthrough`,当switch语句中缺少break语句,编译器就会报告(术语“lint”最初用来描述一种定位C程序中潜在问题的工具、现在通常用于描述查找可以但不违背语法规则的代码问题的工具)
11. Java虚拟机增加了对Java应用程序进行监控和管理的支持,它允许利用虚拟机中的代理跟踪内存消耗,线程使用,类加载等情况,这个功能对于像应用程序服务器这样大型的、长时间运行的Java程序来说特别重要。下面是一个能够展示这种功能的例子:JDK加载了一个称为jconsole的图形工具,可以用于显示虚拟机性能的统计结果。在UNIX/Linux环境下,运行ps实用工具,在Windows环境下,使用任务管理器。然后运行jconsole程序:`jconsole processID`
12. 可以使用jmap使用工具获得一个堆的转储,其中显式了堆中的每个对象。使用命令如下:
- 1
- 2
- 3
jmap -dump:format=b,file=dumpFileName processID
jhat dumpFileName
- 1
- 2
然后,通过在浏览中中输入localhost:7000将会运行一个网络应用程序,借此探查存储对象时堆得内容
13. 如果使用-Xprof标志运行Java虚拟机就会运行一个基本的剖析器来跟踪那些代码中经常被调用的方法,剖析信息发送给System.out,输出结果中还会显示那些方法是由及时编译器编译的
七.GUI程序排错技巧
- 如果查看Swing窗口,想知道设计者如何将所有这些组件排列得如此整齐,可以监视器内容。按下Ctrl+Shift+F1,会按照层次结构打开所有组件的信息
- 如果定义了自己定制的Swing组件,但它看起来不能正确显示,对此有一个很好的工具:Swing图形调试工具(Swing graphics debugger)。即使你并没有编写自己的组件类,能看到组件的内容如何绘制也很有意义,而且很有趣。要对一个Swing组件进行表示,可以使用JComponent类的setDebugGraphicsOptions方法。有以下可用选项:
a. DebugGraphics.FLASHOPTION 绘制各线段、矩形和文本之前,用红色闪烁显示。
b. DebugGraphics.LOG_OPTION 为各个绘制操作分别打印一个消息。
c. DebugGraphics.BUFFERED_OPTION 显示在屏幕外缓冲区上完成的操作。
d. DebugGraphics.NONE_OPTION 关闭图形调试。
要让闪烁选项工作,不许禁用“双重缓冲”(Swing采用这种策略来缓解更新窗口时的屏幕抖动现象)。要打开闪烁选项,可以使用一下“魔咒”:
RepaintManager.currentManager(getRootPane()).
setDoubleBufferingEnable(false);
((JComponent) getContentPane()).
setDebugGraphicsOptions(DebugGraphics.FLASH_OPTION);
- 1
- 2
- 3
- 4
只需要将这几行代码放在框架构造函数的最后。程序运行时,你会看到窗格缓慢地填充。或者,如果要完成更多本地化调试,只需要对单个组件调用setDebugGraphicsOptions。要控制闪烁,可以设置持续时间、次数和闪烁颜色
- 1
3. 如果想得到GUI应用中生成的各个AWT时间记录,可以在发出事件的各个组件中安装一个监听器。借助反射的强大能力,可以很容易地自动实现这一点
4. Robot类可以将案件和点击鼠标的事件发送给AWT程序,并能够对用户界面自动地检测
5. 要想获得Robot对象,首先要得到一个GraphicsDevice对象。通过下面的一系列调用获得默认的屏幕设备:
GraphicEnvironment environment = GraphicsEnvironment.getLocalGraphicsEnvironment();
GraphicsDevice screen = environment.getDefaultScreenDevice();
Robot robot = new Robot(screen);
要想发送一个按键的事件,就要告诉robot模拟按下键和释放建的动作:
robot.keyPress(KeyEvent.VK_TAB);
robot.keyRelease(KeyEvent.VK_TAB);
要想发送一个点击鼠标的事件,首先要模拟移动鼠标的时间,然后在模拟按下和释放鼠标的事件:
robot.mouseMove(x, y);
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
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
6.Robot类自身并不适用与进行用户界面的测试。实际上,它是用于构建基本测试工具基础构件。一个专业的测试工具应该具有捕获、存储和再现用户交互场景的功能,并能够确定组件在名目中的位置,以及调整鼠标点击位置
7. 常用方法:
java.awt.GraphicsEnvironment 1.2:
//返回本地图形环境
static GraphicsEnvironment getLocalGraphicsEnvironment();
//返回默认的屏幕设备。需要注意的是,使用多态监视器的计算机,每一个屏幕有一个图形设备。通过调用getScreenDevices方法可以得到一个保存所有屏幕的数组
GraphicsDevice getDefaultScreenDevice();
java.awt.Robot 1.3:
//构造一个能够与给定设备交互的Robot对象
Robot(GraphicsDevice device);
//模拟按下或释放按键,key键码
void keyPress(int key);
void keyRelease(int key);
//模拟移动鼠标,参数:x, y 用绝对像素坐标表示的鼠标位置
void mouseMove(int x, int y);
//模拟按下或释放鼠标键
void mousePress(int eventMask);
void mouseRelease(int eventMask);
//根据给的你给毫秒数延迟robot
void delay(int milliseconds);
//截取屏幕的一部分,参数:rect 用绝对像素坐标表示的所截取的矩形
BufferedImage createScreenCapture(Rectangle rect);

浙公网安备 33010602011771号