Enigma: Comportamento Estranho na Sobrecarga usando herança e interface

6 respostas
M

Olá Pessoal,

Achei um comportamento bem estranho em um teste de sobrecarga de método com herança de classe e uma interface que fiz.
Para mim, o comportamento dos dois blocos de código, entre sí, que postarei abaixo parece bastante incoerente.
Alguém saberia me explicar o que está acontecendo ?

Bloco 1 (pai (Classe A) e filho (Classe B) implementam a interface):

interface IntF { }

class A implements IntF{ }
class B extends A implements IntF { }

public class OverloadTest {

	public void method(A a_obj) { System.out.println("A"); }
	public void method(IntF intf_obj) { System.out.println("IntF"); }
	
	public static void main(String[] args) {
		OverloadTest teste = new OverloadTest();
		B obj = new B();
		teste.method(obj);
	}
}

Neste Bloco 1 a saída é: A
Sem erros de compilação, execução ou qualquer warning.
Desta forma, parece que a JVM preferiu usar o método com a classe A na assinatura ao invés do método com a interface na assinatura.

Vamos ao Bloco 2 (apenas o filho (Classe B) implementa a interface):
(Vale ressaltar que o Bloco 2 é exatamente igual ao Bloco 1, exceto pelo fato que agora Class A não mais implementa a interface)

interface IntF { }

class A { }
class B extends A implements IntF { }

public class OverloadTest {

	public void method(A a_obj) { System.out.println("A"); }
	public void method(IntF intf_obj) { System.out.println("IntF"); }
	
	public static void main(String[] args) {
		OverloadTest teste = new OverloadTest();
		B obj = new B();
		teste.method(obj);
	}
}

Neste Bloco 2 a saída é: Erro de compilação:
Exception in thread “main” java.lang.Error: Unresolved compilation problem:
The method method(A) is ambiguous for the type OverloadTest
at OverloadTest.main(OverloadTest.java:14)

Bem, agora parece que o compilador se perdeu e não saberia mais qual método usar. Mas o que importa para o compilador se a classe pai A usa a mesma interface do filho ? Afinal o filho continua sendo subtipo de A e implementando a interface IntF, exatamente como no Bloco 1. Porque agora o compilador se perde e antes não ?

Bom, fica aí o que pra mim é um enigma. Aprecio comentários ou possíveis explicações sobre o fato.

Obs: os códigos acima são integrais, todo o código encontra-se em um mesmo arquivo. A versão utilizada é Java 6.

Abraços !

6 Respostas

G

o que acontece é que no bloco1, as duas implementam a interface.

entao se vc passa um objetoB, ele pode ser passado pra qualquer um. Java deve priorizar o metodo(A) porque ele tem uma classe concreta como parametro e nao uma interface. Quando se importa pacotes ou se usa variaveis sem importacao, se existem classes com nomes iguais, se nao me engano, Java prefere os pacotes que foram importados. Deve haver alguma coisa assim…


no bloco 2, B é uma subclasse de A e implementa uma interface. Ai o java ve os metodos, ve que pode passar B para o metodo1 e para o metodo2.

Mas nao tem a certeza de qual usar porque um quer B apenas como subclasse de A e o outro quer como uma interface. Entao qual usar???

A

Esse comportamento é devido a hierarquia que você criou.

Ao escolher qual método chamar o compilador vai preferir um método mais específico a um método mais geral.

No bloco 1, por A ser um IntF, ele chama o método que recebe um A como parâmetro, já que A é mais específico do que IntF.

No bloco 2, A e IntF seguem dois ramos distintos, então o compilador não sabe qual escolher e dá o erro de compilação.

gpmdf2

Mesmo se houvesse dois métodos sobrecarregados aceitando parâmetros de interface, a interface “Filha” seria escolhida (desde que uma estendesse a outra).

M

AbelBueno:
Esse comportamento é devido a hierarquia que você criou.

Ao escolher qual método chamar o compilador vai preferir um método mais específico a um método mais geral.

No bloco 1, por A ser um IntF, ele chama o método que recebe um A como parâmetro, já que A é mais específico do que IntF.

No bloco 2, A e IntF seguem dois ramos distintos, então o compilador não sabe qual escolher e dá o erro de compilação.

gpmdf2

Mesmo se houvesse dois métodos sobrecarregados aceitando parâmetros de interface, a interface “Filha” seria escolhida (desde que uma estendesse a outra).

Olá AbelBueno,

Na minha opinião isso não faz sentido em termos de conceito.
Sei que o Java, neste caso o compilador, escolherá o método com o parâmetro mais específico possível. No entanto, esse específico trata-se de mais específico dentro da hierarquia de classes. Ou seja, heranças. A interface não é parte da herança, é só uma implementação (não existe herança de interfaces neste exemplo). Então não teria como dizer que a classe é mais específica que a interface, já que não estão em uma relação de hierarquia.

Tanto no Bloco 1 como no Bloco 2, a Classe B “é uma” classe A e “é uma” IntF. Ponto final. Para a classe B, tanto um instanceof Classe A quanto instanceof IntF retornaria true.

Imagine o seguinte cenário paralelo na vida real:

  • Um fiscal deve selecionar de um grupo de pessoas aquelas que são filhas do João e as que mancam da perna direita.
  • As pessoas que mancam da perna direita vão para uma sala, e os que são filhos de João para outra Sala.

Entendo que o fiscal não saiba o que fazer quando aparecer uma pessoa ao mesmo tempo filha de João e manca da perna direita.
Agora o que acontece neste caso, no exemplo, é como se o fiscal (compilador) olhasse para o João (o pai) para ver se ele também manca.
Se ele manca, o filho é selecionado para a sala dos filhos de João, se ele não manca, ele diz que a situação é ambígua e não sabe o que fazer.

Afinal, o que importa se o pai manca para selecionar a sala do filho ? Isso que não entendo.

Apenas para infomação: João = Classe A / Mancar da perna direita = IntF

Abs !

A

Acho que tem um detalhe que não percebeu:

Ao dizer que B estende A, ele já é um IntF naturalmente. A declaração implements IntF fica redundante.

A é mais específico que IntF na hierarquia (e nesse sentido ser implements ou extends não faz diferença).

Portanto (no bloco 1) ao dizer que B extends A, você já diz que ele é também IntF.

De qualquer forma recomendo a leitura do SCJP que tem explicações profundas destas regras.
Talvez passe a fazer mais sentido para você.

G

AbelBueno, eu nao estudei ainda pra certificacao…

mas eu disse que a segundo forma nao faz sentido (assim como vc)

e disse que a primeira era preferivel chamar onde tem uma classe concreta, que foi a msm coisa que vc disse…

talvez eu tenha me expressado mal, mas eu quis dizer a mesma coisa que vc. Cheguei às mesmas conclusoes.

T

pow legal o q vc fez
uahuahuahuahauhuahua

mas ai tipo quando vc faz

OverloadTest teste = new OverloadTest();  
        B obj = new B();
         teste.method(obj);

o compilador nao sabe se chama o method(A) ou method(intF) pq a classe b na verdade eh as duas

mas quando vc implementa em A intF nao tem problema pq na verdade ele vai xamar method(intF) ou quem tiver sobre escrevendo

axo q eh isso

Criado 27 de abril de 2011
Ultima resposta 27 de abr. de 2011
Respostas 6
Participantes 4