返回信息流这个是别的帖子里讨论过的,总结一下吧。
如下代码: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
}
}
这是一条镜像帖。来源:北邮人论坛 / java / #27107同步于 2013/10/2
该镜像源已超过 30 天没有更新,可能在源站已被删除。
Java机器人发帖
[黑Java系列]Java也惊喜之2:接口与异常
nuanyangyang
2013/10/2镜像同步18 回复
订阅后,新回复会通过你的通知中心匿名送达。
9 条回复
气死java程序员和java也惊喜是两个子系列。
【 在 iFadeToBlack 的大作中提到: 】
: 为啥3比2先发。。。世界观都被扭曲了
http://bbs.byr.cn/#!article/CPP/74067
【 在 UnrealT 的大作中提到: 】
: 不明白这个总结了什么。。。。。
【 在 nuanyangyang 的大作中提到: 】
: http://bbs.byr.cn/#!article/CPP/74067
这贴我看了,C++和Java异常机制不一样,用途也不一样,没有什么可比性
我赞成Java的异常只是一种特殊的返回值的说法,Java的异常可以用来处理用例中异常情况的部分
比如用户在页面上点了个按钮,要执行某个操作,但发生了异常,一种处理办法就是记录日志,页面给个弹出框告诉用户发生了问题,可以怎样去补救处理,或者是通知管理员
对Java来说,除了空指针、转型、数组越界等异常其实是程序的bug,不应该被处理外,还有相当一部分异常是可以处理的,不应该因为发生这些异常就造成服务崩溃。真正不能被处理的是Error。
这些都是Java的基本常识,我想你也是了解的
那贴发在cpp版,问的是cpp的异常,你说cpp的异常不应该被处理,这没什么问题
以上纯属废话
回到这贴,你说的这些也是总所周知的事实,其实没什么好总结的,所以我才问你总结出什么有意思的观点没
对我来说,接口中有没有写抛异常,这是体现接口设计意图的(设计者是SB这种就不讨论了),所以在使用别人东西的时候这方面需要好好斟酌一下
【 在 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不是用异常来表示特殊情况的吗?难道这次为性能妥协了?
【 在 nuanyangyang 的大作中提到: 】
: 这个需要解释一下。不太理解。我认为机制是一样的,都是会造成“stack unwinding“的特殊控制流。
这个写错了,看来是打字的时候顺手打上去了,不过重点是对待异常不一样,理解就好了XDDD
: 我的观点是,特殊情形(指的是广义的运行时的非正常情形,不一定实现为可以抛的Exception)有两种:一种是可以预料的,另一种是不可以预料的。当然从本质上它们没有什么区别,但是形式不同。
: 可以预料的特殊情形,形同这样:“调用这个函数的时候会产生多种结果:结果1、结果2、结果3……请全部都处理”
: 不可预料的特殊情形,形同这样:“调用这个函数的时候一般会产生结果1。但是在某些特殊情况下会产生结果2、结果3、……请在力所能及的情况下处理它们”。
从文字上来说,不可预料这个说法其实没什么意义,还会给人带来混乱。
什么是不可预料的?给一个路径去读取文件,文件难道不可能不存在?难道不会出现文件被别的程序锁住读不了?或者磁盘损坏?这些都是可预料的,但现实是都做成了Exception。
真正意义上的不可预料,其实就是你没有想到,说白了是程序的bug。
少数特殊的情况,如内存不足,Java中一般认为这不是程序的bug,程序也没法处理,就做成了Error。
说到底,把哪些东西安排到异常,哪些不是,这是设计者自己决定的,没有什么定论。
一般上,会从业务的角度出发,比如说读文件,文件被锁和磁盘损坏不是业务的内容,一般也不容易遇见,就放到异常中去了。
另外,还会故意把一些设计为由使用者确保正确性的情况,这种情况下设计者认为可能是(调用者)程序的bug,比如读文件时的FileNotFoundException、数组的越界。设计者认为使用者应该主动去检查,如果不检查就调用结果出错了就是异常。存在出错可能又不检查,还不做任何处理,这铁定就是程序的bug。做成异常可以让人重视这个bug,出问题了定位起来也比较容易,如果不抛异常反而容易引起更大的问题。
【 在 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会抛出什么异常这一事实。所以异常仍然是“不可预料”的。