文章目錄
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 對象。