如何在Java中实例化泛型类型?

时间:2011-08-02 17:51:46

标签: java generics

我使用java.util.Properties向我的应用添加了一个人类可读的配置文件,并尝试在其周围添加包装,以便更轻松地进行类型转换。具体来说,我希望返回的值从提供的默认值“继承”它的类型。这是我到目前为止所得到的:

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

getProperty("foo", true)的返回值将是一个布尔值,无论是从属性文件中读取还是类似于字符串,整数,双精度和&amp; c。当然,上面的代码段实际上并没有编译:

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

我做错了,还是我只是想做一些无法做到的事情?

修改:使用示例:

// 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);
}

6 个答案:

答案 0 :(得分:13)

由于type erasure,您无法实例化通用对象。通常,您可以保留对表示该类型的Class对象的引用,并使用它来调用newInstance()。但是,这仅适用于默认构造函数。由于您要使用带参数的构造函数,因此您需要查找Constructor对象并将其用于实例化:

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
        }
    }
}

然而,看到实现这一目标有多麻烦,包括使用反射的性能成本,首先要考虑其他方法。

如果你绝对需要采用这种方式,并且如果T仅限于编译时已知的一组不同类型,那么妥协就是保持Map Constructor s,在启动时加载 - 这样你就不必在每次调用这个方法时动态查找它们。例如Map<String, Constructor<?>>Map<Class<?>, Constructor<?>>,使用static block填充。

答案 1 :(得分:1)

泛型是使用Java中的类型擦除实现的。在英语术语中,大多数通用信息在编译时丢失,并且您无法在运行时知道T的实际值。这意味着你根本无法实现泛型类型。

另一种解决方案是在运行时为您的类提供类型:

class Test<T> {

    Class<T> klass;

    Test(Class<T> klass) {
        this.klass = klass;
    }

    public void test() {
        klass.newInstance(); // With proper error handling
    }

}

编辑:更接近您案例的新示例

static <T> T getProperty(String key, T fallback, Class<T> klass) {
    // ...

    if (value == null) {
        return fallback;
    }
    return (T) klass.newInstance(); // With proper error handling
}

答案 2 :(得分:1)

这是你不能做的事情。

由于类型擦除,类型T虽然在编译时已知,但在运行时JVM不可用。

对于您的具体问题,我认为最合理的解决方案是手动编写每种不同类型的代码:

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); }

注意:

  • 在Java标准API中,您会发现许多地方存在一组仅因输入类型而异的相关方法。
  • 在C ++中,您可能可以通过 templates 解决。但是C ++引入了许多其他问题......

答案 3 :(得分:0)

如果您想保留现有的方法签名,请执行此操作。

import java.lang.reflect.InvocationTargetException;
import java.util.Properties;

public class Main
{
    private final Properties properties;

    public Main()
    {
        this.properties  = new Properties();
        this.properties.setProperty("int", "1");
        this.properties.setProperty("double", "1.1");
    }

    public <T> T getProperty(final String key, final T fallback)
    {
        final String value = this.properties.getProperty(key);
        if (value == null)
        {
            return fallback;
        }
        else
        {
            try
            {
                return (T) fallback.getClass().getConstructor(new Class<?>[] { String.class } ).newInstance(value);
            }
            catch (final InstantiationException e)
            {
                throw new RuntimeException(e);
            }
            catch (final IllegalAccessException e)
            {
                throw new RuntimeException(e);
            }
            catch (final InvocationTargetException e)
            {
                throw new RuntimeException(e);
            }
            catch (final NoSuchMethodException e)
            {
                throw new RuntimeException(e);
            }
        }
    }


    public static void main(final String[] args)
    {
        final Main m = new Main();
        final Integer i = m.getProperty("int", new Integer("0"));
        final Double d = m.getProperty("double", new Double("0"));
        System.out.println(i);
        System.out.println(d);
    }
}

答案 4 :(得分:0)

以下使用functional interfaces

您可以更改方法签名以使用提供的“解析器”:

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

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

此外,为了提高效率,您可能希望将T fallback替换为Supplier<? extends T> fallbackSupplier,以防止在不需要时创建备用值:

protected <T> T getProperty(String key, Supplier<? extends T> fallbackSupplier, Function<String, ? extends T> parser) {
    String value = properties.getProperty(key);

    if (value == null) {
        return fallbackSupplier.get();
    } else {
        return parser.apply(value);
    }
}

然后,您可以将method referenceslambda expressions用作解析器和后备供应商,例如:

protected void func2() {
    foobar = getProperty("foobar", () -> 210, Integer::valueOf);
    // Better create own parsing method which properly handles 
    // invalid boolean strings instead of using Boolean#valueOf
    foobaz = getProperty("foobaz", () -> true, Boolean::valueOf);

    // Imagine creation of `ExpensiveObject` is time-wise or 
    // computational expensive
    bar = getProperty("bar", ExpensiveObject::new, ExpensiveObject::parse);
}

此方法的优点是它不再局限于(可能不存在的)构造函数。

答案 5 :(得分:-1)

试试这个:

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

    if (value == null) {
        return fallback;
    } else {
        Class FallbackType = fallback.getClass();
        return (T)FallbackType.cast(value);
    }
}