java ¿Cómo hago que el método devuelva el tipo genérico?




9 Answers

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();
java generics return-value

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?




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.




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.




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.




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 usa double[]
  • TimeSeries<OHLC> delega a una clase interna privada que usa ArrayList<OHLC>

Consulte: Uso de TypeTokens para recuperar parámetros genéricos.

Gracias

Richard Gomes - Blog




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?




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



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

}




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.






Related