With Java reflection, one can get a constructor through getConstructor(klass, args)
.
However, when we pass as args
a derived class of the class specified in the constructor signature, it fails. How to overcome this issue?
For example,
HashSet.class.getConstructor(new Class[]{ HashSet.class });
fails. While
HashSet.class.getConstructor(new Class[]{ Collection.class });
succeeds.
I am looking for something that could easily be used in clojure
. Therefore, I would prefer to have something out of the box and not having to add user-defined functions.
Any idea, how to solve this issue?
HashSet
has no HashSet(HashSet)
constructor, so naturally you don't get one when you ask for it. You have to work your way through the assignment-compatible classes (at least loop through the supers, and probably the implemented interfaces and their supers) to find one.
Here's a fairly simple way of doing this. The getConstructorForArgs
-method walks through all the constructors in given class, and checks to see if the parameters of the constructor match the parameters given (note that the given parameters must be in the same order as in the constructor). Implementations of interfaces and sub-classes work also, because the "compatibility" is checked by calling isAssignableFrom
for the constructor argument (is the given parameter type assignable to parameter type in constructor).
public class ReflectionTest
{
public Constructor<?> getConstructorForArgs(Class<?> klass, Class[] args)
{
//Get all the constructors from given class
Constructor<?>[] constructors = klass.getConstructors();
for(Constructor<?> constructor : constructors)
{
//Walk through all the constructors, matching parameter amount and parameter types with given types (args)
Class<?>[] types = constructor.getParameterTypes();
if(types.length == args.length)
{
boolean argumentsMatch = true;
for(int i = 0; i < args.length; i++)
{
//Note that the types in args must be in same order as in the constructor if the checking is done this way
if(!types[i].isAssignableFrom(args[i]))
{
argumentsMatch = false;
break;
}
}
if(argumentsMatch)
{
//We found a matching constructor, return it
return constructor;
}
}
}
//No matching constructor
return null;
}
@Test
public void testGetConstructorForArgs()
{
//There's no constructor in HashSet that takes a String as a parameter
Assert.assertNull( getConstructorForArgs(HashSet.class, new Class[]{String.class}) );
//There is a parameterless constructor in HashSet
Assert.assertNotNull( getConstructorForArgs(HashSet.class, new Class[]{}) );
//There is a constructor in HashSet that takes int as parameter
Assert.assertNotNull( getConstructorForArgs(HashSet.class, new Class[]{int.class}) );
//There is a constructor in HashSet that takes a Collection as it's parameter, test with Collection-interface
Assert.assertNotNull( getConstructorForArgs(HashSet.class, new Class[]{Collection.class}) );
//There is a constructor in HashSet that takes a Collection as it's parameter, and HashSet itself is a Collection-implementation
Assert.assertNotNull( getConstructorForArgs(HashSet.class, new Class[]{HashSet.class}) );
//There's no constructor in HashSet that takes an Object as a parameter
Assert.assertNull( getConstructorForArgs(HashSet.class, new Class[]{Object.class}) );
//There is a constructor in HashSet that takes an int as first parameter and float as second
Assert.assertNotNull( getConstructorForArgs(HashSet.class, new Class[]{int.class, float.class}) );
//There's no constructor in HashSet that takes an float as first parameter and int as second
Assert.assertNull( getConstructorForArgs(HashSet.class, new Class[]{float.class, int.class}) );
}
}
Edit: Note that this solution is NOT perfect for all cases: if there are two constructors, that have a parameter which is assignable from a given parameter type, the first one will be chosen, even if the second was a better fit. For example, if SomeClass
would have a constructor that takes a HashSet
(A Collection
-implementation) as a parameter, and a constructor taking a Collection
as a parameter, the method could return either one when searching for a constructor accepting a HashSet
as parameter, depending on which came first when iterating through the classes. If it needs to work for such cases also, you need to first collect all the possible candidates, that match with isAssignableFrom
, and then do some more deeper analysis to the candidates to pick the best suited one.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With