jsp 不能拋出checked異常時 處理方法

來源:互聯網
上載者:User

因此必須預先處理異常,另外,可以將異常轉換為運行時異常,或者繞過它而不處理它。但是,應該這樣做嗎,這其中是否隱藏著錯誤?

  問題

  只要看一個例子,問題就清楚了。假設有一個File對象的List,需要按它們的標準路徑以字典順序排序。所謂標準路徑,是指在解析別名、符號連結和/../及/./之後得到的完整絕對路徑。本地方法使用一個比較子,如清單1所示:

  1.清單1.按標準路徑比較兩個檔案

  2.importjava.io.File;

  3.importjava.io.IOException;

  4.importjava.util.ArrayList;

  5.importjava.util.Collections;

  6.importjava.util.Comparator;

  7.

  8.publicclassFileComparatorimplementsComparator<File>{

  9.

  10.publicintcompare(Filef1,Filef2){

  11.returnf1.getCanonicalPath().compareTo(f2.getCanonicalPath());

  12.}

  13.

  14.publicstaticvoidmain(String[]args){

  15.ArrayList<File>files=newArrayList<File>();

  16.for(Stringarg:args){

  17.files.add(newFile(arg));

  18.}

  19.Collections.sort(files,newFileComparator());

  20.for(Filef:files){

  21.System.out.println(f);

  22.}

  23.}

  24.

  25.}

  不幸的是,該代碼不能通過編譯。問題在於,getCanonicalPath()方法拋出一個IOException,因為它需要訪問檔案系統。通常,當使用checked異常時,可以使用以下兩種方法之一:

  1.將出錯的程式碼封裝裝在一個try塊中,並捕捉拋出的異常。

  2.聲明封裝方法(本例為compare())也拋出IOException。

  通常,至於選擇何種方法,取決於是否能在拋出異常時合理地處理異常。如果能,那麼使用try-catch塊。如果不能,那麼聲明封裝方法本身拋出異常。不幸的是,這兩種技巧對於本例都不管用。在compare()方法中無法合理地處理IOException。從技術上講,似乎可以做到-即返回0、1或-1,如清單2所示:

  26.清單2.拋出異常時返回一個預設值

  27.publicintcompare(Filef1,Filef2){

  28.try{

  29.returnf1.getCanonicalPath().compareTo(f2.getCanonicalPath());

  30.}

  31.catch(IOExceptionex){

  32.return-1;

  33.}

  34.}

  然而,這違反了compare()方法的約定,因為它不是一個穩定的結果。對於相同的對象,前後兩次調用可能產生不同的結果。如果使用這個比較子來排序,那麼意味著最終列表沒有被正確排序。所以現在試試第2個選項-聲明compare()拋出IOException:

  35.publicintcompare(Filef1,Filef2)throwsIOException{

  36.returnf1.getCanonicalPath().compareTo(f2.getCanonicalPath());

  37.}

  這也不能通過編譯。因為checked異常是方法簽名的一部分,在覆蓋方法時,不能增加checked異常,就像不能改變return類型一樣。那麼最後還剩下一個折中選項:在compare()中捕捉異常,將它轉換成運行時異常,然後拋出運行時異常,如清單3所示:

  38.清單3.將checked異常轉換成運行時異常

  39.publicintcompare(Filef1,Filef2){

  40.try{

  41.returnf1.getCanonicalPath().compareTo(f2.getCanonicalPath());

  42.}

  43.catch(IOExceptionex){

  44.thrownewRuntimeException(ex);

 

  45.}

  46.}

  不幸的是,雖然這樣可以通過編譯,但是這種方法也不管用,其原因較為微妙。Comparator介面定義一個合約(請參閱參考資料)。這個合約不允許該方法拋出運行時異常(防止因違反泛型型別安全而成為調用代碼中的bug)。使用這個比較子的方法合理地依靠它來比較兩個檔案,而不拋出任何異常。它們沒有準備好處理compare()中意外出現的異常。

  正是由於這個微妙的原因,讓運行時異常成為代碼要處理的外部狀況是一個壞主意。這樣只是逃避問題,並沒有真正處理問題。不處理異常所帶來的不良後果仍然存在,包括毀壞資料和得到不正確的結果。

  這樣便陷入了困境。既不能在compare()內真正有效地處理異常,又不能在compare()之外處理異常。還剩下什麼地方可以處理異常-System.exit()?惟一正確的辦法是完全避免這種困境。幸運的是,至少有兩種方法可以做到這一點。
  
    將問題一分為二

  第一種辦法是將問題一分為二。比較本身不會導致異常。比較的只是字串而已。通過標準路徑將檔案轉換成字串才會導致異常。如果將可能拋出異常的操作與不會拋出異常的操作分開,那麼問題就更容易處理了。也就是說,首先將所有檔案對象轉換為字串,然後通過字串比較器(甚至可以通過java.lang.String的自然排序)對字串排序,最後使用排序後的字串列表對原始的檔案清單排序。這種方法不太直接,但是優點是在列表被改變之前就拋出IOException。如果出現異常,它只會出現在預先設計好的地方,不會造成損害,調用代碼可以指定如何處理異常。清單4對此作了示範:

  1.清單4.先讀取,然後排序

  2.importjava.io.File;

  3.importjava.io.IOException;

  4.importjava.util.ArrayList;

  5.importjava.util.Collections;

  6.importjava.util.HashMap;

  7.

  8.publicclassFileComparator{

  9.

  10.privatestaticArrayList<String>getCanonicalPaths(ArrayList<File>files)

  11.throwsIOException{

  12.ArrayList<String>paths=newArrayList<String>();

  13.for(Filefile:files)paths.add(file.getCanonicalPath());

  14.returnpaths;

  15.}

  16.

  17.publicstaticvoidmain(String[]args)throwsIOException{

  18.ArrayList<File>files=newArrayList<File>();

  19.for(Stringarg:args){

  20.files.add(newFile(arg));

  21.}

  22.

  23.ArrayList<String>paths=getCanonicalPaths(files);

  24.

  25.//tomaintaintheoriginalmapping

  26.HashMap<String,File>map=newHashMap<String,File>();

  27.inti=0;

  28.for(Stringpath:paths){

  29.map.put(path,files.get(i));

  30.i++;

  31.}

  32.

  33.Collections.sort(paths);

  34.files.clear();

  35.for(Stringpath:paths){

  36.files.add(map.get(path));

  37.}

  38.}

  39.

  40.}

  清單4並沒有消除出現I/O錯誤的可能性。這一點無法做到,因為這裡的代碼無力提供這樣的功能。但是,可以將這個問題交給更合適的地方來處理。

  避免問題

  前面提到的方法有點複雜,所以我建議另一種方法:不使用內建的compare()函數或Collections.sort()。使用這樣的函數也許比較方便,但是不適合當前情況。Comparable和Comparator是為確定的、可預測的比較操作而設計的。一旦I/O不再符合這種情況,很可能常用的演算法和介面變得不適用。即使勉強可以使用,其效率也極其例如,假設不是按標準路徑來比較檔案,而是按內容來比較檔案。對於所比較的兩個檔案,每個比較操作都需要讀檔案的內容-甚至可能是完整的內容。這樣一來,高效的演算法會想要盡量減少讀的次數,並且可能會想緩衝每次讀的結果-或者,如果檔案較大,則可能緩衝每個檔案的hashcode-而不是每次比較時重新讀每個檔案。同樣,您會想到首先填充一個比較鍵列表,然後進行排序,而不是進行內聯排序。可以想象定義一個單獨的、並行的IOComparator介面,該介面拋出必要的異常,如清單5所示:

  41.清單5.獨立的IOComparator介面

  42.importjava.io.IOException;

  43.publicinterfaceIOComparator<T>{

  44.intcompare(To1,To2)throwsIOException;

  45.

  46.}
  
   然後基於這個類定義一個單獨的、相近公用程式樹,由它對集合的臨時副本進行必要的操作,從而允許拋出異常,同時又不會使資料結構處於可能受損害的、中間的狀態。例如,清單6提供了一個基本的冒泡排序:

  47.清單6.用冒泡演算法對檔案排序

  48.importjava.io.IOException;

  49.importjava.util.ArrayList;

  50.importjava.util.List;

  51.

  52.publicclassIOSorter{

  53.

  54.publicstatic<T>voidsort(List<T>list,IOComparator<?superT>comparator)

  55.throwsIOException{

  56.List<T>temp=newArrayList<T>(list.size());

  57.temp.addAll(list);

  58.

  59.bubblesort(temp,comparator);

  60.

  61.//copybacktooriginallistnowthatnoexceptionshavebeenthrown

  62.list.clear();

  63.list.addAll(temp);

  64.}

  65.

  66.//ofcourseyoucanreplacethiswithabetteralgorithmsuchasquicksort

  67.privatestatic<T>voidbubblesort(List<T>list,IOComparator<?superT>comparator)

  68.throwsIOException{

  69.for(inti=1;i<list.size();i++){

  70.for(intj=0;j<list.size()-i;j++){

  71.if(comparator.compare(list.get(j),list.get(j+1))>0){

  72.swap(list,j);

  73.}

  74.}

  75.}

  76.}

  77.

  78.privatestatic<T>voidswap(List<T>list,intj){

  79.Ttemp=list.get(j);

  80.list.set(j,list.get(j+1));

  81.list.set(j+1,temp);

  82.}

  83.

  84.}

  這不是唯一的方法。為了清晰,清單6有意模仿已有的Collections.sort()方法;但是,也許更有效方法是返回一個新的列表,而不是直接修改舊列表,以防在修改列表時拋出異常所帶來的損害。

  最終,您實際上承認並著手處理可能出現的I/O錯誤,而不是逃避它,您甚至可以做更進階的錯誤修正。例如,IOComparator也許不會被一次I/O錯誤難倒-因為很多I/O問題是暫時的-可以重試幾次,如清單7所示:

  1.清單7.如果一開始不成功,再試幾次(但是別試太多次)

  2.importjava.io.File;

  3.importjava.io.IOException;

  4.

  5.publicclassCanonicalPathComparatorimplementsIOComparator<File>{

  6.

  7.@Override

  8.publicintcompare(Filef1,Filef2)throwsIOException{

  9.for(inti=0;i<3;i++){

  10.try{

  11.returnf1.getCanonicalPath().compareTo(f2.getCanonicalPath());

  12.}

  13.catch(IOExceptionex){

  14.continue;

  15.}

  16.}

  17.//lastchance

  18.returnf1.getCanonicalPath().compareTo(f2.getCanonicalPath());

 

  19.}

  20.

  21.}

  這種技巧不能解決常規的Comparator的問題,因為必須重試無數次才能避免拋出異常,而且很多I/O問題並不是暫時性的。

  checked異常是壞主意嗎?

  如果java.io.IOException是運行時異常,而不是checked異常,問題是不是有所改觀?答案是否定的。如果IOException擴充RuntimeException而不是java.lang.Exception,那麼更容易編寫出有bug的、不正確的代碼,這種代碼忽略了真正可能發生的I/O錯誤,而在運行時出人意料地失敗。

  然而,編寫正確的、有準備並且能夠處理I/O錯誤的代碼並不會更容易。是的,相對於不會出現意外I/O錯誤,不需要為此做準備的情況,這種方法更加複雜。但是,從Java語言中消除checked異常無助於我們實現那樣的理想情況。I/O錯誤和其他環境問題是常態,積極準備比視而不見要好得多。

  總之,checked異常作為方法簽名的一部分並非沒有道理。當您發現自己想要從一個方法拋出一個checked異常,而這又是不允許的-因而抑制本不該抑制的異常-那麼回過頭來,重新組織一下,考慮為什麼一開始要覆蓋那個方法。很可能,您本應該採取完全不同的方式。
相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.