2010-10-21 10 views
22

Me pregunto cuáles son las opciones para especializar tipos genéricos en Java, es decir, en una clase de plantilla para tener reemplazos específicos para ciertos tipos.Especialización en genéricos Java (plantilla) posible (reemplazando tipos de plantillas con tipos específicos)

En mi caso, era una clase genérica (de tipo T) para devolver normalmente null, pero devolvía "" (la cadena vacía), cuando T es el tipo String, o 0 (cero) cuando es el tipo Integer, etc.

proporcionar meramente una sobrecarga de tipo específico de un método produce un "método es ambiguo" error:

por ejemplo:

public class Hacking { 

    public static void main(String[] args) { 
    Bar<Integer> barInt = new Bar<Integer>(); 
    Bar<String> barString = new Bar<String>(); 

    // OK, returns null 
    System.out.println(barInt.get(new Integer(4))); 

    // ERROR: The method get(String) is ambiguous for the type Bar<String> 
    System.out.println(barString.get(new String("foo"))); 
    } 

    public static class Bar<T> { 

    public T get(T x) { 
     return null; 
    } 

    public String get(String x) { 
     return ""; 
    } 
    } 
} 

es la única opción para subclase de la clase genérica con una específica tipo (ver StringBar en el siguiente ejemplo?

public static void main(String[] args) { 
    Bar<Integer> barInt = new Bar<Integer>(); 
    StringBar barString2 = new StringBar(); 

    // OK, returns null 
    System.out.println(barInt.get()); 

    // OK, returns "" 
    System.out.println(barString2.get()); 
    } 

    public static class Bar<T> { 

    public T get() { 
     return null; 
    } 
    } 

    public static class StringBar extends Bar<String> { 
    public String get() { 
     return ""; 
    } 
    } 
} 

Es esta la única manera, es un poco doloroso tener que crear una subclase para cada tipo que quiera especializar en lugar de una sobrecarga de get() en la clase Bar.

Supongo que podría verificar la instancia en el método Bar.get(), p. Ej. T get (T t) { if (t instanceof String) return ""; if (t instanceof Integer) return 0; else return null; }

Sin embargo, me han enseñado a evitar instanceof y usar polimorfismo cuando sea posible.

+0

Nota Vengo de un mundo en el que C++ C++ especialización es simple de hacer a través de método de sobrecarga – AshirusNW

+2

Java genéricos son muy diferentes de las plantillas de C++, ya que los genéricos son ejecutados por el borrado. Eche un vistazo a esta publicación, probablemente aclarará mucho para usted: http://stackoverflow.com/questions/313584/what-is-the-concept-of-erasure-in-generics-in-java –

+1

I Soy consciente de las diferencias en la implementación, pero me pregunto cómo superar las deficiencias (para mi caso de uso) en los genéricos de Java, específicamente la especialización – AshirusNW

Respuesta

8

Considerando todo, el consenso parece ser que el método del método StringBar mencionado en la pregunta es el único camino a seguir.

public static class StringBar extends Bar<String> { 
    public String get() { 
     return ""; 
    } 
    } 
+1

He creado y aceptado mi propia respuesta porque, aunque las otras respuestas fueron correctas y llegan a esta conclusión, todas contienen puntos engañosos o malinterpretaron la pregunta de alguna manera. – AshirusNW

3

Los genéricos en Java no están hechos para la especialización. Están hechos para generalización! Si desea especializarse para ciertos tipos, debe especializarse ... a través de una subclase.

Sin embargo, a menudo no es necesario que haga algo de manera especializada. Su ejemplo es una especie de StringBar artificial, ya que podría tener esta:

public class Bar<T> { 
    private final T value; 
    public T get() { 
     return value; 
    } 
} 

no veo por qué tiene que especializarse para una cadena aquí.

+0

. ¿Mi única opción es el método StringBar? – AshirusNW

+0

Básicamente, get() recupera una instancia de tipo T de otro lugar que no está almacenada en la clase, pero si hay un error, devuelve nulo, pero me gustaría que devuelva "" para String si hay un error para evitar extra null-checking para la persona que llama en el caso de la especialización String. – AshirusNW

+0

Btw, por supuesto, el ejemplo está ideado: me da piedad de ti al no mostrarte la verdadera fuente que es larga de publicar y desvirtúa la verdadera pregunta. El código fuente real tiene una necesidad específica de especialización. – AshirusNW

4

El compilador es realmente correcto, porque el siguiente código es verificado en tiempo de compilación (Bar<String> barString = new Bar<String>();) una vez recopilados, desde

public static class Bar<T> { 

    public T get(T x) { 
     return null; 
    } 

    public String get(String x) { 
     return ""; 
    } 
    } 

a

public static class Bar<String> { 

    public String get(String x) { 
     return null; 
    } 

    public String get(String x) { 
     return ""; 
    } 
    } 

y es ambigua en cuanto no se puede tener 2 métodos idénticos con los mismos tipos de devolución y los mismos argumentos de parámetros.

Véase una explicación por Jon Skeet:


Usted puede subclase Bar<T> y crear StringBar (NOTA me quita la palabra clave static) y anular get() método .

public class BarString extends Bar<String> { 

    @Override 
    public String get(String x) { 
     return ""; 
    } 
} 
+2

Sí, soy consciente de por qué el compilador arroja un error: esto se ha cubierto adecuadamente en SO. Lo que quiero saber es las diversas soluciones que * do * funcionan. He sugerido el método de creación de subclases (StringBar), me pregunto si hay alguna otra forma de especialización. – AshirusNW

+0

Mi sugerencia es eliminar el método 'public String get (String x)'. ¿Por qué es necesario si tiene 'Bar barString'? –

+0

barString no funciona como me gustaría. Quiero que barString.get() devuelva la cadena vacía, no nula. – AshirusNW

4

Los genéricos en Java son muy diferentes de las plantillas en C++ a este respecto. No es posible escribir una versión específica de una clase genérica para hacer algo diferente para un caso particular, como lo puede hacer C++. Tampoco es posible determinar en tiempo de ejecución qué es T: esto se debe a que esa información no se transmite al código de bytes (código de objeto) y, por lo tanto, ni siquiera existe en el tiempo de ejecución. Esto debido a algo llamado "borrado de tipo".

BarString y BarInt serían la forma obvia de hacerlo, pero hay mejoras que puede hacer. Por ejemplo, puede escribir una barra genérica para cubrir los casos comunes, y luego escribir BarString y BarInt especializados para implementar casos especiales. Asegurar que los casos sólo se pueden crear a través de una fábrica, que toma la clase del objeto a procesar:

class Bar<T> { 
    class BarString extends Bar<String> { 
    // specialist code goes here 
    } 


static Bar<T> createBar(Class<T> clazz) { 
    if (clazz==String.class) { 
    return new BarString(); 
    } else { 
    return new Bar<T>; 
} 

que probablemente no se compilará, pero no tienen el tiempo para hacer ejercicio la sintaxis exacta Ilustra el principio.

Cuestiones relacionadas