第11章 异常,断言,日志,调试
- 处理错误
- 捕获异常
- 使用异常机制的技巧
- 使用断言
- 日志
- 測试技巧
- GUI程序排错技巧
- 使用调试器
11.1 处理错误
11.1.1异常分类
- 都继承自Throwable类
- 分成Error和Exception
- Error类
描写叙述了Java运行时系统的内部错误和资源耗尽错误。
应用程序不应该抛出此种类型的错误。假设出现了这样的内部错误。除了通告给用户,并尽力使程序安全地终止外,再也无能为力
- Exception层次结构:最需关注的
- RuntimeException 程序错误导致的异常
- 错误的类型转换
- 数组訪问越界
- 訪问空指针
- 不是派生于RuntimeException 因为像I/O错误,程序本身没有问题导致的异常
- 试图在文件尾部读取后面数据
- 试图打开一个不存在的文件
- 试图依据给定的字符串查找Class对象。而那个字符串表示的类不存在
- RuntimeException 程序错误导致的异常
- Error类
- java语言规范 派生于 Error类或RuntimeException类的全部异常称为未检查(unchecked)异常,全部其它异常称为已检查(checked)异常。
11.1.2 声明已检查异常
- 下面四种情况自己编写方法时。须要抛出异常
- 调用一个抛出checked异常的方法。比如,FileInputStream构造器
- 程序运行发现错误,而且利用throw语句抛出一个checked异常
- Java虚拟机和运行时库出现的内部错误
- 对于可能被他人使用的Java方法,更具异常规范(exception specification),在方法的首部声明这种方法可能抛出异常,假设有多个用逗号隔开
class MyAnimation { ... public Image loadImage(String s) throws IOException FileNotFoundException { ... } }
- 关于子类覆盖超类的方法那一块没看懂
11.1.3 怎样抛出异常
- 找到一个合适的异常类
- 在方法声明
- 创建这个类的一个对象
- 将对象抛出
String readData(Scanner in) thros EOFException { ... while(...) { if(!in.hasNext()) { if(n<len) throw new EOFException(); } } ... return S; } //还能含有一个字符串參数的构造器 String girpe="Content-length " +len +",Recived" + n ; throw new EOFException(girpe);
11.1.4 创建异常类
- 创建一个派生于Exception的类 ,或者派生于Exception子类的类。
- 一般须要定义两个构造器,一个是默认的,另一个是带有具体描写叙述信息的构造器(超类Throwable的toString方法将会打印这些具体信息)
class FileFormatException extends IOException { public FileFormatException() {} public FileFormatException(String gripe) { super(gripe); } }
String getMessage()
能获得Throwable对象具体描写叙述信息,即构造时丢进去的String。
11.2 捕获异常
- 假设异常没有被捕获,程序将会停止运行
- 假设想捕获异常,下面是最简单的try/catch 语句块
try { code more code more code } catch (ExceptionType e) { handler for this type }
- 假设try语句块中的不论什么代码抛出了在catch子句中说明的异常。那么
- 跳过剩下的try 语句
- 将运行catch子句中的处理器代码
- 下面是个简单的样例
public void read(String filename) { try { InputStrem in = new FileInputStream(filename); int b; while((b!=in.read())!=-1) { process input; } } catch (IOException exception) { exception.printStackTrace(); } }
对于以上代码
通常最好的选择是什么都不做,而是将异常传递给调用者。
假设read方法出现了错误,就让read方法的调用者去担心!
假设採用这样的处理方式,就必须声明这种方法可能会抛出一个IOException
public void read(String filename) throws IOException
{
InputStrem in = new FileInputStream(filename);
int b;
while((b!=in.read())!=-1)
{
process input;
}
}
- 不同意子类覆盖超类的方法中throws说明符超过超类所列出的异常范围。假设有不属于的,不能throws掉,仅仅能自己捕获处理
11.2.1 捕获多个异常
- 基本语句
try { } catch (FileNotFoundException e) { } catch (UnknownHostException e) { } catch (IOException e) { }
- 假设须要获得具体信息
e.getMessage() //具体错误信息 e.getClass().getName() //异常对象的实际类型
- 假设处理方式一样 合并catch语句
try { code.. } catch (FileNotFoundException | UnknownHostException e) { }
11.2.2 再次抛出异常或异常链
- 捕获异常再次抛出的基本方法
try { access the database } catch (SQLException e) { throw new ServletException("database error: "+e.getMessage()); }
- 另一种能不丢失原始异常的方法
try { access the database } catch (SQLException e) { Throwable se=new ServletException("database error"); se.initCause(e); throw se; }
当捕获异常时。能够用下面语句又一次得到原始异常:
Throwable e = se.getCause();
书上强烈建议这样的包装方式,不丢失原异常的细节。
- 有时仅仅是记录异常
try { access the database } catch (SQLException e) { logger.log(level,message,e); throw e; }
11.2.3 finaly子句
- 基本的语法
try { } catch(Exception e) { } finally { in.close(); }
- 不管try中抛出异常,还是catch中抛出异常,总而言之finall总会运行
- 强烈使用try/catch 和 try/finally语句块单独,而不是基本的语法的使用方法。例如以下:
InputStream in= ... ; try { try { code that might throw exceptions } finally { in.close(); } } catch(IOException e) { show error message }
内层的try仅仅有一个职责,确认关闭输入流。外层的try 用来确保报告出现的错误。
- 注意:当
finally
和catch
包括return
语句,会运行finally
的return
。 - 注意:
finally
也可能抛出异常,而导致本来要catch
抛出的异常被覆盖
11.2.4 带资源的try语句
- 假如资源实现了
AutoCloseable
/Closeable
接口的类。能够利用带资源的try
语句try(Resource res=...) { work with res }
- try 退出时,会自己主动调用res.close()。下面给出一个典型的样例。
try (Scanner in=new Scanner(new FileInputStream("/usr/share/dict/words"))) { while (in.hasNext()) System.out.println(in.next()); }
- 还能指定多个资源,比如:
try (Scanner in = new Scanner(new FileInputStream("/usr/share/dict/words")), PrintWriter out=new PrintWriter("out.txt")) { while(in.hasNext()) out.println(in.next().toUpperCase()); }
- 假设
.close()
也抛出了异常,可是不会覆盖原来该抛出的异常,而是被抑制。假设你想知道这些被抑制的异常,能够通过getSuppressed方法。
11.2.5 分析堆栈跟踪元素
- 堆栈跟踪(
stack trace
)是一个方法调用过程的列表。 - 比較灵活的方式是使用
getStackTrace()
。它会得到StackTraceElement对象的一个数组。
比如:
Throwable t = new Throwable(); StackTraceElement[] frames = t.getStackTrace(); for(StackTraceElement f : frames) System.out.println(f); 输出 factorial(4): StackTraceTest.factorial(StackTraceTest.java:8) StackTraceTest.factorial(StackTraceTest.java:14) StackTraceTest.factorial(StackTraceTest.java:14) StackTraceTest.factorial(StackTraceTest.java:14) StackTraceTest.factorial(StackTraceTest.java:14) StackTraceTest.factorial(StackTrcaceTest.java:14) StackTraceTest.main(StackTraceTest.java:23)
能获得文件名称,类名。当前运行的代码行号
- 静态的Thread.getAllStackTrace方法,获得全部线程的堆栈跟踪。下面是样例
Map<Thread,StackTraceElement[]> map=Thread.getAllStackTraces(); for(Thread t : map.keySet()) { StackTraceElement[] frames=map.get(t); for(StackTraceElement f : frames) System.out.println(f); }
11.3 使用异常机制的技巧
- 异常不能取代简单的測试
- 不要过分细化异常
- 利用异常层次结构
- 不要羞于传递异常。有时候你是类设计者,应该由使用者决定对异常怎么样
11.4 使用断言
- assertkeyword有两个表达形式
assert 条件; //为false ,抛出一个AssertionError异常 assert 条件:表达式; //表达式传入异常作为一个消息字符串。
11.4.1 启用或者禁用断言
- -ea或 -enableassertions启用,默认是禁用
- 也能启动部分包的断言,也能金庸部分包的断言
11.4.2 使用断言完毕參数检查
- 断言失败是致命的,不可恢复的错误。
- 断言检查仅仅用于开发和測试阶段。
- 断言是一种測试和调试阶段所使用的战术性工具,而日志记录是一种在程序的整个生命周期都能够使用的策略工具。
11.5 记录日志
11.5.1 基本日志
- 日志系统管理着一个名为Logger.global的默认日志记录器,能够用System.out替换它,并通过info方法记录日志信息
Logger.getGlobal().info("File->Open menu item selected"); //print //三月 15, 2016 7:33:25 下午 log main //信息: File->Open menu item selected
自己主动包括了时间,调用的类名和方法。
Logger.gelGlobal().setLevel(Level.OFF)
来取消全部日志
11.5.2 高级日志
- 调用getLogger方法能够创建或检索记录器
Logger myLogger= Logger.getLogger("log.zhouyong");
- 假设对日志设置了日志级别。那么它的子记录器也会继承这个属性
- 有一下7个日志记录器级别
- SEVERE
- WARNING
- INFO
- CONFIG
- FINE
- FINER
- FINEST
在默认情况。仅仅记录前三个级别。也能够设置其它级别。比如:
logger.setLevel(Level.FINE)
如今,FINE和更高级别的记录都能够记录下来
另外能够使用Level.ALL 开启全部 Level.OFF 关闭全部
- 有下面几种记录方式
logger.warning(message); logger.fine(message); //同一时候还能够用log方法指定级别 logger.log(Level.FINE,message);
- 一般用CONFIG,FINE等记录有助于诊断,但对于程序猿没有太大意义的调试信息。
- 默认的日志记录将显示包括日志调用类名和方法名。可是假设虚拟机进行了优化,可能无法得到准确的信息。此时须要logp方法获得调用类和方法的确切位置,签名例如以下:
void logp(level l,String className,String methodName,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 methodName,Throwable t) void log(Level l,String message ,Throwable t)
典型的使用方法是:
if(...) { IOExcption exception = new IOException("..."); logger.throwing("com.my","read",exception); throw exception; //FINER级别 }
还有
try { ... } catch (IOException e) { Logger.getLogger("...").log(Level.WARNING,"Reading image",e); }
11.5.3 改动日志管理器配置
- 默认情况,配置文件存在于:
e/lib/logging.properties
- 要想使用自己的配置文件 须要
java -Djava.util.logging.config.file=configFile MainClass
- 改动默认的日志记录级别
.level=INFO
还能改动自己日志记录级别
com.mycompany.myapp.level=FINE
- 控制台也有输出级别限制
java.util.logging.ConsoleHandler.level=FINE
- 日志属性由java.util.logging.LogManager类处理。具体看API
11.5.4 本地化
- 不太懂 以后了解
11.5.5 处理器
- 日志记录器先会将记录发送到父处理器中,终于的处理器有一个
ConsoleHandle
。 - 对于一个要被记录的日志记录,它的日志记录级别必须高于日志记录器和处理器的阈值。
要想记录FINE级别的日志。就必须改动配置文件里的默认日志记录级别和处理器级别。
另外,还能够绕过配置文件,安装自己的处理器。
//控制台处理器
Logger logger=Logger.getLogger("log.zhouyong"); logger.setLevel(Level.FINE); logger.setUseParentHandlers(false); Handler handler = new ConsoleHandler(); handler.setLevel(Level.FINE); logger.addHandler(handler); logger.log(Level.FINE,"dddd");
在默认情况,日志记录器会将记录发送给自己的处理器和父处理器。
父处理器就是一般的默认处理器,可是既然我们有了自己的处理器,能够把父处理器关了。免得控制台发送了两次记录
- 要想将日志发送到别的地方,就须要其它处理器。
- FileHandler 收集文件里的日志
- SocketHandler。 发送到特定的主机和port
- FileHandler
FileHandler handler = new FileHandler(); handler.setLevel(Level.FINE); logger.addHandler(handler); logger.log(Level.FINE,"dddd");
文件在User底下。格式为XML。例如以下:
<?xml version="1.0" encoding="GBK" standalone="no"? > <!DOCTYPE log SYSTEM "logger.dtd"> <log> <record> <date>2016-03-15T23:40:17</date> <millis>1458056417956</millis> <sequence>0</sequence> <logger>log.zhouyong</logger> <level>FINE</level> <class>log</class> <method>main</method> <thread>1</thread> <message>dddd</message> </record> </log>
- 还能有很多其它的复杂方式来处理完毕自己想要的要求
11.5.6 过滤器
- 每一个记录器和处理器都能够有一个可选的过滤器来完毕附加的过滤
- 能够通过实现
Filter
接口并定义下列方法来自己定义过滤器boolean isLoggable(LogRecord record)
setFilter
方法安装过滤器
11.5.7 格式化器
ConsoleHandler类
和FileHandler
能够生成文本或 XML格式的日志记录。可是也能够自己定义格式。- 通过继承
Formatter
类,并覆盖一下方法。String format(LogRecord record)
- 能够依据自己意愿对记录的信息进行格式化,并返回结果字符串。
- 然后用setFormatter方法将格式化器安装到处理器中。