Considere este ejemplo (típico en los libros de OOP):
Tengo una Animal
clase, donde cada uno Animal
puede tener muchos amigos.
Y subclases, por ejemplo Dog
, Duck
, Mouse
etc, que añadir un comportamiento específico, como bark()
, quack()
etc.
Aquí está la Animal
clase:
public class Animal {
private Map<String,Animal> friends = new HashMap<>();
public void addFriend(String name, Animal animal){
friends.put(name,animal);
}
public Animal callFriend(String name){
return friends.get(name);
}
}
Y aquí hay un fragmento de código con muchos tipos de letra:
Mouse jerry = new Mouse();
jerry.addFriend("spike", new Dog());
jerry.addFriend("quacker", new Duck());
((Dog) jerry.callFriend("spike")).bark();
((Duck) jerry.callFriend("quacker")).quack();
¿Hay alguna forma de usar genéricos para el tipo de retorno para deshacerme de la conversión de texto, de modo que pueda decir
jerry.callFriend("spike").bark();
jerry.callFriend("quacker").quack();
Aquí hay un código inicial con el tipo de retorno transmitido al método como un parámetro que nunca se usa.
public<T extends Animal> T callFriend(String name, T unusedTypeObj){
return (T)friends.get(name);
}
¿Hay alguna manera de averiguar el tipo de retorno en tiempo de ejecución sin el uso del parámetro adicional instanceof
? O al menos pasando una clase del tipo en lugar de una instancia ficticia.
Entiendo que los genéricos son para la verificación de tipos en tiempo de compilación, pero ¿hay alguna solución para esto?
fuente
No. El compilador no puede saber qué tipo
jerry.callFriend("spike")
devolvería. Además, su implementación solo oculta el reparto en el método sin ningún tipo de seguridad adicional. Considera esto:En este caso específico, crear un
talk()
método abstracto y anularlo de manera apropiada en las subclases te serviría mucho mejor:fuente
Podrías implementarlo así:
(Sí, este es un código legal; consulte Java Generics: Tipo genérico definido como tipo de retorno solamente ).
El tipo de retorno se deducirá de la persona que llama. Sin embargo, tenga en cuenta la
@SuppressWarnings
anotación: eso le dice que este código no es seguro . Debe verificarlo usted mismo, o podría obtenerloClassCastExceptions
en tiempo de ejecución.Desafortunadamente, la forma en que lo está utilizando (sin asignar el valor de retorno a una variable temporal), la única forma de hacer feliz al compilador es llamarlo así:
Si bien esto puede ser un poco más agradable que el casting, probablemente sea mejor darle a la
Animal
clase untalk()
método abstracto , como dijo David Schmitt.fuente
jerry.CallFriend<Dog>(...
lo que creo que se ve mejor.java.util.Collections.emptyList()
función del JRE se implemente exactamente de esta manera, y su javadoc se anuncia como seguro.Esta pregunta es muy similar al Artículo 29 en Java Efectivo - "Considere contenedores heterogéneos de tipo seguro". La respuesta de Laz es la más cercana a la solución de Bloch. Sin embargo, tanto put como get deben usar el literal de clase por seguridad. Las firmas se convertirían en:
Dentro de ambos métodos, debe verificar que los parámetros sean sanos. Consulte Java eficaz y la clase javadoc para obtener más información.
fuente
Además, puede solicitar al método que devuelva el valor de un tipo dado de esta manera
Más ejemplos aquí en la documentación de Oracle Java
fuente
Aquí está la versión más simple:
Código completamente funcional:
fuente
Casting to T not needed in this case but it's a good practice to do
. Quiero decir, si se cuida adecuadamente durante el tiempo de ejecución, ¿qué significa "buena práctica"?Como dijiste que pasar una clase estaría bien, podrías escribir esto:
Y luego úsalo así:
No es perfecto, pero esto es más o menos lo que obtienes con los genéricos de Java. Hay una manera de implementar contenedores heterogéneos Typesafe (THC) utilizando Super Type Tokens , pero eso tiene sus propios problemas nuevamente.
fuente
Basado en la misma idea que Super Type Tokens, podría crear una identificación escrita para usar en lugar de una cadena:
Pero creo que esto puede vencer el propósito, ya que ahora necesita crear nuevos objetos de identificación para cada cadena y conservarlos (o reconstruirlos con la información de tipo correcta).
Pero ahora puede usar la clase de la manera que originalmente quería, sin los moldes.
Esto solo oculta el parámetro de tipo dentro de la identificación, aunque sí significa que puede recuperar el tipo del identificador más adelante si lo desea.
Debería implementar también los métodos de comparación y hash de TypedID si desea poder comparar dos instancias idénticas de una identificación.
fuente
"¿Hay alguna manera de averiguar el tipo de retorno en tiempo de ejecución sin el parámetro adicional usando instanceof?"
Como una solución alternativa, puede utilizar el patrón Visitor como este. Haga que Animal abstract y haga que se implemente Visitable:
Visitable solo significa que una implementación de Animal está dispuesta a aceptar un visitante:
Y la implementación de un visitante puede visitar todas las subclases de un animal:
Entonces, por ejemplo, una implementación de Dog se vería así:
El truco aquí es que, como el Perro sabe de qué tipo es, puede activar el método de visita sobrecargado relevante del visitante v al pasar "esto" como parámetro. Otras subclases implementarían accept () exactamente de la misma manera.
La clase que desea llamar a los métodos específicos de la subclase debe implementar la interfaz de visitante de esta manera:
Sé que son muchas más interfaces y métodos de los que esperaba, pero es una forma estándar de manejar cada subtipo específico con exactamente cero instancias de verificaciones y cero tipos de conversiones. Y todo se realiza de manera independiente del lenguaje estándar, por lo que no es solo para Java, sino que cualquier lenguaje OO debería funcionar igual.
fuente
Imposible. ¿Cómo se supone que el Mapa sabe qué subclase de Animal obtendrá, dada solo una clave de Cadena?
La única forma en que esto sería posible es si cada Animal aceptara solo un tipo de amigo (entonces podría ser un parámetro de la clase Animal), o si el método callFriend () obtuviera un parámetro de tipo. Pero realmente parece que te estás perdiendo el punto de herencia: es que solo puedes tratar las subclases de manera uniforme cuando usas exclusivamente los métodos de superclase.
fuente
He escrito un artículo que contiene una prueba de concepto, clases de soporte y una clase de prueba que demuestra cómo tus clases pueden recuperar Super Type Tokens en tiempo de ejecución. En pocas palabras, le permite delegar a implementaciones alternativas dependiendo de los parámetros genéricos reales pasados por la persona que llama. Ejemplo:
TimeSeries<Double>
delega a una clase interna privada que usadouble[]
TimeSeries<OHLC>
delega a una clase interna privada que usaArrayList<OHLC>
Ver: Uso de TypeTokens para recuperar parámetros genéricos
Gracias
Richard Gomes - Blog
fuente
Aquí hay muchas respuestas excelentes, pero este es el enfoque que tomé para una prueba de Appium donde actuar sobre un solo elemento puede dar lugar a ir a diferentes estados de aplicación según la configuración del usuario. Si bien no sigue las convenciones del ejemplo de OP, espero que ayude a alguien.
Si no desea arrojar los errores, puede atraparlos así:
fuente
En realidad no, porque como dices, el compilador solo sabe que callFriend () está devolviendo un animal, no un perro o un pato.
¿No puede agregar un método abstracto makeNoise () a Animal que sus subclases implementarían como un ladrido o graznido?
fuente
Lo que estás buscando aquí es abstracción. Codifique contra interfaces más y debería hacer menos casting.
El siguiente ejemplo está en C # pero el concepto sigue siendo el mismo.
fuente
Hice lo siguiente en mi lib kontraktor:
subclases:
al menos esto funciona dentro de la clase actual y cuando se tiene una referencia tipada fuerte. La herencia múltiple funciona, pero se vuelve realmente difícil :)
fuente
Sé que esto es algo completamente diferente a lo que preguntó. Otra forma de resolver esto sería la reflexión. Quiero decir, esto no toma el beneficio de los genéricos, pero le permite emular, de alguna manera, el comportamiento que desea realizar (hacer ladrar a un perro, hacer un graznido de pato, etc.) sin tener en cuenta el tipo de conversión:
fuente
qué pasa
}
fuente
Hay otro enfoque, puede reducir el tipo de retorno cuando anula un método. En cada subclase, tendría que anular callFriend para devolver esa subclase. El costo serían las múltiples declaraciones de callFriend, pero podría aislar las partes comunes a un método llamado internamente. Esto me parece mucho más simple que las soluciones mencionadas anteriormente, y no necesita un argumento adicional para determinar el tipo de retorno.
fuente
public int getValue(String name){}
es indistinguible desdepublic boolean getValue(String name){}
el punto de vista de los compiladores. Debería cambiar el tipo de parámetro o agregar / eliminar parámetros para que se reconozca la sobrecarga. Tal vez solo te estoy malinterpretando ...Puede devolver cualquier tipo y recibir directamente como. No es necesario escribir a máquina.
Esto es mejor si desea personalizar cualquier otro tipo de registros en lugar de cursores reales.
fuente
(Cursor) cursor
por ejemplo.cursor
ser unCursor
, y siempre devolverá unPerson
(o nulo). El uso de genéricos elimina la comprobación de estas restricciones, lo que hace que el código sea inseguro.