文章目录
10、异常的一些注意点
10.1 Java.util.logging(JDK自带的记录日志类)
10.1.1 简介
日志用来记录程序的运行轨迹,方便查找关键信息,也方便快速定位解决问题。下面介绍 Java 自带的日志工具类 java.util.logging 的使用。
如果要生成简单的日志记录,可以使用全局日志记录器并调用其 info 方法,代码如下:
Logger.getGlobal().info("打印信息");
JDK Logging 把日志分为如下表 7 个级别,等级依次降低。
级别 | SEVERE | WARNING | INFO | CONFIG | FINE | FINER | FINEST |
---|---|---|---|---|---|---|---|
调用方法 | severe() | warning() | info() | config() | fine() | finer() | finest() |
含义 | 严重 | 警告 | 信息 | 配置 | 良好 | 较好 | 最好 |
Logger 的默认级别是 INFO,比 INFO 级别低的日志将不显示。Logger 的默认级别定义在 jre 安装目录的 lib 下面。
Limit the message that are printed on the console to INFO and above.
java.util.logging.ConsoleHandler.level = INFO
所以在默认情况下,日志只显示前三个级别,对于所有的级别有下面几种记录方法:
logger.warning(message);
logger.fine(message);
同时,还可以使用 log 方法指定级别,例如:
logger.log(Level.FINE, message);
例 1
package exception_use;
import java.util.logging.Logger;
public class TestLog {
private static Logger log = Logger.getLogger(TestLog.class.toString());
public static void main(String[] args) {
// 级别依次升高,后面的日志级别会屏蔽之前的级别
log.finest("finest");
log.finer("finer");
log.fine("fine");
log.config("config");
log.info("info");
log.warning("warning");
log.severe("server");
}
}
输出结果为:
可以使用 setLevel 方法设置级别,例如logger.setLevel(Level.FINE);
可以将 FINE 和更高级别的都记录下来。另外,还可以使用 Level.ALL
开启所有级别的记录,或者使用 Level.OFF
关闭所有级别的记录。
注意:如果将记录级别设计为 INFO 或者更低,则需要修改日志处理器的配置。默认的日志处理器不会处理低于 INFO 级别的信息。
10.1.2 修改日志管理器配置
可以通过编辑配置文件来修改日志系统的各种属性。在默认情况下,配置文件存在于 jre 安装目录下“jre/lib/logging.properties”。要想使用另一个配置文件,就要将 java.util.logging.config.file 特性设置为配置文件的存储位置,并用下列命令启动应用程序。
java -D java.util.logging.config.file = configFile MainClass
日志管理器在 JVM 启动过程中初始化,这在 main 执行之前完成。如果在 main 中调用System.setProperty("java.util.logging.config.file",file)
,也会调用LogManager.readConfiguration()
来重新初始化日志管理器。
要想修改默认的日志记录级别,就需要编辑配置文件,并修改以下命令行。
.level=INFO
可以通过添加以下内容来指定自己的日志记录级别
Test.Test.level=FINE
也就是说,在日志记录器名后面添加后缀 .level。
在稍后可以看到,日志记录并不将消息发送到控制台上,这是处理器的任务。另外,处理器也有级别。要想在控制台上看到 FINE 级别的消息,就需要进行下列设置。
java.util.logging.ConsoleHandler.level=FINE
注意:在日志管理器配置的属性设置不是系统属性,因此,用-Dcom.mycompany.myapp.level=FINE启动应用程序不会对日志记录器产生任何影响
10.2 自动资源管理(java7后加,java9增强)
10.2.1 介绍
public static void main(String[] args) {
FileInputStream fis = null;
try {
fis = new FileInputStream("a.txt");
} catch (FileNotFoundException e) {
e.printStackTrace();
} finally {
// 关闭磁盘文件,回收资源
if (fis != null) {
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
Java 7 以前,上面程序中的 finally 代码块是不得不写的“臃肿代码”,为了解决这种问题,Java 7 增加了一个新特性,该特性提供了另外一种管理资源的方式,这种方式能自动关闭文件,被称为自动资源管理(Automatic Resource Management)。
该特性是在 try 语句上的扩展,主要释放不再需要的文件或其他资源。
自动资源管理替代了 finally 代码块,并优化了代码结构和提高程序可读性。语法如下:
try (声明或初始化资源语句) {//加()来声明!
// 可能会生成异常语句
} catch(Throwable e1){
// 处理异常e1
} catch(Throwable e2){
// 处理异常e1
} catch(Throwable eN){
// 处理异常eN
}
当 try 代码块结束时,自动释放资源。不再需要显式的调用 close() 方法,
该形式也称为“带资源的 try 语句”。
注意:
- try 语句中声明的资源被隐式声明为 final,资源的作用局限于带资源的 try 语句。
- 可以在一条 try 语句中声明或初始化多个资源,每个资源以;隔开即可。
需要关闭的资源必须实现了 AutoCloseable 或 Closeable 接口。
- Closeable 是 AutoCloseable 的子接口,Closeable 接口里的 close() 方法声明抛出了 IOException,因此它的实现类在实现 close() 方法时只能声明抛出 IOException 或其子类;AutoCloseable 接口里的 close() 方法声明抛出了 Exception,因此它的实现类在实现 close() 方法时可以声明抛出任何异常。
下面示范如何使用自动关闭资源的 try 语句。
public class AutoCloseTest {
public static void main(String[] args) throws IOException {
try (
// 声明、初始化两个可关闭的资源
// try语句会自动关闭这两个资源
BufferedReader br = new BufferedReader(new FileReader("AutoCloseTest.java"));
PrintStream ps = new PrintStream(new FileOutputStream("a.txt"))) {
// 使用两个资源
System.out.println(br.readLine());
ps.println("使用带资源的try");
}
}
}
上面程序中分别声明、初始化了两个 IO 流,BufferedReader 和 PrintStream 都实现了 Closeable 接口,并在 try 语句中进行了声明和初始化,所以 try 语句会自动关闭它们。
自动关闭资源的 try 语句相当于包含了隐式的 finally 块(这个 finally 块用于关闭资源),因此这个 try 语句可以既没有 catch 块,也没有 finally 块。
Java 7 几乎把所有的“资源类”(包括文件 IO 的各种类、JDBC 编程的 Connection 和 Statement 等接口)进行了改写,改写后的资源类都实现了 AutoCloseable 或 Closeable 接口。
如果程序需要,自动关闭资源的 try 语句后也可以带多个 catch 块和一个 finally 块。
10.2.2 java9的增强自动资源管理(不用写()只要声明了final或有效final)
Java 9 再次增强了这种 try 语句。Java 9 不要求在 try 后的圆括号内声明并创建资源,只需要自动关闭的资源有 final 修饰或者是有效的 final (effectively final),Java 9 允许将资源变量放在 try 后的圆括号内。上面程序在 Java 9 中可改写为如下形式。
public class AutoCloseTest {
public static void main(String[] args) throws IOException {
// 有final修饰的资源
final BufferedReader br = new BufferedReader(new FileReader("AutoCloseTest.java"));
// 没有显式使用final修饰,但只要不对该变量重新赋值,该变量就是有效的
final PrintStream ps = new PrintStream(new FileOutputStream("a. txt"));
// 只要将两个资源放在try后的圆括号内即可
try (br; ps) {
// 使用两个资源
System.out.println(br.readLine());
ps.println("使用带资源的try");
}
}
}
10.3 final and return执行顺序
在 finally 代码块中改变返回值并不会改变最后返回的内容。且它一定会被执行!
总结为以下几条:
- 当 try 代码块和 catch 代码块中有 return 语句时,finally 仍然会被执行。
- 执行 try 代码块或 catch 代码块中的 return 语句之前,
都会先执行 finally 语句。
无论在 finally 代码块中是否修改返回值,返回值都不会改变,仍然是执行 finally 代码块之前的值。finally 代码块中的 return 语句一定会执行。
10.4 异常规范
10.4.1 C++异常规范(抛弃)
概念:
C++ 规定,异常规范在函数声明和函数定义中必须同时指明,并且要严格保持一致,不能更加严格或者更加宽松。
注意:
C++里边我们知道异常规范是抛弃的,异常规范是 C++98 新增的一项功能,但是后来的 C++11 已经将它抛弃了,不再建议使用。
java里边的异常规范不同!
10.4.2 java的异常规范
参考大佬的博客,作记录:Java 异常处理基本规则,Java异常处理的基本规范
1)不要捕获运行时异常
不要捕获 Java 类库中定义的继承自 RuntimeException 的运行时异常类,如:IndexOutOfBoundsException / NullPointerException ,这类异常由程序员预检查来规避,保证程序健壮性。
2) try-catch 作用域(现有代码出现率较高)
对大段代码进行try-catch ,这是不负责任的表现。catch 时请细分各种类型进行捕获!分清稳定代码和非稳定代码,稳定代码指的是无论如何不会出错的代码。对于非稳定代码的 catch 尽可能进行区分异常类型,再做对应的异常处理。
3)异常的捕捉 & 异常的处理(现有代码出现率较高)
捕获异常是为了处理它
,不要捕获了却什么都不处理而抛弃之,如果不想处理它,请将该异常抛给它的调用者。最外层的业务使用者,必须处理异常,将其转化为用户可以理解的内容。
4)异常 & finally
如果有对IO 流和资源做操作,必须逐一关闭IO 流和资源对象(从里层到外层),有异常也要做处理。
JDK 7 以上可以使用try-with-resources 方式。 (java7的自动资源管理,java9的增强自动资源管理)
5)finally & return
不能在 finally 块中使用 return ,finally 块中的 return 返回后方法结束执行,不会再执行 try 块中的 return 语句。
6)异常需要精确
捕获异常与抛异常,必须是完全匹配,或者捕获异常是抛异常的父类。
如果预期对方抛的是绣球,实际接到的是铅球,就会产生意外情况。
7)程序员的基本修养 & NPE
-
方法(接口)的返回值可以为 null ,但不推荐返回空集合,或者空对象等,必须添加注释充分说明什么情况下会返回 null 值。调用方需要进行 null 判断防止 NPE 问题。
-
防止 NPE ,是程序员的基本修养,注意 NPE 产生的场景。
a.查询数据库返回null ,包括null 对象和null 集合。
b.集合内元素有null 对象。