枚举的构造函数中抛出异常会怎样

编程技术  /  houtizong 发布于 2年前   233
首先从使用enum实现单例说起。

为什么要用enum来实现单例?
这篇文章(http://javarevisited.blogspot.sg/2012/07/why-enum-singleton-are-better-in-java.html)阐述了三个理由:
1.enum单例简单、容易,只需几行代码:
public enum Singleton {INSTANCE;}

2.enum单例自动处理序列化问题
传统的单例实现方式(例如懒汉式饿汉式),如果它implements Serializable,那它就不再是单例了,因为readObject方法总是会返回新的对象。
enum虽然implements Serializable,但它仍然是单例,这是由jvm保证的。

3.enum单例是线程安全的

此外,《Effective Java》也建议用enum实现单例,当然还有stackoverflow的讨论:http://stackoverflow.com/questions/70689/what-is-an-efficient-way-to-implement-a-singleton-pattern-in-java

但是,用enum实现单例的话,它的构造函数不能抛出异常,否则会抛出Error(而不是Exception)。
试想这样一种情况,在远程调用中,服务端抛出了Error,而客户端try-catch的是Exception,那就捕获不到出错信息,客户端就直接崩溃了。
说到远程调用,说点题外话,dubbo当中是不建议传递枚举的(http://dubbo.io/User+Guide-zh.htm#UserGuide-zh-%E5%85%BC%E5%AE%B9%E6%80%A7):

枚举值    如果是完备集,可以用Enum,比如:ENABLE, DISABLE。    如果是业务种类,以后明显会有类型增加,不建议用Enum,可以用String代替。    如果是在返回值中用了Enum,并新增了Enum值,建议先升级服务消费方,这样服务提供方不会返回新值。    如果是在传入参数中用了Enum,并新增了Enum值,建议先升级服务提供方,这样服务消费方不会传入新值。



测试代码:
public enum EnumSingleton {    INSTANCE;        private EnumSingleton () {                //模拟在构造函数中抛出异常的情况。实际情况中可能抛异常的情况包括:读取配置文件时文件不存在,连接数据库失败等等。        int i = 1 / 0;    }        public String hello() {        return "hello";    }}public class LazyClassSingleton {        private static LazyClassSingleton instance;        private LazyClassSingleton () {        int i = 1 / 0;    }        public static synchronized LazyClassSingleton getInstance () {        if (instance == null) {            instance = new LazyClassSingleton();        }        return instance;    }    public String hello() {        return "hello";    }}public class EagerClassSingleton {        private static EagerClassSingleton instance = new EagerClassSingleton();        private EagerClassSingleton () {                int i = 1 / 0;    }        public static EagerClassSingleton getInstance() {        return instance;    }        public String hello() {        return "hello";    }}public class TestSingleton {        public static void main(String[] args){//        testEnumSingleton();//        testEagerClassSingleton();        testLazyClassSingleton();    }            public static void testEnumSingleton() {        try {            System.out.println(EnumSingleton.INSTANCE.hello());        } catch (Throwable e) {                        //抛出的是Error:java.lang.ExceptionInInitializerError。如果“catch Exception”而不是“catch Throwable”的话,那就捕获不到出错信息了            System.out.println(e);        }    }        public static void testEagerClassSingleton() {        try {            System.out.println(EagerClassSingleton.getInstance().hello());        } catch (Throwable e) {                        //抛出的是Error:java.lang.ExceptionInInitializerError。如果“catch Exception”而不是“catch Throwable”的话,那就捕获不到出错信息了            System.out.println(e);        }    }        public static void testLazyClassSingleton() {        try {            System.out.println(LazyClassSingleton.getInstance().hello());        } catch (Exception e) {                        //抛出的是Exception:java.lang.ArithmeticException            System.out.println(e);        }    }}

对三种单例实现的方式(枚举、懒汉模式、饿汉模式)进行测试,发现只有懒汉模式是抛出Exception,其它两种都是抛出ExceptionInInitializerError。
这很好解释,因为懒汉模式是在方法(getInstance)调用中出错,而枚举方式和饿汉方式都是在类加载(Class initialization)时出错(The constructors are invoked when the enum class is initialized)。
类实例化出错显然更严重一些。


所以,在枚举方式和饿汉方式实现单例时,注意不要让构造函数抛出异常。

这就引申出第二个问题,在构造函数中要不要抛出异常呢?
《编写高质量代码-改善Java程序的151个建议》一书当中,作者在第114条建议中认为:不要在构造函数中抛出异常,尽管你可以这么做:
1.抛出unchecked Exception
例如:
public class Person {    public Person (int age) {        if (age < 0) {            throw new IllegalArgumentException("age cannot be less than 0");        }    }}

这也是比较常见的一种做法。

这个做法的问题是,调用者不知道是捕获这个异常还是不捕获。
捕获吧,要看文档或者源码才知道会抛什么异常,而且捕获的代码显得非常难看:

try {            Person p = new Person(20);        } catch (Exception e) {            e.printStackTrace();        }

不捕获吧,出现IllegalArgumentException时,后续代码就无法执行了。

2.抛出checked Exception
这种做法引起的主要问题是,子类的构造函数中也要抛出checked Exception

看看stackoverflow的讨论:
http://stackoverflow.com/questions/6086334/is-it-good-practice-to-make-the-constructor-throw-an-exception
得票最高的看法是:
当参数不合法时,抛出异常是唯一的、合理的做法。但是要选择合适的Exception,而不是直接抛出java.lang.Exception。
也有人认为,在构造函数中抛出异常是“坏的实践”:你应该在传递参数给构造函数之前,检查参数的合法性。

说法不一。

我认为还是按简单的来处理,也就是不抛异常,把参数合法性的检查交给调用方。例如平时代码中我们写得最多的当然是类似这样的:

public class Person {        private int age;    public Person (int age) {        this.age = age;    }}


没有进行参数检查。

如果某个类不是普通的java bean,而且参数合法性非常重要,那可以考虑在构造函数中检查参数并抛出合适的异常。

请勿发布不友善或者负能量的内容。与人为善,比聪明更重要!

留言需要登陆哦

技术博客集 - 网站简介:
前后端技术:
后端基于Hyperf2.1框架开发,前端使用Bootstrap可视化布局系统生成

网站主要作用:
1.编程技术分享及讨论交流,内置聊天系统;
2.测试交流框架问题,比如:Hyperf、Laravel、TP、beego;
3.本站数据是基于大数据采集等爬虫技术为基础助力分享知识,如有侵权请发邮件到站长邮箱,站长会尽快处理;
4.站长邮箱:[email protected];

      订阅博客周刊 去订阅

文章归档

文章标签

友情链接

Auther ·HouTiZong
侯体宗的博客
© 2020 zongscan.com
版权所有ICP证 : 粤ICP备20027696号
PHP交流群 也可以扫右边的二维码
侯体宗的博客