1.處理錯誤java
假設在一個Java程序運行期間出現了一個錯誤。這個錯誤多是因爲文件包含了錯誤信息,或者網絡鏈接出現問題形成的,也有多是由於使用無效的數組下標,或者試圖使用一個沒有被賦值的對象引用而形成的。程序員
用戶指望在出現錯誤時,程序可以採用一些理智的行爲。若是因爲出現錯誤而使得某些操做沒有完成,程序應該:數據庫
1)返回到一種安全狀態,並可以讓用戶執行一些其餘的命令;編程
2)容許用戶保存全部操做的結果,並以妥善的方式終止程序。數組
要作到這些並非一件很容易的事情。其緣由是檢測(或引起)錯誤條件的代碼一般離那些可以讓數據恢復到安全狀態,或者可以保存用戶的操做結果,並正常地退出程序的代碼很遠。安全
異常處理的任務就是將控制權從錯誤產生的地方轉移給可以處理這種狀況的錯誤處理器。網絡
爲了可以在程序中處理異常狀況,必須研究程序中可能會出現的錯誤和問題,以及哪類問題須要關注:app
1)用戶輸入錯誤。除了那些不可避免的鍵盤輸入錯誤外,有些用戶喜歡各行其是,不遵照程序的要求。測試
2)設備錯誤。硬件不老是讓它作什麼,它就作什麼。在一個任務的處理過程當中,硬件常常出現問題。this
3)物理限制。磁盤滿了,可用存儲空間已被用完。
4)代碼錯誤。程序方法有可能沒法正確執行。例如,方法可能返回一個錯誤的答案,或者錯誤地調用了其餘的方法。計算的數組索引不合法,試圖在散列表中查找一個不存在的記錄,或者試圖讓一個空棧執行彈出操做,這些都屬於代碼錯誤。
對於方法中的一個錯誤,傳統的作法是返回一個特殊的錯誤碼,由調用方法分析。例如,對於一個從文件中讀取信息的方法來講,返回值一般不是標準字符,而是一個-1,表示文件結束。這種處理方式對於不少異常情況都是可行的。還有一種表示錯誤情況的經常使用返回值是null引用。
遺憾的是,並非在任何狀況下都可以返回一個錯誤碼。有可能沒法明確地將有效數據與無效數據加以區分。一個返回整型的方法就不能簡單地經過返回-1表示錯誤,由於-1極可能是一個徹底合法的結果。
在Java中,若是某個方法不可以採用正常的途徑完整它的任務,就能夠經過另外一個路徑退出方法。在這種狀況下,方法並不返回任何值,而是拋出(throw)一個封裝了錯誤信息的對象。須要注意的是,這個方法將會當即退出,並不返回任何值。此外,調用這個方法的代碼也將沒法繼續執行,取而代之的是,異常處理機制開始搜索可以處理這種異常情況的異常處理器。
異常分類
在Java中,異常對象都是派生於Throwable類的一個實例。
須要注意的是,全部的異常都是由Throwable繼承而來,但在下一層當即分解爲兩個分支:Error和Exception。
Error類層次結構描述了Java運行時系統的內部錯誤和資源耗盡錯誤。應用程序不該該拋出這種類型的對象。若是出現了這樣的內部錯誤,除了通告給用戶,並盡力使程序安全地終止以外,再也無能爲力了。這種狀況不多出現。
在設計Java程序時,須要關注Exception層次結構。這個層次結構又分解爲兩個分支:一個分支派生於RuntimeException;另外一個分支包含其餘異常。劃分兩個分支的規則是:由程序錯誤致使的異常屬於RuntimeException;而程序自己沒有問題,但因爲像I/O錯誤這類問題致使的異常屬於其餘異常。
派生於RuntimeException的異常包含下面幾種狀況:
1)錯誤的類型轉換。
2)數組訪問越界。
3)訪問null指針。
不是派生於RuntimeException的異常包括:
1)試圖在文件尾部後面讀取數據。
2)試圖打開一個不存在的文件。
3)試圖根據給定的字符串查找Class對象,而這個字符串表示的類並不存在。
「若是出現RuntimeException異常,那麼就必定是你的問題」是一條至關有道理的規則。應該經過檢測數組下標是否越界來避免ArrayIndexOutOfBoundsException異常;應該經過在使用變量以前檢測是否爲null來杜絕NullPointerException異常的發生。
如何處理不存在的文件呢?難道不能先檢查文件是否存在再打開它嗎?恩,這個文件有可能在你檢查它是否存在以前就已經被刪除了。所以,「是否存在」取決於環境,而不僅是取決於你的代碼。
Java語言規範將派生於Error類和RuntimeException類的全部異常稱爲非受查(unchecked)異常,全部其餘的異常稱爲受查異常。編譯器將檢查是否爲全部的受查異常提供了異常處理器。
聲明受查異常
若是遇到了沒法處理的狀況,那麼Java的方法能夠拋出一個異常。這個道理很簡單:一個方法不只須要告訴編譯器將要返回什麼值,還要告訴編譯器有可能發生什麼錯誤。
方法應該在其首部聲明全部可能拋出的異常。這樣能夠從首部反映出這個方法可能拋出哪類受查異常。例如,下面是標準類庫中提供的FileInputStream類的一個構造器的聲明。
public FileInputStream(String name) throws FileNotFoundException
這個聲明表示這個構造器將根據給定的String參數產生一個FileInputStream對象,但也有可能拋出一個FileNotFoundException異常。若是發生了這種糟糕狀況,構造器將不會初始化一個新的FileInputStream對象,而是拋出一個FileNotFoundException類對象。若是這個方法真的拋出了這樣一個異常對象,運行時系統就會開始搜索異常處理器,以便知道如何處理FileNotFoundException對象。
在本身編寫方法時,沒必要將全部可能拋出的異常都進行聲明。至於何時須要在方法中throws子句聲明異常,什麼異常必須使用throws子句聲明,須要記住在遇到下面4種狀況時應該拋出異常:
1)調用一個拋出受查異常的方法,例如,FileInputStream構造器。
2)程序運行過程當中發現錯誤,而且利用throw語句拋出一個受查異常。
3)程序出現錯誤,例如,a[-1]=0會拋出一個ArrayIndexOutOfBoundsException這樣的非受查異常。
4)Java虛擬機和運行時庫出現的內部錯誤。
若是出現前兩種狀況之一,則必須告訴調用這個方法的程序員有可能拋出異常。爲何?由於任何一個拋出異常的方法都有多是一個死亡陷阱。若是沒有處理器捕獲這個異常,當前執行的線程就會結束。
對於那些可能被他人使用的Java方法,應該根據異常規範,在方法的首部聲明這個方法可能拋出的異常。
class MyAnimation{ ... public Image loadImage(String s) throws IOException{ ... } }
若是一個方法有可能拋出多個受查異常類型,那麼就必須在方法的首部列出全部的異常類。每一個異常類之間用逗號隔開。
class MyAnimation{ ... public Image loadImage(String s) throws EOFException,FileNotFoundException{ ... } }
可是,不須要聲明Java的內部錯誤,即從Error繼承的錯誤。任何程序代碼都具備拋出那些異常的潛能,而咱們對其沒有任何控制能力。
一樣,也不該該聲明從RuntimeException繼承的那些非受查異常。
總之,一個方法必須聲明全部可能拋出的受查異常,而非受查異常要麼不控制(Error),要麼就應該避免發生(RuntimeException)。若是方法沒有聲明全部可能發生的受查異常,編譯器就會發出一個錯誤信息。
固然,從前面的示例能夠知道:除了聲明異常以外,還能夠捕獲異常。這樣會使異常不會被拋到方法以外,也不須要throws規範。
注:若是在子類中覆蓋了超類的一個方法,子類方法中聲明的受查異常不能比超類方法中聲明的異常更通用(也就是說,子類方法中能夠拋出更特定的異常,或者根本不拋出任何異常)。特別須要說明的是,若是超類方法沒有拋出任何受查異常,子類也不能拋出任何受查異常。
若是類中的一個方法聲明將會拋出一個異常,而這個異常是某個特定類的實例時,則這個方法就有可能拋出一個這個類的異常,或者這個類的任意一個子類的異常。例如,FileInputStream構造器聲明將有可能拋出一個IOException,然而並不知道具體是哪一種IOException異常。
如何拋出異常
假設在程序代碼中發生了一些很糟糕的事情。一個名爲readData的方法正在讀取一個首部具備下列信息的文件:
Content-length:1024
然而,讀到733個字符以後文件就結束了。咱們認爲這是一種不正常的狀況,但願拋出一個異常。
首先要決定應該拋出什麼類型的異常。將上述異常歸結爲IOException是一種很好的選擇。仔細地閱讀Java API文檔以後會發現:EOFException異常描述的是:「在輸入過程當中,遇到了一個未預期的EOF後的信號」。這正是咱們要拋出的異常。下面是拋出這個異常的語句:
throw new EOFException(); //or EOFException e=new EOFException(); throw e;
下面將這些代碼放在一塊兒:
String readData(Scanner in)throws EOFException{ ... while(...){ if(!in.hasNext()){ if(n<len) throw new EOFException(); } ... } return s; }
在前面已經看到,對於一個已經存在的異常類,將其拋出很是容易。在這種狀況下:
1)找到一個合適的異常類。
2)建立這個類的一個對象。
3)將對象拋出。
一旦方法拋出了異常,這個方法就不能返回到調用者。也就是說,沒必要爲返回的默認值或錯誤代碼擔心。
建立異常類
在程序中,可能會遇到任何標準異常類都沒有可以充分地描述清楚的問題。這這種狀況下,建立本身的異常類就是一件瓜熟蒂落的事情下。咱們須要作的只是定義一個派生於Exception的類,或者派生於Exception子類的類。例如,定義一個派生於IOException的類。習慣上,定義的類應該包含兩個構造器,一個默認的構造器;另外一個是帶有詳細信息描述信息的構造器(超類Throwable的toString方法將會打印出這些詳細信息,這在調試中很是有用)。
class FileFormatException extends IOException{ public FileFormatException(){} public FileFormatException(String gripe){ super(gripe); } }
如今,就能夠拋出本身定義的異常類型了。
String readData(BufferReader in)throws FileFormatException{ ... while(...) { if(ch==-1){ if(n<len) throw FileFormatException(); } ... } return s; }
Throwable API 1:
Throwable()
構造一個新的Throwable對象,這個對象沒有詳細的描述信息。
Throwable(String message)
構造一個新的throwable對象,這個對象帶有特定的詳細描述信息。習慣上,全部派生的異常類都支持一個默認的構造器和一個帶有詳細描述信息的構造器。
String getMessage()
得到Throwable對象的詳細描述信息。
2.捕獲異常
若是某個異常發生的時候沒有在任何地方進行捕獲,那程序就會終止執行,並在控制檯上打印出異常信息,其中包括異常的類型和堆棧的內容。
要想捕獲一個異常,必須設置try/catch語句塊。最簡單的try語句塊以下所示:
try{ code more code more code }
catch(ExceptionType e){ handler for this type }
若是在try語句塊中的任何代碼拋出了一個在catch子句中說明的異常類,那麼
1)程序將跳過try語句塊的其他代碼。
2)程序將執行catch子句中的處理器代碼。
若是在try語句塊中的代碼拋出了一個在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(); }
須要注意的是,try語句中的大多數代碼都很容易理解:讀取並處理字節,直到遇到文件結束符爲止。正如在Java API中看到的那樣,read方法有可能拋出一個IOException異常。在這種狀況下,將跳出整個while循環,進入catch子句,並生成一個棧軌跡。對於一個普通的程序來講,這樣處理異常基本上合乎情理。還有其餘的選擇嗎?
一般,最好的選擇是什麼都不作,而是將異常傳遞給調用者。若是read方法出現了錯誤,就讓read方法的調用者去操心!若是採用這種處理方式,就必須聲明這個可能會拋出一個IOException。
public void read(String filename)throws IOException{ InputStream in=new FileInputStream(filename); int b; while((b=in.read())!=-1){ process input } }
請記住,編譯器嚴格地執行throws說明符。若是調用了一個拋出受查異常的方法,就必須對它進行處理,或者繼續傳遞。
一般,應該捕獲那些知道如何處理的異常,而將那些不知道怎樣處理的異常繼續進行傳遞。
若是想傳遞一個異常,就必須在方法的首部添加一個throws說明符,以便告知調用者這個方法可能會拋出異常。
仔細閱讀一下Java API文檔,以便知道每一個方法可能會拋出哪一種異常,而後再決定是本身處理,仍是添加到throws列表中。對於後一種狀況,也沒必要猶豫。將異常直接交給可以勝任的處理器進行處理要比壓制對它的處理更好。
同時請記住,這個規則也有一個例外。前面曾經提到過:若是編寫一個覆蓋超類的方法,而這個方法又沒有拋出異常,那麼這個方法就必須捕獲方法代碼中出現的每個受查異常。不容許在子類的throws說明符中出現超過超類方法所列出的異常類範圍。
在Java SE 7中,同一個catch子句中能夠捕獲多個異常類型。例如,假設對應缺乏文件和未知主機異常的動做是同樣的,就能夠合併catch子句:
try{ code that might throw exception }catch(FileNotFoundException | UnknownHostException e){ emergency action for missing files and unknown hosts }catch(IOException e){ emergency action for all other I/O problems }
只有當捕獲的異常類彼此之間不存在子類關係時才須要這個特性。
注:1.捕獲多個異常時,異常變量隱含爲final變量。例如,不能在如下子句體中爲e賦不一樣的值:
catch(FileNotFoundException | UnknownHostException e){...}
2.捕獲多個異常不只會讓你的代碼看起來更簡單,還會更高效。生成的字節碼之包含一個對應公共catch子句的代碼塊。
再次拋出異常與異常鏈
在catch子句中能夠拋出一個異常,這樣作的目的是改變異常的類型。若是開發了一個供其餘程序員使用的子系統,那麼,用於表示子系統故障的異常類型可能會產生多種解釋。ServletException就是這樣一個異常類型的例子。執行servlet的代碼可能不想知道發生錯誤的細節緣由,但但願明確地知道servlet是否有問題。
下面給出了捕獲異常並將它再次拋出的基本方法:
try{ access the database } catch(SQLException e){ throw new ServletException("database error: "+e.getMessage()); }
不過,能夠有一種更好的處理方法,而且將原始異常設置爲新異常的「緣由」:
try{ access database } catch(SQLException e){ Throwable se=new ServletException("database error"); se.initCause(e); throw se; }
當捕獲到異常,就可使用下面這條語句從新獲得原始異常:
Throwable e=se.getCause();
強烈建議使用這種包裝技術。這樣可讓用戶拋出子系統中的高級異常,而不會丟失原始異常的細節。
有時你可能只想記錄一個異常,再將它從新拋出,而不作任何改變:
try{ access the database } catch(Exception e){ logger.log(level,message,e); throw e }
在Java SE 7以前,這種方法存在一個問題。假設這個代碼在如下方法中:
public void updateRecord() throws SQLException
Java編譯器查看catch塊中的throw語句,而後查看e的類型,會指出這個方法能夠拋出任何Exception而不僅是SQLException。如今這個問題已經有所改進。編譯器會跟蹤到e來自try塊。假設這個try塊中僅有的已檢查異常是SQLException實例,另外,假設e在catch塊未改變,將外圍方法聲明爲throws SQLException就是合法的。
注:若是在一個方法中發生了一個受查異常,而不容許拋出它,那麼包裝技術就十分有用。咱們能夠捕獲這個受查異常,並將它包裝成一個運行時異常。
finally子句
當代碼拋出一個異常時,就會終止方法中剩餘代碼的處理,並退出這個方法的執行。若是方法得到了一些本地資源,而且只有這個方法本身知道,又若是這些資源在退出方法以前必須被回收,那麼就會產生資源回收問題。一種解決方案是捕獲並從新拋出全部的異常。可是,這種解決方案比較乏味,這是由於須要在兩個地方清除所分配的資源。一個在正常的代碼中;另外一個在異常代碼中。
Java有一種更好的解決方案,這就是finally子句。下面將介紹Java中如何恰當地關閉一個文件。若是使用Java編寫數據庫程序,就須要使用一樣的技術關閉與數據庫的鏈接。當發生異常時,恰當地關閉全部數據庫的鏈接是很是重要的。
無論是否異常被捕獲,finally子句中的代碼都被執行。在下面的示例中,程序將在全部狀況下關閉文件。
InputStream in =new FileInputStream(...); try{ //1 code that might throw exceptions //2 } catch(IOException){ //3 show error message //4 } finally{ //5 in.close(); } //6
在上面這段代碼中,有下列3種狀況會執行finally子句:
1)代碼沒有拋出異常。在這種狀況下,程序首先執行try語句塊中的所有代碼,而後執行finally子句中的代碼。隨後,繼續執行try語句塊以後的第一條語句。也就是說,執行標註的1,2,5,6處。
2)拋出一個在catch子句中捕獲的異常。在上面的示例就是IOException異常。在這種狀況下,程序將執行try語句塊的全部代碼,知道發生異常爲止。此時,將跳過try語句塊中的剩餘代碼,轉去執行與該異常匹配的catch子句中的代碼,最後執行finally子句中的代碼。
若是catch子句沒有拋出異常,程序將執行try語句塊以後的第一條語句。在這裏,執行1,3,4,5,6處的語句。
若是catch子句拋出了一個異常,但這個異常將被拋回這個方法的調用者。在這裏,執行標註的1,3,5。
3)代碼拋出了一個異常,但這個異常不是由catch子句捕獲的。在這種狀況下,程序將執行try語句塊中的全部語句,直到有異常被拋出爲止。此時將跳過try語句塊中的剩餘代碼,而後執行finally子句中的語句,並將異常拋給這個方法的調用者。這裏執行標註的1,5處的語句。
try語句能夠只有finally子句,而沒有catch子句。例如,下面這條try語句:
InputStream in=...; try{ code that might throw exceptions } finally{ in.close(); }
不管在try語句塊中是否遇到異常,finally子句中的in.close()語句都會被執行。固然,若是真的遇到一個異常,這個異常將會被從新拋出,而且必須由另外一個catch子句捕獲。
事實上,咱們認爲在須要關閉資源時,用這種方式使用finally子句是一種不錯的選擇。
注:1.強烈建議解耦合try/catch和try/finally語句塊。這樣能夠提升代碼的清晰度。例如:
InputStream in =...; try{ try{ code that might throw exceptions } finally{ in.close(); } } catch(IOException e){ show error message }
內層的try語句塊只有一個職責,就是確保關閉輸入流。外層的try語句塊也只有一個職責,就是確保報告出現的錯誤。這種設計方式不只清楚,並且還具備一個功能,就是將會報告finally子句中出現的錯誤。
2.當finally子句包含return語句時,將會出現一種意想不到的結果。假設利用return語句從try語句塊中退出。在方法返回前,finally子句的內容將被執行。若是finally子句中也有一個return語句,這個返回值將會覆蓋原始的返回值。
public static int f(int n){ try{ int r=n*n; return r; } finally{ if(n==2) return 0; } }
若是調用f(2),那麼try語句塊的計算結果爲r=4,並執行return語句。然而,在方法真正返回以前,還要執行finally子句。finally子句將使得方法返回0,這個返回值覆蓋了原始的返回值4。
帶資源的try語句
對於如下代碼模式:
open a resource try{ work with the resource } finally{ close the resource }
假設資源屬於一個實現了AutoCloseable接口的類,Java SE 7爲這種代碼模式提供了一個頗有用的快捷方式。AutoCloseable接口有一個方法:
void close() throws Exception
注:另外,還有一個Closeable接口。這是AutoCloseable的子接口,也包含一個close方法,不過,這個方法聲明爲拋出一個IOException。
帶資源的try語句的最簡形式爲:
try(Resource res=...){ work with res }
try塊退出時,會自動地調用res.close()。下面給出一個典型的例子,這裏要讀取一個文件中的全部單詞:
try(Scanner in=new Scanner(new FileInputStream("/usr/share/dict/words")),"UTF-8"){ while(in.hasNext()){ System.out.println(in.next()); } }
這個塊正常退出時,或者存在一個異常時,都會調用in,close()方法,就好像使用了finally塊同樣。
還能夠指定多個資源。例如
try(Scanner in=new Scanner(new FileInputStream("/usr/share/dict/words")),"UTF-8"); PrintWriter out=new PrintWriter("out.txt")){ while(in.hasNext()){ out.println(in.next()).toUpperCase()); } }
不論這個塊如何退出,in和out都會關閉。若是你用常規方式手動編程,就須要兩個嵌套的try/finally語句。
注:帶資源的try語句自身也能夠有catch子句和一個finally子句。這些子句會在關閉資源以後執行。不過在實際中,一個try語句中加入這麼多內容可能不是一個好注意。
Throwable API 2:
Throwable(Throwable cause)
Throwable(String message,Throwable cause)
用給定的「緣由」構造一個Throwable對象。
Throwable initCause(Throwable cause)
將這個對象設置爲「緣由」。若是這個對象已經被設置爲「緣由」,則拋出一個異常。返回this引用。
Throwable getCause()
得到設置爲這個對象的「緣由」的異常對象。若是沒有設置「緣由」,則返回null。
3.使用斷言
1)斷言的概念
假設確信某個屬性符合要求,而且代碼的執行依賴於這個屬性。例如,須要計算
double y=Math.sqrt(x);
咱們確信,這裏的x是一個非負數值。咱們但願進行檢查,以免「不是一個數」的數值參與計算操做。
斷言機制容許在測試期間向代碼中插入一些檢查語句。當代碼發佈時,這些插入的檢測語句將會被自動地移走。
Java引入了關鍵字assert。這個關鍵字有兩種形式:
assert 條件 ;
和
assert 條件 : 表達式 ;
這兩種形式都會對條件進行檢測,若是結果爲false,則拋出一個AssertionError異常。在第二種形式中,表達式將被傳入AssertionError的構造器,並轉換爲一個消息字符串。
注:「表達式」部分的惟一目的是產生一個消息字符串。AssertionError對象並不存儲表達式的值。所以,不可能在之後獲得它。正如JDK文檔鎖描述的那樣:若是使用表達式的值,就會鼓勵程序員試圖從斷言中恢復程序的運行,這不符合斷言機制的初衷。
要想斷言x是一個非負數值,只須要簡單地使用下面這條語句
assert x>= 0;
或者將x的實際值傳遞給AssertionError對象,從而能夠在後面顯式出來。
assert x>=0 : x;
2)棄用和禁用斷言
在默認狀況下,斷言被禁用。能夠在運行程序時用-enableassertions或-ea選項啓用:
java -ea MyApp
須要注意的是,在啓用或禁用斷言時沒必要從新編譯程序。啓用或禁用斷言是類加載器(class loader)的功能。當斷言被禁用時,類加載器將跳過斷言代碼,所以,不會下降程序運行的速度。
也能夠在某個類或某個包中使用斷言,例如:
java -ea:MyClass -ea:com.mycompany.mylib... MyApp
這條命令將開啓MyClass類以及在com.mycompany.mylib包和它的子包中的全部類的斷言。選項-ea將開啓默認包中的全部類的斷言。
也能夠用-disableassertions或-da禁用某個特定類和包的斷言:
java -ea:... -da:MyClass MyApp
有些類不是由類加載器加載,而是直接由虛擬機加載。可使用這些開關有選擇地啓用或禁用那些類中的斷言。
然而,啓用和禁用斷言的-ea和-da開關不能應用到那些沒有類加載器的「系統類」上。對於這些系統類來講,須要使用-enablesystemassertions/-esa開關啓用斷言。
3)使用斷言完成參數檢查
在Java中,給出了3種處理系統錯誤的機制:
a.拋出一個異常。
b.日誌。
c.使用斷言。
何時應該選擇使用斷言呢?請記住下面幾點:
a.斷言失敗是致命的,不可恢復的錯誤。
b.斷言檢查只用於開發和測階段。
所以,斷言只應該用於在測試階段肯定程序內部的錯誤位置。
java.lang.ClassLoader:
void setDefaultAssertionStatus(boolean b)
對於經過類加載器的全部類來講,若是沒有顯式地說明類或包的斷言狀態,就啓用或禁用斷言。
void setClassAssertionStatus(String className,boolean b)
對於給定的類和它的內部類,啓用或禁用斷言。
void setPackageAssertionStatus(String packageName,boolean b)
對於給定包和其餘子包中的全部類,啓用或禁用斷言。
void clearAssertionStatus()
移去全部類和包的顯式斷言狀態設置,並禁用全部經過這個類加載器加載的類的斷言。
4.記錄日誌
記錄日誌API的優勢:
a.能夠很容易地取消所有日誌記錄,或者僅僅取消某個級別的日誌,並且打開和關閉這個操做也很容易。
b.能夠很簡單地禁止日誌記錄的輸出,所以,將這些日誌代碼留在程序中的開銷很小。
c.日誌記錄能夠被定向到不一樣的處理器,用於控制檯中顯示,用於存儲在文件中等。
d.日誌記錄器和處理器均可以對記錄進行過濾。過濾器能夠根據過濾實現制定的標準丟棄那些無用的記錄項。
e.日誌記錄能夠採用不一樣的方式格式化,例如,純文本或XML。
f.應用程序可使用多個日誌記錄器,它們使用相似包名的這種具備層次結構的名字,例如,com.mycompany.myapp。
g.在默認狀況下,日誌系統的配置由配置文件控制。若是須要的話,應用程序能夠替換這個配置。
1)基本日誌
要生成簡單的日誌記錄,可使用全局日誌記錄器(global logger)並調用其info方法:
Logger.getGlobal().info("File->Open menu item selected");
在默認狀況下,這條記錄將會顯示如下內容:
May 10,2013 10:12:13 PM LoggingImageViewer fileOpen INFO: File->Open menu item selected
可是,若是在適當的地方(如main開始)調用
Logger.getGlobal().setLevel(Level.OFF);
將會取消全部的日誌。
2)高級日誌
從前面已經看到「虛擬日誌」,下面繼續看一下企業級(industrial-strength)日誌。在一個專業的應用程序中,不要將全部的日誌都記錄到一個全局日誌記錄器中,而是能夠自定義日誌記錄器。
能夠調用getLogger方法建立或獲取記錄器:
private static final Logger myLogger=Logger.getLogger("com.mycompany.myapp");
注:未被任何變量引用的日誌記錄器可能會被垃圾回收。爲了防止這種狀況發生,要像上面的例子中同樣,用一個靜態變量存儲日誌記錄器的一個引用。
與包名相似,日誌記錄器名也具備層次結構。事實上,與包名相比,日誌記錄器的層次性更強。
對於包來講,一個包的名字與其父包的名字之間沒有語義關係,可是日誌記錄器的父與子之間將共享某些屬性。例如,若是對com.company日誌記錄器設置了日誌級別,它的子記錄器也會繼承這個級別。
一般,有如下7個日誌記錄器級別:
a.SEVERE
b.WARNING
c.INFO
d.CONFIG
e.FINE
f.FINER
g.FINEST
在默認狀況下,只記錄前三個級別。也能夠設置其餘的級別。例如,
logger.setLevel(Level.FINE);
如今,FINE和更高級別的記錄均可以記錄下來。
另外,還可使用Level.ALL開啓全部級別的記錄,或者使用Level.OFF關閉全部級別的記錄。
對於全部的級別有下面幾種記錄方法:
logger.warning(message); logger.fine(message);
同時,還可使用log方法指定級別,例如,
logger.log(Level.FINE,message);