Reputation: 5888
I grok that for capturing lambdas, there needs to be an object allocated (be it Object[]
or some abc$Lambda$xyz
type). Is it possible to customize this process anyhow? Let's say I have this code:
private void test() {
int x = 5;
Supplier<Integer> supplier = () -> x;
foo(supplier); // potentially passes the supplier to another thread etc.
}
and I don't want to allocate the object capturing x
, but instead just get it from a pool and fill in the value; I also know that at some point I can return the object to the pool.
I could write
Supplier<Integer> supplier = pool.get(x, v -> v);
and I could have specialized versions for different argument types (as using Object...
would do the allocation (ok, there's a chance that the allocation would be eliminated by escape analysis...) but that would render the code quite unreadable. Therefore I am looking for a more aspect-like way.
Is such thing possible?
EDIT: to make the pool's functionality more obvious, the get
could be implemented as
class IntHolderSupplier implements Supplier<Integer> {
int value;
IntFunction<Integer> func;
@Override public Integer get() {
return func.apply(value);
}
}
class Pool {
Supplier<Integer> get(int arg, IntFunction<Integer> func) {
IntHolderSupplier holder = ...;
holder.value = arg;
holder.func = func;
return holder;
}
}
and I would need such holder with specific signatures for all possible types lambdas I want to use.
Maybe I have complicated the example a bit by providing the function - but I wanted to capture the fact that there may be a additional computation applied to the captured argument at time of Supplier.get()
invocation.
And please ignore the fact that the int is boxed which can produce an allocation.
Upvotes: 1
Views: 336
Reputation: 298409
To “pool capturing lambdas” is a misnomer. Lambda expressions are a technical solution to get an instance of a functional interface. Since you don’t pool the lambda expressions but the interface instances, dropping every technical aspect of lambda expressions, like immutability or the fact that the JRE/JVM controls their life time, you should name it “pool functional interface instances”.
So you can implement a pool for these instance, just like you can implement a pool for any kind of object. It’s rather unlikely that such a pool performs better than the JVM managed objects created for lambda expressions, but well, you can try it.
It’s simple, if you keep them immutable, thus, don’t try to reuse them for a different value, but only when encountering a previously captured value again. Here is an example for a Supplier
cache holding the suppliers for the last 100 encountered values:
class SupplierCache {
static final int SIZE = 100;
static LinkedHashMap<Object,Supplier<Object>> CACHE =
new LinkedHashMap<Object, Supplier<Object>>(SIZE, 1f, true) {
@Override
protected boolean removeEldestEntry(Map.Entry<Object, Supplier<Object>> eldest) {
return size() > SIZE;
}
};
@SuppressWarnings("unchecked")
static <T> Supplier<T> getSupplier(T t) {
return (Supplier<T>)CACHE.computeIfAbsent(t, key -> () -> key);
}
}
(add thread safety, if you need it). So by replacing Supplier<Integer> supplier = () -> x;
with Supplier<Integer> supplier = SupplierCache.getSupplier(x);
you’ll get the cache functionality and since you don’t have to release them, you don’t have to make error prone assumptions about its life cycle.
Creating a pool of objects implementing Supplier
and returning the value of a mutable field, so that you can manually reclaim instances, is not too hard if you simply create an ordinary class implementing Supplier
, but well, you open a whole can of worms with manual memory management including the risk of reclaiming an object still being in use. These objects can’t be shared like the immutable object like in the example above. And you replace object allocation with the action of finding a reclaimable pooled instance plus the action of explicitly putting back an instance after use—there’s no reason why this should be faster.
Upvotes: 1