Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Java Instantiate Class at Runtime with parameters

I am using an abstract factory to return instances of concrete subclasses.I would like to instantiate the subclasses at runtime given a String of the concrete class name. I also need to pass a parameter to the constructors. The class structure is as follows:

abstract class Parent {

  private static HashMap<String, Child> instances = new HashMap<String,Child>()

  private Object constructorParameter;  

  public static Child factory(String childName, Object constructorParam){

     if(instances.keyExists(childName)){
       return instances.get(childName);
     }

     //Some code here to instantiate the Child using constructorParam, 
     //then save Child into the HashMap, and then return the Child.
     //Currently, I am doing:
     Child instance = (Child) Class.forName(childClass).getConstructor().newInstance(new Object[] {constructorParam});
     instances.put(childName, instance);
     return instance;
  }

  //Constructor is protected so unrelated classes can't instantiate
  protected Parent(Object param){ 
    constructorParameter = param;
  }

}//end Parent

class Child extends Parent {
    protected Child(Object constructorParameter){
      super(constructorParameter);
    }
}

My attmept above is throwing the following exception: java.lang.NoSuchMethodException: Child.<init>(), followed by the stack trace.

Any help is appreciated. Thanks!

like image 305
bibs Avatar asked Nov 23 '11 21:11

bibs


2 Answers

Constructor<?> c = Class.forName(childClass).getDeclaredConstructor(constructorParam.getClass());
c.setAccessible(true);
c.newInstance(new Object[] {constructorParam});

The getConstructor method takes Class arguments to distinguish between constructors. But it returns only public constructors, so you'd need getDeclaredConstructor(..). Then you'd need setAccessible(true)

like image 94
Bozho Avatar answered Sep 21 '22 12:09

Bozho


The error : You are invoking the wrong constructor - and the compiler has no way of helping you.

The problem you were having is simply that you were accessing the zero-argument constructor, rather than the one with arguments. Remember that constructors in java are ultimately just methods, albeit special ones - and with reflection , all bets are off --- the compiler won't help you if you do something silly. In your case, you had a scope issue AND also a method signature issue at the same time.

How to solve this problem and NEVER have to deal with it again in this application

Its a good idea to wrap constructor invocations in a static helper method that can be directly tested, and then put a test explicitly for them in my unit tests, because if the constructor changes and you forget to update your reflection code, you will again see these cryptic errors creep up again.

You can also simply invoke the constructor as follows :

public static Child create(Integer i, String s) throws Exception
{
  Constructor c = Class.forName(childClass).getConstructor(new Object[]{Integer.class, String.class});
  c.setAccessible(true);
  Child instance = (Child) c.newInstance(new Object[]{i , s}) ; 
  return instance;
}

and of course add to your tests

    @Test 
    public void testInvoke()
    {
        try{ 
   MyClass.create(1,"test");
   }
   catch(Exception e)
   {
       Assert.fail("Invocation failed : check api for reflection classes in " + MyClass.class);
   }
    }
like image 35
jayunit100 Avatar answered Sep 22 '22 12:09

jayunit100