Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What's the proper way to use reflection to instantiate objects of unknown classes at runtime?

I am working on a Configuration Loader class so that I can change the parameters of my program via an external text file (config.txt) rather than having to recompile my code with every change I make.

It has been suggested that I use Java's Reflection to do this, but I'm a little confused as to how I might actually implement this.

I have been able to successfully extract the class name and the arguments for its constructor from my text file, but how do I go from this to an instantiated object?

here's what I have of my method so far:

public void loadObject(String classString, HashMap hm)
  {
  String className = props.getProperty(classString);
  Class c = Class.forName(className);
  }

classString is a string containing the name of the class, and hm is a hashmap where the class' constructor parameters map to their intended values.

I.e., for class Foo (int xPos, float yPos), "xPos" would map to a string of the intended int, and "yPos" maps to a string of the intended float. I want to be able to return, new Foo(hm.get"xPos".toInt, hm.get"yPost".toFloat), but I'm unsure how to dynamically use a constructor like that (the issue is, there are multiple possible classes -- perhaps it's a bar instead of a foo, for instance).

I know that its possible to do an if/else based off the classString, and simply call the proper constructor after identifying it that way, but I am looking to create a more extensible code that doesn't have to be rewritten every time I add a new class to the program.

All of the possible objects inherit from a single parent object.

like image 603
Raven Dreamer Avatar asked Oct 08 '11 16:10

Raven Dreamer


2 Answers

You would use Class.getConstructor(Class<?>... parameterTypes) to get a reference to the constructor followed by Constructor.newInstance(Object... initargs).

However I would suggest taking a look at a dependency injection framework such as Spring or Guice as it sounds like what you are creating is a basic version of what they do.

Upon request for expanding this answer:

Class c = Class.forName(name);
Constructor ctor = c.getConstructor(Integer.class, Integer.class);
Integer param1 = hm.get("xPos") ...;
Integer param2 = hm.get("yPos") ...;
Object instanceOfTheClass = ctor.newInstance(param1, param2);

Of course instead of param1 and param2 you would create an array of arguments based upon what was in the input file (the same goes for the arguments to getConstructor()), etc.

like image 88
matt b Avatar answered Oct 27 '22 12:10

matt b


Here's an example of doing it from program arguments:

import java.lang.reflect.Constructor;
import java.util.*;

public class InstantiateWithReflectionIncludingArgs {
    public static void main(String[] args) throws Exception {
        String className = args[0];
        List<Object> argList = new ArrayList<Object>();
        if (args.length > 1) {
            argList.addAll(Arrays.asList(args).subList(1, args.length));
        }
        Class c = Class.forName(className);
        List<Class<?>> argTypes = new ArrayList<Class<?>>();
        for (Object arg : argList) {
            argTypes.add(arg.getClass());
        }
        Constructor constructor = c.getConstructor(
            argTypes.toArray(new Class<?>[argTypes.size()]));
        Object o = constructor.newInstance(
            argList.toArray(new Object[argList.size()]));
        System.out.println("Created a " + o.getClass() + ": " + o);
    }
}

Naturally, the argList can only ever have Strings in this case because they're pulled from a String[], but you could add args of any type. Note that constructor args are positional, not named, so the names in the map won't do you much good. They need to be in the proper order.

Try running it and passing "java.util.Date" as an argument.

like image 23
Ryan Stewart Avatar answered Oct 27 '22 12:10

Ryan Stewart