您的位置:首页 > 编程语言 > Java开发

[core java学习笔记][第十一章异常断言日志调试]

2016-04-02 16:52 561 查看

第11章 异常,断言,日志,调试

处理错误

捕获异常

使用异常机制的技巧

使用断言

日志

测试技巧

GUI程序排错技巧

使用调试器

11.1 处理错误

11.1.1异常分类

都继承自Throwable类

分成Error和Exception

Error类


描述了Java运行时系统的内部错误和资源耗尽错误。应用程序不应该抛出此种类型的错误。如果出现了这样的内部错误,除了通告给用户,并尽力使程序安全地终止外,再也无能为力



Exception层次结构:最需关注的

RuntimeException 程序错误导致的异常

错误的类型转换

数组访问越界

访问空指针

不是派生于RuntimeException 由于像I/O错误,程序本身没有问题导致的异常

试图在文件尾部读取后面数据

试图打开一个不存在的文件

试图根据给定的字符串查找Class对象,而那个字符串表示的类不存在

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 使用断言

assert关键字有两个表达形式

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。 发送到特定的主机和端口

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方法将格式化器安装到处理器中。

11.5.8 日志记录说明

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: