519 votos

¿Cómo puedo hacer el tipo de retorno método genérico?

Considere este ejemplo (típico en la programación orientada a objetos de los libros):
Tengo una clase de Animal, donde cada Animal puede tener muchos amigos.
Y subclases igual que el Perro, Pato, Ratón, etc que añadir el comportamiento específico como bark(), quack() etc.

He aquí la clase de Animal:

public class Animal {
    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);
    }
}

Y he aquí algunas 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 manera de que pueda utilizar genéricos para el tipo de retorno para deshacerse de la encasillamiento, por lo que puedo decir

jerry.callFriend("spike").bark();
jerry.callFriend("quacker").quack();

He aquí algunas código inicial con el tipo de retorno transmite al método como un parámetro que no se utilizan nunca.

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 adicional utilizando instanceof? O, al menos, por el paso de una clase de tipo en lugar de una instancia ficticia.
Entiendo que los medicamentos genéricos son por tiempo de compilación el tipo de comprobación, pero hay una solución para esto?

803voto

laz Puntos 12212

Se podría definir callFriend de esta manera:

 public <T extends Animal> T callFriend(String name, Class<T> type) {
    return type.cast(friends.get(name));
}
 

Entonces llamarlo 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 sólo una versión actualizada de la fundición de los días pre-genéricos y no añade ninguna seguridad adicional.

111voto

David Schmitt Puntos 29384

No. El compilador no puede saber qué tipo jerry.callFriend("spike") volvería. Además, su aplicación sólo se esconde el elenco en el método sin ningún tipo de seguridad adicional. Considere esto:

 jerry.addFriend("quaker", new Duck());
jerry.callFriend("quaker", /* unused */ new Dog()); // dies with illegal cast
 

En este caso concreto, la creación de un abstract talk() método y anulando de manera adecuada en las subclases podría servirle mucho mejor:

 Mouse jerry = new Mouse();
jerry.addFriend("spike", new Dog());
jerry.addFriend("quacker", new Duck());

jerry.callFriend("spike").talk();
jerry.callFriend("quacker").talk();
 

99voto

Michael Myers Puntos 82361

Se podría implementar algo como esto:

@SuppressWarnings("unchecked")
public <T extends Animal> T callFriend(String name){
    return (T)friends.get(name);
}

(Sí, este es el código legal; ver a esta pregunta.)

El tipo de retorno se deducirá de la persona que llama. Sin embargo, tenga en cuenta el @SuppressWarnings anotación: que le dice a usted que este código no es typesafe. Usted tiene que comprobar usted mismo, o usted podría conseguir ClassCastExceptions en tiempo de ejecución.


EDIT: por Desgracia, la manera en que vas a usar (sin asignar el valor devuelto a una variable temporal), la única manera de hacer que el compilador feliz es llamar así:

jerry.<Dog>callFriend("spike").bark();

Mientras que esto puede ser un poco más agradable que el de fundición, usted es probablemente mejor dar la Animal clase abstracta talk() método, como David Schmitt, dijo.

25voto

Craig P. Motlin Puntos 11814

Esta pregunta es muy similar al Artículo 29 en Efectivo Java - "Considerar la posibilidad de typesafe heterogéneo de contenedores." Laz la respuesta es la más cercana a Bloch de la solución. Sin embargo, ambos put y get debería utilizar la Clase literal de seguridad. Las firmas sería:

public <T extends Animal> void addFriend(String name, Class<T> type, T animal);
public <T extends Animal> Animal callFriend(String name, Class<T> type);

Dentro de los métodos se debe comprobar que los parámetros son los sane. Ver Eficaz de Java y la Clase de javadoc para más información.

5voto

Michael Borgwardt Puntos 181658

No es posible. ¿Cómo se supone que el mapa para saber qué subclase de Animal que va a conseguir, dado solamente una llave de cuerda?

La única manera de esto sería posible si cada animal es aceptada sólo un tipo de amigo (entonces podría ser un parámetro de la clase Animal), o el método de callFriend () tiene un parámetro de tipo. Pero lo que realmente parece que estás perdiendo el punto de la herencia: es que sólo se puede tratar de manera uniforme subclases utilizando exclusivamente los métodos de superclase.

Iteramos.com

Iteramos es una comunidad de desarrolladores que busca expandir el conocimiento de la programación mas allá del inglés.
Tenemos una gran cantidad de contenido, y también puedes hacer tus propias preguntas o resolver las de los demás.

Powered by:

X