11

I would like to create a lambda function in Java 8, get it's classname and then later instantiate the function again from its classname.

This is what I try:

import java.util.function.Consumer;

public class SimpleLambda
{
    public static void call(String aLambdaClassName, String aArg) throws Exception
    {
        Class<Consumer<String>> lClass = (Class<Consumer<String>>) Class.forName(aLambdaClassName);
        Consumer<String> newlamba = lClass.newInstance();
        newlamba.accept(aArg);
    }

    public static void main(String[] args) throws Exception
    {
        {
            // Attempt with a static method as lambda
            Consumer<String> lambda = Host::action;
            String classname = lambda.getClass().getName();
            call(classname, "Hello world");
        }

        {
            // Attempt with a locally defined lambda
            Consumer<String> lambda = (s) -> { System.out.println(s); };
            String classname = lambda.getClass().getName();
            call(classname, "Hello world");
        }
    }
}

class Host {
    public static void action(String aMessage) {
        System.out.println(aMessage);
    }
}

However, with this code (in both variants, using the static method reference and using the locally declared lambda), I get an exception:

Exception in thread "main" java.lang.ClassNotFoundException: mypackage.SimpleLambda$$Lambda$1/471910020
    at java.lang.Class.forName0(Native Method)
    at java.lang.Class.forName(Class.java:264)
    at mypackage.SimpleLambda.main(SimpleLambda.java:12)

I would have expected that at I can at least re-instantiate the static method reference... nope, apparently not.

I have been using a similar approach with Groovy Closures and that worked nicely. So am I just doing something wrong with the Java 8 lambdas, or is it not possible to instantiate lambdas by name? I found some hints on the net that lambdas can be (de)serialized, so I would expect it should also be possible to instantiate them by name.

rec
  • 10,340
  • 3
  • 29
  • 43
  • 2
    What are you trying to do with them? Lambdas aren't instantiated, they just *are*. – 4castle Jul 20 '16 at 21:11
  • Essentially, I want to treat a lambda like a static inner class with a single function. I want to pass it per it's class name because I need to pass it through an API which only accepts String parameters. So once I get the name inside the API, I want to re-create the lambda again from its classname so that I can call it. Sounds weird? Yes, it is... but as I said: did it in Groovy and works nicely. I have a feeling it won't work in Java, but if somebody has a clue how to do it, would be really cool. – rec Jul 20 '16 at 21:14
  • I updated the example to make the use more clear. – rec Jul 20 '16 at 21:24
  • Hmm, this is really strange. [It works](https://ideone.com/tSDbes) if you declare the `Consumer` using an anonymous class instead. – 4castle Jul 20 '16 at 21:40
  • 1
    Uh, what is the problem you are trying to solve by creating them by name? – VLAZ Jul 20 '16 at 21:45
  • I cannot pass any instances of any class but primitive types and strings across my API boundary. So I want to convert a lambda into a String from from which I can recreate it again once I passed the API boundary. I could probably pass the code as string and then compile it inside my API and then call it... but I don't really want to have code embedded in a string ;) – rec Jul 20 '16 at 21:49
  • 1
    I think you should try with `LambdaMetafactory`. See [this answer](http://stackoverflow.com/a/26776042/1876620), it might point you in the right direction. – fps Jul 20 '16 at 22:03
  • 2
    I would be absolutely shocked if this worked. – Louis Wasserman Jul 20 '16 at 22:07
  • 2
    Lambdas are [anonymous functions](https://en.m.wikipedia.org/wiki/Anonymous_function), they shouldn't have names. This is contradictive, doesn't make sense to do it. Why is your API so restrictive? An API should be easy to use. That doesn't sound easy to use. – Vince Jul 20 '16 at 23:06

2 Answers2

5

Well, it is a special property of Oracle’s JRE/OpenJDK to use “anonymous classes”, which can’t be accessed by name at all. But even without this, there is no reason why this ought to work:

  • Class.forName(String) tries to resolve the class via the caller’s ClassLoader. So even if lambda expressions were implemented using ordinary classes, there were not accessible if loaded via a different ClassLoader
  • Class.newInstance() only works if there is a public no-arg constructor. You can’t assume that there is a no-arg constructor nor that it is public
  • The assumption that the entire function’s logic has to reside in a single class is wrong. A counter-example would be java.lang.reflect.Proxy which generates interface implementations delegating to an InvocationHandler. Trying to re-instantiate such a proxy via its class name would fail, because you need the to pass the actual InvocationHandler instance to the proxy’s constructor. In principle, the JRE specific lambda expression implementation could use a similar pattern

Considering the points above, it should be clear that you can’t say that it worked with inner classes in general. There are a lot of constraints you have to fulfill for that.


Regarding Serialization, it works for serializable lambda expressions, because the persistent form is completely detached from the runtime implementation class, as described in this answer. So the name of the generated class is not contained in the serialized form and the deserializing end could have an entirely different runtime implementation.

Community
  • 1
  • 1
Holger
  • 285,553
  • 42
  • 434
  • 765
-1

Store the lambda instances in Map, keyed on the instance name. You can make the map globally available trough a singleton wrapper class (just watch out for synchronization issues).

class LambdaMap {

    private HashMap<String, Consumer<String>> theMap;

    private LambdaMap() {
        theMap = new HashMap<>();
    }

    private static class INSTANCE_HOLDER {
        private static LambdaMap INSTANCE = new LambdaMap();
    }

    public static LambdaMap getInstance() {
        return INSTANCE_HOLDER.INSTANCE;
    }

    public Consumer<String> put(String key, Consumer<String> value) {
        return theMap.put(key, value);
    }

    public static void Call(String aLambdaClassName, String aArg) {
        Consumer<String> func = getInstance().theMap.get(aLambdaClassName);
        if (func != null) {
            func.accept(aArg);
        }
    }

}

class Host {
    public static void action(String aMessage) {
        System.out.println("Goodbye, " + aMessage);
    }
}

public class GlobalLambdas {

    public static void main(String[] args) {
        LambdaMap.getInstance().put("print greeting", s -> {
            System.out.println("Hello, " + s);
        });
        LambdaMap.getInstance().put("print goodbye", Host::action);

        LambdaMap.Call("print greeting", "John");
        LambdaMap.Call("print goodbye", "John");
    }
}

run:
Hello, John
Goodbye, John
NickF
  • 469
  • 4
  • 7