Java核心技术(第8版) – 读书笔记 – 第11章

本章讨论的内容:异常、断言、日志。

1、错误分类:
(1)用户输入错误:输入url不合法
(2)设备错误:打印机被关掉了
(3)物理限制:如磁盘满了
(4)代码错误:下标越界等

2、有的时候出错用返回码表示,如false,java.io中常用的-1表示EOF等。但更多的时候,这些信息不足以表示错误类型。异常(Exception)更为常用。

3、Java中的所有可抛出错误和异常继承自Throwable,分为两类:
(1)Error:不可恢复的错误,如OutOfMemoryError,它们不应该由用户主动抛出。一般发生这些错误的时候,就应该结束程序了。
(2)Exception:可恢复或者处理的异常。

4、Exception又分为两类:
(1)RuntimeException:类型转换出错、URL格式错误、加载不存在的Class等。这些都应该是在程序设计中进行避免或主动判断的。
(2)IOException:I/O错误导致的。

5、如果类的某个方法内显示的throw出了某个Excpeton,则应该在方法定义的时候,声明”检查异常“,比如FileInputStream的构造函数:

public FileInputStream(String name) throws FileNotFoundException

它将构造一个FileInputStream对象,但有可能抛出一个FileNotFoundException异常。对于继承自RuntimeException的异常,不应该写在throws里,而是应该在代码中避免的。比如ArrayIndexOutOfBoundsException,也不应该写Error继承的类。

6、如果调用一个声明”检查异常“的方法,则要么在自己的方法上throws它,要么内部处理它。

7、如何在代码中抛出异常:

throws new IOException();
//有的还可以又更详细的构造函数:
throws new EOFException("File end...");

8、有时候,Java内置的异常体系不能满足你的需求,可以实现自己的异常类:

class FileFormatException extends IOException
{
    public FileFormatException() {}
    public FileFormatException(String msg)
    {
        super(msg);
    }
}

上面的super(msg),会把msg存在Throwable中,通过getMessage()方法可以返回这个msg。

然后,就可以抛出自己的异常了:

throws new FileFormatExcption("Format illigel");

9、捕获异常:

try
{
    //code may throw exception
    //code may throw exception
}
catch(Exception e)
{
    //handler for exception
}

如果try块内抛出了异常,将直接执行catch块中的内容。
如果try内没有抛出异常,将跳过catch块。

10、在定义基础逻辑时,最好不在内部处理异常,而直接定义throws,将异常交给调用者处理。

11、编译器将严格检查throws说明,如果调用了一个抛出异常的方法,就必须要么对它进行处理(try/catch),要么把它传递出去。

12、如果覆盖一个超类方法,那么子类的throws不许出现超过超类所列出的异常范围。也就是说,需要在子类内部处理所有超类没有列出throws的异常。

13、捕获多个异常:

try
{
    //code may throw exception
    //code may throw exception
}
catch(Exception e1)
{
    //handler for exception 1
}
catch(Exception e2)
{
    //handler for exception 2
}

14、获取异常信息:

e1.getMessag();
e1.getClass().getName();

15、有时候,在catch块内,可以还会抛出异常,即补上异常更详细的信息,方便调用这查看。有一种不太损失调用信息的好方法,如下:

try
{
  access the database
}
catch(SQLException e)
{
   Throwable se= new ServletException("database error");
   se.setCause(e);
   throw se;
}

恢复时候,用如下方法:

Throwable e=se.getCause();

16、finally放在catch后,不管是否进入了catch,都会执行finally。适用于资源的释放(文件句柄,数据库链接等)。有时候,.close()会抛出异常,要注意处理。例如InputStream。

17、堆栈跟踪是很有用的技术,可以分析程序执行的流程,调试时候非常有用:

Throwable t=new Throwable();
StackTraceElement[] farames=t.getStackTrace();
for(StackTracelElement frame:frames)
{
    analyze frame
}

