Pergunta

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

Eu tenho uma classe Animal, onde cada Animal pode ter muitos amigos.
E subclasses como Dog, Duck, Mouse etc que adicionam comportamento específico como bark(), quack() etc.

Aqui está a classe 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);
    }
}

E aqui está algum 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 eu posso usar os genéricos para o tipo de retorno para se livrar do typecasting, para que eu possa dizer

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

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

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 um exemplo fictício.
Eu entendo os genéricos são para o tempo de compilação verificação de tipo, mas há uma solução para isso?

Foi útil?

Solução

Você pode definir callFriend desta maneira:

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

Em seguida, chamá-lo como tal:

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

Este código tem a vantagem de não gerar quaisquer avisos do compilador. Claro que isto é realmente apenas uma versão atualizada do elenco desde os dias pré-genérico e não adiciona qualquer segurança adicional.

Outras dicas

No. O compilador não pode saber que tipo jerry.callFriend("spike") voltaria. Além disso, sua implementação apenas esconde o elenco no método sem qualquer tipo de segurança adicional. Considere o seguinte:

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

Neste caso específico, a criação de um método talk() abstrato e substituindo-lo adequadamente nas subclasses iria atendê-lo 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 como este:

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

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

O tipo de retorno será inferida a partir do chamador. No entanto, note a anotação @SuppressWarnings: que lhe diz que este código não é typesafe . Você tem que verificar isso sozinho, ou você pode obter ClassCastExceptions em tempo de execução.

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

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

Embora isso possa ser um pouco melhor do que o casting, você é provavelmente melhor fora dando a classe Animal um método talk() abstrato, como disse David Schmitt.

Esta questão é muito semelhante à Item 29 em Effective Java - "Considere typesafe containers heterogêneos." A resposta de Laz é o mais próximo de solução de Bloch. No entanto, tanto colocar e obter deve usar o literal Classe para a segurança. As assinaturas se tornaria:

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 dois métodos que você deve verificar se os parâmetros estão sãos. Veja Effective Java eo Classe javadoc para mais informações.

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

<T> T methodName(Class<T> var);

Mais exemplos aqui a documentação do Oracle Java

Aqui é a versão mais simples:

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 Funcionando:

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

        }
    }

Como você disse passar uma classe seria OK, você pode escrever o seguinte:

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

E, em seguida, usá-lo como este:

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

Não é perfeito, mas isso é muito bonito, tanto quanto você começa com os genéricos Java. Existe uma maneira de implementar Typesafe Heterogenous Containers (THC), utilizando Super Tipo Tokens , mas que tem seus próprios problemas novamente.

Com base na mesma idéia como Super Tipo Tokens, você poderia criar uma ID digitado para usar em vez de uma string:

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

Mas eu acho que isso pode derrotar a finalidade, já que agora você precisa criar novos objetos de ID para cada corda e segurá-los (ou reconstruí-las com as informações de tipo correto).

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

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

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

Este é apenas escondendo o parâmetro do tipo dentro do id, embora isso não significa que você pode recuperar o tipo do identificador mais tarde, se desejar.

Você precisaria implementar a comparação e hashing métodos de TypedID também se você quer ser capaz de comparar duas instâncias idênticas de um id.

"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. Faça animal abstrato e torná-lo implementar 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 apenas significa que uma implementação animal está disposto a aceitar um visitante:

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

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

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

Assim, por exemplo uma implementação do cão, então, parecido com este:

public class Dog extends Animal {
    public void bark() {}

    @Override
    public void accept(Visitor v) { v.visit(this); }
}

O truque aqui é que, como o cão sabe que tipo é ele pode acionar o método visita sobrecarregado relevante do visitante v passando "isto" como um parâmetro. Outras subclasses iria implementar accept () exatamente da mesma maneira.

A classe que quer métodos específicos de chamada subclasse deve, em seguida, implementar a interface Visitor assim:

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 métodos que você esperava, mas é uma forma padrão para obter uma alça em cada subtipo específico com precisão de zero instanceof cheques e zero tipo elencos. E tudo é feito de forma agnóstica linguagem padrão por isso não é apenas para Java, mas qualquer linguagem OO deve funcionar da mesma.

Não é possível. Como é o mapa deveria saber que subclasse de animal que vai obter, dada apenas uma chave de Cordas?

A única maneira isso seria possível é se cada animal aceita apenas um tipo de amigo (então ele poderia ser um parâmetro da classe Animal), ou do método callFriend () tem um parâmetro de tipo. Mas realmente parece que você está perdendo o ponto de herança: é que você pode subclasses apenas tratam de maneira uniforme ao utilizar exclusivamente os métodos da superclasse

.

Eu escrevi um artigo que contém uma prova de conceito, aulas de apoio e uma classe de teste que demonstra como Super Tipo Tokens podem ser recuperados por suas classes em tempo de execução. Em poucas palavras, ele permite que você delegar para implementações alternativas dependendo dos parâmetros genéricos reais passados ??pelo chamador. Exemplo:

  • delegados TimeSeries<Double> para uma classe interna privada que usa double[]
  • delegados TimeSeries<OHLC> para uma classe interna privada que usa ArrayList<OHLC>

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

Graças

Richard Gomes - Blog

Há um monte de grandes respostas aqui, mas esta é a abordagem que levou para um teste Appium onde agindo em um único elemento pode resultar em ir a diferentes estados de aplicação com base nas configurações do usuário. Enquanto ele não seguir as convenções de exemplo do OP, espero que ajude alguém.

public <T extends MobilePage> T tapSignInButton(Class<T> type) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
    //signInButton.click();
    return type.getConstructor(AppiumDriver.class).newInstance(appiumDriver);
}
  • MobilePage é a super classe que o tipo se estende ou seja, você pode usar qualquer um dos seus filhos (duh)
  • type.getConstructor (Param.class, etc) permite que você interaja com o construtor do tipo. Este construtor deve ser o mesmo entre todas as classes esperados.
  • newInstance leva uma variável declarada que você deseja passar para os novos objetos construtor

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

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

Não é verdade, porque, como você diz, o compilador só sabe que callFriend () está retornando um animal, não um cão ou pato.

Você pode não adicionar um makeNoise abstrato () para animal que seria implementado como um latido ou charlatão por suas subclasses?

O que você está procurando aqui é abstração. Código contra as interfaces mais e você deve ter que fazer menos casting.

O exemplo abaixo é 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<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!");
    }
}
}

Eu sei que isto é uma coisa completamente diferente que o pediu. Outra maneira de resolver isso seria reflexão. Quer dizer, isso não leva a vantagem de genéricos, mas permite que você emule, de alguma forma, o comportamento que você deseja executar (fazer um cão latir, fazer um pato quack, etc.) sem cuidar do tipo de elenco:

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

    }

}

o que acontece com

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

}

Eu fiz o seguinte no meu lib kontraktor:

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

subclassificação:

public class MyHttpAppSession extends Actor<MyHttpAppSession> {
   ...
}

Pelo menos obras dentro da classe atual e ao ter uma referência digitado forte. herança múltipla funciona, mas fica realmente complicado então:)

Há uma outra abordagem, você pode restringir o tipo de retorno quando você substituir um método. Em cada subclasse você teria que substituir callFriend para retornar que subclasse. O custo seria as múltiplas declarações de callFriend, mas você poderia isolar as partes comuns a 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,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;
}

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

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

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

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top