Pergunta sobre java, generics, overloading, polymorphism – Método aceitando dois tipos diferentes como parâmetro

25

Eu estou escrevendo um método que deve aceitar como seu parâmetro um objeto de um dos dois tipos que não compartilham um tipo pai diferente de Object. Por exemplo, os tipos são Sonhos e Alho. Você pode fazer as duas coisasdreams.crush() egarlic.crush(). Eu quero ter um métodoutterlyDestroy(parameter), que aceitaria como parâmetro os sonhos e o alho.

utterlyDestroy(parameter) {
    parameter.crush()
}

Tanto o Alho quanto os sonhos fazem parte de alguma biblioteca, então eles implementam uma interface ICrushable (para que eu possa escreverutterlyDestroy(ICrushable parameter) ) não é uma opção.

Meu corpo do método é bastante longo, então sobrecarregar significaria duplicar o código. Feio. Eu tenho certeza que eu poderia usar reflexão e fazer alguns hacks de classe. Feio.

Eu tentei usar genéricos, mas aparentemente não posso escrever algo como

utterlyDestroy(<T instanceof Dreams || T instanceof Garlic> parameter)

É possível fazer o typecast do Garlic to Dreams?

utterlyDestroy(Object parameter) {
    ((Dreams)parameter).crush()
}

Isso ainda seria feio. Quais são minhas outras opções e qual é o método preferido para lidar com a situação?

Sua resposta

8   a resposta
1

Se você for tratá-los da mesma maneira em muitos lugares do seu projeto, sugiro envolvê-los em uma classe, algo como um adaptador.

Não, é apenas em um só lugar. Obrigado pela resposta embora. JohnEye
1

Criando uma interface Crushable parece ser o caminho mais limpo para ir. Está subtipando Garlic or Dreams uma opção e adicionando sua interface ao subtipo?

Exceto isso, você pode colocar o código comum em um método privado, e fazer com que as duas versões de utterlyDestroy façam o que elas têm que fazer com os objetos individuais antes de chamar o código comum. Se o corpo do método for longo, provavelmente precisará dividi-lo em métodos privados de qualquer maneira. Eu estou supondo que você já pensou nisso, porém, como é ainda mais óbvia uma solução do que adicionar uma interface.

Você pode trazer o parâmetro como um objeto e, em seguida, lançá-lo. É isso que você quer dizer com reflexão? ou seja,

public void utterlyCrush(Object crushable) {
    if (crushable instanceOf Dream) {
         ...
    }
    if (curshable instanceOf Garlic) {
         ...
    }

Mas lançar do alho ao sonho não é uma opção, dado que um não é um subtipo do outro.

Não, por reflexão eu quis dizer usando alguns dos recursos interessantes para encontrar um método chamado crush () e executá-lo ou algo parecido. Eu só escrevi isso para desencorajar os "trolls de reflexão" de responder. JohnEye
4

Você pode implementar uma classe Haskell-esque em Java; algo assim:

class Either<L,R>
{
    private Object value;

    public static enum Side {LEFT, RIGHT}

    public Either(L left)  {value = left;}
    public Either(R right) {value = right;}

    public Side getSide() {return value instanceof L ? Side.LEFT : Side.RIGHT;}

    // Both return null if the correct side isn't contained.
    public L getLeft() {return value instanceof L ? (L) value : null;}
    public R getRight() {return value instanceof R ? (R) value : null;}
}

Então você deixa esse método pegar algo do tipoEither<Dreams, Garlic>.

2

Você poderia usar uma interface eadaptar seus tipos para isso.

Interface:

public interface Crushable {
  public void crush();
}

Exemplo de invocação:

public class Crusher {
  public static void crush(Crushable crushable) {
    crushable.crush();
  }
}

Exemplo de método de fábrica do adaptador:

public final class Dreams {
  public static Crushable asCrushable(final Dream dream) {
    class DreamCrusher implements Crushable {
      @Override
      public void crush() {
        dream.crush();
      }
    }
    return new DreamCrusher();
  }

  private Dreams() {}
}

O código do consumidor é assim:

  Dream dream = new Dream();
  Crushable crushable = Dreams.asCrushable(dream);
  Crusher.crush(crushable);

Se você tiver muitos tipos para se adaptar, você pode considerar a reflexão. Aqui está uma fábrica de adaptadores (não otimizada) que usa oProcuração tipo:

public final class Crushables {
  private static final Class<?>[] INTERFACES = { Crushable.class };

  public static Crushable adapt(final Object crushable) {
    class Handler implements InvocationHandler {
      @Override
      public Object invoke(Object proxy, Method method, Object[] args)
          throws Throwable {
        return crushable.getClass()
            .getMethod(method.getName(), method.getParameterTypes())
            .invoke(crushable, args);
      }
    }

    ClassLoader loader = Thread.currentThread()
        .getContextClassLoader();
    return (Crushable) Proxy.newProxyInstance(loader, INTERFACES, new Handler());
  }

  private Crushables() {}
}

Para o consumidor da API, isso não é assim tão feio:

  Dream dream = new Dream();
  Crushable crushable = Crushables.adapt(dream);
  Crusher.crush(crushable);

No entanto, como é habitual com a reflexão, você sacrifica a verificação do tipo em tempo de compilação.

2

Basta usar sobrecarga de método.

public void utterlyDestroy(Dreams parameter) {
    parameter.crush();
}

public void utterlyDestroy(Garlic parameter) {
    parameter.crush();
}

Se você quiser suportar mais do que esses dois tipos da mesma maneira, você pode definir uma interface comum para todos eles e usar genéricos.

Como eu disse, meu método é bastante longo e isso significaria duplicar o código. JohnEye
@ JohnEye - Chame uma interface já em apenas duas classes, então. Veja também a resposta da AadvarkSoup caso você não possa estruturar corretamente sua hierarquia de classes. Jirka Hanika
0

Como estou usando:

void fooFunction(Object o){
Type1 foo=null;
if(o instanceof Type1) foo=(Type1)o;
if(o instanceof Type2) foo=((Type2)o).toType1();
// code
}

Mas isso só funciona se o Type2 puder ser convertido em Type1

29

Que tal agora:

interface ICrushable {
    void crush();
}

utterlyDestroy(ICrushable parameter) {
    // Very long crushing process goes here
    parameter.crush()
}

utterlyDestroy(Dreams parameter) {
    utterlyDestroy(new ICrushable() { crush() {parameter.crush();});
}

utterlyDestroy(Garlic parameter) {
    utterlyDestroy(new ICrushable() { crush() {parameter.crush();});
}

O novo desenvolvimento deve implementar a interface ICrushable, mas para as Classes existentes, o parâmetro é empacotado em um ICrushable e passado para o utterlyDestroy (ICrushable) que faz todo o trabalho.

6

Como sobre algo tão simples como isso?

utterlyDestroy(Object parameter) {    
    if(parameter instanceOf Dreams){  
        Dream dream = (Dreams)parameter;  
        dream.crush();
        //Here you can use a Dream 
    }  
    else if(parameter instanceOf Garlic){  
       Garlic garlic = (Garlic)parameter;   
        //Here you can use a Garlic  
       garlic.crush();  
   }
} 

Se outterlyDestroy é muito complexo e grande e você só quer chamar ocrush então isso faz o que você quer

Naturalmente isso me ocorreu, mas usar Object como um parâmetro parece errado. JohnEye
Você não está repetindo o mesmo código aqui? Não faz sentido para mim. Akshay

Perguntas relacionadas