0

I have class names unknown to my factory class that I obtained via runtime input.

Currently I have solution to use reflection to create instances of these classes in my factory class, but I want to see if it is possible to pass the method reference new of these classes to my factory class so that I don't need to use reflection. e.g.:

public interface MyFactory {
    MyClass getInstance(String s);
}
public static MyFactory getRuntimeClass(Class<? extends MyClass> cls) {
    return cls::new;
}
public static void main(String[] args) {
    MyClass mySubClass = getRuntimeClass(Class.forName(args[0]).asSubClass(MyClass.class)).getInstance("test");
}

The above does not work because the method reference new is not obtainable from the Class object cls.

How do you fix the above with method reference, without reverting back to use reflection?

P.S. Yes I have code to check the input, all the subclasses all have a constructor that takes a String as input.

user1589188
  • 5,316
  • 17
  • 67
  • 130
  • I think you misunderstand what a method reference is. Just use your standard reflection, `Class#newInstnace`. – Sotirios Delimanolis Feb 05 '17 at 16:37
  • 1
    `cls::new` tries to obtain a reference to constructor of `Class`. It fails because `Class` doesn't have public constructors accepting single `String` argument. There's no way of doing what you want with method references. I suggest just going with what @SotiriosDelimanolis suggests. – M. Prokhorov Feb 06 '17 at 09:43
  • 1
    @Sotirios Delimanolis: `Class.newInstance` only supports the default constructor. – Holger Feb 06 '17 at 09:50

1 Answers1

3

Well, working with types specified at runtime, i.e. unknown at compile-time, is the definition of Reflection. You can’t do reflective operations without Reflection.

What you can do, is to generate an instance of your functional interface which can instantiate the class without using reflection, i.e. having everything resolved prior to calling the interface method, much like the method reference to a member known at compile time works at runtime:

public static MyFactory getRuntimeClass(Class<? extends MyClass> cls) {
    try {
        MethodHandles.Lookup l = MethodHandles.lookup();
        MethodHandle c = l.findConstructor(cls,
                             MethodType.methodType(void.class, String.class));
        MethodType ft = c.type().changeReturnType(MyClass.class);
        return (MyFactory)LambdaMetafactory.metafactory(l, "getInstance",
            MethodType.methodType(MyFactory.class), ft, c, ft).getTarget().invokeExact();
    }
    catch(RuntimeException | Error t) {
        throw t;
    }
    catch(Throwable t) {
        throw new IllegalArgumentException(t);
    }
}

Of course, generating the instance is a reflective operation with all of its disadvantages, like detecting errors at runtime only. Since there is no safety of compile-time checks, there wouldn’t be any generic type safety, if you try this with a generic functional interface.

If you change the interface and forget to adapt this code, it might happen, that you even don’t spot the mistake when executing this getRuntimeClass method, but only when you actually try to invoke the interface method on the generated instance.

Unlike real method references, this doesn’t bear any caching. That’s ok, if you only map some class names to MyFactory instances at initialization time and keep the MyFactory instances. But if you find yourself having to map class names to MyFactory instances repeatedly, perhaps with the same names, you might want to remember them. The easiest solution is:

static ClassValue<MyFactory> MY_FACTORIES = new ClassValue<MyFactory>() {
    protected MyFactory computeValue(Class<?> type) {
        return getRuntimeClass(type.asSubclass(MyClass.class));
    }
};

which can be used like

MyClass mySubClass = MY_FACTORIES.get(Class.forName(args[0])).getInstance("test");
Holger
  • 285,553
  • 42
  • 434
  • 765