2009-12-24 22 views
13

Aquí hay un patrón genérico de Java:Java: cómo devolver el tipo genérico

public <T> T getResultData(Class<T> resultClass, other_args) { 
    ... 
    return resultClass.cast(T-thing); 
} 

Una llamada típica se parece a:

DoubleBuffer buffer; 
    buffer = thing.getResultData(DoubleBuffer.class, args); 

nunca he sido capaz de averiguar cómo utilizar este patrón está limpio cuando el tipo de devolución deseado es, en sí mismo, genérico. Para ser 'concreto', ¿qué ocurre si una función como esta quiere devolver Map<String,String>? Dado que no puede obtener un objeto de clase para un genérico, por supuesto, la única opción sería pasar Map.class, y luego necesita un yeso y un @SuppressWarning después de todo.

Uno termina con una llamada como:

Map<String, String> returnedMap; 
returnedMap = thing.getResultData(Map.class, some_other_args); 

Ahora se trata de volver a la necesidad de moldes y la supresión de una advertencia.

Supongo que se podría tomar algo de la familia java.lang.reflect.Type en lugar del Class, pero no son especialmente fáciles de confeccionar. Que se parece a:

class Dummy { 
Map<String, String> field; 
} 

... 

Type typeObject = Dummy.class.getField("field").getGenericType(); 

Ante esto, la función llamada podría extraer el tipo base y el uso que para la fundición o newInstance-ción, pero el negocio campo ficticio seguro que se ve feo.

Tenga en cuenta que funciones como esta no siempre llaman al newInstance. Obviamente, si lo hacen, no necesitan llamar al resultClass.cast.

Respuesta

7

No puede hacer eso en la API estándar de Java. Sin embargo, hay clases de utilidad disponibles que pueden obtener el Type de una clase genérica. En, por ejemplo, Google Gson (que convierte JSON en Javabeans completo y viceversa) hay una clase TypeToken. Puede usarlo de la siguiente manera:

List<Person> persons = new Gson().fromJson(json, new TypeToken<List<Person>>() {}.getType()); 

Tal construcción es exactamente lo que necesita. Puede encontrar here el recurso fuente, también puede ser útil. Solo requiere un método adicional getResultData() que puede aceptar un Type.

1

Puede usar TypeLiteral si desea utilizar clases de contenedor tipo seguro. Se usan en Guice para proporcionar enlaces de tipo seguro. Consulte el código fuente de Guice para obtener más ejemplos y la clase TypeLiteral.

Dicho sea de paso, no es necesario transmitir, si llama al resultClass.newInstance(), por ejemplo.

public <T> T getResultData(Class<T> resultClass, other_args) {  
    ... 
    return resultClass.newInstance(); 
} 
+2

Excepto que no puede obtener y, por lo tanto, pasar la instancia de clase para la clase genérica como 'Map ', así que esto no ayudará en absoluto con la pregunta de OP. – ChssPly76

+0

Sí. Así es exactamente como llegamos aquí. – bmargulies

+0

No son compatibles con el lenguaje pero utiliza TypeLiteral, inventado por Neal Gafter. http://gafter.blogspot.com/2006/12/type-literals.html –

1

Así que si he entendido bien lo que realmente tratando de hacer es (ilegal pseudo-sintaxis):

public <T, S, V> T<S, V> getResultData(Class<T<S, V>> resultClass, other_args) { 
    ... 
    return resultClass.cast(T-thing); 
} 

Puede no porque no se puede parametrizar más genérica tipo T. Perder el tiempo con java.lang.reflect.* no ayudará a:

  1. No hay tal cosa como Class<Map<String, String>> y por lo tanto no hay manera de crear dinámicamente su instancia.
  2. No hay manera de declarar realidad el método en cuestión como devolver T<S, V>
+0

