Search code examples
javaclassconstructorclassloader

Why does class.getConstructor(parameters) not allow child objects as parameters?


Lets say I have a parent class and a child class which extends my parent and I have the following code.

public class SomeClass {
  private Parent myParent;

  public SomeClass(Parent myParent) {
    this.myParent = myParent;
  }
}

Why is this allowed

Class<SomeClass> clazz = SomeClass.class;
SomeClass someClass = clazz.getConstructor(Parent.class).getInstance(Child);

and this isnt't

Class<SomeClass> clazz = SomeClass.class;
SomeClass someClass = clazz.getConstructor(Child.class).getInstance(Child);

The second one throws an NoSuchMethodeException. Why isn't there dynamic binding in this case but when using the normal Constructor dynamic binding works just fine? And is there a way to workaround this problem?

Edit: I'm trying to load jar files at runtime. At this moment I have the classes I need, loaded with an URLClassLoader . Next I want to create a new instance of one of the loaded classes. To create the instance i call urlClassLoader.loadClass(nameOfClass).getConstructor(parameterType).newInstance(initArguments); The parameterTypewould be child.class in this case.


Solution

  • The method getConstructorAcceptingSupertype below finds a constructor that accepts the given parameter type or one of its supertypes.

    import java.lang.reflect.Constructor;
    
    public class ConstructorTester {
        
        public ConstructorTester(Object o) {
            System.out.println("Object constructor");
        }
        
        public ConstructorTester(CharSequence o) {
            System.out.println("CharSequence constructor");
        }
        
        public static void main(String[] args) throws Throwable {
            Object[] arg = {null};
            getConstructorAcceptingSupertype(ConstructorTester.class, Object.class).newInstance(arg); //Object constructor
            getConstructorAcceptingSupertype(ConstructorTester.class, CharSequence.class).newInstance(arg); //CharSeq constructor
            getConstructorAcceptingSupertype(ConstructorTester.class, Integer.class).newInstance(arg); //Object constructor
            getConstructorAcceptingSupertype(ConstructorTester.class, String.class).newInstance(arg); //CharSeq constructor
            getConstructorAcceptingSupertype(ConstructorTester.class, StringBuilder.class).newInstance(arg); //CharSeq constructor
        }
        
        /**
         * Returns a one-arg {@link Constructor} from {@code clazz} that accepts the given {@code parameterType}. This
         * method guarantees that there is no other one-arg constructor in {@code clazz} that accepts a superclass or
         * superinterface of the type that the returned constructor accepts. 
         */
        private static Constructor<?> getConstructorAcceptingSupertype(Class<?> clazz, Class<?> parameterType) {
            Constructor<?> correctConstructor = null;
            for(Constructor<?> constructor : clazz.getConstructors()) {
                if( constructor.getParameterCount() == 1 &&
                    constructor.getParameters()[0].getType().isAssignableFrom(parameterType)) {
                    if(correctConstructor == null) {
                        correctConstructor = constructor;
                    }
                    else { //see if this constructor is more specific than the current correctConstructor.
                        Class<?> currentType = correctConstructor.getParameters()[0].getType();
                        Class<?> newType = constructor.getParameters()[0].getType();
                        if(currentType.isAssignableFrom(newType))
                            correctConstructor = constructor;
                    }
                }
            }
            if(correctConstructor == null)
                throw new IllegalArgumentException("No one-arg constructor exists that accepts the given parameter type");
            return correctConstructor;
        }
        
    }
    

    Note that there is no way to avoid an ambiguous situation where an interface, say C, extends two other interfaces A and B, and your class has two constructors (SomeClass(A) and SomeClass(B)). In such a scenario, even an explicit constructor invocation such as new SomeClass(instanceOfC) would fail at compile-time. In such a scenario, My method above will return an arbitrary constructor (either SomeClass(A) or SomeClass(B)), but does not guarantee which.