Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I instantiate a generic type in Java?

Tags:

java

generics

I've added a human-readable configuration file to my app using java.util.Properties and am trying to add a wrapper around it to make type conversions easier. Specifically, I want the returned value to "inherit" it's type from the provided default value. Here's what I've got so far:

protected <T> T getProperty(String key, T fallback) {
    String value = properties.getProperty(key);

    if (value == null) {
        return fallback;
    } else {
        return new T(value);
    }
}

(Full example source.)

The return value from getProperty("foo", true) would then be a boolean regardless of whether it was read from the properties file and similarly for strings, integers, doubles, &c. Of course, the above snippet doesn't actually compile:

PropertiesExample.java:35: unexpected type
found   : type parameter T
required: class
                        return new T(value);
                                   ^
1 error

Am I doing this wrong, or am I simply trying to do something which can't be done?

Edit: Usage example:

// I'm trying to simplify this...
protected void func1() {
    foobar = new Integer(properties.getProperty("foobar", "210"));
    foobaz = new Boolean(properties.getProperty("foobaz", "true"));
}

// ...into this...
protected void func2() {
    foobar = getProperty("foobar", 210);
    foobaz = getProperty("foobaz", true);
}
like image 648
Ben Blank Avatar asked Aug 02 '11 17:08

Ben Blank


2 Answers

This is something that you cannot do.

Because of type erasure, the type T, while known at compile time, is not available to the JVM at run time.

For your specific problem, I think the most reasonable solution is to manually write the code for each different type:

protected String getProperty(String key, String fallback) { ... return new String(value); }
protected Double getProperty(String key, Double fallback) { ... return new Double(value); }
protected Boolean getProperty(String key, Boolean fallback) { ... return new Boolean(value); }
protected Integer getProperty(String key, Integer fallback) { ... return new Integer(value); }

Notes:

  • In the Java standard API, you will find many places where there is a set of related methods that only differ by the input types.
  • In C++, your probably could probably be solved by templates. But C++ introduces many other problems...
like image 120
Nayuki Avatar answered Sep 21 '22 19:09

Nayuki


Due to type erasure, you can't instantiate generic objects. Normally you could keep a reference to the Class object representing that type and use it to call newInstance(). However, this only works for the default constructor. Since you want to use a constructor with parameters, you'll need to look up the Constructor object and use it for the instantiation:

protected <T> T getProperty(String key, T fallback, Class<T> clazz) {
    String value = properties.getProperty(key);

    if (value == null) {
        return fallback;
    } else {

        //try getting Constructor
        Constructor<T> constructor;
        try {
            constructor = clazz.getConstructor(new Class<?>[] { String.class });
        }
        catch (NoSuchMethodException nsme) {
            //handle constructor not being found
        }

        //try instantiating and returning
        try {
            return constructor.newInstance(value);
        }
        catch (InstantiationException ie) {
            //handle InstantiationException
        }
        catch (IllegalAccessException iae) {
            //handle IllegalAccessException
        }
        catch (InvocationTargetException ite) {
            //handle InvocationTargetException
        }
    }
}

However, seeing how much trouble it is to achieve this, including the performance cost of using reflection, it's worth looking into other approaches first.

If you absolutely need to take this route, and if T is limited to a distinct set of types known at compile time, a compromise would be to keep a static Map of Constructors, which is loaded at startup - that way you don't have to dynamically look them up at every call to this method. For example a Map<String, Constructor<?>> or Map<Class<?>, Constructor<?>>, which is populated using a static block.

like image 44
Paul Bellora Avatar answered Sep 25 '22 19:09

Paul Bellora