日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程语言 > java >内容正文

java

Java核心技术笔记 异常、断言和日志

發布時間:2023/12/20 java 35 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Java核心技术笔记 异常、断言和日志 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

《Java核心技術 卷Ⅰ》 第7章 異常、斷言和日志

  • 處理錯誤
  • 捕獲異常
  • 使用異常機制的技巧
  • 記錄日志

處理錯誤

如果由于出現錯誤而是的某些操作沒有完成,程序應該:

  • 返回到一種安全狀態,并讓用戶執行一些其他操作;或者
  • 允許用戶保存所有操作,并以妥善方式終止程序

檢測(或引發)錯誤條件的代碼通常離:

  • 能讓數據恢復到安全狀態
  • 能保存用戶的操作結果并正常退出程序

的代碼很遠。

異常處理的任務:將控制權從錯誤產生地方轉移給能夠處理這種情況的錯誤處理器

異常分類

在Java中,異常對象都是派生于Throwable類的一個實例,如果Java中內置的異常類不能滿足需求,用戶還可以創建自己的異常類。

Java異常層次結構:

  • Throwable
    • Error
      • ...
    • Exception
      • IOException
        • ...
      • RuntimeException
        • ...

可以看到第二層只有Error和Exception。

Error類層次結構描述了Java運行時系統的內部錯誤資源耗盡錯誤,應用程序不應該拋出這種類型的對象,這種內部錯誤的情況很少出現,出現了能做的工作也很少。

設計Java程序時,需關注Exception層次結構,這個層次又分為兩個分支,RuntimeException和包含其他異常的IOException。

劃分兩個分支的規則是:

  • 由程序錯誤導致的異常屬于RuntimeException
  • 程序本身無問題,由于像I/O錯誤這類問題導致的異常屬于其他異常IOException

派生于RuntimeException的異常包含下面幾種情況:

  • 錯誤類型轉換
  • 數組訪問越界
  • 訪問null指針

派生于IOException的異常包含下面幾種情況:

  • 試圖在文件尾部后面讀取數據
  • 試圖打開一個不存在的文件
  • 試圖根據指定字符串查找Class對象,而這個字符串表示的類并不存在

Java語言規范將派生于Exception類和RuntimeException類的所有異常統稱非受查(unchecked)異常,所有其他異常都是受查(checked)異常。

編譯器將核查是否為所有的受查異常提供了異常處理器

聲明受查異常

一個方法不僅要告訴編譯器將要返回什么值,還要告訴編譯器有可能發生什么錯誤

異常規范(exception specification):方法應該在其首部聲明所可能拋出的異常。

public FileInputStream(String name) throws FileNotFoundException 復制代碼

如果這個方法拋出了這樣的異常對象,運行時系統會開始搜索異常處理器,以便知道如何處理這個異常對象。

