2011-08-02 15 views
11

He agregado un archivo de configuración legible para mi aplicación usando java.util.Properties y estoy tratando de agregar un envoltorio para facilitar las conversiones de tipo. Específicamente, deseo que el valor devuelto "herede" su tipo del valor predeterminado proporcionado. Esto es lo que tengo hasta ahora:¿Cómo puedo instanciar un tipo genérico en 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.)

El valor de retorno de getProperty("foo", true) sería entonces un valor lógico, independientemente de si se lee desde el archivo de propiedades y de manera similar para las cadenas, enteros, dobles, & c. Por supuesto, el fragmento anterior en realidad no compilar:

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

estoy haciendo esto mal, o estoy simplemente tratando de hacer algo que no se puede hacer?

Editar: Ejemplo de uso:

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

Respuesta

12

Debido a type erasure, no puede crear instancias de objetos genéricos. Normalmente, puede mantener una referencia al objeto Class que representa ese tipo y usarlo para llamar al newInstance(). Sin embargo, esto solo funciona para el constructor predeterminado. Dado que desee utilizar un constructor con parámetros, tendrá que buscar el objeto Constructor y utilizarla para la creación de instancias:

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

Sin embargo, en vista de la cantidad de problemas que es lograr esto, incluyendo el costo de rendimiento de usar la reflexión, vale la pena buscar otros enfoques primero.

Si es absolutamente necesario tomar esta ruta, y si T se limita a un conjunto distinto de los tipos conocidos en tiempo de compilación, un compromiso sería mantener una estática Map de Constructor s, que se carga en el arranque - de esa manera no tiene que buscarlos dinámicamente en cada llamada a este método. Por ejemplo, Map<String, Constructor<?>> o Map<Class<?>, Constructor<?>>, que se completa con static block.

+1

Incluso con un constructor predeterminado, es mejor usar un objeto 'Constructor' que usar' Class.newInstance() '. El manejo de errores es diferente; utilizando el método 'Clase ', algunas excepciones se informan con un tipo engañoso. El método 'Constructor' es consistente con otras invocaciones dinámicas. – erickson

+0

@Kublai Khan - ¡Funciona! Mejor aún, pude obtener 'klazz' como' Clase klazz = (Clase ) fallback.getClass(); 'para eliminar el parámetro adicional. ¡Muchas gracias por su ayuda! ** Editar: ** Empecé a preguntarme sobre el almacenamiento en caché tan pronto como la reflexión entró en escena; Veré bloques estáticos para eso. –

+0

Me alegro de poder ayudar. Solo tenga en cuenta que todas las búsquedas dinámicas que utilizan el reflejo son bastante costosas, ya que no se pueden optimizar en tiempo de compilación. –

1

genéricos se implementan utilizando el tipo de borrado en Java. En términos ingleses, la mayoría de la información genérica se pierde en tiempo de compilación, y no se puede conocer el valor real de T en tiempo de ejecución. Esto significa que simplemente no puedes instanciar tipos genéricos.

Una solución alternativa es proporcionar a su clase con el tipo en tiempo de ejecución:

class Test<T> { 

    Class<T> klass; 

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

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

} 

edición: Nueva ejemplo más cercano a su caso

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

Necesito tener una única instancia de mi clase ('PropertiesExample', aquí) poder leer las propiedades de varios tipos del mismo archivo. Agregaré un ejemplo de uso a mi pregunta. :-) –

+0

El primer bit del código no se compila. T en el constructor está enmascarando el argumento genérico, y está en la posición incorrecta. Además, se necesita una verificación de excepción para newInstance(). –

+0

Typo, corregido ahora. Se omitió la comprobación de excepciones para la legibilidad, de ahí el comentario "Con el manejo adecuado de errores". –

-1

Prueba esto:

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

Ver la publicación editada. –

+0

Esto es mejor, pero necesita crear una nueva instancia a través de la reflexión, usando el valor como argumento. El voto a favor no es mío, por cierto; Yo mismo iba a sugerir este enfoque, pero no fui capaz de encontrar una manera de hacerlo de forma segura. Intenta deshacerte del lanzamiento inseguro a 'T'. – erickson

1

Esto es algo que no puedes hacer.

Debido a la supresión de tipo, el tipo T, aunque se conoce en tiempo de compilación, no está disponible para la JVM en tiempo de ejecución.

Para su problema específico, creo que la solución más razonable es escribir manualmente el código para cada tipo diferente:

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

Notas:

  • En la API estándar de Java, se encuentra muchos lugares donde hay un conjunto de métodos relacionados que solo difieren según los tipos de entrada.
  • En C++, probablemente probablemente se pueda resolver con las plantillas . Pero C++ presenta muchos otros problemas ...
0

Si desea mantener su firma de método existente, hágalo de esta manera.

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