Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using Java Reflection, how to get the constructor of a class specifying a derived class of the constructor args?

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?

like image 989
viebel Avatar asked Feb 09 '12 15:02

viebel


2 Answers

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.

like image 102
T.J. Crowder Avatar answered Sep 18 '22 00:09

T.J. Crowder


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.

like image 23
esaj Avatar answered Sep 19 '22 00:09

esaj