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

首先从使用enum实现单例说起。

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

public enum Singleton {
INSTANCE;
}

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

3.enum单例是线程安全的

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

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

枚举值

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

说法不一。

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

public class Person {

private int age;

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


没有进行参数检查。

如果某个类不是普通的java bean,而且参数合法性非常重要,那可以考虑在构造函数中检查参数并抛出合适的异常。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 是的,我熟悉ThreadPoolExecutor的构造函数参数。 下面是ThreadPoolExecutor的构造函数的参数的说明: 1. `max_workers`: 这个参数指定了线程池最多可以有多少个线程同时执行任务。如果设置为`None`,则使用默认值,即最多可以有多少线程同时执行任务取决于系统资源。 2. `thread_name_prefix`: 这个参数指定了线程名称的前缀,默认情况下不指定前缀。 3. `initializer`: 这个参数指定了新创建的线程的初始化函数,如果没有指定,则默认为`None`。 4. `initargs`: 这个参数是一个元组,用于提供给初始化函数的参数。 5. `daemon`: 这个参数指定了线程是否为守护线程。如果设置为`True`,则线程将在主线程结束时终止;如果设置为`False`,则线程将在主线程结束时继续执行。 希望这对你有所帮助。 ### 回答2: ThreadPoolExecutor是Java的线程池类,通过它可以方便地管理线程的创建与销毁。构造函数包含以下几个参数,我能解释它们的作用: 1. corePoolSize(核心线程数):指定线程池的核心线程数,即线程池能保持活动状态的线程数量。当任务数量大于核心线程数时,线程池创建新的线程来处理任务,直到达到最大线程数。 2. maximumPoolSize(最大线程数):指定线程池允许存在的最大线程数。当任务数量大于核心线程数且小于最大线程数时,线程池创建新的线程来处理任务;当任务数量大于最大线程数时,任务被放入阻塞队列等待。 3. keepAliveTime(线程空闲时间):当线程处于空闲状态且线程池的线程数量大于核心线程数时,多余的线程在经过keepAliveTime时间后被销毁,以控制线程池的大小。 4. unit(线程空闲时间单位):用于指定keepAliveTime的时间单位,可以是TimeUnit的几种枚值,如秒、毫秒等。 5. workQueue(工作队列):用于存放等待执行的任务的阻塞队列。当任务数量大于核心线程数时,新的任务被放入工作队列等待执行。 6. threadFactory(线程工厂):用于创建新的线程。 7. rejectExecutionHandler(饱和策略):当线程池和工作队列都满了,无法执行新的任务时,可以通过饱和策略来处理新任务的方法。可以选择的饱和策略有四种,分别是AbortPolicy(直接异常)、CallerRunsPolicy(使用调用者所在的线程来执行任务)、DiscardPolicy(直接弃任务)和DiscardOldestPolicy(从队列头部弃一个最旧的任务)。 通过合理设置这些参数,可以根据实际需求来管理线程池的大小、控制任务的执行方式,以及避免系统资源的浪费和溢。 ### 回答3: ThreadPoolExecutor是线程池的实现类,在其构造函数有多个参数,下面对每个参数的作用进行解释。 1. corePoolSize(核心线程数):定义了线程池核心线程的最大数量。当任务数大于核心线程数时,线程池创建新的线程来处理任务,直到达到核心线程数为止。 2. maximumPoolSize(最大线程数):定义了线程池最多能容纳的线程数量。当任务数大于最大线程数并且阻塞队列已满时,线程池创建新的线程来处理任务。当任务数减少时,线程池回收多余的线程,直到线程数量不超过核心线程数。 3. keepAliveTime(线程存活时间):指定了当线程池线程数量超过核心线程数时,多余的空闲线程在被回收之前等待新任务的最长时间。超过这个时间后,空闲线程被终止。 4. unit(线程存活时间单位):用于指定keepAliveTime参数的时间单位,比如毫秒、秒等。 5. workQueue(阻塞队列):用于存放还未执行的任务的队列。当任务数大于核心线程数时,新的任务被放入阻塞队列,等待被执行。 6. threadFactory(线程工厂):用于创建新线程的工厂类。可以通过自定义线程工厂来指定线程的名称、优先级等。 7. handler(饱和策略):用于处理当线程池和阻塞队列都满时的策略。常用的饱和策略有以下四种:ThreadPoolExecutor.AbortPolicy(默认),直接RejectedExecutionException异常;ThreadPoolExecutor.CallerRunsPolicy,使用调用者所在的线程来执行任务;ThreadPoolExecutor.DiscardOldestPolicy,丢弃队列最早的任务;ThreadPoolExecutor.DiscardPolicy,直接丢弃新的任务。 通过合理的配置这些参数,可以根据具体的业务需求来创建一个高效可靠的线程池。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值