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

前端 未结 3 1012
一向
一向 2020-12-02 10:55

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

I have a method (myMethod(List list, Stri

相关标签:
3条回答
  • 2020-12-02 11:17

    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

    0 讨论(0)
  • 2020-12-02 11:17

    Take a look at the http://commons.apache.org/beanutils/ package. It allows to access fields by name.

    0 讨论(0)
  • 2020-12-02 11:36

    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

    0 讨论(0)
提交回复
热议问题