Amar
Amar

Reputation: 763

Dynamic Casting Using Java Reflection

I have two classes, as follows:

public class Person {
    private String dob;
    private PersonName personName;
}

public class PersonName {
    private String firstName;
    private String lastName;
}

I am setting these values dynamically using Java Reflection.

First, I create an instance of Person and I set the value for dob. After that, I need to set a PersonName value in Person. So I created another instance of PersonName and I set the values in that PersonName. After that, I am trying to set the PersonName instance in the Person entity.

For that I used code like this:

Class componentClass = Class.forName(clazz.getName());
Field field = parentClass.getDeclaredField(Introspector
                        .decapitalize(clazz.getSimpleName()));
field.setAccessible(true);
field.set(parentClass, componentClass);

Here, parentClass is a Person instance and componentClass is a PersonName instance. I am trying to set the PersonName in the Person, but I am getting the following exception:

java.lang.IllegalArgumentException: Can not set com.rise.common.model.PersonName field
com.rise.common.model.Person.personName to java.lang.Class

So how can I set the values dynamically?

Thanks.

My Whole Code:

protected void assignProperties(List<Object[]> argResults,
        List<Class> argAllClassesList, Class argParentClass)
        throws ClassNotFoundException, NoSuchFieldException,
        SecurityException, IllegalArgumentException,
        IllegalAccessException, InvocationTargetException, InstantiationException {
    List<Object[]> results = (List<Object[]>) Precondition.ensureNotEmpty(
            argResults, "Output List");
    List<Class<?>> personClassList = new ArrayList<Class<?>>();
    for (Object[] recordValues : results) {
        Class parentClass = Class.forName(this.getPersistentClass()
                .getName());
        parentClass.newInstance();
        int count = 0;
        count = assignValues(recordValues, parentClass, count);
        for (Class clazz : argAllClassesList) {
            Class componentClass = Class.forName(clazz.getName());
            componentClass.newInstance();
            String decapitalize = Introspector.decapitalize(clazz
                    .getSimpleName());
            Field field = parentClass.getDeclaredField(decapitalize);
            field.setAccessible(true);
            assignValues(recordValues, componentClass, count);
             field.set(parentClass, componentClass);
        }
        personClassList.add(parentClass);
    }

    for (Class<?> class1 : personClassList) {
        Class<Person> person = (Class<Person>) class1;
        System.out.println(person);
    }

}

private int assignValues(Object[] argRecordValues, Class argClass,
        int argCount) {
    String paramName = Introspector.decapitalize(argClass.getSimpleName());
    if (Precondition.checkNotEmpty(paramName)) {
        List<Field> fieldNames = TenantConfigHelper.getInstance()
                .getModelNameVsFieldsMap().get(paramName);
        try {
            for (Field field : fieldNames) {
                BeanUtils.setProperty(argClass, field.getName(),
                        argRecordValues[argCount]);
                ++argCount;
            }
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
    }
    return argCount;
}

Upvotes: 2

Views: 18547

Answers (4)

PaperBeatsRock-PFFFT
PaperBeatsRock-PFFFT

Reputation: 101

I was looking for this same type of answer and basically what everyone is specifying with the Object is correct. I also created a generic list like List<Object> instead of the actual class name. Below is a function that returns a List of type Object's. I pass in the class name and load that with the .loadClass. Than create a new object with that new class instance with the .newInstance. The dList gets loaded with all the information for each objectClass which is the class I pass in with the className variable. The rest is basically just dynamically invoking all of the "set" methods within that particular class with the values from the result set.

protected List<Object> FillObject(ResultSet rs, String className)
    {
        List<Object> dList = new ArrayList<Object>();

        try
        {
            ClassLoader classLoader = GenericModel.class.getClassLoader();

            while (rs.next())
            {
                Class reflectionClass = classLoader.loadClass("models." + className);

                Object objectClass = reflectionClass.newInstance();

                Method[] methods = reflectionClass.getMethods();

                for(Method method: methods)
                {
                    if (method.getName().indexOf("set") > -1)
                    {
                        Class[] parameterTypes = method.getParameterTypes();

                        for(Class pT: parameterTypes)
                        {
                            Method setMethod = reflectionClass.getMethod(method.getName(), pT);

                            switch(pT.getName())
                            {
                                case "int":
                                    int intValue = rs.getInt(method.getName().replace("set", ""));
                                    setMethod.invoke(objectClass, intValue);
                                    break;

                                case "java.util.Date":
                                    Date dateValue = rs.getDate(method.getName().replace("set", ""));
                                    setMethod.invoke(objectClass, dateValue);
                                    break;

                                case "boolean":
                                    boolean boolValue = rs.getBoolean(method.getName().replace("set", ""));
                                    setMethod.invoke(objectClass, boolValue);
                                    break;

                                default:
                                    String stringValue = rs.getString(method.getName().replace("set", ""));
                                    setMethod.invoke(objectClass, stringValue);
                                    break;
                            }
                        }
                    }
                }

                dList.add(objectClass);
            }
        }
        catch (Exception e)
        {
            this.setConnectionMessage("ERROR: reflection class loading: " + e.getMessage());
        }

        return dList;
    }

Upvotes: 1

user506069
user506069

Reputation: 781

I think there may be some confusion over the difference between Java class definitions and instances at work here. You want to set the values of fields on particular instances, not the classes themselves. Something like this may work:

Object parentClassInstance = parentClass.newInstance();
Class componentClass = Class.forName(clazz.getName());
Object componentClassInstance = componentClass.newInstance();
Field field = parentClass.getDeclaredField(Introspector
                        .decapitalize(clazz.getSimpleName()));
field.setAccessible(true);
field.set(parentClassInstance, componentClassInstance);

Looking at the whole code sample, however, it is a little hard to follow. Why have a List of Classes, with a name like personClassList, which would seem to indicate that each class should be the same class, Person? I feel it should probably instead be a List<Person> or perhaps List<Object>, which you would populate with your Person instances, not the Class objects themselves.

Edit to answer the following question in a comment:

I have to return List instances insetad of List so how can I type case from Class to Person dynamically...?

You can't cast from Class to Person, since Person is probably not a subclass of Class.

Instead, declare your list as a List<Person> instead of a List<Class<?>>

List<Person> personList = new ArrayList<Person>();

Then add the Person objects instead of the Class objects to your list at the bottom of your first for loop.

personList.add((Person)parentClassInstance);

And the loop at the bottom will need to change too:

for (Person person : personList) {
    System.out.println(person);
}

Upvotes: 1

JB Nizet
JB Nizet

Reputation: 691765

The message explains what's wrong: componentClass is not an instance of PersonName. It's an object of type Class (probably Class<PersonName>). You probably forgot to instantiate the class.

Edit:

Your code does:

parentClass.newInstance();

and

componentClass.newInstance();

This is the equivalent of doing

new Parent();

and

new ParentName();

So it creates an instance, but doesn't assign it to any variable, and thus doesn't do anything with the created instance, which will be garbage-collectable immediately.

You want

Object parent = parentClass.newInstance();
Object component = componentClass.newInstance();
field.set(parent, component);

Upvotes: 3

Edwin Buck
Edwin Buck

Reputation: 70909

The type system is verified by the compiler. While there is some additional safety possible during runtime, that additional safety is not even close to the safety the compiler imposes. Which begs the question, Why?

Assuming you could get the stuff to work, exactly how would you be able to justify casting class1 below into a Class<Person> type? You declared it to be a Class<?>!

for (Class<?> class1 : personClassList) {
    Class<Person> person = (Class<Person>) class1;
    System.out.println(person);
}

Upvotes: 0

Related Questions