Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
417 views
in Technique[技术] by (71.8m points)

reflection - 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.

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

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


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...