我知道 Java 编译器会根据上下文和闭包为 lambda 函数生成不同的类。当我接收 lambda 作为参数(使用Consumer<>类)时,我可以知道参数的生命周期吗?
例如,我有以下Observable类,它保持对其观察的弱引用。
class Observable {
private final List<WeakReference<Consumer<Object>>> observables = new ArrayList<>();
private Object obj;
public Observable(Object obj){
this.obj = obj;
}
public void observe(Consumer<Object> cons){
this.observables.add(new WeakReference<>(cons));
}
public void set(Object obj){
this.obj = obj;
// notify observes
for(WeakReference<Consumer<Object>> cons : this.observables){
if(cons.get() != null)
cons.get().accept(this.obj);
// clearing the non-existing observes from the list is ommited for simplicity
}
}
}
Run Code Online (Sandbox Code Playgroud)
现在我使用它如下。
public class Main {
public static void main(String[] args) {
Object c = new Object();
Observable obs = new Observable(c);
new ContainingClass(obs);
obs.set(c);
System.gc();
obs.set(c);
}
}
Run Code Online (Sandbox Code Playgroud)
代码只是创建对象及其观察者并创建ContainingClass(定义如下)观察者。然后对象被设置一次,垃圾收集器显式调用(因此创建的 ContainingClass 被删除)并第二次设置对象。
现在,只要 lambda 是特定于实例的(引用 this 或其实例方法),它只会被调用一次(因为它被 GC 销毁)。
public class ContainingClass {
public ContainingClass(Observable obs){
obs.observe(this::myMethod);
}
private void myMethod(Object obj) {
System.out.println("Hello here");
}
}
Run Code Online (Sandbox Code Playgroud)
public class ContainingClass {
private Object obj;
public ContainingClass(Observable obs){
obs.observe(obj -> {
this.obj = obj;
System.out.println("Hello here");
});
}
}
Run Code Online (Sandbox Code Playgroud)
但是当 lambda 变为静态时,它会被调用两次,即使在 GC 之后也是如此。
public class ContainingClass {
public ContainingClass(Observable obs){
obs.observe((obj) -> System.out.println("Hello here"));
}
}
Run Code Online (Sandbox Code Playgroud)
对这个 lambda 的引用永远不会被销毁,因此每次ContainingClass创建实例时都会添加为观察者。结果,它会一直卡在观察者中,直到程序结束。
有没有办法检测到这一点并至少显示一个警告,即永远不会删除 lambda?
我发现的一件事是,具有实例生命周期的 lambda 具有arg$1属性,因此我可以询问属性的数量。
public void observe(Consumer<Object> cons){
if(cons.getClass().getDeclaredFields().length == 0)
System.out.println("It is static lifetime lambda");
this.observables.add(new WeakReference<>(cons));
}
Run Code Online (Sandbox Code Playgroud)
它是一种通用方法吗?可能会出现这不起作用的情况吗?
我认为一个好的解决方案是 @Olivier 暗示的:您可以使用一种方法返回一个对象,remove该方法在调用时将您从列表中删除Consumer,如下例所示:
@FunctionalInterface
public interface Registration {
void remove();
}
Run Code Online (Sandbox Code Playgroud)
class Observable {
private final List<Consumer<Object>> observables = new ArrayList<>();
private Object obj;
public Observable(Object obj) {
this.obj = obj;
}
public Registration observe(Consumer<Object> cons) {
this.observables.add(cons);
return () -> this.observables.remove(cons);
}
public void set(Object obj) {
[...]
}
}
Run Code Online (Sandbox Code Playgroud)
另一种方法是检查 lambda 所属的类是否是静态的,如 @kutschkem 所建议的,但如果有好的替代方案,我不喜欢诉诸内省。
正如 @shalk 已经指出的,依赖WeakReference处理 GC 可能会导致不良行为,因为无法确保您的内容Consumer不会在其他地方被引用(可能是错误的)。