自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

Java實(shí)用技巧:當(dāng)不能拋出checked異常時(shí)

開(kāi)發(fā) 后端
文章假設(shè)了一種環(huán)境,同時(shí)實(shí)例分析當(dāng)不能處理、也不能拋出checked異常時(shí),我們有哪些選擇,每種選擇對(duì)我們的程序有著怎樣的影響。

checked異常的一個(gè)問(wèn)題是,有時(shí)候不允許拋出這樣的異常。特別是,如果要覆蓋超類中聲明的方法,或者實(shí)現(xiàn)接口中聲明的方法,而那個(gè)方法沒(méi)有聲明任何checked異常,那么新的實(shí)現(xiàn)也不能聲明checked異常。

因此必須預(yù)先處理異常,另外,可以將異常轉(zhuǎn)換為運(yùn)行時(shí)異常,或者繞過(guò)它而不處理它。但是,應(yīng)該這樣做嗎,這其中是否隱藏著錯(cuò)誤?

相關(guān)文章推薦:Java三種常見(jiàn)異常及解決 如何更合理的利用Java中的異常拋出

問(wèn)題

只要看一個(gè)例子,問(wèn)題就清楚了。假設(shè)有一個(gè)File對(duì)象的List,需要按它們的標(biāo)準(zhǔn)路徑以字典順序排序。所謂標(biāo)準(zhǔn)路徑,是指在解析別名、符號(hào)鏈接和/../及/./之后得到的完整絕對(duì)路徑。本地方法使用一個(gè)比較器,如清單1所示:

  1. 清單1.按標(biāo)準(zhǔn)路徑比較兩個(gè)文件  
  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.  

不幸的是,該代碼不能通過(guò)編譯。問(wèn)題在于,getCanonicalPath()方法拋出一個(gè)IOException,因?yàn)樗枰L問(wèn)文件系統(tǒng)。通常,當(dāng)使用checked異常時(shí),可以使用以下兩種方法之一:

1.將出錯(cuò)的代碼包裝在一個(gè)try塊中,并捕捉拋出的異常。
2.聲明包裝方法(本例為compare())也拋出IOException。

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

  1. 清單2.拋出異常時(shí)返回一個(gè)默認(rèn)值  
  2. publicintcompare(Filef1,Filef2){  
  3. try{  
  4. returnf1.getCanonicalPath().compareTo(f2.getCanonicalPath());  
  5. }  
  6. catch(IOExceptionex){  
  7. return-1;  
  8. }  

然而,這違反了compare()方法的約定,因?yàn)樗皇且粋€(gè)穩(wěn)定的結(jié)果。對(duì)于相同的對(duì)象,前后兩次調(diào)用可能產(chǎn)生不同的結(jié)果。如果使用這個(gè)比較器來(lái)排序,那么意味著最終列表沒(méi)有被正確排序。所以現(xiàn)在試試第2個(gè)選項(xiàng)—聲明compare()拋出IOException:

  1. publicintcompare(Filef1,Filef2)throwsIOException{  
  2. returnf1.getCanonicalPath().compareTo(f2.getCanonicalPath());  

這也不能通過(guò)編譯。因?yàn)閏hecked異常是方法簽名的一部分,在覆蓋方法時(shí),不能增加checked異常,就像不能改變r(jià)eturn類型一樣。那么最后還剩下一個(gè)折中選項(xiàng):在compare()中捕捉異常,將它轉(zhuǎn)換成運(yùn)行時(shí)異常,然后拋出運(yùn)行時(shí)異常,如清單3所示:

  1. 清單3.將checked異常轉(zhuǎn)換成運(yùn)行時(shí)異常  
  2. publicintcompare(Filef1,Filef2){  
  3. try{  
  4. returnf1.getCanonicalPath().compareTo(f2.getCanonicalPath());  
  5. }  
  6. catch(IOExceptionex){  
  7. thrownewRuntimeException(ex);  
  8. }  

不幸的是,雖然這樣可以通過(guò)編譯,但是這種方法也不管用,其原因較為微妙。Comparator接口定義一個(gè)合約(請(qǐng)參閱參考資料)。這個(gè)合約不允許該方法拋出運(yùn)行時(shí)異常(防止因違反泛型類型安全而成為調(diào)用代碼中的bug)。使用這個(gè)比較器的方法合理地依靠它來(lái)比較兩個(gè)文件,而不拋出任何異常。它們沒(méi)有準(zhǔn)備好處理compare()中意外出現(xiàn)的異常。

正是由于這個(gè)微妙的原因,讓運(yùn)行時(shí)異常成為代碼要處理的外部狀況是一個(gè)壞主意。這樣只是逃避問(wèn)題,并沒(méi)有真正處理問(wèn)題。不處理異常所帶來(lái)的不良后果仍然存在,包括毀壞數(shù)據(jù)和得到不正確的結(jié)果。

這樣便陷入了困境。既不能在compare()內(nèi)真正有效地處理異常,又不能在compare()之外處理異常。還剩下什么地方可以處理異常—System.exit()?惟一正確的辦法是完全避免這種困境。幸運(yùn)的是,至少有兩種方法可以做到這一點(diǎn)。

#p#

將問(wèn)題一分為二

第一種辦法是將問(wèn)題一分為二。比較本身不會(huì)導(dǎo)致異常。比較的只是字符串而已。通過(guò)標(biāo)準(zhǔn)路徑將文件轉(zhuǎn)換成字符串才會(huì)導(dǎo)致異常。如果將可能拋出異常的操作與不會(huì)拋出異常的操作分開(kāi),那么問(wèn)題就更容易處理了。也就是說(shuō),首先將所有文件對(duì)象轉(zhuǎn)換為字符串,然后通過(guò)字符串比較器(甚至可以通過(guò)java.lang.String的自然排序)對(duì)字符串排序,最后使用排序后的字符串列表對(duì)原始的文件列表排序。這種方法不太直接,但是優(yōu)點(diǎn)是在列表被改變之前就拋出IOException。如果出現(xiàn)異常,它只會(huì)出現(xiàn)在預(yù)先設(shè)計(jì)好的地方,不會(huì)造成損害,調(diào)用代碼可以指定如何處理異常。清單4對(duì)此作了演示:

  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.  

清單4并沒(méi)有消除出現(xiàn)I/O錯(cuò)誤的可能性。這一點(diǎn)無(wú)法做到,因?yàn)檫@里的代碼無(wú)力提供這樣的功能。但是,可以將這個(gè)問(wèn)題交給更合適的地方來(lái)處理。

