[改善Java代码]不要在构造函数中抛出异常
2017-10-23 23:34
246 查看
[改善Java代码]不要在构造函数中抛出异常转载(有改动)
Java的三种异常机制有:一、Error类以及其子类表示的是错误,它是不需要程序员处理也不能处理的异常,比如 VirtualMachineError 虚拟机错误,ThreadDeath 线程僵尸等。
二、RuntimeException 类及其子类表示的是非受检查异常,是系统可能会抛出的异常,程序员可以去处理,也可以不去处理,最经典的就是 NullPointerException 空指针异常和 IndexOutOfBoundsException 指标越界异常。
三、Exception类及其子类(不包含非受检查异常)表示的是受检异常,这是程序员必须处理的异常,不处理则程序不能通过编译,比如 IOException 表示 I/O 异常,SQLException表示数据库访问异常。
我们知道 一个对象的创建,需要经过内存分配,静态代码初始化,构造函数执行等过程,对象生成的关键步骤是构造函数,那是不是允许在构造函数中抛出异常呢?
从Java语法上来说,完全可以在构造函数中抛出异常,三类异常都可以,但是从系统设计和开发的角度来分析,则尽量不要在构造函数中抛出异常,我们以三种不同类型的异常来说明:
(1) 构造函数抛出错误是程序员无法处理的
(2) 构造函数不应该抛出非受检查异常
看如下的例子:
class Person{ public Person(int _age){ //不满18岁得用户对象不能建立 if(_age<18){ //throw new RuntimeException("年龄必须大于18岁。"); } } //看限制级的电影 public void seeMovie(){ System.out.println("看限制级电影"); } }
代码的意图很明显,不满18岁的用户根本就不会生成一个 Person 实例对象,没有对象,类行为 seeMovie 方法就不可以执行,想法很好,但这会导致不可预测的结果,比如我们这样引用 Person 类:
public static void main(String[] args) { while(true){ Person p = new Person(17); p.seeMovie(); } /*其他的逻辑处理*/ }
很明显,p 对象不能建立,因为是一个 RuntimeException 异常,开发人员可以捕捉也可以不捕捉,代码看上去逻辑很正确,没有任何的瑕疵,但是事实上,这段程序会抛出异常,无法执行,这段代码给了我们两个警示:
① 加重了上层代码编写者的负担。
② 后续代码不会执行。
(3) 构造函数尽可能不要抛出受检查异常
//父类 class Base{ //父类抛出IOException public Base() throws IOException{ throw new IOException(); } //父类方法抛出Exception public void method() throws Exception{ } } //子类 class Sub extends Base{ //子类抛出Exception异常 public Sub() throws Exception { } //子类方法的异常类型必须是覆写方法的子类型 @Override public void method() throws IOException{ } }
cfbb
就这么一段代码,展示了在构造函数中抛出受检查异常的三个不利方面:
① 导致子类代码膨胀
上面的例子中,子类的无参构造函数不能省略,原因是父类的无参构造函数抛出了 IOException 异常,子类的无参构造函数默认调用的是父类的构造函数,所以子类的无参构造函数必须抛出 IOException 或其父类。
② 违背了里氏替换原则
里氏替换原则说”父类能出现的地方子类就可以出现,而且将父类替换为子类也不会产生任何异常”,那我们回过头来看看 Sub 类是否可以替换 Base 类,比如我们的上层代码是这样写的:
public static void main(String[] args) { try{ Base base = new Base(); }catch(IOException e){ //异常处理 } }
然后我们希望把 new Base() 替换成 new Sub(),而且代码能够正常编译和运行。非常可惜编译不通过,原因是Sub的构造函数抛出了 Exception 异常,它比父类的构造函数抛出的异常范围要宽,必须增加新的 catch 块才能解决。
public class Client { public static void main(String[] args) { try{ Base base = new Base(); //Base base = new Sub();这样是编译不通过的,因为Sub的构造抛出Exception,它比父类的构造函数 //抛出的异常范围要宽,必须增加新的catch块才能解决。 }catch(IOException e){ //异常处理 } } } class Base{ //父类抛出IOException public Base() throws IOException{ throw new IOException(); } } class Sub extends Base{ //子类抛出Exception异常 public Sub() throws Exception { } }
可能读者会问,为什么Java的构造函数允许子类的构造函数抛出更广泛的异常类呢?
这正好与类方法的异常机制相反,类方法的异常是这样要求的:
class Base{ //父类方法抛出Exception public void method() throws Exception{ } } class Sub extends Base{ //子类方法的异常类型必须是覆写方法的子类型 @Override public void method() throws IOException{ } }
子类方法可以抛出多个异常,但是都必须是覆写方法的子类型,对我们的例子来说,Sub 类的 method 方法抛出的异常必须是 Exception 的子类或者 Exception 类,这是 Java 覆写的要求。构造函数之所以与此相反,是因为构造函数没有覆写的概念,只是构造函数间的引用调用而已,所以在构造函数中抛出受检查异常会违背里氏替换原则,使我们的程序缺乏灵活性。
③ 子类构造函数扩展有限
以上汇总起来就是:非受检查异常不要抛出,抛出了对人对己都是有害的,受检查异常尽量不抛出。
总之一句话:在构造函数中尽可能的不出现异常。
原文:
[改善Java代码]不要在构造函数中抛出异常
相关文章推荐
- [改善Java代码]不要在构造函数中抛出异常
- [改善Java代码]小心switch带来的空值异常
- [编写高质量代码:改善java程序的151个建议]建议34:构造函数尽量简化
- java抛出异常后代码继续执行的情况
- [编写高质量代码:改善java程序的151个建议]建议31-在接口中不要存在实现代码
- 编写高质量代码改善C#程序的157个建议——建议153:若抛出异常,则必须要注释
- [改善Java代码]构造函数尽量简化
- 怎样确保java代码在抛出异常时都回滚,而不只是运行时异常
- [编写高质量代码:改善java程序的151个建议]建议35:不要在类中初始化其他类
- Java的构造函数抛出异常如何处理?
- 编写高质量代码改善C#程序的157个建议——建议58:用抛出异常代替返回错误代码
- [编写高质量代码:改善java程序的151个建议]建议33:不要覆写静态方法
- [编写高质量代码:改善java程序的151个建议]建议79 HashMap中的key不要重复
- [改善Java代码]使用匿名类的构造函数
- CAD调试时抛出“正试图在 os 加载程序锁内执行托管代码。不要尝试在 DllMain 或映像初始化函数内运行托管代码”异常的解决方法
- java代码----I/O流从控制台输入信息判断并抛出异常
- [编写高质量代码:改善java程序的151个建议]建议31-在接口中不要存在实现代码
- [编写高质量代码:改善java程序的151个建议]建议34:构造函数尽量简化