有两个很好的(大多数人认为的)Java 实践我尝试结合但失败了。
- 永远不要在构造函数中泄漏this。
- 使用枚举代替单例模式。
所以,我想要一个在创建后立即监听某个事件的单例。这是一个例子。一、事件监听接口(interface):
public interface EventListener {
void doSomething();
}
然后,事件生产者:
public class EventProducer implements Runnable{
private EventListener listener;
public EventProducer(EventListener listener) {
if (listener == null) {
throw new NullPointerException("Listener should not be null.");
}
this.listener = listener;
}
@Override
public void run() {
listener.doSomething(); //This may run before the listener is initialized.
do {
long startTime = System.currentTimeMillis();
long currentTime;
do {
currentTime = System.currentTimeMillis();
} while ((currentTime - startTime) < 1000);
listener.doSomething();
} while (!Thread.currentThread().isInterrupted());
listener = null; //Release the reference so the listener may be GCed
}
}
然后,枚举(如第二个列出的 java 实践所建议的那样):
public enum ListenerEnum implements EventListener{
INSTANCE;
private int counter;
private final ExecutorService exec;
private ListenerEnum() {
EventProducer ep = new EventProducer(this); //Automatically unregisters when the producer is done.
counter = 0;
exec = Executors.newSingleThreadExecutor();
exec.submit(ep);
}
@Override
public void doSomething() {
System.out.println("Did something.");
counter++;
if (counter >= 5) {
exec.shutdownNow();
}
}
}
最后,让事情开始:
public class TestRunner {
public static void main(String[] args) {
ListenerEnum.INSTANCE.doSomething();
}
}
问题出在 ListenerEnum 构造函数的第一行,因为我们正在泄漏 this,因此不符合第一个列出的 java 实践。这就是为什么我们的事件生产者可以在构造监听器之前调用监听器的方法。
我该如何处理?通常我会使用 Builder 模式,但枚举怎么可能呢?
编辑: 对于重要的那些,我程序中的事件生产者实际上扩展了一个 BroadcastReceiver,所以我的枚举不能是事件生产者,它们必须是分开的。生产者是在枚举的构造函数中创建的(作为示例),稍后以编程方式注册。所以我实际上没有泄漏this的问题。不过,我想知道我是否可以避免它。
编辑 2:
好的,既然有解决我问题的建议,我想澄清一些事情。首先,大多数建议都是解决方法。他们建议以完全不同的方式做同样的事情。我很欣赏这些建议,并且可能会接受一个作为答案并实现它。但真正的问题应该是“如何使用枚举实现构建器模式?”我已经知道的答案和人们的建议是“你不知道,用其他方式做”。有没有人可以发布类似“你这样做!你这样做。”这样的话?
我被要求提供接近我的实际用例的代码。修改如下:
public enum ListenerEnum implements EventListener{
INSTANCE;
private EventProducer ep;
private int counter;
private ExecutorService exec;
private ListenerEnum() {
ep = new EventProducer(this); //Automatically unregisters when the producer is done.
counter = 0;
}
public void startGettingEvents() {
exec = Executors.newSingleThreadExecutor();
exec.submit(ep);
}
public void stopGettingEvents() {
exec.shutdownNow();
}
@Override
public void doSomething() {
System.out.println("Did something.");
counter++;
if (counter >= 5) {
stopGettingEvents();
}
}
}
还有这个:
public class TestRunner {
public static void main(String[] args) {
ListenerEnum.INSTANCE.startGettingEvents();
}
}
现在我要做的就是将 EventsProducer 创建移动到 startGettingEvents() 方法来解决我的问题。就是这样。但这也是一种解决方法。我想知道的是:一般来说,您如何避免在监听器枚举的构造函数中泄漏 this,因为您不能使用 Builder 模式?或者你真的可以通过枚举使用 Builder 模式吗?是否只能根据具体情况通过变通办法来完成?还是有一种我不知道的通用方法来处理这个问题?
最佳答案
只需创建一个静态初始化 block :
public enum ListenerEnum implements EventListener{
INSTANCE;
private int counter;
private static final ExecutorService exec; //this looks strange. I'd move this service out of enum.
private static final EventProducer ep;
static{
exec = Executors.newSingleThreadExecutor();
ep = new EventProducer(INSTANCE); //Automatically unregisters when the producer is done.
exec.submit(ep);
}
@Override
public void doSomething() {
System.out.println("Did something.");
counter++;
if (counter >= 5) {
exec.shutdownNow();
}
}
}
只要枚举值是最终的和静态的,它们就会 are initialized before静态初始化 block 。如果你反编译枚举,你会看到一个初始化 block :
static{
INSTANCE = new ListenerEnum();
exec.submit(INSTANCE.ep);
}
关于在创建时注册为监听器的 Java 枚举,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/27440701/