How to invoke constructor using LambdaMetaFactory?
Asked Answered
W

1

9

I want to try and avoid reflection for invoking a constructor and am trying to follow the LamdaMetaFactory approach taken in this post - Faster alternatives to Java's reflection

My class that I want to construct looks like:

interface DBBroker {}

public class NativeBroker implements DBBroker {
    public NativeBroker(BrokerPool brokerPool, final Configuration configuration) {
    }
}

Using LambaMetaFactory I am trying to construct a BiFunction<BrokerPool, Configuration, DBBroker> to replace a direct call to the constructor.

My code so far looks like:

Class<? extends DBBroker> clazz =
    (Class<? extends DBBroker>) Class.forName("org.exist.storage.NativeBroker");

MethodHandles.Lookup lookup = MethodHandles.lookup();
MethodHandle mh =
    lookup.findConstructor(clazz, MethodType.methodType(void.class, new Class[] {BrokerPool.class, Configuration.class}));

BiFunction<BrokerPool, Configuration, DBBroker> constructor 
    (BiFunction<BrokerPool, Configuration, DBBroker>)
        LambdaMetafactory.metafactory(
                    lookup, "apply", MethodType.methodType(BiFunction.class),
                    mh.type(), mh, mh.type()).getTarget().invokeExact();

final DBBroker broker = constructor.apply(database, conf);

Unfortunately this returns the error -

AbstractMethodError: Method org/exist/storage/BrokerFactory$$Lambda$55.apply(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object; is abstract.

I have tried modifying the various types passed to LambdaMetafactory.metafactory but I can't quite seem to get this right, and the Javadoc is certainly not easily understood.

Can someone advise please?

Whenever answered 7/5, 2018 at 9:43 Comment(0)
I
6

The mistake you made is in the SAM type you used. The erased type of the apply method has to be used for that, so that would be

methodType(Object.class, Object.class, Object.class)

But you could also use mh.type().generic() which returns the same thing:

BiFunction<BrokerPool, Configuration, DBBroker> constructor =
(BiFunction<BrokerPool, Configuration, DBBroker>)
    LambdaMetafactory.metafactory(
                lookup, "apply", methodType(BiFunction.class),
                mh.type().generic(), mh, mh.type()).getTarget().invokeExact();
//              ^^^^^^^^^^^^^^^^^^^
Inheritance answered 7/5, 2018 at 9:59 Comment(8)
Usually, the classes generated by LambdaMetafactory are more efficient than the proxies provided by MethodHandleProxies, so it’s not a question of whether you need the LambdaMetafactoryJocundity
@Jocundity Huh yeah, I just wondered about the implementation myself and looked it up. It just uses Proxy.newInstance internally :/ I still think it's simpler, but since the goal is performance I'll remove that part.Inheritance
@JornVernee mh.type().generic() is what I wanted to post before your edit! +1Nonmetallic
For most cases, erase() is closer to the intention as generic().Jocundity
@JornVernee Now wondering how to do this when the number of args to the constructor is not known until runtime. e.g. my construct function, is supplied with: Class<?>[] paramClasses and Object[] paramValues.Whenever
@Whenever Afaik there is no way to find the constructor method handle if you don't know the exact type, although you could use unreflectConstructor to convert a Constructor<...> object to a method handle. But if you're only calling it once and then discarding it, you might as well call newInstance directly.Inheritance
@Whenever Deriving the constructor type from the paramClasses would work as long as they match the constructors parameter types exactly (and are not sub types). But you still wouldn't know the interface to use as a wrapper. I guess you could just use the method handle directly.Inheritance
Thanks, that was naive conclusion too - that the Interface type could not be derived statically.Whenever

© 2022 - 2024 — McMap. All rights reserved.