@ ChssPly76: sucede que hay un objeto GenericType para Map . Si tiene un campo de tipo Map y llama a Field.getGenericType, lo obtendrá. Sin embargo, la única forma de adquirir uno de ellos es, efectivamente, obtenerlo de la reflexión en un campo o tipo de devolución. – bmargulies

+0

Quiere decir 'ParameterizedType'.Sí, hay y sí, puede obtenerlo de la declaración de campo/método. Y no, es ** no ** lo mismo que 'Clase 'y por lo tanto ** no ** te ayudará de ninguna manera: no puedes convertirlo y no puedes crear una instancia de la clase correspondiente usando solo' ParameterizedType' tampoco. – ChssPly76

+0

Bueno, en realidad, esto no es tan imposible como parece. La función llamada puede llamar a getRawType() en un ParameterizedType para propósitos de newInstance/cast. Es feo, pero parece que es tan bueno como se pone. – bmargulies

1

bien ,. aquí hay una solución fea (parcial). Puede no genéricamente paramaterize un parámetro genérico, por lo que no se puede escribir

public <T<U,V>> T getResultData ... 

pero se puede devolver una colección con parámetros, por ejemplo,para un conjunto

public < T extends Set<U>, U > T getResultData(Class<T> resultClass, Class<U> paramClass) throws IllegalAccessException, InstantiationException { 
     return resultClass.newInstance(); 
    } 

donde podría deshacerse del U-param si existiera en la firma de clase.

+0

Interesante, y funcionará también para dos parámetros (extendiendo 'Map'). Pero, honestamente, eligiendo entre esto y '@ SuppressWarnings' tomaría este último cada vez :-) – ChssPly76

+1

Estoy de acuerdo - Dije" feo ";) –

1

Sí, no creo que esto sea posible.

Imagine este escenario. Usted tiene un archivo con un objeto serializado que desea leer. Si hace que la función sea genérica, podrá usarla sin un molde. Pero digamos que tiene un mapa serializado en un archivo.

Cuando lo deserializa, no hay manera de saber cuál era el tipo de mapa genérico original. En lo que respecta a Java, es solo un mapa

Me encontré con esto con listas, y fue molesto. Pero lo que en realidad terminé haciendo fue crear una clase genérica de "conversión de conversión" que toma una colección y un "convertidor" (que puede ser un tipo interno anónimo).

Luego, cuando itera a través de la colección de conversión, cada elemento se convierte. Y eso resolvió el problema de advertencia. Es mucho trabajo simplemente deshacerse de la advertencia, pero no creo que esa haya sido la razón principal por la que surgió ese marco. También puede hacer cosas más potentes, como tomar una colección de nombres de archivos, y luego escribir un convertidor que carga los datos en el archivo, o hace una consulta, o lo que sea.

1

Si realmente no necesita el tipo genérico exacto del mapa y su código no depende de ese tipo, pero simplemente está molesto por las advertencias se podría escribir:

Map<?, ?> returnedMap; 
returnedMap = thing.getResultData(Map.class, some_other_args); 

entonces se puede use cualquiera de los métodos de mapa que no estén parametrizados por tipo como containsKey, clear, iterator, etc. o itere sobre entrySet o pase returnedMap a cualquier método genérico y todo será seguro.

Map<?, ?> returnedMap; 
returnedMap = thing.getResultData(Map.class, some_other_args); 

Object myKey = ...; 

Object someValue = returnedMap.get(myKey); 

for (Iterator<? extends Map.Entry<?,?>> it = returnedMap.entrySet().iterator(); it.hasNext();) 
    if (it.next().equals(myKey)) 
    it.remove(); 

for (Map.Entry<?,?> e : returnedMap.entrySet()) { 
    if (e.getKey().equals(myKey)) 
    e.setValue(null); // if the map supports null values 
} 

doSomething(returnedMap); 

... 

<K,V> void doSomething(Map<K,V> map) { ... } 

En realidad, hay muchas cosas que puede hacer con un mapa de forma segura sin saber la clave o el tipo de valor.

Cuestiones relacionadas