下面是一个打印堆栈跟踪信息的例子:

public class TestException
{

    public void gofunc(String msg)
    {
        String a = msg;
        a = "";
        Throwable t = new Throwable();
        StackTraceElement[] ss = t.getStackTrace();
        for(StackTraceElement s: ss)
        {
            System.out.println(s);
        }
    }   

    public static void main(String [] args)
    {
        TestException t = new TestException();
        t.gofunc("aa");
    }
}

打印出来的结果:

TestException.gofunc(TestException.java:8)
TestException.main(TestException.java:19)

18、JDK5之后,为线程Thread增加了静态的getAllStackTraces(),可以对所有线程进行跟踪。

19、对异常的建议:
(1)不要用异常代替简单判断,因为异常处理会消耗大量性能。
(2)不要过分细化异常处理,可以将多个异常放在一个try块内,附加多个catch即可。
(3)传递异常不可耻,在必要时候应当使用。

20、JDK1.4后开始支持断言,用于程序调试时的诊断,格式:

assert 条件;

当条件为false时,抛出AssertionError异常。

21、断言可以在java命令行被禁用:

java -enableassertions MyApp

22、断言用法:
(1)断言是致命、不可恢复的错误
(2)断言只用于开发和调试阶段

23、使用日志的优势:
(1)在发布阶段,可以很容易的取消调试日志(按照级别分类)
(2)可以记录到更多的文件格式,如XML、纯文本、数据库等。

24、默认的日志管理器,可以与System.out互换:

import java.util.logging.Logger;
import java.util.logging.Level;

public class TestLog
{
    public static void main(String args [])
    {
        Logger def_log = Logger.getLogger(Logger.GLOBAL_LOGGER_NAME);
        def_log.setLevel(Level.INFO);
        def_log.info("First log");
    }
}

25、自创建日志管理器:

Logger my_log = Logger.getLogger("com.coder4.TestLog");

26、日志级别:
(1)SEVERE
(2)WARNING
(3)INFO
(4)CONFIG
(5)FINE
(6)FINER
(7)FINEST
Level.OFF可以关闭所有级别的日志。
默认日志管理器是INFO及以上

27、记录日志的方法:

logger.warning(msg);
//或者
logger.log(LEVEL.WARNING, msg);

28、默认的日志管理只记录了类和方法名,没法具体到行,例如:

2011-12-29 22:50:05 TestLog main
信息: First log

29、Logger可以直接用于记录日志:

public void (Logger.)log(Level level, String msg, Throwable thrown)

30、可以通过修改java命令行或者JVM配置来更改默认的日志级别,以我的Ubuntu为例,在:

/usr/lib/jvm/java-6-sun/jre/lib/logging.properties里面,里面部分内容:

# Default global logging level.
# This specifies which kinds of events are logged across
# all loggers.  For any given facility this global level
# can be overriden by a facility specific level
# Note that the ConsoleHandler also has a separate level
# setting to limit messages printed to the console.
.level= INFO

31、命令行方式改变默认日志级别:

#通过指定其他logging.properties文件的方式
java -Djava.util.logging.config.file=config_file MainClass
#也可以通过System实现:
System.setProperty("java.util.logging.config.file", "other_file")

32、我们在刚才的例子中已经看到,一部分日志已经是国际化的了(如显示”信息:“而不是INFO:)。

我们对于输出的msg也可以进行国际化:比如对于中文用户提示”用户名“,对英文提示"username"。

一个程序可以包含多个语种的资源包,用ResourceBundle类对它们自动定位,这一般按照类名完成。

绑定资源包要用两个String参数的工厂方法函数:

static Logger 	getLogger(String name, String resourceBundleName)

例如,新建如下文件com/TestLogger_en.properties,这是英文的国际化,内容如下:

hint=Please enter password for username {0}

对于中文资源文件com/TestLogger_zh_CN.properties,如下:

hint=请输入用户 {0} 的密码

