`

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

阅读更多
首先从使用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,而且参数合法性非常重要,那可以考虑在构造函数中检查参数并抛出合适的异常。

2
3
分享到:
评论

相关推荐

    Java 实例 - Enum(枚举)构造函数及方法的使用源代码-详细教程.zip

    2. `valueOf(String)`:根据枚举名获取枚举实例,如果找不到对应的枚举值,则会抛出`IllegalArgumentException`。 ```java Color color = Color.valueOf("RED"); ``` 3. 自定义方法:枚举类型可以像普通类一样定义...

    FileStream 构造函数

    构造函数可能会抛出多种异常,如: - `ArgumentNullException`:当`path`为空引用(在Visual Basic中为`Nothing`)时。 - `ArgumentException`:当`path`无效(如空字符串、仅空格、含有非法字符等)时。 - `...

    C++ 程序设计课件:第十章 异常处理.ppt

    第二个`try`块尝试将栈中所有元素弹出到数组`b`,如果栈空,`Pop`函数会抛出`popOnEmpty&lt;int&gt;`异常,同样会被相应的`catch`子句捕获并处理。 异常处理不仅限于类对象,还可以抛出基本类型(如枚举、整数)的对象。...

    JAVA基础-反射-枚举

    如果没有无参构造函数,则会抛出异常。 #### 三、Constructor类 1. **Constructor类**:`Constructor`类表示类中的构造函数。每个`Constructor`对象代表一个具体的构造函数。 - **获取Constructor对象**: - `...

    C 程序设计课件:第十章 异常处理.ppt

    在`throw`表达式中,通过调用构造函数创建异常对象。 4. **栈展开与异常捕获**:当异常被抛出时,程序会沿着函数调用栈回溯,寻找合适的`catch`子句进行处理。这个过程称为栈展开(stack unwinding),在此过程中,...

    软件开发中异常分析

    例如,在Hibernate框架中,如果尝试通过getter方法获取一个并不存在的属性值,则会抛出此类异常。具体到案例中,当调用`getGAccountId()`时,如果对应的JavaBean模型中没有定义相应的`GAccountId`属性,则会引发异常...

    Java枚举类型.pdf

    这显著提高了代码的健壮性,因为编译器会在尝试赋值非法值时抛出错误。 枚举类型还支持方法和字段。你可以为枚举添加方法,比如计算平均分或判断是否及格。同时,枚举还可以有构造函数,尽管它们是私有的。这样,...

    C++程序设计练习题

    - B:引发异常,构造函数本身不会引发异常,但在执行某些操作(如分配内存)失败时可能会抛出异常。 - C:设置异常安全的状态,确保即使构造过程失败也能使对象处于一个合理且可预测的状态。 - D:设置`this`指针...

    effective c++ 第三版 高清 带书签

    在析构函数中抛出异常可能会导致未定义行为,因为当异常在析构函数中抛出时,C++标准并不保证有其他异常会被正确处理。条款8建议在析构函数中处理错误,而不是抛出异常。 9. 条款9:理解new和delete的合理替换时机 ...

    .Net基础视频教程之8-面向对象初级.zip

    `try`块中包含可能抛出异常的代码,`catch`块捕获并处理异常,`finally`块确保即使发生异常也能执行的代码。 10. 构造函数链:在继承关系中,当子类实例化时,会先调用父类的构造函数,然后是自身的构造函数。这...

    第11章抽奖——随机数与枚举.ppt

    如果参数为空,可以抛出异常表示没有数据。 **11.4 枚举类型** 枚举类型在Java中是一种特殊的类,用于定义一组固定的常量。在抽奖系统中,枚举可以用来表示奖项等级或其他特定状态。例如,可以定义一个`PrizeLevel...

    effective的学习心得,希望可以帮助大家

    - 析构函数不应抛出异常,因为这可能导致堆栈展开时的问题。 - 避免在构造函数和析构函数中调用虚函数,因为此时对象可能未完全构造或正在析构。 10. **赋值运算符(Operator=)**: - 赋值运算符应返回`*this`...

    .NET-第5部分.ppt

    10. `System.TypeInitializationException`:当静态构造函数引发异常且没有被捕获的catch子句时抛出。 异常处理的机制主要是通过`try-catch-finally`语句来实现的。`try`块包含可能抛出异常的代码,`catch`块用来...

    12 @Autowired是如何工作的?-Spring注解源码深度揭秘-慕课专栏1

    对于构造函数,如果有多个符合类型的构造函数,Spring会根据参数类型和名称进行精确匹配,或者如果没有明确匹配,可以根据`required`属性决定是否抛出异常。 在实际应用中,`@Autowired`可以配合`@Qualifier`一起...

    ASP.NET程序中常用代码汇总(二)

    在`AppException`的构造函数中,如果配置文件中启用了事件日志(`ApplicationConfiguration.EventLogEnabled`),则会自动将异常信息记录到Windows NT/2000的应用程序日志中。这增强了异常的可追踪性,有助于在出现...

    C#面向对象程序设计 (2).docx

    C是显式转换,如果转换失败,会抛出异常。选项D是错误的,因为没有`Student.Convert`这样的静态方法。 以上内容详细解释了C#中面向对象编程的一些关键概念,包括.NET框架、互操作性、命名空间、访问修饰符、构造...

    C#常用函数和办法集

    `try-catch`块用于捕获并处理异常,`throw`关键字用于抛出异常,`finally`块则确保即使有异常发生也能执行清理代码。 文件I/O操作在C#中也很常见。`File.WriteAllText()`用于写入文本文件,`File.ReadAllLines()`...

Global site tag (gtag.js) - Google Analytics