I know the Java compiler generates different classes for lambda functions depending on the context and closure they have. When I receive the lambda as a parameter (using the Consumer<>
class), may I know the lifetime of the parameter?
For example, I have the following Observable
class, that keeps a weak reference to its observes.
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
}
}
}
Now I use it as follows.
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);
}
}
The code just creates the object and its observer and creates ContainingClass
(definition follows) that observes. Then the object is set once, garbage collector explicitly called (so the created ContainingClass is deleted) and set the object a second time.
Now, as long as the lambda is instance-specific (reference either this or its instances method) it's called only once (because it is destroyed by the 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");
});
}
}
But as the lambda becomes static, it is called twice, even after GC.
public class ContainingClass {
public ContainingClass(Observable obs){
obs.observe((obj) -> System.out.println("Hello here"));
}
}
The reference to this lambda is never destroyed and therefore add as an observer every time ContainingClass
instance is created. As a result, it will be stuck in observers until the program ends.
Is there a way to detect this and at least show a warning, that the lambda will be never removed?
One thing I figured out is that lambda with instance lifetime has arg$1
property, so I can ask about the number of properties.
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));
}
Is it a universal approach? May there be a situation when this doesn't work?