7.1 异常
7.1.1 异常的分类
Java异常类都是继承于Throwable类。
Error类层次结构描述了Java运行时系统的内部错误和资源耗尽错误。这类错误应该抛出,因为无法处理。
Exception是可以处理的异常。这里又分为两类。RuntimeException一般是编程时引起的异常,例如,数组越界或者指针为null等。另一类是由于运行环境的不确定性引起的异常,例如文件不存在等。这类异常应该被捕捉并处理。
Java将Error类和RuntimeException类的派生类称为非检查性(unchecked)异常,把剩余的其他异常称为检查型(checked)异常。
7.1.2 异常的创建与抛出
自定义的异常类应该包含两个构造器,一个是默认的构造器,另一个是包含详细描述信息的构造器(超类Throwable会在toString中返回这个描述信息)。
class FileFormatException extends IOException{
public FileFormatException(){}
public FileFormatException(String msg){
super(msg);
}
}
String readData(BufferedReader in) throws FileFormatException {
...
while(...){
if(ch == -1){
if (n < len) throw new FileFormatException("文件格式错误");
}
}
}
7.1.3 异常的捕获
代码模板:
try {
code that might throw exception
}
catch(Exception1 | Exception2 e){
处理异常;
}
catch(Exception3 e){
throw new Exception4("msg"); //也可以抛出新的异常
or
//更好的处理方法,将异常包装在另一个异常中抛出,这样上层类可以找到最原始的异常原因
var ne = new Exception4("msg");
ne.initCause(e);
throw ne;
//捕获这个异常的时候可以用下面这个语句
Throwable original = caughtException.getCause();
}
finally{
无论如何都会执行的代码,通常在这里执行关闭资源等操作;
}
7.1.4 try-with-Resources语句
finally语句是为了关闭一些资源,在Java7以后出现了更精妙的解决方案,就是try-with-Resources语句。形式如下:
try(Resource res = ...) {
work with res
}
该语句可以在try运行完之后,自动关闭实现了AutoCloseable接口的类。另外还有一个Closeable接口,它是AutoCloseable接口的子接口,不过这个接口的close方法会抛出IOException。
public class MyAutoClosable implements AutoCloseable {
public void doIt() {
System.out.println("MyAutoClosable doing it!");
}
@Override
public void close() throws Exception {
System.out.println("MyAutoClosable closed!");
}
public static void main(String[] args) {
try(MyAutoClosable myAutoClosable = new MyAutoClosable()){
myAutoClosable.doIt();
} catch (Exception e) {
e.printStackTrace();
}
}
}
还可以指定多个资源,只需要用分号隔开即可。
try(var in = new Scanner(System.in);
var out = new PrintWriter("out.txt", StandardCharsets.UTF_8))
{
while(in.hasNext()){
out.println(in.next());
}
} catch (IOException ioException) {
ioException.printStackTrace();
}
在Java9中,可以在try首部中不用new了,直接提供之前的事实最终变量。
7.2 断言
7.2.1 断言的语法
断言机制允许在测试期间向代码插入一些检查,而在生产代码中会自动删除这些检查。语法如下:
assert condition;
or
assert condition : expression
这两个语句都会计算condition,如果结果为False就会抛出AssetionError异常。在第二个语句中,expression的值将会传入Assertion的构造器中,并转换成一个消息字符串。注意,condition并不会传给Assertion构造器,如果要看到这个判断条件,需要把条件以字符串的形式写到expression中。
7.2.2 启用和禁用断言
在默认情况下,断言是禁用的。可以在运行程序时用-enableassertions或-ea选项启用断言:
java -enableassertion MyApp
7.3 日志
7.3.1 基本日志
生成简单的日志可以使用全局日志记录器,并调用其info方法。
Logger.getGlobal().info("File->Open menu item selected");
除此之外,在项目中,我们也要定义自己的日志记录器。与包名类似,日志记录器也有层及结构。而且日志记录器的层次结构比包结构更强烈。对于包来说,包与父包之间是没有语义关系的,但是日志记录器父与子之间共享一些属性。例如对父记录器设置了日志级别,他的子记录器也会继承这个日志级别。
var loggerA = Logger.getLogger("A");
var loggerB = Logger.getLogger("A.B");
loggerA.setLevel(Level.OFF);
loggerB.finest("finest");
可以看到,日志记录器B是日志记录器A的子类。当A的日志级别设置成关闭之后,在B没有另外设置日志级别的情况下,B是直接继承A的日志级别的。
日志级别有以下7种。
级别 | 值 | 备注 |
---|---|---|
OFF | Integer.MAX_VALUE | 关闭所有日志 |
SEVERE | 1000 | 非常严重的错误 |
WARNING | 900 | 有潜在的问题 |
INFO | 800 | the INFO level should only be used for reasonably significant messages that will make sense to end users and system administrators. |
CONFIG | 700 | CONFIG message might include the CPU type, the graphics depth, the GUI look-and-feel, etc |
FINE | 500 | FINE is a message level providing tracing information. |
FINER | 400 | FINER indicates a fairly detailed tracing message. By default logging calls for entering, returning, or throwing an exception are traced at this level. |
FINEST | 300 | FINEST indicates a highly detailed tracing message. |
ALL | Integer.MIN_VALUE | ALL indicates that all messages should be logged. |
默认情况下,日志记录器只记录SEVERE、WARNING、INFO三个级别。确定一个日志级别是否打印,依靠的是级别的值。所以当日志级别设置成ALL的时候,所有大于Integer.MIN_VALUE的日志级别全部打印。
下面是几种日志打印方法,注意举一反三。
logger.warning(msg);
logger.log(Level.FINE,msg);
//logp方法获得调用类和方法的确切位置
void logp(Level l, String className, String methodName, String message);
//下面是一些跟踪执行流的日志打印方法。entering会生成FINER级别以ENTRY开头的日志记录,exiting会生成FINER级别以RETURN开头的日志记录
void entering(String className, String methodName);
void exiting(String className, String methodName);
//可以用以下方法打印包含异常的描述.throwing可以生成一条FINER级别以THROW开头的日志记录。
void throwing(String className, String methodName, Throwable t);
void log(Level l, String messge, Throwable t);
7.3.2 日志处理器
Java日志记录器是层次化的。Java的子日志记录器会把日志发给自己的日志处理器(Handler)和父日志记录器的日志处理器。如果不从中截停这种转发,那么会最终到达祖先日志记录器的处理器上进而显示到控制台。为了更好的控制我们自己的日志输出方向,所以要关闭向父日志处理器的转发。
logger.setUseParentHandlers(false);
Java提供的日志处理器最常用的有以下几种:
- FileHandler:向文件中存储日志
- ConsoleHandler:向控制台发送日志
- SocketHandler:通过网络发送到指定的主机和端口
- StreamHandle:以指定的!OutputStream实例输出日志
- !MemoryHandler:将信息暂存在内存中
一个日志记录器可以有多个日志处理器,同时日志处理器和记录器都提供了丰富的自定义方法。下面是一个小的例子。
try {
logger = Logger.getLogger("umad.index");
logger.setUseParentHandlers(false);
//设置文件handler
var fileHandler = new FileHandler("%t/umad.log"); //日志文件存储到系统临时目录
fileHandler.setFormatter(new SimpleFormatter());
fileHandler.setEncoding("utf-8");
fileHandler.setLevel(Debug.LEVEL);
//设置ConsoleHandler
var consoleHandler = new ConsoleHandler();
consoleHandler.setFormatter(new SimpleFormatter());
consoleHandler.setEncoding("utf-8");
consoleHandler.setLevel(Debug.LEVEL);
logger.addHandler(fileHandler);
logger.addHandler(consoleHandler);
logger.setLevel(Debug.LEVEL);
} catch (IOException e) {
logger.log(Level.WARNING, "创建日志文件失败!", e);
}
当存储到文件的时候可以通过一下几个变量描述存储路径。
变量 | 描述 |
---|---|
%h | 系统属性user.home的值 |
%t | 系统临时目录 |
%u | 用于解决冲突的唯一编号 |
%g | 循环日志生成号 |
%% | %字符 |
7.3.3 格式化日志
Formatter为格式化LogRecords提供支持。
一般来说,每个Handler都有关联的Formatter。Formatter接受LogRecord,并将它转换为一个字符串。
默认提供了两种Formatter:
-
java.util.logging.SimpleFormatter:标准日志格式
-
java.util.logging.XMLFormatter:XML形式的日志格式,如果为Logger添加了一个new XMLFormatter(),那么就会以XML形式输出,不过更常用的是使用上面介绍的!FileHandler输出到XML文件中。
从上一节的例子可知,FileHandler的默认格式是java.util.logging.XMLFormatter,而ConsolerHandler的默认格式是java.util.logging.SimpleFormatter,可以使用Handler实例的setFormatter()方法来设定信息的输出格式。
例如:
fileHandler.setFormatter(new SimpleFormatter());
FileHandler的Formatter设定为SimpleFormatter,则输出的日志文件内容就是简单的文字信息,打开文件后会发现与命令行模式下看到的信息内容相同
7.3.4 过滤器
默认情况会根据日志级别进行过滤。但是每个日志记录器和处理器都有一个可选的过滤器来完成附加的过滤。注意同一时刻最多只能有一个过滤器。过滤器要实现Filter接口。
loggerA.setFilter(new Filter() {
@Override
public boolean isLoggable(LogRecord record) {
if(record.getMessage().startsWith("ENTRY"))
return false;
return true;
}
});
注意这里也可以用lambda表达式实现,效率更高。