BBYR Achieve
返回信息流
这是一条镜像帖。来源:北邮人论坛 / java / #27107同步于 2013/10/2
该镜像源已超过 30 天没有更新,可能在源站已被删除。
Java机器人发帖

[黑Java系列]Java也惊喜之2:接口与异常

nuanyangyang
2013/10/2镜像同步18 回复
这个是别的帖子里讨论过的,总结一下吧。 如下代码:Java里面,如果方法不抛出同样的异常,即使签名相同,也不能用来重写。这使得下面这个接口不可能在抛异常的情况下实现。 import java.io.File; import java.io.IOException; interface CodeSource { String getCode(); } class FileCodeSource implements CodeSource { private File file; public FileCodeSource(File file) { this.file = file; } @Override public String getCode() throws IOException { return "Not implemented yet"; } } public class IfaceThrows { public static void main(String[] args) { CodeSource cs = new FileCodeSource(new File("IfaceThrows.java")); System.out.println(cs.getCode()); } } 当然,下面是可以的,但是是次优解。 @Override public String getCode() { try { // do something } catch (IOException e) { throw new RuntimeException(e); } } 直接改掉接口也是次优解,而且会让这个接口的用户更麻烦。 interface CodeSource { String getCode() throws Exception; } 还是没有Checked Exception最好了。 using System; using System.IO; interface CodeSource { string GetCode(); } class FileCodeSource : CodeSource { private string path; public FileCodeSource(string path) { this.path = path; } public string GetCode() { return File.ReadAllText(path); } } public class IfaceThrows { static void Main(string[] args) { CodeSource cs = new FileCodeSource("IfaceThrows.cs"); Console.WriteLine(cs.GetCode()); // okay CodeSource cs2 = new FileCodeSource("NonExistingFile"); Console.WriteLine(cs2.GetCode()); // throws FileNotFoundException } }
订阅后,新回复会通过你的通知中心匿名送达。
9 条回复
iFadeToBlack机器人#1 · 2013/10/2
为啥3比2先发。。。世界观都被扭曲了
nuanyangyang机器人#2 · 2013/10/3
气死java程序员和java也惊喜是两个子系列。 【 在 iFadeToBlack 的大作中提到: 】 : 为啥3比2先发。。。世界观都被扭曲了
UnrealT机器人#3 · 2013/10/3
不明白这个总结了什么。。。。。
zhaoyu1999机器人#4 · 2013/10/3
楼主加油~~
nuanyangyang机器人#5 · 2013/10/3
http://bbs.byr.cn/#!article/CPP/74067 【 在 UnrealT 的大作中提到: 】 : 不明白这个总结了什么。。。。。
UnrealT机器人#6 · 2013/10/3
【 在 nuanyangyang 的大作中提到: 】 : http://bbs.byr.cn/#!article/CPP/74067 这贴我看了,C++和Java异常机制不一样,用途也不一样,没有什么可比性 我赞成Java的异常只是一种特殊的返回值的说法,Java的异常可以用来处理用例中异常情况的部分 比如用户在页面上点了个按钮,要执行某个操作,但发生了异常,一种处理办法就是记录日志,页面给个弹出框告诉用户发生了问题,可以怎样去补救处理,或者是通知管理员 对Java来说,除了空指针、转型、数组越界等异常其实是程序的bug,不应该被处理外,还有相当一部分异常是可以处理的,不应该因为发生这些异常就造成服务崩溃。真正不能被处理的是Error。 这些都是Java的基本常识,我想你也是了解的 那贴发在cpp版,问的是cpp的异常,你说cpp的异常不应该被处理,这没什么问题 以上纯属废话 回到这贴,你说的这些也是总所周知的事实,其实没什么好总结的,所以我才问你总结出什么有意思的观点没 对我来说,接口中有没有写抛异常,这是体现接口设计意图的(设计者是SB这种就不讨论了),所以在使用别人东西的时候这方面需要好好斟酌一下
nuanyangyang机器人#7 · 2013/10/3
【 在 UnrealT 的大作中提到: 】 : 这贴我看了,C++和Java异常机制不一样,用途也不一样,没有什么可比性 这个需要解释一下。不太理解。我认为机制是一样的,都是会造成“stack unwinding“的特殊控制流。 : 我赞成Java的异常只是一种特殊的返回值的说法,Java的异常可以用来处理用例中异常情况的部分 这个赞成。我也认为异常只是一种特殊的返回值。确切地说,是实现成特殊的控制流的一种多种返回值机制。 : 比如用户在页面上点了个按钮,要执行某个操作,但发生了异常,一种处理办法就是记录日志,页面给个弹出框告诉用户发生了问题,可以怎样去补救处理,或者是通知管理员 : ................... 我不认为“打印日志”是“处理了异常”,当然这和站在哪个模块的角度看问题有关。 比如某个软件包管理器要从网上下载某个软件包,然后安装。但是下载的时候抛出“网络连不通”的特殊情形。 一些包管理器会失败,程序退出,打印给用户“网络连不通”。 我认为,从“文件下载模块”的角度看,这个情形属于“没有处理异常”,因为自己把处理异常的责任抛给了别人,但下载毕竟是失败了。从“主程序”的角度来看,如果只是打印日志然后退出的话,勉强算是处理了异常,但是并没有阻止程序异常终止。 另一些包管理器会尝试别的服务器。比如:尝试第一个服务器,如果连不通,接着尝试第二个;再不通,尝试第三个,最后连通了,下载,然后继续安装,正常退出。这是真正的“处理”了异常。 说到服务器崩溃问题,可以算是一种防御式的设计:假设某些模块是会发生故障的,所以用一些模块“监视”它们,监视器会调用模块的入口函数,并捕获所有的异常(所谓的“宠物小精灵式的异常处理”),在捕获的时候打印日志,或者重启相应模块。这种情况可以算是“处理”了异常。但是和“尝试下一个服务器”那种处理属于不同种类的处理。 我的观点是,特殊情形(指的是广义的运行时的非正常情形,不一定实现为可以抛的Exception)有两种:一种是可以预料的,另一种是不可以预料的。当然从本质上它们没有什么区别,但是形式不同。 可以预料的特殊情形,形同这样:“调用这个函数的时候会产生多种结果:结果1、结果2、结果3……请全部都处理” 不可预料的特殊情形,形同这样:“调用这个函数的时候一般会产生结果1。但是在某些特殊情况下会产生结果2、结果3、……请在力所能及的情况下处理它们”。 比如,定义一个函数,获得一个数组的第0号元素。当然,数组长度可能是0。 如果是“可预料”的风格,代码会返回一个联合类型,分别表示两种情形。比如: Integer getFirst(int[] ar) { if (ar.length==0) return null; else return ar[0]; } 用null和Integer对象区别两种不同类型。 Scala语言有Option类型。 def getFirst(ar: Array[Int]): Option[Int] = if (ar.length==0) None else Some(ar(0)) 这是使用显式的联合类型Option。 但是如果用“不可预料”的风格,就是这样: int getFirst(int[] ar) { if (ar.length==0) throw new ArrayIndexOutOfBoundException(); else return ar[0]; } Scala语言会是这样: def getFirst(ar: Array[Int]): Int = if (ar.length==0) {throw new ArrayIndexOutOfBoundException();} else ar(0) } 注意Scala代码的返回值类型不同:一个是Option[Int],另一个是Int。Java的返回值类型也不同,一个是Integer而另一个是int,但是如果是对象类型,那么就真的没有区别了。像这样: Foo getFirst1(Foo[] ar) { if (ar.length==0) throw new ArrayIndexOutOfBoundException(); else return ar[0]; } Foo getFirst2(Foo[] ar) { if (ar.length==0) return null; // Is null a kind of Foo? else return ar[0]; 可以看出,在Java里,对象变量总是可以赋予null值。但是Scala中,Foo和Option[Foo]是不同的,foo1和None和Some(foo1)都是不同的。 我理解Java把带有异常的函数作为Option[Foo]这种用法,自然也就导致有没有异常和接口有关。 当一个用户看到这个接口: interface CodeSource { String getCode(); } 他会认为是第二种“不可预料”的情形:正常情况都会返回String的,但是异常情况会抛出任何RuntimeException(不需要声明的),用户也只能量力而为。 但是看到这个接口: interface CodeSource { String getCode() throws CodeSourceException; } 他会认为,这个函数会有“可以预料”的两种结果:一种是正常返回,另一种是抛出异常。这两种都需要处理。 这两种情况正好对应了上述两种“可预料”和“不可预料”的情形。 我认为“Exception”是用来处理不可预料的情况用的。上述“防止服务器崩溃”属于处理不可预料的异常的情形,是应该用异常处理的。 但是,Java的问题就是将大多数异常都认为是可预料的(当然也有少数的比如IllegalArgumentException, IllegalStateException, ArrayIndexOutOfBoundException等)。这等于把异常当成“联合类型”来使用。问题是,毕竟异常就是异常,它是很特殊的。 从语法的角度看,try-catch语法远不如Scala的pattern match语法优雅。 从效率上看,异常处理是非正常的控制流,效率不如正常控制流程,将大量的正常情况(如数组里找不到某个元素)作为异常使用,会影响效率。优化器也有可能不会去优化异常流程,即使“异常”情况比正常情况还常见。 仔细想想,一些“惊喜”比如,为什么java.util.List接口中,get(int i)方法(取出第i个元素)在i超出数组范围的情况下抛出IndexOutOfBoundException;但是indexOf(Object o)(寻找某个元素的下标)在找不到这个元素的时候返回-1?都是异常情况,为什么区别对待呢? 再思考一个问题:已有一个数组,有很多元素,有少量重复。现在请给它去重,对于重复的元素,请指出上一个下标在哪里。思路是用一个Map映射元素和旧的下标。对于新元素,先在Map中找找,找不到就加进去。但是,很显然,因为重复极少,绝大多数是找不到的。朴素的实现会把“找不到”作为异常跑出来,但是效率就是问题了。可以看看,java.Util.Map.get方法在找不到的情况下返回null而不是抛出异常。但这似乎不是Java的风格。传统的Java不是用异常来表示特殊情况的吗?难道这次为性能妥协了?
UnrealT机器人#8 · 2013/10/3
【 在 nuanyangyang 的大作中提到: 】 : 这个需要解释一下。不太理解。我认为机制是一样的,都是会造成“stack unwinding“的特殊控制流。 这个写错了,看来是打字的时候顺手打上去了,不过重点是对待异常不一样,理解就好了XDDD : 我的观点是,特殊情形(指的是广义的运行时的非正常情形,不一定实现为可以抛的Exception)有两种:一种是可以预料的,另一种是不可以预料的。当然从本质上它们没有什么区别,但是形式不同。 : 可以预料的特殊情形,形同这样:“调用这个函数的时候会产生多种结果:结果1、结果2、结果3……请全部都处理” : 不可预料的特殊情形,形同这样:“调用这个函数的时候一般会产生结果1。但是在某些特殊情况下会产生结果2、结果3、……请在力所能及的情况下处理它们”。 从文字上来说,不可预料这个说法其实没什么意义,还会给人带来混乱。 什么是不可预料的?给一个路径去读取文件,文件难道不可能不存在?难道不会出现文件被别的程序锁住读不了?或者磁盘损坏?这些都是可预料的,但现实是都做成了Exception。 真正意义上的不可预料,其实就是你没有想到,说白了是程序的bug。 少数特殊的情况,如内存不足,Java中一般认为这不是程序的bug,程序也没法处理,就做成了Error。 说到底,把哪些东西安排到异常,哪些不是,这是设计者自己决定的,没有什么定论。 一般上,会从业务的角度出发,比如说读文件,文件被锁和磁盘损坏不是业务的内容,一般也不容易遇见,就放到异常中去了。 另外,还会故意把一些设计为由使用者确保正确性的情况,这种情况下设计者认为可能是(调用者)程序的bug,比如读文件时的FileNotFoundException、数组的越界。设计者认为使用者应该主动去检查,如果不检查就调用结果出错了就是异常。存在出错可能又不检查,还不做任何处理,这铁定就是程序的bug。做成异常可以让人重视这个bug,出问题了定位起来也比较容易,如果不抛异常反而容易引起更大的问题。
nuanyangyang机器人#9 · 2013/10/3
【 在 UnrealT 的大作中提到: 】 : 这个写错了,看来是打字的时候顺手打上去了,不过重点是对待异常不一样,理解就好了XDDD : 从文字上来说,不可预料这个说法其实没什么意义,还会给人带来混乱。 : 什么是不可预料的?给一个路径去读取文件,文件难道不可能不存在?难道不会出现文件被别的程序锁住读不了?或者磁盘损坏?这些都是可预料的,但现实是都做成了Exception。 : ................... “可预料”和“不可预料”体现在接口上。返回一种值的函数和返回两种值的函数是不兼容的。从另一种意义上看,就是如果多个实现都实现了同一个接口,是否用户对它们的期待都应该是相同的。还拿CodeSource举例,FileCodeSource会抛出FileNotFoundException,而InternetCodeSource则会抛出URLException。既然他们都实现了同一个接口,它们的执行的结果是不是都应该一样呢?显然从接口的角度看,应该是一样的。 如果我们这样写接口: interface CodeSource { String getCode(); } 隐含的意思是用户应该认为“返回字符串”是“可预料”的,而其它可能性(包括抛出任何RuntimeException)都是“不可预料”的。 但是如果这样写接口(Java没有Option类型,所以用Scala表达): trait CodeSource { def getCode(): Option[String]; } 用户知道执行getCode时有两种可以预料的结果:失败的时候返回None,成功的时候返回Some(x),其中x是返回的载荷。当然还有像内存不足之类的仍然会抛出OutOfMemoryException,这些就真的不可预料了。 如果这样写接口(还是用Scala): trait CodeSource { def getCode(): Either[String, String]; } 它的意思是:有两种可以预料的结果:失败的时候返回Left(e),其中e是一个字符串,描述错误信息;成功的时候返回Right(x),其中x是载荷,这里碰巧也是字符串。同样地,因为没有指明抛异常,所有的异常无法预料的。 如果这样写接口(这次可以用Java): interface CodeSource { List<String> getCode(); } 它的意思是:getCode可能有很多种可以预料的结果:空列表,表示完全失败,没有返回结果;只有一个元素的列表,表示获得了唯一的结果;一个有很多元素的列表,表示获得了很多结果。同样地,因为没有指明抛异常,所有的异常无法预料的。 尽管可以说:计算机里任何事情都是可以意料的,没有“不可意料”的情况。但是从接口的角度看,接口规定了一些行为,起码从接口的用户的角度看,某些行为是可以预料的而另一些是不可预料的。 从接口的实现者的角度看,这就麻烦了:因为具体的实现不同,可能出现什么样的故障也是各不相同。即使自己可以预料到某些故障,也要想到使用这个接口的人可能并不知道这种故障的存在。比如某人做了一个古怪的CodeSource public class TopSecretCodeSource implements CodeSource { public TopSecretCodeSource(String passPhrase) { ... } @Override public String getCode() throws PermissionDeniedException { // ERROR! ... } } 制作这个实现的人自己很清楚:当用户权限不够的时候。但是CodeSource接口用户可能想不到会有“权限”这一个东西,自然根本无法处理这种异常。 如果CodeSource的用户足够严谨,想在这个方法调用失败的时候不至于崩掉,或者有别的替代策略,这就是Pokemon Catch的用武之地了: class User { public void doSomethingWithCode(CodeSource cs) { try { String code = cs.getCode(); // do something return; } catch (Exception e) { logger.info("Oops. I can't get the code, but I can do something else."); } // do something else return; } } 但这并没有改变User并不知道getCode会抛出什么异常这一事实。所以异常仍然是“不可预料”的。