package com.cheng.improve151suggest; import android.os.Bundle; import android.support.v7.app.AppCompatActivity; import android.widget.ListView; import java.util.Arrays; import com.cheng.highqualitycodestudy.R; import com.cheng.improve151suggest.adapter.I151SuggestListAdapter; /** 第8章 异常 建议110: 提倡异常封装 建议111: 采用异常链传递异常 建议112: 受检异常尽可能转化为非受检异常 建议113: 不要在finally块中处理返回值 建议114: 不要在构造函数中抛出异常 建议115: 使用throwable获得栈信息 建议116: 异常只为异常服务 建议117: 多使用异常,把性能问题放一边 */ public class I151SChapter08Activity extends AppCompatActivity { private static final String TAG = "I151SChapter08Activity"; private ListView mChapterLV; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_i151suggestchapter); initView(); initData(); } private void initView() { this.mChapterLV = (ListView) findViewById(R.id.isi_chapter_lv); } private void initData() { String[] suggests = getResources().getStringArray(R.array.i151schapter8); I151SuggestListAdapter adapter = new I151SuggestListAdapter(this, Arrays.asList(suggests)); mChapterLV.setAdapter(adapter); } /** * 建议110: 提倡异常封装 */ private void suggest110() { /* Java语音的异常处理机制可以确保程序的健壮性,提高系统的可用率,但是Java API提供的异常都是比较 低级的(这里的低级别是指“低级别”的异常),只有开发人员才能看得懂,才明白发生了什么问题。而对于 用户来说,这些异常是没意义的,与业务无关,是纯计算机语言的描述,那该怎么办?这就需要我们对异常 进行封装了。异常封装有三方面的优点: 1)提高系统的友好性 可以把异常的阅读者分为两类:开发人员和用户。开发人员查找问题,需要打印出堆栈信息,而用户则需要 了解具体的业务原因,比如文件太长、不能同时编写文件等,代码如下: public void doStuff() throws MyBusinessException { try { InputStream is = new FileInputStream("无效文件.txt"); } catch (FileNotFoundException e) { // 为方便开发和维护人员而设置的异常信息 e.printStackTrace(); // 抛出业务异常 throw new MyBusinessException(); } } 2)提高系统的可维护性 写一个catch块来处理所有的异常,这种方式不可取,虽然JVM会打印出栈中的出错信息,但是该信息只有开 发人员自己才看得懂,维护人员看到这段异常时基本上无法处理,因为需要深入到代码逻辑中去分析问题。正 确的做法是对异常进行分类处理,并进行封装输出,代码如下: public void doStuff() { try { // do something } catch (FileNotFoundException e) { log.info("文件未找到,使用默认配置文件..."); } catch (SecurityException e) { log.error("无权访问,可能原因是..."); e.printStackTrace(); } } 如此包装后,维护人员看到这样的异常就有了初步的判断,或者检查配置,或者初始化环境,不需要直接到 代码层级去分析了 3)解决Java异常机制自身的缺陷 Java中的异常一次只能抛出一个,比如doStuff方法有两个逻辑代码片段,如果在第一个逻辑片段中抛出异常, 则第二个逻辑片段就不再执行了,也就无法抛出第二个异常了,现在的问题是:如何才能一次抛出两个(或多 个)异常呢? 其实,使用自行封装的异常可以解决该问题,代码如下: class MyException extends Exception { // 容纳所有的异常 private List<Throwable> causes = new ArrayList<Throwable>(); // 构造函数,传递一个异常列表 public MyException(List<? extends Throwable> _cause) { causes.addAll(_causes); } // 读取所有的异常 public List<Throwable> getExceptions() { return causes; } } MyException异常只是一个异常容器,可以容纳多个异常,但它本身并不代表任何异常含义,它所解决的是一次 抛出多个异常的问题,具体调用如下: public void doStuff() throws MyException { List<Throwable> list = new ArrayList<Throwable>(); // 第一个逻辑片段 try { // do something } catch (Exception e) { list.add(e); } // 第二个逻辑片段 try { // do something } catch (Exception e) { list.add(e); } // 检查是否有必要抛出异常 if (list.size() > 0) { throw new MyException(list); } } 这样一来,doStuff方法的调用者就可以一次获得多个异常了,也能够为用户提供完整的例外情况说明。可能有 疑问:这种情况可能出现吗?怎么会要求一个方法抛出多个异常呢? 绝对可能出现,例如Web界面注册时,展现层依次把User对象传递到逻辑层,Register方法需要对各个Field进 行校验并注册,例如用户名不能重复,密码必须符合密码策略等,不要出现用户第一次提交时系统提示“用户名重 复”,在用户修改用户名再次提交后,系统又提示“密码长度少于6位”的情况,这种操作模式下用户体验非常糟糕, 最好的解决办法就是封装异常,建立异常容器,一次性对User对象进行校验,然后返回所有的异常 */ /** * 注意 * 异常封装很重要,作用很大 */ } /** * 建议111: 采用异常链传递异常 */ private void suggest111() { /* 设计模式中有一个模式叫做责任链模式(Chain of Responsibility),它的目的是将多个对象连成一条链, 并沿着这条链传递该请求,直到有对象处理它为止,异常的传递处理也应该采用责任链模式 异常需要封装,但仅仅封装还是不够的,还需要传递异常。比如,JEE项目一般都有三层结构:持久层、逻辑 层、展现层,持久层负责与数据库交互,逻辑层负责业务逻辑的实现,展现层负责UI数据的处理。有这样一个 模块:用户第一次访问的时候,需要持久层从user.xml中读取信息,如果该文件不存在则提示用户创建之,那 问题来了:如果直接把持久层异常FileNotFoundException抛弃掉,逻辑层根本无从得知发生了何事,也就不 能为展现层提供一个友好的处理结果了,最终倒霉的就是展现层:没有办法提供异常信息,只能告诉用户说“出 错了,我也不知道出什么错了”--毫无友好性可言 正确的做法是先封装,然后传递,过程如下: 1)把FileNotFoundException封装为MyException 2)抛出到逻辑层,逻辑层根据异常代码(或者自定义的异常类型)确定后续处理逻辑,然后抛出到展现层 3)展现层自行决定要展现什么,如果是管理员则可以展现低层级的异常,如果是普通用户则展示封装后的异常 那异常如何传递了?很简单,使用异常链进行异常的传递,以IOException为例来看看如何传递的,代码如下: public class IOException extends Exception { // 定义异常原因 public IOException(String message) { super(message); } // 定义异常原因,并携带原始异常 public IOException(String message, Throwable cause) { super(message, cause); } // 保留原始异常信息 public IOException(Throwable cause) { super(cause); } } 在IOException的构造函数中,上一个层级的异常可以通过异常链进行传递,链中传递异常的代码如下所示: try { // do something } catch (Exception e) { throw new IOException(e); } 捕捉到Exception异常,然后把它转化为IOException异常并抛出(此种方式也叫作异常转译),调用者获得 该异常后再调用getCause方法即可获得Exception的异常信息,如此即可方便地查找到产生异常的根本信息, 便于解决问题 */ /** * 注意 * 异常需要封装和传递,我们在进行系统开发时不要“吞噬”异常,也不要“赤裸裸”地抛出异常,封装后再抛 * 出,或者通过异常链传递,可以达到系统更健壮、友好的目的 */ } /** * 建议112: 受检异常尽可能转化为非受检异常 */ private void suggest112() { /* 受检异常是正常逻辑的一种补偿处理手段,特别是对可靠性要求比较高的系统来说,在某些条件下必须抛出受 检异常以便程序进行补偿处理,也就是说受检异常有合理的存在理由,那为什么要把受检异常转化为非受检异 常呢?难道说受检异常有什么缺陷或不足吗?是的,受检异常确实有不足的地方: 1)受检异常使接口声明脆弱 OOP要求我们尽量多地面向接口编程,可以提高代码的扩展性、稳定性等,但是一旦涉及异常问题就不一样了, 例如系统初期是这样设计一个接口的: interface User { // 修改用户名密码,抛出安全异常 void changePassword() throws MySecurityException; } 随着系统的开发,User接口有了多个实现者,比如普通的用户UserImpl、 模拟用户MockUserImpl(用作测 试或系统管理)。非实体用户NonUserImpl(如自动执行机、逻辑处理器等),此时如果发现changePassword 方法可能还需要抛出RejectChangeException(拒绝修改异常,如自动执行机正在处理任务时不能修改密码), 那就需要抛出User接口了:changePassword方法增加抛出RejectChangeException异常,这会导致所有的User 调用者都要追加对RejectChangeException异常问题的处理 这里产生了两个问题:一是异常是主逻辑的补充逻辑,修改一个补充逻辑,就会导致主逻辑也被修改,也就是出 现了实现类“逆影响”接口的情景,我们知道实现类是不稳定的,而接口是稳定的,一旦定义了异常,则增加了接 口的不稳定性,这是对面向对象设计的严重亵渎;二是实现的类变更最终会影响到调用者,破坏了封装性,这也 是迪米特法则所不能容忍的 2)受检异常使代码的可读性降低 一个方法增加了受检异常,则必须有一个调用者对异常进行处理,try catch降低了可读性 3)受检异常增加了开发工作量 我们知道,异常需要封装和传递,只有封装才能让异常更容易理解,上层模块才能更好的处理,可这也会导致低 层级的异常没完没了的封装,无端加重了开发的工作量。比如FileNotFoundException在持久层抛出,就需要 定义一个MyException进行封装,并抛出到上一个层级,于是增加了开发工作量 受检异常有这么多的缺点,那有没有什么办法可以避免或减少这些缺点呢?有,很简单的一个规则:将受检异常 转化为非受检异常即可,但是我们也不能把所有的受检异常转化为非受检异常,原因是在编码期上层模块不知道 下层模块会抛出何种非受检异常,只有通过规则或文档来约束,可以这样说: 受检异常提出的是“法律下的自由”,必须遵守异常的约定才能自由编写代码 非受检异常则是“协约性质的自由”,你必须告诉我你要抛出什么异常,否则不会处理 以User接口为例,我们在声明接口时不再声明异常,而是在具体实现时根据不同的情况产生不同的非受检异常, 这样持久层和逻辑层抛出的异常将会由展现层自行决定如何展示,不再受异常的规则约束了,大大简化开发工作, 提供了代码的可读性 那问题又来了:在开发和设计时什么样的受检异常有必要转化为非受检异常呢?“尽可能”是以什么作为判断依据 呢?受检异常转换为非受检异常是需要根据项目的场景来决定的,例如同样是刷卡,员工拿着自己的工卡到考勤 机上打考勤,此时如果附近有磁性物质干扰,则考勤机可以把这种受检异常转化为非受检异常,黄灯闪烁后不做 任何记录登记,因为考勤失败这种情况不是“致命”的业务逻辑,出错了,重新刷新一下即可。但是到银行网点取 钱就不一样了,拿着银行卡到银行取钱,同样有磁性物质干扰,刷不出来,那这种异常就必须登记处理,否则会 称为威胁银行卡安全的事件。汇总成一句话:当受检异常威胁到了系统的安全性、稳定性、正确性时,则必须处 理,不能转化为非受检异常,其他情况则可以转换为非受检异常 */ /** * 注意 * 受检异常威胁到了系统的安全性、稳定性、正确性时,则必须处理,不能转化为非受检异常 */ } /** * 建议113: 不要在finally块中处理返回值 */ private void suggest113() { /* 在项目中绝对不能在finally代码块中出现return语句,这是因为这种处理方式非常容易产生“误解”,会严重 误导开发者。例如如下代码: public static void main(String[] args) { try { doStuff(-1); doStuff(100); } catch (Exception e) { Log.e(TAG, "这里是永远都不会到达的"); } } // 该方法抛出受检异常 public static int doStuff(int _p) throws Exception { try { if (_p < 0) { throw new DataFormatException("数据格式错误"); } else { return _p; } } catch (Exception e) { // 异常处理 throw e; } finally { return -1; } } 对于这段代码,有两个问题:main方法中的doStuff方法的返回值是什么?doStuff方法永远都不会抛出异常? 答案是:doStuff(-1)的值是-1,doStuff(100)的值也是-1,调用doStuff方法永远都不会抛出异常,有这么 神奇?原因就是我们在finally代码块中加入了return语句,而这会导致出现以下两个问题: 1)覆盖了try代码块中的return返回值 当执行doStuff(-1)时,doStuff方法产生了DataFormatException异常,catch块在捕捉此异常后直接抛出, 之后代码执行到finally代码块,就会重置返回值,结果就是-1了,也就是出现了先返回,再执行finally,再 重置返回值的情况 思考下:是不是可以定义一个变量,在finally中修改后再return呢?代码如下: public static int doStuff() { int a = 1; try { return a; } catch (Exception e) { } finally { // 重新修改一下返回值 a = -1; } return 0; } 该方法的返回值永远是1,而不会是-1或0(为什么不会执行到“return 0”呢?原因是finally执行完毕后该方法 已经有返回值了,后续代码就不会再执行了),这都是源于异常代码块的处理方式,在代码中加上try代码块就标 志着运行时会有一个Throwable线程监视着该方法的运行,若出现异常,则交由异常逻辑处理 我们知道方法是在栈内存中运行的,并且会按照“先进后出”的原则执行,main方法调用了doStuff方法,则main 方法在下层,doStuff在上层,当doStuff方法执行完“return a”时,此方法的返回值已经确定是int类型1(a 变量的值,注意基本类型都是值拷贝,而不是引用),此后finally块再修改a的值已经与doStuff返回值没有任 何关系了,因此该方法永远都会返回1 问:那是不是可以在finally代码块中修改引用类型的属性以达到修改返回值的效果呢?代码如下: public static Person doStuff() { Person person = new Person(); person.setName("张三"); try { return person; } catch (Exception e) { } finally { // 重新修改一下返回值 person.setName("李四"); } person.setName("王五"); return person; } class Person { private String name; / name的getter/setter方法省略 / } 此方法的返回值永远都是name为李四的Person对象,原因是Person是一个引用对象,try代码块中的返回值是 Person对象的地址,finally中再修改那当然会是李四了 2)屏蔽异常 为什么明明把异常throw出去了,但main方法却捕捉不到呢?这是因为异常线程在监视到有异常发生时,就会登 记当前的异常类型为DataFormatException,但是当执行器执行finally代码块时,则会重新为doStuff方法赋 值,也就是告诉调用者”该方法执行正确,没有产生异常,返回值是1“,于是乎,异常神奇的消失了,简化如下: public static void doSomething() { try { // 正常抛出异常 throw new RuntimeException(); } finally { // 告诉JVM:该方法正常返回 return; } } public static void main(String[] args) { try { doSomething(); } catch (RuntimeException e) { Log.e(TAG, "这里永远都不会到达!"); } } 上面finally代码块中的return已经告诉JVM:doSomething方法正常执行结束,没有异常,所以main方法就不 可能获得任何异常信息了。这样的代码会使可读性大大降低,读者很难理解作者的意图,增加了修改难度 在finally中处理return返回值,代码看上去很完美,都符合逻辑,但是执行起来就会产生逻辑错误,最重要的 一点是finally是用来做异常的收尾处理的,一旦加上了return语句就会让程序的复杂度陡然提升,而且会产生 一些隐蔽性非常高的错误 与return语句相似,System.exit(0)或Runtime.getRuntime().exit(0)出现在异常代码块中会产生非常多的 错误假象,增加代码的复杂性 */ /** * 注意 * 不要在finally代码块中出现return语句 */ } /** * 建议114: 不要在构造函数中抛出异常 */ private void suggest114() { /* Java的异常机制有三种: Error类及其子类表示的是错误,它是不需要程序员处理也不能处理的异常,比如VirtualMachineError虚拟 机错误,ThreadDeath线程僵死等 RuntimeException类及其子类表示的是非受检异常,是系统可能会抛出的异常,程序员可以去处理,也可以 不处理,最经典的就是NullPointerException空指针异常和IndexOutOfBoundsException越界异常 Exception类及其子类(不包含非受检异常)表示的是受检异常,这是程序员必须处理的异常,不处理则程序 不能通过编译,比如IOException表示I/O异常,SQLException表示数据库访问异常 我们知道,一个对象的创建要进过内存分配、静态代码初始化、构造函数执行等过程,对象生成的关键步骤是 构造函数,那是不是也允许在构造函数中抛出异常呢?从Java语法上来说,完全可以在构造函数中抛出异常, 三类异常都可以,但是从系统设计和开发的角度来分析,则尽量不要在构造函数中抛出异常,下面以三种不同 类型的异常来说明之。 1)构造函数抛出错误是程序员无法处理的 2)构造函数不应该抛出非受检异常 在构造函数中抛出非受检异常,加重了上层代码编写者的负担 如果抛出非受检异常,而没有对其进行捕捉,异常最终会抛到JVM,这会导致整个线程执行结束后,后面 所有的代码都不会继续执行了,这就对业务逻辑产生了致命的影响 3)构造函数尽可能不要抛出受检异常 -导致子类代码膨胀 -违背了里氏替换原则(子类抛出比父类抛出的异常范围要宽,如果在使用父类的地方替换为子类,就需要 增加新的catch块才能解决) 为什么Java的构造函数允许子类的构造函数抛出更广泛的异常类呢?这正好与类的异常机制相反,类方法 的异常是这样要求的: 子类的方法可以抛出多个异常,但都必须是被覆写方法的子类型,这是Java覆写的要求。构造函数之所以 与此相反,是因为构造函数没有覆写的概念,只是构造函数间的引用调用而已,所以在构造函数中抛出受 检异常会违背里氏替换原则,使程序缺乏灵活性 -子类构造函数扩展受限 子类存在的原因就是期望实现并扩展父类的逻辑,但是父类构造函数抛出异常却会让子类构造函数的灵活 性大大降低,例如我们期望这样的构造函数 class Sub extends Base { public Sub() throws Exception { try { super(); } catch (IOException e) { // 异常处理后再抛出 throw e; } finally { // 收尾处理 } } } 很不幸,这段代码编译通不过,原因是构造函数Sub中没有把super()放在第一句话中,想把父类的异常 重新包装后再抛出是不可行的(当然,这里有很多种”曲线“的实现手段,比如重新定义一个方法,然后父 子类的构造函数都调用该方法,那么子类构造函数就可以自由处理异常了),这是Java语法限制 */ /** * 注意 * 在构造函数中不要抛出异常,尽量曲线救国 */ } /** * 建议115: 使用throwable获得栈信息 */ private void suggest115() { /* AOP编程可以很轻松地控制一个方法调用哪些类,也能够控制哪些方法允许被调用,一般来说切面(比如AspectJ) 只能控制到方法级别,不能实现代码级别的植入(Weave),比如一个方法被类A的m1方法调用时返回1,在 类B的m2方法调用时返回0(同参数情况下),这就要求被调用者具有识别调用者的能力。这种情况下,可以 使用Throwable获得栈信息,然后鉴别调用者并分别输出,代码如下: class Foo { public static boolean m() { // 取得当前栈信息 StackTraceElement[] sts = new Throwable().getStackTrace(); // 检查是否是m1方法调用 for (StackTraceElement st : sts) { if (st.getMethodName().equals("m1")) { return true; } } return false; } } // 调用者 class Invoker { // 调用方法打印出true public static void m1() { Log.e(TAG, Foo.m() + ""); } // 该方法打印出false public static void m2() { Log.e(TAG, Foo.m() + ""); } } 注意看Invoker类,两个方法m1和m2都调用了Foo的m方法,都是无参调用,返回值却不同,这是我们的 Throwable类发挥效能了。JVM在创建一个Throwable类及其子类时会把当前线程的栈信息记录下来,以 便在输出异常时准确定位异常原因,来看Throwable源代码: public class Throwable implements Serializable { // 出现异常的栈记录 private StackTraceElement[] stackTrace; // 默认构造函数 public Throwable() { // 记录栈帧 fillInStackTrace(); } // 本地方法,抓取执行时的栈信息 public synchronized native Throwable fillInStackTrace(); } 在出现异常时(或主动声明一个Throwable对象时),JVM会通过fillInStackTrace方法记录下栈帧信息, 然后生成一个Throwable对象,这样就可以知道类间的调用顺序、方法名称及当前行号等了 获得栈信息可以对调用者进行判断,然后决定不同的输出,比如我们的m1和m2方法,同样的输入参数,同样 的调用方法,但是输出却不同,这看起来很像是一个Bug:方法m1调用m方法是正常显示,而方法m2调用却会 返回”错误“数据。因此我们虽然可以依据调用者不同产生不同的逻辑,但这仅局限在对此方法的广泛认知上。 更多的时候我们使用m方法的变形体,代码如下: class Foo { public static boolean m() { // 取得当前栈信息 StackTraceElement[] sts = new Throwable().getStackTrace(); // 检查是否是m1方法调用 for (StackTraceElement st : sts) { if (st.getMethodName().equals("m1")) { return true; } } throw new RuntimeException("除m1方法外,该方法不允许其他方法调用"); } } 只是把”return false“替换成一个运行期异常,除了m1方法外,其他方法调用都会产生异常,该方法常用 作离线注册码校验,当破解者试图暴力破解时,由于主执行者不是期望的值,因此会返回一个包装和混淆的 异常信息,大大增加了破解的难度 */ /** * 注意 * Throwable需要认真研究研究 */ } /** * 建议116: 异常只为异常服务 */ private void suggest116() { /* 异常只为异常服务,这是何解?难道异常还能为其他服务不成?确实能,异常原本是正常逻辑的一个补充, 但是有时候会被当作主逻辑使用,看如下代码: // 判断一个枚举是否包含String枚举项 public static <T extends Enum<T>> boolean Contain(Class<T> c, String name) { boolean result = false; try { Enum.valueOf(c, name); result = true; } catch (RuntimeException e) { // 只要抛出异常,则认为是不包含 } return result; } 判断一个枚举是否包含指定的枚举项,这里会根据valueOf方法是否抛出异常来进行判断,如果抛出异常(一 般是IllegalArgumentException异常),则认为是不包含,若不抛出异常则可以认为包含该枚举项,看上去 这段代码很正常,但是其中却有三个错误: 异常判断降低了系统性能 降低了代码的可读性,只是详细了解valueOf方法的人才能读懂这样的代码,因为valueOf抛出的是非受检异常 隐藏了运行期可能产生的错误,catch到异常,但没有做任何处理 这段代码是用一段异常实现了一个正常的业务逻辑,这导致代码产生了坏味道。要解决此问题也很容易,即不在 主逻辑中使用异常,代码如下: // 判断一个枚举是否包含String枚举项 public static <T extends Enum<T>> boolean Contain(Class<T> c, String name) { // 遍历枚举项 for (T t : c.getEnumConstants()) { // 枚举项名称是否相等 if (t.name().equals(name)) { return true; } } return false; } 异常只能用在非正常的情况下,不能成为正常情况的主逻辑,也就是说,异常只是主场景中的辅助场景,不能喧 宾夺主。而且,异常虽然是描述例外事件的,但能避免则避免之,除非是确实无法避免的异常,例如: public static void main(String[] args) { File file = new File("文件.txt"); try { FileInputStream fis = new FileInputStream(file); / 其他业务逻辑处理 / } catch (FileNotFoundException e) { // 异常处理 } } 这样一段代码经常会出现在我们的项目中,但经常写并不代表不可优化,这里的异常类FileNotFoundException 完全可以在它诞生前就消除掉:先判断文件是否存在,然后再生成FileInputStream对象,代码如下: public static void main(String[] args) { File file = new File("文件.txt"); // 经常出现的异常情况,可以先做判断 if (file.exists() && !file.isDirectory()) { try { } catch () { } } } 虽然增加了if判断语句,增加了代码量,但是却会减少FileNotFoundException异常出现的几率,提高了程序 的性能和稳定性 */ /** * 注意 * 异常只为确实异常的事件服务 */ } /** * 建议117: 多使用异常,把性能问题放一边 */ private void suggest117() { /* 我们在编写用例文档时,其中有一项叫作”例外事件“,是用来描述主场景外的例外场景的,例如用户登录的用例, 就会在”例外事件“中说明”连续3次登录失败即锁定用户帐号“,这就是登录事件的一个异常处理,代码如下: public void login() { try { // 正常登录 } catch (InvalidLoginException lie) { // 用户名无效 } catch (InvalidPasswordException pe) { // 密码错误的异常 } catch (TooMuchLoginException tmle) { // 多次登录失败的异常 } } 如此设计可以让我们的login方法更符合实际的处理逻辑,同时使主逻辑(正常登录,try代码块)更加清晰。当 然了,使用异常还有很多优点,比如可以让正常代码和异常代码分离、能快速查找问题(栈信息快照)等,但是 异常有一个缺点:性能比较慢 Java的异常处理机制确实比较慢,这个”比较慢“是相对于诸如String、Integer等对象来说,单单从对象的创建 来说,new一个IOException会比String慢5倍,这从异常的处理机制也可以解释:因为它要执行fillInStackTrace 方法,要记录当前栈的快照,而String类则是直接申请一个内存创建对象,异常类慢一筹也就在所难免了 而且,异常类是不能缓存的,期望预先建立大量的异常对象以提高异常性能也是不现实的 难道异常的性能问题就没有任何可提高的办法了?确实不没有,但是我们不能因为性能问题而放弃使用异常,而且 经过测试,在 JDK 1.6 下,一个异常对象创建的时间只需要1.4毫秒左右(注意是毫秒,通常一个交易处理是在 100毫秒左右),难道如此微小的性能消耗都不允许吗? */ /** * 注意 * 性能问题不是拒绝异常的借口 */ } }