我要投稿

Java实用技巧:当不能抛出checked异常时

时间:2010-05-06 07:40 作者:ceshi123 阅读:2963

扫描二维码关注公众号,可以查询淘宝、京东商品优惠券
  checked异常的一个问题是,有时候不允许抛出这样的异常。特别是,如果要覆盖超类中声明的方法,或者实现接口中声明的方法,而那个方法没有声明任何checked异常,那么新的实现也不能声明checked异常。

  因此必须预先处理异常,另外,可以将异常转换为运行时异常,或者绕过它而不处理它。但是,应该这样做吗,这其中是否隐藏着错误?

  问题

  只要看一个例子,问题就清楚了。假设有一个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()?惟一正确的办法是完全避免这种困境。幸运的是,至少有两种方法可以做到这一点。
关键词: 系统

最新评论

ceshi1232010-05-06 07:40
  将问题一分为二

  第一种办法是将问题一分为二。比较本身不会导致异常。比较的只是字符串而已。通过标准路径将文件转换成字符串才会导致异常。如果将可能抛出异常的操作与不会抛出异常的操作分开,那么问题就更容易处理了。也就是说,首先将所有文件对象转换为字符串,然后通过字符串比较器(甚至可以通过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.}
ceshi1232010-05-06 07:41
  然后基于这个类定义一个单独的、相近实用程序树,由它对集合的临时副本进行必要的操作,从而允许抛出异常,同时又不会使数据结构处于可能受损害的、中间的状态。例如,清单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异常,而这又是不允许的-因而抑制本不该抑制的异常-那么回过头来,重新组织一下,考虑为什么一开始要覆盖那个方法。很可能,您本应该采取完全不同的方式。
dldl55052010-08-23 15:02
我回来了,新的一个星期又开始了,两天不在线,发现好多人等级比我高了,可恶的恶猫,他竟然已经20+了,我还在15级徘徊,╮(╯▽╰)╭,那些挂机的卑鄙小人~~~~
废话不多说,今天带来哆啦大冒险新增qq表情20枚,话说这只鹌鹑长的真不想鹌鹑(这话听着怎么这么废啊+_+),我真的一开始以为他是只乌鸦hoho~~不过他真的是太可爱了,跟着姐姐走吧(你这不良姐姐又要拐带小动物了)~~听说他有个帅气的名字,叫做亚力士当德,花痴ing~~~
无奈ing~~~(人生是杯具~~~)

开心ing~~~(小身子摆的真可爱)

叹气ing(鹌鹑也是有很多烦恼的)

伤心ing(怎么了~~谁欺负你了~~姐姐替你撑腰)

流汗ing(新陈代谢真不错)

shoking(受了点刺激~~没关系在挫折中成长才是好鹌鹑)

惊吓ing(看到什么?凤姐???)

努嘴ing(我很不满~~~)

发怒ing(麻麻~~小孩子不要大动肝火~小心伤了身子)

可怜ing(哥哥姐姐们 收留了我吧 众:快上!!!)

发呆ing(什么 1+1等于几??)
[img]http://image163.poco.cn/mypoco/myphoto/20100823/13/55501788201008231327021454993592325_010.gif[/img]
招呼ing(大家好 叫我亚力士当德啊)
[img]http://image163.poco.cn/mypoco/myphoto/20100823/13/55501788201008231327021454993592325_011.gif[/img]
晕眩ing(眼花缭乱)
[img]http://image163.poco.cn/mypoco/myphoto/20100823/13/55501788201008231327021454993592325_012.gif[/img]
摇头ing(nonono!!各位)
[img]http://image163.poco.cn/mypoco/myphoto/20100823/13/55501788201008231327021454993592325_013.gif[/img]
闪人ing(本期表情我最喜欢的一个 naruto最高)
[img]http://image163.poco.cn/mypoco/myphoto/20100823/13/55501788201008231327021454993592325_014.gif[/img]
花痴ing(看到漂亮小姑娘了 hoho)
[img]http://image163.poco.cn/mypoco/myphoto/20100823/13/55501788201008231327021454993592325_015.gif[/img]
点头ing(yesyesyes!!!)
[img]http://image163.poco.cn/mypoco/myphoto/20100823/13/55501788201008231327021454993592325_016.gif[/img]
奸笑ing(我没有想w琐的事啦~~~众:此地无银三百两)
[img]http://image163.poco.cn/mypoco/myphoto/20100823/13/55501788201008231327021454993592325_017.gif[/img]
颤抖ing(这是什么???!!!)
[img]http://image163.poco.cn/mypoco/myphoto/20100823/13/55501788201008231327021454993592325_018.gif[/img]
狂吐ing(是什么)
[img]http://image163.poco.cn/mypoco/myphoto/20100823/13/55501788201008231327021454993592325_019.gif[/img]
这一期的表情就这些了,请大家多多支持哆啦大冒险啊~~~姐姐我要去打副本了~~~恶猫!!神气什么!!你等着我这就追上你 哼哼!!!
我要投稿 我要评论
插入url链接 添加表情
限 100000 字节
响应相关主管单位规定,关闭回帖发帖功能
[VB毕业设计] [ASP毕业设计] [JAVA毕业设计] [JSP毕业设计] [PHP毕业设计] [asp.net毕业设计] [.net毕业设计] [网络毕业设计] [Delphi毕业设计] [VC毕业设计] [VF毕业设计] [机械毕业设计] [工艺毕业设计] [模具毕业设计] [其他毕业设计]
站长联系 cccbbsnet#163.com 本站系公益性质网站,站内广告维系本站运作。欢迎赞助本站。
大学生计算机相关方向|软件编程|网络工程|web开发|数据库技术|学习资料等
本社区仅供学习计算机相关技术所使用,访客发言不得违反国家法律法规
Time now is:07-17 04:37, Gzip enabled 蜀ICP备07004838号Powered by cccbbs.net v7.5 SP3 Code © 2003-06 cccbbs
计算机毕业设计