É possível através de generics definir um método que só aceita 2 interfaces ou uma classe que implementa essas duas interface? (veja exmplo)

Seria algo assim:

public <E extends BarListener | FooListener> void addListener(E listener) {

}

Que obviamente não compila. Para deixar claro, eu quero um método que aceite:

  • ou BarListener
  • ou FooListener
  • ou alguma class que implemente ambos

Estou tentando fugir de utilizar:

public void addListener(Object listener) {

}

Com instanceof dentro, o que é horrível. Alguma alternativa melhor?

1 curtida

Embora não seja possível usar o |, é possível usar o & e assim alcançar o seu terceiro requisito que é só aceitar classes que implementem as 2 interfaces:

interface FooListener {}

interface BarListener {}

class MyListener implements FooListener, BarListener {}

public class Program {
  public static <E extends BarListener & FooListener> void addListener(E listener) {}

  public static void main(String... args) {
    addListener(new MyListener());
  }
}

O problema é que vc não conseguiria passar uma classe que só implementa uma das interfaces.

Vc pode mostrar essas 2 interfaces?

3 curtidas

Tem um requisito implícito no que você está querendo que é o seguinte: BarListener e FooListener tem alguns métodos em comuns que você quer usar no seu método addListener. Senão ficaria impossível implementar esse método certo?

Por exemplo, imagine que essas interfaces sejam assim:

interface FooListener {
  void listen(Object something);
}

interface BarListener {
  void hear(String somethingElse);
}

Nesse exemplo acima fica óbvio que você precisaria implementar seu método addListener de formas diferentes para cada interface.

Ou seja, provavelmente BarListener e FooListener já possuem alguns métodos em comum, que você pode extrair numa interface e implementar um método sem generics:

interface FooListener extends CommonInterface {}

interface BarListener extends CommonInterface {}

void addListener(CommonInterface common) {}

3 curtidas

Não possuem métodos em comum :frowning:

Complementando o que o @AbelBueno disse, talvez seja melhor rever o design, pois esta situação na qual você se encontra pode ser o sintoma de um problema anterior (estou chutando, claro, pois sem ver o código é o melhor que dá pra sugerir) :slight_smile:


Vamos supor que fosse possível fazer isso:

public <T extends String | Integer> void fazAlgo(T valor) {
    ...
}

Ou seja, o método fazAlgo recebe um valor que pode ser uma String ou um Integer.

Como você usaria o valor dentro do método? Ou seja, como fazer para saber se ele é uma String ou um Integer, já que esses tipos não tem nada a ver um com o outro (não há nenhuma relação de herança entre eles, um não é sub-tipo do outro)? A única coisa que eles têm em comum é que ambos herdam de Object, e só. Todo o resto é diferente.

Então a única forma de saber seria testar com instanceof, seguido de cast. Ou, se não quer fazer cast, só daria para usar os métodos que eles têm em comum (que no caso, seriam os métodos herdados de Object). Na prática, seria o mesmo que o método receber um Object. Você até poderia ter o “ganho” de saber em tempo de compilação se não está passando um tipo incorreto, mas dentro do método não haveria nenhum outro ganho em termos de saber o tipo de valor para usá-lo adequadamente.

Obs: eu não sei se é por isso que a linguagem não permitiu tal recurso. Só sei que se isso fosse permitido, teria que ser criado algum outro mecanismo para descobrir automaticamente o tipo, senão não haveria nenhum ganho significativo.

Ou seja, uma solução seria sobrecarregar os métodos:

public void fazAlgo(String valor) {
    ...
}
public void fazAlgo(Integer valor) {
    ...
}

Assim, dentro de cada método você trata o valor da forma mais adequada, de acordo com o tipo.


No seu caso, como você disse que as interfaces não têm métodos em comum, então elas são dois tipos diferentes e sem nenhuma relação entre si (a não ser a “coincidência semântica” de ambos serem listeners). Então o jeito é ter dois métodos mesmo:

public void addListener(BarListener listener) {
    ...
}
public void addListener(FooListener listener) {
    ...
}

Ou então reveja o design da aplicação e crie um listener comum, como já sugerido. Veja se faz sentido ter dois tipos diferentes de listener:

  • se fizer, é porque ambos são de fato diferentes entre si, então o tratamento deve ser diferenciado, o que justificaria ter métodos sobrecarregados
  • se não fizer, é porque ambos possuem algo em comum e faria sentido ter uma interface comum para ambos

Ou coloque o código e dê mais contexto, quem sabe assim a gente pode sugerir algo melhor e mais específico. :wink:


Uma outra alternativa

Nas versões mais recentes da linguagem (se não me engano a partir do Java 15) você pode usar sealed interfaces, ou seja, você pode definir que uma interface só pode ser implementada por determinadas classes. Por exemplo, se eu criar esta interface:

public sealed interface Listener permits BarListener, FooListener {
    public void listen();
}

Esta interface é sealed e só pode ser implementada por BarListener e FooListener. E nestas classes eu tenho que indicar:

public final class BarListener implements Listener {
    public void listen() {
        System.out.println("bar listen");
    }
    // outros métodos específicos de BarListener
}

public final class FooListener implements Listener {
    public void listen() {
        System.out.println("foo listen");
    }
    // outros métodos específicos de FooListener
}

Assim, você pode criar um método que recebe um Listener, mas como esta interface é sealed, isso me garante que o método só pode receber um BarListener ou um FooListener.

Lembrando que até o Java 16 você tem que compilar com a opção --enable-preview (já que é uma versão preview, pois o recurso está previsto para ser oficial - ou seja, “não-preview” - somente no Java 17):

# para compilar
javac --enable-preview -source 16 *java

# e para executar (supondo que a classe principal é Main.java)
java --enable-preview Main

Outro recurso disponível nas versões mais novas é o pattern matching, que deixa o instanceof um pouco “menos chato”, pois não precisa do casting:

public void fazAlgo(Listener listener) {
    listener.listen(); // chama o método da interface Listener
    if (listener instanceof FooListener foo) {
        // aqui posso usar a variável foo e chamar métodos específicos de FooListener 
    } else if (listener instanceof BarListener bar) {
        // aqui posso usar a variável bar e chamar métodos específicos de BarListener     }
    }
}

E complementando, a partir do Java 21, outra opção é usar uma switch expression:

public void fazAlgo(Listener listener) {
    switch (listener) {
        case BarListener bar -> {
            // usar o listener "bar" aqui
            bar.metodoEspecificoDoBarListener();
        }
        case FooListener foo -> {
            // usar o listener "foo" aqui
            foo.metodoEspecificoDoFooListener();
        }
        default -> {
            // não é BarListener nem FooListener, faz outra coisa
        }
    }
}