java - sirve - ¿Cómo hago que el método devuelva el tipo genérico?
metodos de jtable en java (19)
"¿Hay una manera de averiguar el tipo de retorno en tiempo de ejecución sin el parámetro extra utilizando instanceof?"
Como solución alternativa, podría utilizar el patrón de visitante de esta manera. Hacer abstracto animal y hacerlo implementable visitable:
abstract public class Animal implements Visitable {
private Map<String,Animal> friends = new HashMap<String,Animal>();
public void addFriend(String name, Animal animal){
friends.put(name,animal);
}
public Animal callFriend(String name){
return friends.get(name);
}
}
Visitable solo significa que una implementación de Animal está dispuesta a aceptar un visitante:
public interface Visitable {
void accept(Visitor v);
}
Y una implementación de visitante puede visitar todas las subclases de un animal:
public interface Visitor {
void visit(Dog d);
void visit(Duck d);
void visit(Mouse m);
}
Entonces, por ejemplo, una implementación de Dog se vería así:
public class Dog extends Animal {
public void bark() {}
@Override
public void accept(Visitor v) { v.visit(this); }
}
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 métodos específicos de subclase debe implementar la interfaz de visitante de esta manera:
public class Example implements Visitor {
public void main() {
Mouse jerry = new Mouse();
jerry.addFriend("spike", new Dog());
jerry.addFriend("quacker", new Duck());
// Used to be: ((Dog) jerry.callFriend("spike")).bark();
jerry.callFriend("spike").accept(this);
// Used to be: ((Duck) jerry.callFriend("quacker")).quack();
jerry.callFriend("quacker").accept(this);
}
// This would fire on callFriend("spike").accept(this)
@Override
public void visit(Dog d) { d.bark(); }
// This would fire on callFriend("quacker").accept(this)
@Override
public void visit(Duck d) { d.quack(); }
@Override
public void visit(Mouse m) { m.squeak(); }
}
Sé que son muchas más interfaces y métodos de los que esperabas, pero es una forma estándar de controlar cada subtipo específico con exactamente cero instancias de verificación y conversión de tipo cero. Y todo se hace de una manera agnóstica de lenguaje estándar, por lo que no es solo para Java, sino que cualquier lenguaje OO debería funcionar igual.
Considere este ejemplo (típico en los libros de OOP):
Tengo una clase de Animal
, donde cada Animal
puede tener muchos amigos.
Y las subclases como Dog
, Duck
, Mouse
, etc., que agregan un comportamiento específico como bark()
, quack()
etc.
Aquí está la clase de Animal
:
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 un montón de encasillamiento:
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 en que pueda usar los genéricos para el tipo de devolución para deshacerme del encasillado, por lo que puedo 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 una manera de averiguar el tipo de retorno en tiempo de ejecución sin el parámetro extra utilizando instanceof
? O al menos pasando una clase del tipo en lugar de una instancia ficticia.
Entiendo que los genéricos son para la compilación de tiempo de compilación, pero ¿hay alguna solución para esto?
Además, puede solicitar al método que devuelva el valor en un tipo dado de esta manera
<T> T methodName(Class<T> var);
Más ejemplos here en la documentación de Oracle Java.
Aquí está la versión más simple:
public <T> T callFriend(String name) {
return (T) friends.get(name); //Casting to T not needed in this case but its a good practice to do
}
Código completamente funcional:
public class Test {
public static class Animal {
private Map<String,Animal> friends = new HashMap<>();
public void addFriend(String name, Animal animal){
friends.put(name,animal);
}
public <T> T callFriend(String name){
return (T) friends.get(name);
}
}
public static class Dog extends Animal {
public void bark() {
System.out.println("i am dog");
}
}
public static class Duck extends Animal {
public void quack() {
System.out.println("i am duck");
}
}
public static void main(String [] args) {
Animal animals = new Animal();
animals.addFriend("dog", new Dog());
animals.addFriend("duck", new Duck());
Dog dog = animals.callFriend("dog");
dog.bark();
Duck duck = animals.callFriend("duck");
duck.quack();
}
}
Basándose en la misma idea que los Super Type Tokens, podría crear una identificación escrita para usar en lugar de una cadena:
public abstract class TypedID<T extends Animal> {
public final Type type;
public final String id;
protected TypedID(String id) {
this.id = id;
Type superclass = getClass().getGenericSuperclass();
if (superclass instanceof Class) {
throw new RuntimeException("Missing type parameter.");
}
this.type = ((ParameterizedType) superclass).getActualTypeArguments()[0];
}
}
Pero creo que esto puede anular el propósito, ya que ahora necesita crear nuevos objetos de identificación para cada cadena y mantenerlos (o reconstruirlos con la información de tipo correcta).
Mouse jerry = new Mouse();
TypedID<Dog> spike = new TypedID<Dog>("spike") {};
TypedID<Duck> quacker = new TypedID<Duck>("quacker") {};
jerry.addFriend(spike, new Dog());
jerry.addFriend(quacker, new Duck());
Pero ahora puede usar la clase de la forma que quería originalmente, sin los lanzamientos.
jerry.callFriend(spike).bark();
jerry.callFriend(quacker).quack();
Esto es solo ocultar el parámetro type dentro del id, aunque sí significa que puede recuperar el tipo del identificador más adelante si lo desea.
Necesitará implementar los métodos de comparación y hashing de TypedID también si desea poder comparar dos instancias idénticas de una identificación.
Como dijiste que pasar una clase estaría bien, podrías escribir esto:
public <T extends Animal> T callFriend(String name, Class<T> clazz) {
return (T) friends.get(name);
}
Y luego úsalo así:
jerry.callFriend("spike", Dog.class).bark();
jerry.callFriend("quacker", Duck.class).quack();
No es perfecto, pero esto es casi todo lo que se puede hacer con los genéricos de Java. Hay una manera de implementar Contenedores heterogéneos seguros para tipos (THC) utilizando tokens Super Type , pero eso tiene sus propios problemas nuevamente.
Esta pregunta es muy similar al Artículo 29 en Java efectiva : "Considere contenedores heterogéneos seguros para los tipos". La respuesta de Laz es la más cercana a la solución de Bloch. Sin embargo, tanto poner y obtener deben usar el literal de la clase para la seguridad. Las firmas se convertirían en:
public <T extends Animal> void addFriend(String name, Class<T> type, T animal);
public <T extends Animal> T callFriend(String name, Class<T> type);
Dentro de ambos métodos debes comprobar que los parámetros son sensatos. Ver Eficaz Java y la Class javadoc para más información.
Existe otro enfoque: puede restringir el tipo de retorno cuando reemplaza un método. En cada subclase tendrías que reemplazar a 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.
Hay muchas respuestas excelentes aquí, pero este es el enfoque que adopté para una prueba de Appium en la que actuar sobre un solo elemento puede llevar 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.
public <T extends MobilePage> T tapSignInButton(Class<T> type) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
//signInButton.click();
return type.getConstructor(AppiumDriver.class).newInstance(appiumDriver);
}
- MobilePage es la súper clase con la que se extiende el tipo, lo que significa que puedes usar cualquiera de sus hijos (duh)
- type.getConstructor (Param.class, etc.) le permite interactuar con el constructor del tipo. Este constructor debe ser el mismo entre todas las clases esperadas.
- newInstance toma una variable declarada que desea pasar al nuevo constructor de objetos
Si no quieres tirar los errores puedes atraparlos así:
public <T extends MobilePage> T tapSignInButton(Class<T> type) {
// signInButton.click();
T returnValue = null;
try {
returnValue = type.getConstructor(AppiumDriver.class).newInstance(appiumDriver);
} catch (Exception e) {
e.printStackTrace();
}
return returnValue;
}
He escrito un artículo que contiene una prueba de concepto, clases de soporte y una clase de prueba que demuestra cómo las clases de Super Type pueden ser recuperadas por sus clases en tiempo de ejecución. En pocas palabras, le permite delegar a implementaciones alternativas dependiendo de los parámetros genéricos reales pasados por el llamante. Ejemplo:
-
TimeSeries<Double>
delega a una clase interna privada que usadouble[]
-
TimeSeries<OHLC>
delega a una clase interna privada que usaArrayList<OHLC>
Consulte: Uso de TypeTokens para recuperar parámetros genéricos.
Gracias
Richard Gomes - Blog
Hice lo siguiente en mi kontraktor lib:
public class Actor<SELF extends Actor> {
public SELF self() { return (SELF)_self; }
}
subclases
public class MyHttpAppSession extends Actor<MyHttpAppSession> {
...
}
al menos esto funciona dentro de la clase actual y cuando se tiene una referencia de tipo fuerte. La herencia múltiple funciona, pero se vuelve realmente complicada entonces :)
Imposible. ¿Cómo se supone que el Mapa sepa qué subclase de Animal obtendrá, solo con 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 () obtuvo un parámetro de tipo. Pero realmente parece que te estás perdiendo el punto de herencia: es que solo puedes tratar subclases de manera uniforme cuando usas exclusivamente los métodos de superclase.
Lo que estás buscando aquí es abstracción. Codifica contra interfaces más y deberías hacer menos casting.
El siguiente ejemplo está en C # pero el concepto sigue siendo el mismo.
using System;
using System.Collections.Generic;
using System.Reflection;
namespace GenericsTest
{
class MainClass
{
public static void Main (string[] args)
{
_HasFriends jerry = new Mouse();
jerry.AddFriend("spike", new Dog());
jerry.AddFriend("quacker", new Duck());
jerry.CallFriend<_Animal>("spike").Speak();
jerry.CallFriend<_Animal>("quacker").Speak();
}
}
interface _HasFriends
{
void AddFriend(string name, _Animal animal);
T CallFriend<T>(string name) where T : _Animal;
}
interface _Animal
{
void Speak();
}
abstract class AnimalBase : _Animal, _HasFriends
{
private Dictionary<string, _Animal> friends = new Dictionary<string, _Animal>();
public abstract void Speak();
public void AddFriend(string name, _Animal animal)
{
friends.Add(name, animal);
}
public T CallFriend<T>(string name) where T : _Animal
{
return (T) friends[name];
}
}
class Mouse : AnimalBase
{
public override void Speak() { Squeek(); }
private void Squeek()
{
Console.WriteLine ("Squeek! Squeek!");
}
}
class Dog : AnimalBase
{
public override void Speak() { Bark(); }
private void Bark()
{
Console.WriteLine ("Woof!");
}
}
class Duck : AnimalBase
{
public override void Speak() { Quack(); }
private void Quack()
{
Console.WriteLine ("Quack! Quack!");
}
}
}
No realmente, 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 de MakeNoise () a Animal que se implementaría como un ladrido o curandero por sus subclases?
No. El compilador no puede saber qué tipo de jerry.callFriend("spike")
devolvería. Además, su implementación simplemente oculta el modelo en el método sin ningún tipo de seguridad adicional. Considera esto:
jerry.addFriend("quaker", new Duck());
jerry.callFriend("quaker", /* unused */ new Dog()); // dies with illegal cast
En este caso específico, crear un método de talk()
abstracto y anularlo adecuadamente en las subclases le sería mucho mejor:
Mouse jerry = new Mouse();
jerry.addFriend("spike", new Dog());
jerry.addFriend("quacker", new Duck());
jerry.callFriend("spike").talk();
jerry.callFriend("quacker").talk();
Podrías definir callFriend
esta manera:
public <T extends Animal> T callFriend(String name, Class<T> type) {
return type.cast(friends.get(name));
}
Entonces llámalo como tal:
jerry.callFriend("spike", Dog.class).bark();
jerry.callFriend("quacker", Duck.class).quack();
Este código tiene la ventaja de no generar ninguna advertencia del compilador. Por supuesto, esto es solo una versión actualizada del casting de los días pre-genéricos y no agrega ninguna seguridad adicional.
Podrías implementarlo así:
@SuppressWarnings("unchecked")
public <T extends Animal> T callFriend(String name) {
return (T)friends.get(name);
}
(Sí, este es un código legal; consulte Genéricos de Java: tipo genérico definido solo como tipo de devolución ).
El tipo de retorno se deducirá de la persona que llama. Sin embargo, tenga en cuenta la anotación @SuppressWarnings
: que le dice que este código no es seguro para el tipo . Tiene que verificarlo usted mismo, o podría obtener ClassCastExceptions
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 manera de hacer feliz al compilador es llamarlo así:
jerry.<Dog>callFriend("spike").bark();
Si bien esto puede ser un poco mejor que el casting, probablemente sea mejor que le Animal
clase Animal
un método abstracto de talk()
, como dijo David Schmitt.
Sé que esto es una cosa completamente diferente que la que pidió. Otra forma de resolver esto sería la reflexión. Quiero decir, esto no toma el beneficio de los genéricos, pero te permite emular, de alguna manera, el comportamiento que quieres realizar (hacer un ladrido de perro, hacer un pato, etc.) sin tener que cuidar el tipo de lanzamiento:
import java.lang.reflect.InvocationTargetException;
import java.util.HashMap;
import java.util.Map;
abstract class AnimalExample {
private Map<String,Class<?>> friends = new HashMap<String,Class<?>>();
private Map<String,Object> theFriends = new HashMap<String,Object>();
public void addFriend(String name, Object friend){
friends.put(name,friend.getClass());
theFriends.put(name, friend);
}
public void makeMyFriendSpeak(String name){
try {
friends.get(name).getMethod("speak").invoke(theFriends.get(name));
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (SecurityException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
}
public abstract void speak ();
};
class Dog extends Animal {
public void speak () {
System.out.println("woof!");
}
}
class Duck extends Animal {
public void speak () {
System.out.println("quack!");
}
}
class Cat extends Animal {
public void speak () {
System.out.println("miauu!");
}
}
public class AnimalExample {
public static void main (String [] args) {
Cat felix = new Cat ();
felix.addFriend("Spike", new Dog());
felix.addFriend("Donald", new Duck());
felix.makeMyFriendSpeak("Spike");
felix.makeMyFriendSpeak("Donald");
}
}
qué pasa
public class Animal {
private Map<String,<T extends Animal>> friends = new HashMap<String,<T extends Animal>>();
public <T extends Animal> void addFriend(String name, T animal){
friends.put(name,animal);
}
public <T extends Animal> T callFriend(String name){
return friends.get(name);
}
}
public <X,Y> X nextRow(Y cursor) {
return (X) getRow(cursor);
}
private <T> Person getRow(T cursor) {
Cursor c = (Cursor) cursor;
Person s = null;
if (!c.moveToNext()) {
c.close();
} else {
String id = c.getString(c.getColumnIndex("id"));
String name = c.getString(c.getColumnIndex("name"));
s = new Person();
s.setId(id);
s.setName(name);
}
return s;
}
Puede devolver cualquier tipo y recibir directamente como. No es necesario encasillarlo.
Person p = nextRow(cursor); // cursor is real database cursor.
Esto es lo mejor si desea personalizar cualquier otro tipo de registros en lugar de cursores reales.