【问题标题】:How can I instantiate a generic type in Java?如何在 Java 中实例化泛型类型?
【发布时间】:2018-12-28 02:15:14
【问题描述】:

我已经使用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) 的返回值将是一个布尔值,无论它是否是从属性文件中读取的,对于字符串、整数、双精度数等也是如此。当然,上面的 sn -p 并没有真正编译:

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

【问题讨论】:

    标签: java generics


    【解决方案1】:

    由于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 仅限于编译时已知的一组不同类型,则折衷方案是保留 Constructors 的静态 Map,它已加载在启动时 - 这样您就不必在每次调用此方法时动态查找它们。例如 Map&lt;String, Constructor&lt;?&gt;&gt;Map&lt;Class&lt;?&gt;, Constructor&lt;?&gt;&gt;,使用 static block 填充。

    【讨论】:

    • 即使使用默认构造函数,使用Constructor 对象也比使用Class.newInstance() 更好。错误处理不同;使用Class 方法,会报告一些具有误导性类型的异常。 Constructor 方法与其他动态调用一致。
    • @Kublai Khan — 有效!更好的是,我能够将klazz 设为Class&lt;T&gt; klazz = (Class&lt;T&gt;)fallback.getClass(); 以消除额外的参数。非常感谢你的帮助! 编辑: 当反射进入图片时,我开始怀疑缓存;我会看看静态块。
    • 很高兴我能帮上忙。请记住,所有使用反射的动态查找都非常昂贵,因为它们无法在编译时进行优化。
    • @Kublai Khan — 在这一点上,我想我可以在开发期间使用这个通用版本,但在发布之前切换到显式类型的方法。通过检查缓存,我将能够知道我最终需要支持哪些类型。
    【解决方案2】:

    这是你做不到的。

    由于类型擦除,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++ 中,您可能可以通过 模板 解决。但是 C++ 引入了许多其他问题......

    【讨论】:

    • 应该使用各自的解析方法(例如Integer#valueOf)而不是创建新实例。从 Java 9 开始,构造函数甚至被标记为已弃用:Integer(String)
    【解决方案3】:

    泛型是在 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
    }
    

    【讨论】:

    • 我需要我的类的单个实例(PropertiesExample,此处)能够从同一个文件中读取各种类型的属性。我将在我的问题中添加一个使用示例。 :-)
    • 第一位代码无法编译。构造函数上的 T 掩盖了泛型参数,并且位置错误。此外,newInstance() 需要进行异常检查。
    • 错字,现已修正。为了可读性,省略了异常检查,因此有“正确的错误处理”注释。
    【解决方案4】:

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

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

    【讨论】:

      【解决方案5】:

      以下使用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&lt;? extends T&gt; 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);
      }
      

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

      【讨论】:

        【解决方案6】:

        试试这个:

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

        【讨论】:

        • 这样更好,但是您需要通过反射创建一个新实例,使用该值作为参数。顺便说一句,反对票不是来自我。我本来打算自己建议这种方法,但我无法找到一种安全打字的方法。尝试摆脱对T 的不安全强制转换。
        猜你喜欢
        • 1970-01-01
        • 2013-09-18
        • 1970-01-01
        • 2021-12-06
        • 1970-01-01
        • 1970-01-01
        • 2013-10-03
        • 2011-04-02
        相关资源
        最近更新 更多