避免問(wèn)題

前面提到的方法有點(diǎn)復(fù)雜,所以我建議另一種方法:不使用內(nèi)置的compare()函數(shù)或Collections.sort()。使用這樣的函數(shù)也許比較方便,但是不適合當(dāng)前情況。Comparable和Comparator是為確定的、可預(yù)測(cè)的比較操作而設(shè)計(jì)的。一旦I/O不再符合這種情況,很可能常用的算法和接口變得不適用。即使勉強(qiáng)可以使用,其效率也極其低下。

例如,假設(shè)不是按標(biāo)準(zhǔn)路徑來(lái)比較文件,而是按內(nèi)容來(lái)比較文件。對(duì)于所比較的兩個(gè)文件,每個(gè)比較操作都需要讀文件的內(nèi)容—甚至可能是完整的內(nèi)容。這樣一來(lái),高效的算法會(huì)想要盡量減少讀的次數(shù),并且可能會(huì)想緩存每次讀的結(jié)果—或者,如果文件較大,則可能緩存每個(gè)文件的hashcode—而不是每次比較時(shí)重新讀每個(gè)文件。同樣,您會(huì)想到首先填充一個(gè)比較鍵列表,然后進(jìn)行排序,而不是進(jìn)行內(nèi)聯(lián)排序。可以想象定義一個(gè)單獨(dú)的、并行的IOComparator接口,該接口拋出必要的異常,如清單5所示:

  1. 清單5.獨(dú)立的IOComparator接口  
  2. importjava.io.IOException;  
  3. publicinterfaceIOComparator<T>{  
  4. intcompare(To1,To2)throwsIOException;  
  5.  

然后基于這個(gè)類定義一個(gè)單獨(dú)的、相近實(shí)用程序樹(shù),由它對(duì)集合的臨時(shí)副本進(jìn)行必要的操作,從而允許拋出異常,同時(shí)又不會(huì)使數(shù)據(jù)結(jié)構(gòu)處于可能受損害的、中間的狀態(tài)。例如,清單6提供了一個(gè)基本的冒泡排序:

  1. 清單6.用冒泡算法對(duì)文件排序  
  2. importjava.io.IOException;  
  3. importjava.util.ArrayList;  
  4. importjava.util.List;  
  5.  
  6. publicclassIOSorter{  
  7.  
  8. publicstatic<T>voidsort(List<T>list,IOComparator<?superT>comparator)  
  9. throwsIOException{  
  10. List<T>temp=newArrayList<T>(list.size());  
  11. temp.addAll(list);  
  12.  
  13. bubblesort(temp,comparator);  
  14.  
  15. //copybacktooriginallistnowthatnoexceptionshavebeenthrown  
  16. list.clear();  
  17. list.addAll(temp);  
  18. }  
  19.  
  20. //ofcourseyoucanreplacethiswithabetteralgorithmsuchasquicksort  
  21. privatestatic<T>voidbubblesort(List<T>list,IOComparator<?superT>comparator)  
  22. throwsIOException{  
  23. for(inti=1;i<list.size();i++){  
  24. for(intj=0;j<list.size()-i;j++){  
  25. if(comparator.compare(list.get(j),list.get(j+1))>0){  
  26. swap(list,j);  
  27. }  
  28. }  
  29. }  
  30. }  
  31.  
  32. privatestatic<T>voidswap(List<T>list,intj){  
  33. Ttemp=list.get(j);  
  34. list.set(j,list.get(j+1));  
  35. list.set(j+1,temp);  
  36. }  
  37.  

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

#p#

最終,您實(shí)際上承認(rèn)并著手處理可能出現(xiàn)的I/O錯(cuò)誤,而不是逃避它,您甚至可以做更高級(jí)的錯(cuò)誤修正。例如,IOComparator也許不會(huì)被一次I/O錯(cuò)誤難倒—因?yàn)楹芏郔/O問(wèn)題是暫時(shí)的—可以重試幾次,如清單7所示:

  1. 清單7.如果一開(kāi)始不成功,再試幾次(但是別試太多次)  
  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.  

這種技巧不能解決常規(guī)的Comparator的問(wèn)題,因?yàn)楸仨氈卦嚐o(wú)數(shù)次才能避免拋出異常,而且很多I/O問(wèn)題并不是暫時(shí)性的。

