Considere este exemplo (típico nos livros de POO):
Eu tenho uma Animal
aula, onde cada um Animal
pode ter muitos amigos.
E subclasses como Dog
, etc Duck
, Mouse
que adicionam comportamento específico bark()
, quack()
etc.
Aqui está a Animal
turma:
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);
}
}
E aqui está um trecho de código com muita tipografia:
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 de usar genéricos para o tipo de retorno para me livrar da conversão de texto, para que eu possa dizer
jerry.callFriend("spike").bark();
jerry.callFriend("quacker").quack();
Aqui está um código inicial com o tipo de retorno transmitido ao método como um parâmetro que nunca é usado.
public<T extends Animal> 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 classe do tipo em vez de uma instância fictícia.
Entendo que os genéricos são para verificação de tipo em tempo de compilação, mas existe uma solução alternativa para isso?
fonte
Não. O compilador não pode saber que tipo
jerry.callFriend("spike")
retornaria. Além disso, sua implementação apenas oculta a conversão no método sem nenhum tipo de segurança adicional. Considere isto:Nesse caso específico, criar um
talk()
método abstrato e substituí-lo adequadamente nas subclasses ajudaria você muito melhor:fonte
Você poderia implementá-lo assim:
(Sim, este é um código legal; consulte Java Generics: tipo genérico definido apenas como tipo de retorno .)
O tipo de retorno será deduzido do chamador. No entanto, observe a
@SuppressWarnings
anotação: que informa que esse código não é seguro . Você precisa verificar por conta própria ou poderá obterClassCastExceptions
em tempo de execução.Infelizmente, do jeito que você o está usando (sem atribuir o valor de retorno a uma variável temporária), a única maneira de fazer o compilador feliz é chamá-lo assim:
Embora isso possa ser um pouco melhor do que o elenco, é melhor você dar à
Animal
classe umtalk()
método abstrato , como disse David Schmitt.fonte
jerry.CallFriend<Dog>(...
que acho melhor.java.util.Collections.emptyList()
função do JRE seja implementada exatamente dessa maneira, e seu javadoc se anuncia como sendo tipicamente seguro.Essa pergunta é muito semelhante ao item 29 do Java efetivo - "Considere contêineres heterogêneos com segurança de tipo". A resposta de Laz é a mais próxima da solução de Bloch. No entanto, tanto o put quanto o get devem usar o literal de classe por segurança. As assinaturas se tornariam:
Dentro dos dois métodos, você deve verificar se os parâmetros estão corretos. Consulte Java efetivo e javadoc de classe para obter mais informações.
fonte
Além disso, você pode solicitar ao método que retorne o valor em um determinado tipo dessa maneira
Mais exemplos aqui na documentação do Oracle Java
fonte
Aqui está a versão mais simples:
Código totalmente funcional:
fonte
Casting to T not needed in this case but it's a good practice to do
. Quero dizer, se foi resolvido adequadamente durante o tempo de execução, o que significa "boa prática"?Como você disse que aprovar uma aula seria bom, você poderia escrever o seguinte:
E então use-o assim:
Não é perfeito, mas isso é praticamente o máximo possível com os genéricos Java. Existe uma maneira de implementar os Contentores Heterogêneos Typesafe (THC) usando Super Type Tokens , mas isso tem seus próprios problemas novamente.
fonte
Com base na mesma idéia que os Super Type Tokens, você pode criar um ID digitado para usar em vez de uma string:
Mas acho que isso pode anular o objetivo, já que agora você precisa criar novos objetos de identificação para cada string e segurá-los (ou reconstruí-los com as informações de tipo corretas).
Mas agora você pode usar a classe da maneira que queria originalmente, sem os elencos.
Isso está apenas ocultando o parâmetro type dentro do id, embora isso signifique que você pode recuperar o tipo do identificador posteriormente, se desejar.
Você também precisará implementar os métodos de comparação e hash de TypedID, se quiser comparar duas instâncias idênticas de um ID.
fonte
"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 assim. Tornar o Animal abstrato e implementá-lo Visível:
Visível significa apenas que uma implementação Animal está disposta a aceitar um visitante:
E uma implementação de visitante pode visitar todas as subclasses de um animal:
Então, por exemplo, uma implementação Dog seria assim:
O truque aqui é que, como o cão sabe que tipo é, ele pode acionar o método de visita sobrecarregado relevante do visitante v, passando "this" como parâmetro. Outras subclasses implementariam accept () exatamente da mesma maneira.
A classe que deseja chamar métodos específicos da subclasse deve implementar a interface do Visitante desta forma:
Sei que são muito mais interfaces e métodos do que você esperava, mas é uma maneira padrão de lidar com cada subtipo específico com exatamente zero instância de verificações e zero tipos de conversão. E tudo é feito de uma maneira independente de linguagem padrão, portanto não é apenas para Java, mas qualquer linguagem OO deve funcionar da mesma maneira.
fonte
Não é possivel. Como o Mapa deve saber qual subclasse de Animal será obtida, considerando apenas uma chave String?
A única maneira de isso ser possível é se cada Animal aceitar apenas um tipo de amigo (então pode ser um parâmetro da classe Animal) ou o método callFriend () obter um parâmetro de tipo. Mas realmente parece que você está perdendo o ponto de herança: é que você só pode tratar subclasses de maneira uniforme ao usar exclusivamente os métodos da superclasse.
fonte
Escrevi um artigo que contém uma prova de conceito, classes de suporte e uma classe de teste que demonstra como os Super Type Tokens podem ser recuperados por suas classes em tempo de execução. Em poucas palavras, permite delegar a implementações alternativas, dependendo dos parâmetros genéricos reais passados pelo chamador. Exemplo:
TimeSeries<Double>
delega a uma classe interna privada que usadouble[]
TimeSeries<OHLC>
delega a uma classe interna privada que usaArrayList<OHLC>
Consulte: Usando TypeTokens para recuperar parâmetros genéricos
obrigado
Richard Gomes - Blog
fonte
Há muitas ótimas respostas aqui, mas essa é a abordagem que eu adotei para um teste do Appium em que agir em um único elemento pode resultar em diferentes estados de aplicativos com base nas configurações do usuário. Embora não siga as convenções do exemplo do OP, espero que ajude alguém.
Se você não deseja lançar os erros, pode pegá-los da seguinte maneira:
fonte
Na verdade, não, porque, como você diz, o compilador sabe apenas que callFriend () está retornando um Animal, não um Cachorro ou Pato.
Você não pode adicionar um método abstrato makeNoise () ao Animal que seria implementado como uma casca ou charlatão por suas subclasses?
fonte
O que você está procurando aqui é abstração. Código contra interfaces mais e você deve ter que fazer menos transmissões.
O exemplo abaixo está em C #, mas o conceito permanece o mesmo.
fonte
Eu fiz o seguinte no meu kontraktor lib:
subclasse:
pelo menos isso funciona dentro da classe atual e ao ter uma referência digitada forte. Herança múltipla funciona, mas fica realmente complicado :)
fonte
Eu sei que isso é uma coisa completamente diferente que a pergunta. Outra maneira de resolver isso seria a reflexão. Quero dizer, isso não tira o benefício da Generics, mas permite imitar, de alguma forma, o comportamento que você deseja executar (fazer um cachorro latir, fazer um pato grasnar, etc.) sem cuidar do tipo de conversão:
fonte
A respeito
}
fonte
Há outra abordagem: você pode restringir o tipo de retorno ao substituir um método. Em cada subclasse, você teria que substituir o callFriend para retornar essa subclasse. O custo seria as várias declarações de callFriend, mas você poderia isolar as partes comuns em um método chamado internamente. Isso me parece muito mais simples do que as soluções mencionadas acima e não precisa de um argumento extra para determinar o tipo de retorno.
fonte
public int getValue(String name){}
é indistinguível dopublic boolean getValue(String name){}
ponto de vista dos compiladores. Você precisaria alterar o tipo de parâmetro ou adicionar / remover parâmetros para que a sobrecarga fosse reconhecida. Talvez eu esteja apenas mal-entendido que você embora ...Você pode retornar qualquer tipo e receber diretamente como. Não precisa digitar.
É melhor se você deseja personalizar qualquer outro tipo de registro em vez de cursores reais.
fonte
(Cursor) cursor
por exemplo.cursor
ser umCursor
e sempre retornará umPerson
(ou nulo). O uso de genéricos remove a verificação dessas restrições, tornando o código inseguro.