當然不是所有方法都需要聲明異常,下面4種情況應該拋出異常:

  • 調用一個拋出受查異常的方法時
  • 程序運行過程中發現錯誤,并且利用throw語句拋出一個受查異常
  • 程序出現錯誤,一般是非受查異常
  • Java虛擬機和運行時庫出現的內部錯誤
  • 出現前兩種情況之一,就必須告訴調用者這個方法可能的異常,因為如果沒有處理器捕獲這個異常,當前執行的線程就會結束

    如果一個方法有多個受查異常類型,就必須在首部列出所有的異常類,異常類之間用逗號隔開:

    class MyAnimation {...public Image loadImage(String s) throws FileNotFoundException, EOFException{...} } 復制代碼

    但是不需要聲明Java的內部錯誤,即從Error繼承的錯誤。

    關于子類和超類在這部分的問題:

    • 子類方法聲明的受查異常不能比超類中方法聲明的異常更通用(即子類能拋出更特定的異常或者根本不拋出任何異常)
    • 如果超類沒有拋出任何受查異常,子類也不能

    如果類中的一個方法聲明拋出一個異常,而這個異常是某個特定類的實例時:

    • 這個方法可能拋出一個這個類的異常(比如IOExcetion)
    • 或拋出這個類的任意一個子類的異常(比如FileNotFoundException)

    如何拋出異常

    假設程序代碼中發生了一些很糟糕的事情。

    首先要決定應該拋出什么類型的異常(通過查閱已有異常類的Java API文檔)。

    拋出異常的語句是:

    throw new EOFException(); // 或者 EOFException e = new EOFException(); throw e; 復制代碼

    一個名為readData的方法正在讀取一個首部有信息Content-length: 1024的文件,然而讀到733個字符之后文件就結束了,這是一個不正常的情況,希望拋出一個異常。

    String readData(Scanner in) throws EOFException {...while(...){if(!in.hasNext()) // EOF encountered{if(n < len)throw new EOFException();}...}return s; } 復制代碼

    EOFException類還有一個含有一個字符串類型參數的構造器,這個構造器可以更加細致的描述異常出現的情況。

    String gripe = "Content-length:" + len + ", Received:" + n; throw new EOFException(gripe); 復制代碼

    對于一個已經存在的異常類,將其拋出比較容易:

  • 找到一個合適的異常類
  • 創建這個類的一個對象
  • 將對象拋出
  • 一旦拋出異常,這個方法就不可能返回到調用者,即不必為返回的默認值或錯誤代碼擔憂。

    創建異常類

    實際情況中,可能會遇到任何標準異常類不能充分描述的問題,這時候就應該創建自己的異常類。

    需要做的只是定義一個派生于Exception的類,或者派生于Exception子類的類。

    習慣上,定義的類應該包含兩個構造器:

    • 一個是默認的構造器
    • 另一個是帶有詳細描述信息的構造器(超類Throwable的toString方法會打印出這些信息,在調試中有很多用)
    class FileFormatException extends IOException {public FileFormatException() {}public FileFormatException(String gripe){super(gripe);} } 復制代碼

    捕獲異常

    捕獲異常

    要想捕獲一個異常,必須設置try/catch語句塊。

    try {code... } catch(ExceptionType e) {handler for this type } 復制代碼

    如果try語句塊中任何代碼拋出了一個在catch子句中說明的異常類,那么:

  • 程序將跳過try語句塊的其余代碼
  • 程序將執行catch子句中的處理器代碼
  • 如果沒有代碼拋出任何異常,程序跳過catch子句。

    如果方法中的任何代碼拋出了一個在catch子句中沒有聲明的異常類型,那么這個方法就會立即退出

    // 讀取數據的典型代碼 public void read(String filename) {try{InputStream in = new FileInputStream(filename);int b;while((b = in.read()) != -1){// process input...}}catch(IOException exception){exception.printStackTrace();} } 復制代碼

    read方法有可能拋出一個IOException異常,這種情況下,將跳出整個while循環,進入catch子句,并生成一個棧軌跡

    還有一種選擇就是什么也不做,而是將異常傳遞給調用者

    public void read(String filename) throws IOException {InputStream in = new FileInputStream(filename);int b;while((b = in.read()) != -1){// process input...} } 復制代碼

    編譯器嚴格地執行throws說明符,如果調用了一個拋出受查異常的方法,就必須對它進行處理,或者繼續傳遞。

    兩種方式哪種更好

    通常,應該捕獲那些知道如何處理的異常,將那些不知道怎么樣處理的異常進行傳遞。

    這個規則也有一個例外:如果編寫一個覆蓋超類的方法,而這個方法又沒有拋出異常,那么這個方法就必須捕獲方法代碼中出現的每一個受查異常;并且不允許在子類的throws說明符中出現超過超類方法所列出的異常類范圍。

    捕獲多個異常

    為每個異常類型使用一個單獨的catch子句:

    try {code... } catch(FileNotFoundException e) {handler for missing files } catch(UnknownHostException e) {handler for unknown hosts } catch(IOException e) {handler for all other I/O problems } 復制代碼

    異常對象可能包含與異常相關的信息,可以使用e.getMessage()獲得詳細的錯誤信息,或者使用e.getClass().getName()得到異常對象的實際類型。

    在Java SE 7中,同一個catch子句中可以捕獲多個異常類型,如果動作一樣,可以合并catch子句:

    try {code... } catch(FileNotFoundException | UnknownHostException e) {handler for missing files and unknown hosts } catch(IOException e) {handler for all other I/O problems } 復制代碼

    只有當捕獲的異常類型彼此之間不存在子類關系時才需要這個特性。

    再次拋出異常與異常鏈

    在catch子句中可以拋出一個異常,這樣做的目的是改變異常的類型

    try {access the database } catch(SQLException e) {throws new ServletException("database error:" + e.getMessage()); } 復制代碼

    ServletException用帶有異常信息文本的構造器來構造。

    不過還有一種更好的處理方法,并將原始異常設置為新異常的“原因”

    try {access the database } catch(SQLException e) {Throwable se = new ServletException("database error");se.initCause(e);throw se; } 復制代碼

    當捕獲到異常時,可以使用下面這條語句重新得到原始異常:

    Throwable e = se.getCause(); 復制代碼

    這樣可以讓用戶拋出子系統中的高級異常,而不會丟失原始異常的細節。

    finally子句

    當代碼拋出一個異常時,就會終止方法中剩余代碼的處理,并退出這個方法的執行。

    如果方法獲得了一些本地資源,并且只有這個方法自己知道,又如果這些資源在退出方法之前必須被回收(比如數據庫連接的關閉),那么就會產生資源回收問題。

    一種是捕獲并重新拋出所有異常,這種需要在兩個地方清除所分配的資源,一個在正常代碼中,另一個在異常代碼中。

    Java有一種更好地解決方案,就是finally子句。

    不管是否有異常被捕獲,finally子句的代碼都會被執行。

    InputStream in = new FileInputStream(...); try {// 1code that might throw exception// 2 } catch(IOException e) {// 3show error message// 4 } finally {// 5in.close(); } // 6 復制代碼

    上面的代碼中,有3種情況會執行finally子句:

  • 代碼沒有拋出異常,執行序列為1、2、5、6
  • 拋出一個在catch子句中捕獲的異常
  • 如果catch子句沒有拋出異常,執行序列為1、3、4、5、6
  • 如果catch子句拋出一個異常,異常將被拋回這個方法的調用者,執行序列為1、3、5(注意沒有6)
  • 代碼拋出了一個異常,但是這個異常沒有被捕獲,執行序列為1、5
  • try語句可以只有finally子句,沒有catch子句。

    有時候finally子句也會帶來麻煩,比如清理資源時也可能拋出異常。

    如果在try中發生了異常,并且被catch捕獲了異常,然后在finally中進行處理資源時如果又發生了異常,那么原有的異常將會丟失,轉而拋出finally中處理的異常。

    這個時候的一種解決辦法是用局部變量Exception ex暫存catch中的異常:

    • 在try中進行執行的時候加入嵌套的try/catch,并在catch中暫存ex并向上拋出
    • 在finally中處理資源的時候加入嵌套的try/catch,并且在catch中進行判斷ex是否存在來進一步處理
    InputStream in = ...; Exception ex = null; try {try{code that might throw exception}catch(Exception e){ex = e;throw e;} } finally {try{in.close();}catch(Exception e){if(ex == null)throw e;} } 復制代碼

    下一節會介紹,Java SE 7中關閉資源的處理會容易很多。

    帶資源的try語句

    對于以下代碼模式:

    open a resource try {work with the resource } finally {close the resource } 復制代碼

    假設資源屬于一個實現了AutoCloseable接口的類,Java SE 7位這種代碼提供了一個很有用的快捷方式,AutoCloseable接口有一個方法:

    void close() throws Exception 復制代碼

    帶資源的try語句的最簡形式為:

    try(Resource res = ...) {work with res } 復制代碼

    try塊退出時,會自動調用res.close()。

    try(Scanner in = new Scanner(new FileInputStream("..."), "UTF-8")) {while(in.hasNext())System.out.println(in.next()); } 復制代碼

    這個塊正常退出或存在一個異常時,都會調用in.close()方法,就好像使用了finally塊一樣。

    還可以指定多個資源:

    try(Scanner in = new Scanner(new FileInputStream("..."), "UTF-8");PrintWriter out = new PrintWriter("...")) {while(in.hasNext())System.out.println(in.next().toUpperCase()); } 復制代碼

    不論如何這個塊如何退出,in和out都會關閉,但是如果用常規手動編程,就需要兩個嵌套的try/finally語句。

    之前的close拋出異常會帶來難題,而帶資源的try語句可以很好的處理這種情況,原來的異常會被重新拋出,而close方法帶來的異常會“被抑制”。

    分析堆棧軌跡元素

    堆棧軌跡(stack trace)是一個方法調用過程的列表,包含了程序執行過程中方法調用的特定位置。

    可以調用Throwable類的printStackTrace方法訪問堆棧軌跡的文本描述信息。

    Throwable t = new Throwable(); StringWriter out = new StringWriter(); t.printStackTrace(new PrintWriter(out)); String description = out.toString(); 復制代碼

    一種更靈活的方法是使用getStackTrace方法,會得到StackTraceElement對象的一個數組,可以在程序中分析這個對象數組:

    StackTraceElement[] frames = t.getStackTrace(); for(StackTraceElement frame : frames)analyze frame 復制代碼

    StackTraceElement類含有能夠獲得文件名和當前執行的代碼行號的方法,同時還含有能獲得類名和方法名的方法,toString方法會產生一個格式化的字符串,其中包含所獲得的信息。

    靜態的Thread.getAllStackTraces方法,可以產生所有線程的堆棧軌跡。

    Map<Thread, StackTraceElement[]> map = Thread.getAllStackTraces(); for(Thread t : map.keySet()) {StackTraceElememt[] frames = map.get(t);analyze frames } 復制代碼

    java.lang.Throwable

    • Throwable(Throwable cause)
    • Throwable(String message, Throwable cause)
    • Throwable initCause(Throwable cause):將這個對象設置為“原因”,如果這個對象已經被設置為“原因”,則拋出一個異常,返回this引用。
    • Throwable getCause():獲得設置為這個對象的“原因”的異常對象,如果沒有則為null
    • StackTraceElement[] getStackTrace():獲得構造這個對象時調用堆棧的跟蹤
    • void addSuppressed(Throwable t):為這個異常增加一個抑制異常
    • Throwable[] getSuppressed():得到這個異常的所有抑制異常

    java.lang.StackTraceElement

    • String getFileName()
    • int getLineNumber()
    • String getClassName()
    • String getMethodName()
    • boolean isNativeMethod():如果這個元素運行時在一個本地方法中,則返回true
    • String toString():如果存在的話,返回一個包含類名、方法名、文件名和行數的格式化字符串,如StackTraceTest.factorial(StackTraceTest.java:18)

    使用異常機制的技巧

    1.異常處理不能代替簡單的測試

    在進行一些風險操作時(比如出棧操作),應該先檢測當前操作是否有風險(比如檢查是否已經空棧),而不是用異常捕獲來代替這個測試。

    與簡單的測試相比,捕獲異常需要花費更多的時間,所以:只在異常情況下使用異常機制

    2.不要過分細分化異常

    如果可以寫成一個try/catch(s)的語句,那就不要寫成多個try/catch。

    3.利用異常層次結構

    不要只拋出RuntimeException異常,應該尋找更適合的子類或創建自己的異常類。

    不要只拋出Throwable異常,否則會使程序代碼可讀性、可維護性下降。

    4.不要壓制異常

    在Java中,傾向于關閉異常。

    public Image loadImage(String s) {try{codes}catch(Exception e){} } 復制代碼

    這樣代碼就可以通過編譯了,如果發生了異常就會被忽略。當然如果認為異常非常重要,就應該對它們進行處理。

    5.檢測錯誤時,“苛刻”要比放任更好

    6.不要羞于傳遞異常

    有時候傳遞異常比捕獲異常更好,讓高層次的方法通知用戶發生了錯誤,或者放棄不成功的命令更加適宜。

    斷言

    這部分和測試相關,以后有需要的話單獨開設一章進行說明。

    記錄日志

    不要再使用System.out.println來進行記錄了

    使用記錄日志API吧

    基本日志

    簡單的日志記錄,可以使用全局日志記錄器(global logger)并調用info方法:

    Logger.getGlobal().info("File->Open menu item selected"); 復制代碼

    默認情況下會顯示:

    May 10, 2013 10:12:15 .... INFO: File->Open menu item selected 復制代碼

    如果在適當的地方調用:

    Logger.getGlobal().setLevel(Level.OFF); 復制代碼

    高級日志

    可以不用將所有的日志都記錄到一個全局日志記錄器中,也可以自定義日志記錄器:

    private static final Logger myLogger = Logger.getLogger("com.mycompany.myapp"); 復制代碼

    未被任何變量引用的日志記錄器可能會被垃圾回收,為了避免這種情況,可以用一個靜態變量存儲日志記錄器的一個引用。

    與包名類似,日志記錄器名也具有層次結構,并且層次性更強。

    對于包來說,包的名字與其父包沒有語義關系,但是日志記錄器的父與子之間共享某些屬性。

    例如,如果對com.mycompany日志記錄器設置了日志級別,它的子記錄器也會繼承這個級別。

    通常有以下7個日志記錄器級別Level:

    • SEVERE
    • WARNING
    • INFO
    • CONFIG
    • FINE
    • FINER
    • FINEST

    默認情況下,只記錄前三個級別。

    另外,可以使用Level.ALL開啟所有級別的記錄,或者使用Level.OFF關閉所有級別的記錄。

    對于所有的級別有下面幾種記錄方法:

    logger.warning(message); logger.info(message); 復制代碼

    也可以使用log方法指定級別:

    logger.log(Level.FINE, message); 復制代碼

    如果記錄為INFO或更低,默認日志處理器不會處理低于INFO級別的信息,可以通過修改日志處理器的配置來改變這一狀況。

    默認的日志記錄將顯示包含日志調用的類名和方法名,如同堆棧所顯示的那樣。

    但是如果虛擬機對執行過程進行了優化,就得不到準確的調用信息,此時,可以調用logp方法獲得調用類和方法的確切位置,這個方法的簽名為:

    void logp(Level l, String className, String methodName, String message) 復制代碼

    記錄日志的常見用途是記錄那些不可預料的異常,可以使用下面兩個方法提供日志記錄中包含的異常描述內容:

    if(...) {IOException exception = new IOException("...");logger.throwing("com.mycompany.mylib.Reader", "read", exception);throw exception; } 復制代碼

    還有

    try {... } catch(IOException e) {Logger.getLogger("com.mycompany.myapp").log(Level.WARNING, "Reading image", e);z } 復制代碼

    調用throwing可以記錄一條FINER級別的記錄和一條以THROW開始的信息。

    剩余部分暫時不做介紹,初步了解到這即可,一把要結合IDE一起來使用這個功能。如果后續的高級知識部分有需要的話會單獨開設專題來介紹。

    Java異常、斷言和日志總結

    • 處理錯誤
    • 異常分類
    • 受查異常
    • 拋出異常
    • 創建異常類
    • 捕獲異常
    • 再次拋出異常與異常鏈
    • finally子句
    • 在資源的try語句
    • 分析堆棧軌跡元素
    • 使用異常機制的技巧
    • 基本日志與高級日志

    個人靜態博客:

    • 氣泡的前端日記: rheabubbles.github.io

    轉載于:https://juejin.im/post/5c5153696fb9a049df247b42

    總結

    以上是生活随笔為你收集整理的Java核心技术笔记 异常、断言和日志的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。