Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Dynamically create an object in java from a class name and set class fields by using a List with data

I have a List that contains data with String type -> ["classField1", "classField2", "classField3"]

I have a method (myMethod(List list, String className)) that accept as parameter the List. So, I can pass this List through the parameter to myMethod(List list, String className).

In myMethod, I want to create one object, that will be instance of the className, that is the second parameter. After that I want to set the fields of the class by using the data of the List. Due to the fact that I want to obtain dynamically the fields of the class, the result of the above is that I have to cast each String value of the list, to the type of each field of the class.

I am sure that the order of the Strings inside to the List, are in the right order, and correspond to the fields of the class with the same order.

Does anybody have any idea how to perform the above?

Example:

["StringtempValue", "StringUnitOfMeasurement"] =>

Create instance object:

public class TempStruct {

   private double tempValue;
   private String unitOfMeasurement;

   public TempStruct(double tempValue, String unitOfMeasurement) {
     this.tempValue = tempValue;
     this.unitOfMeasurement = unitOfMeasurement;
   }

}

I try to give a solution with the following way:

Actually I want to create an object of an existing class and I tried to do that with reflection. I use the following code:

Class<?> cls = Class.forName(name);
Object clsInstance = (Object) cls.newInstance();
Field[] objectFields = clsInstance.getClass().getDeclaredFields();

But I get an exception to the 2nd line, when it tries to create the new object. As @JB Nijet said I didn't know that the method getDeclaredFields() does not return the fields sorted.

Actually, I have a method that accept only List of Strings, so by using reflection I convert the object to List of string, and after that I want to do the opposite. I didn't think any other way to do it.

like image 344
Georgios Bouloukakis Avatar asked Dec 13 '12 21:12

Georgios Bouloukakis


People also ask

How do you create a dynamic class object in Java?

In order to dynamically create an object in Java from an inner class name, you should use the $ sign. For Example: String className = "MyTestClass"; String innerClassName = "MyInnerTestClass"; String fullPathOfTheClass = "full.

How do you create a dynamic object class?

You can create custom dynamic objects by using the classes in the System. Dynamic namespace. For example, you can create an ExpandoObject and specify the members of that object at run time. You can also create your own type that inherits the DynamicObject class.

Which method is called automatically when you use a class to create an object?

If a class does not explicitly declare any, the Java compiler automatically provides a no-argument constructor, called the default constructor. This default constructor calls the class parent's no-argument constructor, or the Object constructor if the class has no other parent.


2 Answers

Dynamic instantiation of objects can get pretty complex, and your scenario touches upon several aspects:

  • converting the object values from String to the appropriate type
  • loading the right class from the class name and creating an instance
  • assigning those values into the object

A thorough discussion of each of those points would take up an entire chapter in a no-doubt riveting treatment of Java as a dynamic language. But, assuming you don't have the time to learn these intricacies, or take a dependency on some huge third party library, let's whip up something that gets you on your way. Please keep your hands inside the vehicle at all times as the ride is going to get bumpy.

Let's tackle the issue of type conversion first. The values are provided as Strings, but your object will store them as double, long, int, etc. So we need a function that parses a String into the appropriate target type:

static Object convert(Class<?> target, String s) {
    if (target == Object.class || target == String.class || s == null) {
        return s;
    }
    if (target == Character.class || target == char.class) {
        return s.charAt(0);
    }
    if (target == Byte.class || target == byte.class) {
        return Byte.parseByte(s);
    }
    if (target == Short.class || target == short.class) {
        return Short.parseShort(s);
    }
    if (target == Integer.class || target == int.class) {
        return Integer.parseInt(s);
    }
    if (target == Long.class || target == long.class) {
        return Long.parseLong(s);
    }
    if (target == Float.class || target == float.class) {
        return Float.parseFloat(s);
    }
    if (target == Double.class || target == double.class) {
        return Double.parseDouble(s);
    }
    if (target == Boolean.class || target == boolean.class) {
        return Boolean.parseBoolean(s);
    }
    throw new IllegalArgumentException("Don't know how to convert to " + target);
}

Ugh. This is ugly and handles only intrinsic types. But we're not looking for perfection here, right? So please enhance as appropriate. Note the conversion from String to some other type is effectively a form of deserialization, and so you're placing constraints on your clients (whoever is giving you the Strings) to provide their values in specific formats. In this case, the formats are defined by the behavior of the parse methods. Exercise 1: At some point in the future, change the format in a backwards incompatible way to incur someone's wrath.

Now let's do the actual instantiation:

static Object instantiate(List<String> args, String className) throws Exception {
    // Load the class.
    Class<?> clazz = Class.forName(className);

    // Search for an "appropriate" constructor.
    for (Constructor<?> ctor : clazz.getConstructors()) {
        Class<?>[] paramTypes = ctor.getParameterTypes();

        // If the arity matches, let's use it.
        if (args.size() == paramTypes.length) {

            // Convert the String arguments into the parameters' types.
            Object[] convertedArgs = new Object[args.size()];
            for (int i = 0; i < convertedArgs.length; i++) {
                convertedArgs[i] = convert(paramTypes[i], args.get(i));
            }

            // Instantiate the object with the converted arguments.
            return ctor.newInstance(convertedArgs);
        }
    }

    throw new IllegalArgumentException("Don't know how to instantiate " + className);
}

We're taking a lot of shortcuts here, but hey this isn't the sistine chapel we're creating. Simply load the class and search for a constructor whose number of parameters matches the number of arguments (i.e., arity). Overloaded constructors of the same arity? Nope, not gonna work. Varargs? Nope, not gonna work. Non-public constructors? Nope, not gonna work. And if you can't guarantee your class will provide a constructor that sets all the fields like your example TempStruct does, then I'll call it a day and grab a beer, because this approach is DOA.

Once we find the constructor, loop over the String args to convert them to the types expected by the constructor. Assuming that works, we then invoke the constructor via reflection, wave the magic wand and say abracadabra. Voilà: you have a new object.

Let's try it with an extremely contrived example:

public static void main(String[] args) throws Exception {
    TempStruct ts =
        (TempStruct)instantiate(
            Arrays.asList("373.15", "Kelvin"),
            TempStruct.class.getName());

    System.out.println(
        ts.getClass().getSimpleName() + " " +
        ts.tempValue + " " +
        ts.unitOfMeasurement);
}

Output:

TempStruct 373.15 Kelvin

GLORIOUS

like image 62
cambecc Avatar answered Sep 21 '22 17:09

cambecc


I used to have the same kind of problem and a hashMap turned out to be the solution for me.

Check it out: http://docs.oracle.com/javase/6/docs/api/java/util/HashMap.html

like image 22
TRU7H Avatar answered Sep 17 '22 17:09

TRU7H