记录日志时,可以用附加Object[]的方法将{0}这些占位符替换掉。

public void log(Level level, String msg, Object[] params)

例子,注意注释:

package com;
import java.util.logging.Logger;
import java.util.logging.Level;

public class TestLog
{
    public static void main(String args [])
    {
        //Get ResourceBundle would read com/TestLogger_zh_CN.properties
        Logger my_log = Logger.getLogger("mylog","com.TestLog");
        my_log.setLevel(Level.INFO);
        //key hint is paramlized in TestLogger_zh_CN.properties
        my_log.log(Level.INFO,"hint","liheyuan");
    }
}

33、日志处理器(Log Handler):如果说日志级别决定了是否输出,那么日志处理器决定了输出到哪里。默认的ConsoleHandler是类似System.out,输出到屏幕。

其他常用的Handler还有:
java.util.logging.FileHandler  // 输出到文件

FileHandler handler = new FileHandler("my.log");
logger.addHandler(handler);

更详细的配置一般是设置logging.properties,具体的可以参考网上的文章。

34、过滤器,也可以自己实现过滤器Filter接口,决定哪些日志可以被写入,哪些直接丢弃了。

public interface Filter
boolean 	isLoggable(LogRecord record)

35、格式化器,类似于Log4j中的格式化器,但在默认的java.util.logging中要自己实现Formatter接口,并添加到Logger中。

public abstract class Formatter

abstract String 	format(LogRecord record)

36、日志其他注意事项:
(1) 最好同一个应用共用一个日志,private static final Logger logger = Logger.getLogger(......);
(2) 默认的这个日志很难用,玩玩还凑合,正规系统建议还是log4j吧……

37、常用调试技术
(1)System.out / System.err 结合shell管道等打印到文件
(2)Logger
(3)IDE
(4)借助外界程序(一般是性能分析)

38、想观察执行Java过程时的类加载过程,则加入-verbose:

#加上-verbose,可显示详细的类加载信息
java -verbose TestException
#
liheyuan@liheyuan-desktop:tmp$ java -verbose TestException
[Opened /usr/lib/jvm/java-6-sun-1.6.0.26/jre/lib/rt.jar]
[Loaded java.lang.Object from /usr/lib/jvm/java-6-sun-1.6.0.26/jre/lib/rt.jar]
[Loaded java.io.Serializable from /usr/lib/jvm/java-6-sun-1.6.0.26/jre/lib/rt.jar]
[Loaded java.lang.Comparable from /usr/lib/jvm/java-6-sun-1.6.0.26/jre/lib/rt.jar]
......很长很长的......

39、JDK5后支持lint,即编译静态检查,在javac时候加上-Xlint:all即可:

javac -Xlint:all ./TestException.java
./TestException.java:19: 警告:[fallthrough] 可能无法实现 case
			case 2:
			^
1 警告

40、JDK5后可以用jconsole processID观察性能变化。当然现在一般借助更为专业的商业化第三方软件。

41、java.awt.Robot可以移动鼠标、模拟点击、模拟键盘事件,还可以截屏幕。书中介绍这个是说可以模拟用户进行测试,有点扯吧,截屏功能倒还是实用。

42、java也支持调试。。

#编译时候加上-g
java -c TestException
#然后就是调试模式了
java TestException

一般这个是IDE完成的,调试单步什么的还是Ecelipse等IDE吧。

本章完。

2 thoughts on “Java核心技术(第8版) – 读书笔记 – 第11章

  1. vanxining

    请问博主是怎么记下这些读书笔记的呢?是看电子版还是实体书?假如是后者,那么很佩服楼主啊~

    Reply
    1. coder4 Post author

      看扫描的电子版,所以和纸质差不多,没法复制。都是手敲进去的,过过脑子才能记住啊,呵呵。

      Reply

Leave a Reply to coder4 Cancel reply

Your email address will not be published. Required fields are marked *