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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/6916346/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-30 17:53:16  来源:igfitidea点击:

How can I instantiate a generic type in Java?

javagenerics

提问by Ben Blank

I've added a human-readable configuration file to my app using java.util.Propertiesand 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:

我已经将一个人类可读的配置文件添加到我的应用程序中,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.)

(完整的示例源。)

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:

getProperty("foo", true)无论它是否是从属性文件中读取的,来自的返回值都将是一个布尔值,对于字符串、整数、双精度数等也是如此。当然,上面的代码片段实际上并没有编译:

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

回答by Paul Bellora

Due to type erasure, you can't instantiate generic objects. Normally you could keep a reference to the Classobject 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 Constructorobject and use it for the instantiation:

由于类型擦除,您无法实例化通用对象。通常,您可以保留对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
        }
    }
}

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 Tis limited to a distinct set of types known at compile time, a compromise would be to keep a static Mapof 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.

如果你绝对需要走这条路,如果T仅限于一组不同的在编译时已知的类型,一个折衷办法是保持静态MapConstructors,这是在启动时加载-这样你就不必动态在每次调用此方法时查找它们。例如 a Map<String, Constructor<?>>or Map<Class<?>, Constructor<?>>,它使用静态块填充。

回答by Nayuki

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.

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

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...
  • 在Java标准API中,你会发现很多地方都有一组相关的方法,只是输入类型不同。
  • 在 C++ 中,您可能可以通过templates解决。但是 C++ 引入了许多其他问题......

回答by Vivien Barousse

Generics are implemented using type erasure in Java. In English terms, most generic information are lost at compile time, and you can't know the actual value of Tat runtime. This means you simply can't instanciate generic types.

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

An alternate solution is to provide your class with the type at runtime:

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

class Test<T> {

    Class<T> klass;

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

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

}

Edit: New example closer to your case

编辑:新的例子更接近你的情况

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

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

回答by Vivien Barousse

If you want to keep your existing method signature do it this way.

如果您想保留现有的方法签名,请这样做。

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

回答by Marcono1234

The following uses functional interfaces.

下面使用函数式接口

You could change the method signature to use a provided "parser":

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

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

Also, for efficiency you might want to replace T fallbackwith Supplier<? extends T> fallbackSupplierto prevent having to create fallback values when they are not needed:

此外,为了提高效率,您可能希望替换T fallbackSupplier<? 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);
    }
}

Then you can use method referencesand lambda expressionsas parser and fallback supplier, e.g.:

然后你可以使用方法引用lambda 表达式作为解析器和后备供应商,例如:

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


The advantage of this approach is that it is not restricted to a (potentially non-existent) constructor anymore.

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

回答by Matt MacLean

Try this:

试试这个:

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