Como faço o tipo de retorno do método genérico?

Considere este exemplo (típico em livros OOP):

Eu tenho uma class Animal , onde cada Animal pode ter muitos amigos.
E subclasss como Dog , Duck , Mouse etc, que adicionam comportamentos específicos como bark() , quack() etc.

Aqui está a class Animal :

 public class Animal { private Map friends = new HashMap(); public void addFriend(String name, Animal animal){ friends.put(name,animal); } public Animal callFriend(String name){ return friends.get(name); } } 

E aqui está um trecho de código com muita typecasting:

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

Existe alguma maneira que eu possa usar genéricos para o tipo de retorno para se livrar da typecasting, para que eu possa dizer

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

Aqui está algum código inicial com o tipo de retorno transmitido para o método como um parâmetro que nunca é usado.

 public T callFriend(String name, T unusedTypeObj){ return (T)friends.get(name); } 

Existe uma maneira de descobrir o tipo de retorno em tempo de execução sem o parâmetro extra usando instanceof ? Ou, pelo menos, passando uma class do tipo em vez de uma instância fictícia.
Eu entendo genéricos são para verificação de tipo de tempo de compilation, mas existe uma solução para isso?

    Você pode definir o callFriend desta maneira:

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

    Então chame como tal:

     jerry.callFriend("spike", Dog.class).bark(); jerry.callFriend("quacker", Duck.class).quack(); 

    Esse código tem o benefício de não gerar nenhum aviso do compilador. É claro que esta é apenas uma versão atualizada do casting dos dias pré-genéricos e não adiciona nenhuma segurança adicional.

    Não. O compilador não pode saber que tipo jerry.callFriend("spike") retornaria. Além disso, sua implementação apenas oculta o lançamento no método sem nenhum tipo adicional de segurança. Considere isto:

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

    Neste caso específico, criar um método talk() abstrato e substituí-lo apropriadamente nas subclasss serviria muito melhor:

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

    Você poderia implementá-lo assim:

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

    (Sim, este é o código legal; consulte Java Generics: tipo genérico definido apenas como tipo de retorno .)

    O tipo de retorno será inferido do chamador. No entanto, observe a anotação @SuppressWarnings : que informa que esse código não é @SuppressWarnings . Você tem que verificá-lo, ou você pode obter ClassCastExceptions em tempo de execução.

    Infelizmente, da maneira como você está usando (sem atribuir o valor de retorno a uma variável temporária), a única maneira de deixar o compilador feliz é chamá-lo assim:

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

    Embora isso possa ser um pouco melhor do que o casting, provavelmente é melhor dar à class Animal um método talk() abstrato, como David Schmitt disse.

    Esta questão é muito semelhante ao item 29 em Java efetivo – “Considerar contêineres heterogêneos de tipos seguros”. A resposta de Laz é a mais próxima da solução de Bloch. No entanto, colocar e obter devem usar o literal de class para segurança. As assinaturas se tornariam:

     public  void addFriend(String name, Class type, T animal); public  T callFriend(String name, Class type); 

    Dentro dos dois methods, você deve verificar se os parâmetros são estáveis. Veja Effective Java e o javadoc da class para mais informações.

    Além disso, você pode pedir ao método para retornar o valor em um determinado tipo dessa maneira

      T methodName(Class var); 

    Mais exemplos aqui na documentação do Oracle Java

    Como você disse, passar uma aula seria OK, você poderia escrever isto:

     public  T callFriend(String name, Class clazz) { return (T) friends.get(name); } 

    E então use isto assim:

     jerry.callFriend("spike", Dog.class).bark(); jerry.callFriend("quacker", Duck.class).quack(); 

    Não é perfeito, mas isso é praticamente o que você consegue com os genéricos Java. Existe uma maneira de implementar Contêineres Heterogêneos Typesafe (THC) usando Tokens de Tipo Super , mas isso tem seus próprios problemas novamente.

    Com base na mesma ideia de Super Type Tokens, você poderia criar um ID typescript para usar em vez de uma string:

     public abstract class TypedID { 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]; } } 

    Mas acho que isso pode frustrar o propósito, já que agora você precisa criar novos objects id para cada string e segurá-los (ou reconstruí-los com as informações corretas do tipo).

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

    Mas agora você pode usar a class da maneira que você queria originalmente, sem os castings.

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

    Isso é apenas esconder o parâmetro type dentro do id, embora isso signifique que você pode recuperar o tipo do identificador mais tarde, se desejar.

    Você precisaria implementar os methods de comparação e hashing do TypedID também se quiser comparar duas instâncias idênticas de um id.

    Não é possivel. Como o mapa supostamente saberá qual subclass de Animal ele obterá, considerando apenas uma chave de String?

    A única maneira de isso ser possível é se cada Animal aceitasse apenas um tipo de amigo (então poderia ser um parâmetro da class Animal), ou se o método callFriend () tivesse um parâmetro de tipo. Mas realmente parece que você está perdendo o ponto da inheritance: é que você só pode tratar as subclasss uniformemente ao usar exclusivamente os methods da superclass.

    “Existe uma maneira de descobrir o tipo de retorno em tempo de execução sem o parâmetro extra usando instanceof?”

    Como uma solução alternativa, você pode utilizar o padrão Visitor como este. Tornar Animal abstrato e torná-lo implementar Visitável:

     abstract public class Animal implements Visitable { private Map friends = new HashMap(); public void addFriend(String name, Animal animal){ friends.put(name,animal); } public Animal callFriend(String name){ return friends.get(name); } } 

    Visitável significa apenas que uma implementação Animal está disposta a aceitar um visitante:

     public interface Visitable { void accept(Visitor v); } 

    E uma implementação de visitante é capaz de visitar todas as subclasss de um animal:

     public interface Visitor { void visit(Dog d); void visit(Duck d); void visit(Mouse m); } 

    Então, por exemplo, uma implementação Dog seria assim:

     public class Dog extends Animal { public void bark() {} @Override public void accept(Visitor v) { v.visit(this); } } 

    O truque aqui é que, como o Cachorro sabe que tipo é, ele pode acionar o método de visita sobrecarregado relevante do visitante v passando “isto” como um parâmetro. Outras subclasss implementariam accept () exatamente da mesma maneira.

    A class que deseja chamar methods específicos de subclass deve, então, implementar a interface Visitor dessa forma:

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

    Eu sei que é muito mais interfaces e methods do que você esperava, mas é uma maneira padrão de obter um identificador em cada subtipo específico com precisão zero de ocorrências de checks e zero type casts. E tudo é feito de uma forma padronizada e agnóstica, portanto não é apenas para Java, mas qualquer linguagem OO deve funcionar da mesma forma.

    Escrevi um artigo que contém uma prova de conceito, classs de suporte e uma class de teste que demonstra como os Tokens Super Type podem ser recuperados por suas classs em tempo de execução. Em suma, permite delegar a implementações alternativas, dependendo dos parâmetros genéricos reais passados ​​pelo chamador. Exemplo:

    • TimeSeries delega para uma class interna privada que usa double[]
    • TimeSeries delega para uma class interna privada que usa ArrayList

    Veja: Usando TypeTokens para recuperar parâmetros genéricos

    obrigado

    Richard Gomes – Blog

    Aqui está a versão mais simples:

     public  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 totalmente funcional:

      public class Test { public static class Animal { private Map friends = new HashMap<>(); public void addFriend(String name, Animal animal){ friends.put(name,animal); } public  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(); } } 

    Não realmente, porque, como você diz, o compilador só sabe que callFriend () está retornando um Animal, não um Dog ou Duck.

    Você não pode adicionar um método abstrato makeNoise () ao Animal que seria implementado como um latido ou charlatão por suas subclasss?

    O que você está procurando aqui é abstração. Codifique mais as interfaces e você deve fazer menos conversão.

    O exemplo abaixo está em C #, mas o conceito permanece o mesmo.

     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(string name) where T : _Animal; } interface _Animal { void Speak(); } abstract class AnimalBase : _Animal, _HasFriends { private Dictionary friends = new Dictionary(); public abstract void Speak(); public void AddFriend(string name, _Animal animal) { friends.Add(name, animal); } public T CallFriend(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!"); } } } 

    Há muitas ótimas respostas aqui, mas essa é a abordagem que tomei para um teste do Appium em que agir em um único elemento pode resultar em diferentes estados do aplicativo com base nas configurações do usuário. Embora não siga as convenções do exemplo do OP, espero que ajude alguém.

     public  T tapSignInButton(Class type) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException { //signInButton.click(); return type.getConstructor(AppiumDriver.class).newInstance(appiumDriver); } 
    • MobilePage é a superclass que o tipo estende significando que você pode usar qualquer um dos seus filhos (duh)
    • type.getConstructor (Param.class, etc) permite que você interaja com o construtor do tipo. Esse construtor deve ser o mesmo entre todas as classs esperadas.
    • newInstance leva uma variável declarada que você deseja passar para o novo construtor de objects

    Se você não quer jogar os erros, você pode pegá-los assim:

     public  T tapSignInButton(Class type) { // signInButton.click(); T returnValue = null; try { returnValue = type.getConstructor(AppiumDriver.class).newInstance(appiumDriver); } catch (Exception e) { e.printStackTrace(); } return returnValue; } 

    Eu sei que isso é uma coisa completamente diferente que a pessoa perguntou. Outra maneira de resolver isso seria reflection. Quer dizer, isso não tira o benefício do Generics, mas permite imitar, de alguma forma, o comportamento que você quer realizar (fazer um latido de cachorro, fazer um charlatão de pato, etc.) sem cuidar do tipo de casting:

     import java.lang.reflect.InvocationTargetException; import java.util.HashMap; import java.util.Map; abstract class AnimalExample { private Map> friends = new HashMap>(); private Map theFriends = new HashMap(); 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"); } } 

    sobre o quê

     public class Animal { private Map> friends = new HashMap>(); public  void addFriend(String name, T animal){ friends.put(name,animal); } public  T callFriend(String name){ return friends.get(name); } 

    }

    Eu fiz o seguinte no meu lib kontraktor:

     public class Actor { public SELF self() { return (SELF)_self; } } 

    subsorting:

     public class MyHttpAppSession extends Actor { ... } 

    pelo menos isso funciona dentro da class atual e ao ter uma referência digitada forte. A inheritance múltipla funciona, mas fica realmente complicada, então 🙂

    Existe outra abordagem, você pode restringir o tipo de retorno ao replace um método. Em cada subclass você teria que sobrescrever callFriend para retornar aquela subclass. O custo seria as múltiplas declarações de callFriend, mas você poderia isolar as partes comuns para um método chamado internamente. Isso parece muito mais simples para mim do que as soluções mencionadas acima e não precisa de um argumento extra para determinar o tipo de retorno.

     public  X nextRow(Y cursor) { return (X) getRow(cursor); } private  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; } 

    Você pode retornar qualquer tipo e receber diretamente como. Não precisa de typecast.

     Person p = nextRow(cursor); // cursor is real database cursor. 

    Isso é melhor se você quiser personalizar qualquer outro tipo de registro em vez de cursores reais.