我可以在运行时查询lambda函数的生存期吗



我知道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
}
}
}

现在我使用它如下。

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);
}
}

该代码只创建对象及其观察者,并创建观察者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");
}
}
public class ContainingClass {
private Object obj;
public ContainingClass(Observable obs){
obs.observe(obj -> {
this.obj = obj;
System.out.println("Hello here");
});
}
}

但是,当lambda变为静态时,它会被调用两次,甚至在GC之后也是如此。

public class ContainingClass {
public ContainingClass(Observable obs){
obs.observe((obj) -> System.out.println("Hello here"));
}
}

对该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));
}

这是一种普遍的方法吗?可能会有这种情况不起作用吗?

我认为一个好的解决方案是@Olivier暗示的解决方案:您可以使用remove方法返回一个对象,该方法在调用时将您的Consumer从列表中删除,如以下示例:

@FunctionalInterface
public interface Registration {
void remove();
} 
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) {
[...]
}
}

另一种选择是检查lambda所属的类是否是静态的,正如@kutschkem所建议的那样,但如果有好的选择,我不喜欢求助于内省。

正如@shalk所说,依赖WeakReference来处理GC可能会导致不必要的行为,因为无法确保您的Consumer没有被引用(可能是错误的(。

你的问题与这个问题相似,所以这里的答案也适用。

静态嵌套类有一个可以检查的标志:

Modifier.isStatic(clazz.getModifiers())   // returns true if a class is static, false if not

最新更新