checked異常是壞主意嗎?

如果java.io.IOException是運(yùn)行時(shí)異常,而不是checked異常,問(wèn)題是不是有所改觀?答案是否定的。如果IOException擴(kuò)展RuntimeException而不是java.lang.Exception,那么更容易編寫(xiě)出有bug的、不正確的代碼,這種代碼忽略了真正可能發(fā)生的I/O錯(cuò)誤,而在運(yùn)行時(shí)出人意料地失敗。

然而,編寫(xiě)正確的、有準(zhǔn)備并且能夠處理I/O錯(cuò)誤的代碼并不會(huì)更容易。是的,相對(duì)于不會(huì)出現(xiàn)意外I/O錯(cuò)誤,不需要為此做準(zhǔn)備的情況,這種方法更加復(fù)雜。但是,從Java語(yǔ)言中消除checked異常無(wú)助于我們實(shí)現(xiàn)那樣的理想情況。I/O錯(cuò)誤和其他環(huán)境問(wèn)題是常態(tài),積極準(zhǔn)備比視而不見(jiàn)要好得多。

總之,checked異常作為方法簽名的一部分并非沒(méi)有道理。當(dāng)您發(fā)現(xiàn)自己想要從一個(gè)方法拋出一個(gè)checked異常,而這又是不允許的—因而抑制本不該抑制的異常—那么回過(guò)頭來(lái),重新組織一下,考慮為什么一開(kāi)始要覆蓋那個(gè)方法。很可能,您本應(yīng)該采取完全不同的方式。

【編輯推薦】

  1. Java編程中異常問(wèn)題處理方式的區(qū)別和分析
  2. 淺析Java語(yǔ)言中兩種異常的差別
  3. Java開(kāi)發(fā)中常見(jiàn)的異常問(wèn)題
  4. 如何更合理的利用Java中的異常拋出
  5. Java三種常見(jiàn)異常及解決
責(zé)任編輯:王曉東 來(lái)源: IMB
相關(guān)推薦

2009-09-04 10:27:28

Linux實(shí)用技巧linux操作系統(tǒng)linux

2022-03-23 09:18:10

Git技巧Linux

2009-12-21 15:50:39

2009-01-03 09:34:30

ASP.NET.NET性能優(yōu)化

2011-04-08 15:40:01

Oracle認(rèn)證

2022-10-11 08:00:47

多線程開(kāi)發(fā)技巧

2022-11-03 10:28:59

PandasSAC機(jī)制

2024-05-17 08:52:43

SQL實(shí)用技巧行列轉(zhuǎn)換

2010-02-01 15:01:34

C++拋出異常

2010-09-14 10:41:24

DIV+CSS排版

2009-12-09 11:21:30

Linux實(shí)用技巧

2019-11-25 10:12:59

Python技巧工具

2010-10-08 15:44:17

vim

2019-12-22 23:10:19

LinuxSSH加密

2009-12-23 17:32:35

Linux構(gòu)建軟路由

2019-10-10 16:31:51

PyCharmPythonWindows

2010-11-02 15:36:30

jQuery

2019-10-12 15:42:36

CSS代碼前端

2022-05-30 09:01:13

CSS技巧前端

2022-09-15 07:05:09

Windows電腦技巧
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)