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