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.

9
  • 2
    What are you trying to do with them? Lambdas aren't instantiated, they just are. Commented Jul 20, 2016 at 21:11
  • 1
    Uh, what is the problem you are trying to solve by creating them by name? Commented Jul 20, 2016 at 21:45
  • 1
    I think you should try with LambdaMetafactory. See this answer, it might point you in the right direction. Commented Jul 20, 2016 at 22:03
  • 2
    I would be absolutely shocked if this worked. Commented Jul 20, 2016 at 22:07
  • 2
    Lambdas are anonymous functions, 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. Commented Jul 20, 2016 at 23:06

2 Answers 2

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.

Sign up to request clarification or add additional context in comments.

Comments

-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

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.