2011-08-02 71 views
11

我已经使用java.util.Properties向我的应用程序添加了一个可读的配置文件,并且试图在其中添加一个包装来使类型转换更容易。具体而言,我希望返回的值从所提供的默认值“继承”它的类型。下面是我到目前为止有:如何在Java中实例化泛型类型?

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)的返回值将被一个布尔无论它是从属性文件读取,同样的字符串,整数,双打, & 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); 
} 

回答

12

由于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仅限于一组不同的在编译时已知的类型,一个折衷办法是保持Constructor s,这是在启动时加载一个静态的Map - 这样您无需在每次调用此方法时动态查找它们。例如Map<String, Constructor<?>>Map<Class<?>, Constructor<?>>,其使用static block填充。

+1

即使使用默认构造函数,最好使用'Constructor'对象而不是使用'Class.newInstance()'。错误处理是不同的;使用'Class'方法,一些例外情况会以误导性的类型报告。 'Constructor'方法与其他动态调用一致。 – erickson

+0

@Kublai Khan - Works!更好的是,我可以将'klazz'作为'类 klazz =(类)fallback.getClass();'来消除额外的参数。非常感谢你的帮助! **编辑:**一旦反射进入图片,我开始怀疑缓存;我会看那个静态块。 –

+0

很高兴我能帮到你。请记住,所有使用反射的动态查找都非常昂贵,因为它们无法在编译时进行优化。 –

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

我需要我的类的一个实例('PropertiesExample',这里)能够从同一个文件中读取各种类型的属性。我将在我的问题中添加一个使用示例。 :-) –

+0

第一位代码不能编译。构造函数上的T掩盖了泛型参数,并且处于错误的位置。另外,newInstance()需要异常检查。 –

+0

错字,现在修复。为了可读性,异常检查被忽略,因此“有适当的错误处理”评论。 –

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

查看编辑的文章。 –

+0

这样更好,但是您需要通过反射创建一个新实例,并将该值用作参数。顺便说一下,downvote不是来自我的;我本人会提出这种方法,但我无法找到一种安全的方法。试着摆脱不安全的演员到'T'。 – erickson

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++中,您可能可以通过模板来解决。但C++引入了许多其他问题...
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); 
    } 
}