我有一个Swing操作类,它的工作原理如下:
package org.trypticon.hex.gui.datatransfer;
import java.awt.Toolkit;
import java.awt.datatransfer.Clipboard;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.FlavorListener;
import java.awt.event.ActionEvent;
import javax.annotation.Nonnull;
import javax.swing.Action;
import javax.swing.JComponent;
import javax.swing.TransferHandler;
import org.trypticon.hex.gui.Resources;
import org.trypticon.hex.gui.util.FinalizeGuardian;
import org.trypticon.hex.gui.util.FocusedComponentAction;
public class PasteAction extends FocusedComponentAction {
private final FlavorListener listener = (event) -> {
// this method in the superclass calls back `shouldBeEnabled`
updateEnabled();
};
@SuppressWarnings({"UnusedDeclaration"})
private final Object finalizeGuardian = new FinalizeGuardian(() -> {
Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
clipboard.removeFlavorListener(listener);
});
public PasteAction() {
Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
clipboard.addFlavorListener(listener);
}
@Override
protected boolean shouldBeEnabled(@Nonnull JComponent focusOwner) {
TransferHandler transferHandler = focusOwner.getTransferHandler();
if (transferHandler == null) {
return false;
}
Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
DataFlavor[] flavorsInClipboard = clipboard.getAvailableDataFlavors();
return transferHandler.canImport(focusOwner, flavorsInClipboard);
}
@Override
protected void doAction(@Nonnull JComponent focusOwner) throws Exception {
Action action = TransferHandler.getPasteAction();
action.actionPerformed(new ActionEvent(
focusOwner, ActionEvent.ACTION_PERFORMED, (String) action.getValue(Action.NAME)));
}
}
这里所指的FinalizeGuardian
当前使用finalize()
:来实现
package org.trypticon.hex.gui.util;
public final class FinalizeGuardian {
private final Runnable cleanupLogic;
public FinalizeGuardian(Runnable cleanupLogic) {
this.cleanupLogic = cleanupLogic;
}
@Override
protected final void finalize() throws Throwable {
try {
cleanupLogic.run();
} finally {
super.finalize();
}
}
}
因此,出于显而易见的原因,我想切换到使用Cleaner
。
第一次尝试是这样的:
package org.trypticon.hex.gui.util;
import java.lang.ref.Cleaner;
public final class FinalizeGuardian {
private static final Cleaner cleaner = Cleaner.create();
public FinalizeGuardian(Runnable cleanupLogic) {
cleaner.register(this, cleanupLogic);
}
}
问题是,现在物体永远不会变成幻影可及,因为:
Cleaner
本身强烈引用了cleanupLogic
cleanupLogic
保存对listener
的引用,以便删除侦听器listener
保存对操作类的引用,以便对其调用updateEnabled
- 操作类保存对
FinalizeGuardian
的引用,这样就不会过早地收集它
因为FinalizeGuardian
本身永远不会变为幻影可达,所以永远不会调用清洁器。
因此,我想知道的是,是否有一种方法可以对其进行重组,以遵循使Cleaner
正确工作所需的规则,即不会通过将侦听器移动到我的操作类之外来破坏封装?
只要FlavorListener
在事件源处注册,它就永远不会变得不可访问(只要事件源仍然可访问(。这意味着侦听器更新的PasteAction
实例也永远不会变得不可访问,因为侦听器对它有很强的引用
解耦其可达性的唯一方法是更改侦听器,只保留对其更新的对象的弱引用。请注意,当使用Cleaner
而不是finalize()
时,FinalizeGuardian
已过时。
代码看起来像
public class PasteAction extends FocusedComponentAction {
static FlavorListener createListener(WeakReference<PasteAction> r) {
return event -> {
PasteAction pa = r.get();
if(pa != null) pa.updateEnabled();
};
}
private static final Cleaner CLEANER = Cleaner.create();
static void prepareCleanup(
Object referent, Clipboard clipboard, FlavorListener listener) {
CLEANER.register(referent, () -> clipboard.removeFlavorListener(listener));
}
public PasteAction() {
Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
FlavorListener listener = createListener(new WeakReference<>(this));
clipboard.addFlavorListener(listener);
prepareCleanup(this, clipboard, listener);
}
…
请注意,关键部分已被放入static
方法中,以使this
参考的意外捕获成为不可能。这些方法获得了完成其工作所需的最小值,createListener
只接收到对该操作的弱引用,prepareCleanup
将引用作为Object
,因为清理操作不能访问该操作的任何成员,而是接收作为单独参数的必要值。
但在展示了更清洁的使用可能是什么样子之后,我不得不强烈反对使用这种机制,尤其是作为唯一的清洁机制。在这里,它不仅会影响内存消耗,还会影响程序的行为,因为只要引用没有被清除,侦听器就会不断得到通知,并不断更新过时的对象。
由于垃圾收集器只由内存需求触发,因此它完全有可能不运行或不关心这几个对象,因为有足够的可用内存,而CPU负载过重,因为许多过时的侦听器正忙于更新过时的对象(我在实践中见过这样的场景(。
更糟糕的是,对于并发垃圾收集器,它们的收集周期甚至可能与侦听器触发的updateEnabled()
的实际过时执行重复重叠(因为引用尚未清除(。这将积极防止这些对象的垃圾收集,即使垃圾收集器运行并以其他方式收集它们也是如此。
简而言之,这样的清理不应该依赖于垃圾收集器。