Dúvida simples sobre herança

13 respostas
T

Galera, tenho as seguintes classes em Java:

class Comida
{
 private String nome;
 public Comida(String s)
 { nome=s; }
 public String getNome()
 {return nome;}
}
class Sanduiche extends Comida
{ public Sanduiche(String s)
  { super(s); }
}
class Panela
{
 public void fazer (Comida c)
 { System.out.println("Estou fazendo "+ c.getNome()); }
}
class Frigideira extends Panela
{
 
 public void fazer (Sanduiche s)
 { System.out.println("Estou tostando "+ s.getNome()); }
	
 public void fazer (Comida c)
 { System.out.println("Estou fritando "+ c.getNome()); }
}

E tenho uma outra classe para testar as anteriores

public class Teste
{
 public static void main(String a[])
 {
  Panela p, q;
  p = new Panela();
  q = new Frigideira();
  
  Comida c = new Comida("galinha");
  Comida s = new Sanduiche("bauru");
  p.fazer(c);
  q.fazer(c);
  q.fazer(s);
 }
}

Enfim, a saída da classe de teste é:

Estou fazendo galinha
Estou fritando galinha
Estou fritando bauru

Mas o bauru não deveria ser tostado? Notem que q é uma frigideira, e eu estou invocando o método fazer passando um sanduíche como parâmetro. Na classe frigideira, o sanduíche deveria ser tostado.

Trata-se de um exercício da minha faculdade, nada de mais, mas gostaria de responder. Alguém pode me dar uma força nessa?

Abraço.

13 Respostas

getAdicted

Pelo visto o poliformismo não entrou em ação nesse caso. Quando você fez:Comida s = new Sancuiche();… o argumento passado para o método fazer() foi o da classe Comida e não o da classe Sanduiche.

[]'s

T

getAdicted:
Pelo visto o poliformismo não entrou em ação nesse caso. Quando você fez:Comida s = new Sancuiche();… o argumento passado para o método fazer() foi o da classe Comida e não o da classe Sanduiche.

[]'s

Sim cara, é justamente isso que acontece.

Queria saber o porque desse comportamento.

lucas_guj

Sanduiche estende Comida, logo Sanduiche é uma comida. Não foi por isso? O.o

getAdicted

Nesse caso, pelo que eu andei pesquisando, nem ao menos eh feita uma espécie de ligação tardia, ligação prematura, etc. O tipo a ser passado como argumento, portanto, será definido em tempo de compilação. Daqui a pouco aparece alguém que possa explicar melhor.

[]'s

viniciusalvess

Em caso de sobrecarga de métodos o compilados entende que não é muito inteligente gerar dois métodos assim

public void fazer (Sanduiche s)  
{ System.out.println("Estou tostando "+ s.getNome()); }  
      
public void fazer (Comida c)  
{ System.out.println("Estou fritando "+ c.getNome()); }  
}

já que sanduiche herda de Comida .
O compilador chamará o método da super classe ! Ele acaba aprimorando o código.

Se não me engano tem isso no livro SCJP 6 , depois se encontrar o exemplo do livro , complemento o post.

Andersonrms

O polimorfismo funcionou sim, pois ao invés d executar o método fazer da classe Panela, executou o da classe Frigideira.
O truque ai é q têm 2 métodos sobrecarregados, sendo q o parâmetro d um método é um “subtipo” do parâmetro do outro método. Nesse caso é chamado o método que tem como parâmetro o tipo da variável de referência passada como argumento, q no caso é Comida e não Sanduiche.

Andersonrms

Fiz mais uns testes para me certificar melhor. Sem o polimorfismo, os métodos sobrecarregados funcionam da maneira q eu mencionei no post anterior. Vamos supor q a classe Panela seja modificada da seguinte forma:

class Panela
{
 public void fazer (Comida c)
 { System.out.println("Estou fazendo "+ c.getNome()); }
 
 public void fazer (Sanduiche s)
 { System.out.println("Estou tostando "+ s.getNome()); }
}

e a classe Teste da seguinte:

public class Teste
{
 public static void main(String a[])
 {
  Panela p = new Panela();
  
  Comida c = new Comida("galinha");
  Comida s = new Sanduiche("bauru");
  Sanduiche x = new Sanduiche("x-tudo");
  p.fazer(c);
  p.fazer(s);
  p.fazer(x);
 }
}

Nesse caso a saída será:

Estou fazendo galinha
Estou fazendo bauru
Estou tostando x-tudo

Repare q as duas primeiras chamadas são executadas o mesmo método, pois não importa o tipo do objeto e sim o tipo da variável de referência. Isso se prova na terceira chamada, q executa outro método, pois passa uma variável de referência com um tipo diferente.

Quando é usado polimorfismo, como no seu exemplo, é sempre executado o método q tem como parâmetro um tipo Comida, mesmo q seja passado como argumento uma variável d referência do tipo Sanduiche. Fiz um teste tb, tirando apenas o método com o parâmetro Comida da classe Frigideira, e nesse caso executou o método da classe Panela. Portanto, o compilador admite q vc passe um subtipo como argumento, e a JVM procurará na classe filha um método com o msm tipo d parâmetro da classe pai, não importando ai o tipo passado como argumento. Caso a JVM não encontre um método com o tipo d parâmetro exatamente igual ao da classe pai, qualquer outro método é descartado e é executado o método da classe pai. No seu exemplo, a JVM encontra o método na classe filha.

lucas_guj

Acho que entendi mais ou menos o que você falou “Andersonrms”. Fiz os testes como você, olha.

Coloquei esse metodo na classe Panela:

class Panela {  
	public void fazer (Comida c) {
		System.out.println("Estou fazendo "+ c.getNome());
	}
	
	public void fazer (Sanduiche s) {
		System.out.println("Estou tostando "+ s.getNome());
	}
}

E deixei a classe Frigideira assim:

public void fazer (Sanduiche s) {
    System.out.println("Estou tostandooooo "+ s.getNome());
}  
          
public void fazer (Comida c) {
    System.out.println("Estou fritando "+ c.getNome());
}

Coloquei tostandooooooo para diferenciar do tostando da super classe panela, para ver quem ele iria chamar, pois se tivesse igual não saberia diferenciar. Então deixei a classe Teste assim: Mudando a variavel de referencia s de Comida, para sanduiche e olha o resultado:

public class Teste {  
	public static void main(String a[]) {  
		Panela p, q;  
		p = new Panela();  
		q = new Frigideira();  
		
		Comida c = new Comida("galinha");  
		Sanduiche s = new Sanduiche("bauru");  
		p.fazer(c);  
		q.fazer(c);  
		q.fazer(s);  
	}  
}

Estou fazendo galinha
Estou fritando galinha
Estou tostandooooo bauru

Agora se eu pegar e mudar o TIPO da variavel de referencia s de Sanduiche para Comida ele exibi o seguinte resultado:

Estou fazendo galinha
Estou fritando galinha
Estou fritando bauru

Isso só acontece se criarmos o método fazer com parametro Sanduiche na classe Panela. Porque senão criarmos, independente da variavel de referencia ser Comida ou Sanduiche, ele continua executando o método fazer de comida. Como você disse deve ser porque Sanduiche é um subtipo de Comida. Mais não entendi porque ele só funciona se criarmos o método na classe pai Panela. AI SIM, depois que criamos ele diferencia a variavel de referencia. Como eu mostrei ali, com a variavel de referencia Sanduiche, ele executou o metodo fazer da subclasse Panela que é a Frigideira. Agora se eu mudar de Sanduiche para Comida ele executa o metodo fazer de Frigideira que usa comida. Porem isso só está funcionando porque eu acrescentei o método fazer Sanduiche na classe Panela. Porque sem ele, independente da variavel de referencia, continua dando aquele resultado que o criador do topico postou. Gostaria de entender porque quando acrescentamos esse metodo na classe Panela as coisas mudaram???

getAdicted

Andersonrms:
O polimorfismo funcionou sim, pois ao invés d executar o método fazer da classe Panela, executou o da classe Frigideira.
O truque ai é q têm 2 métodos sobrecarregados, sendo q o parâmetro d um método é um “subtipo” do parâmetro do outro método. Nesse caso é chamado o método que tem como parâmetro o tipo da variável de referência passada como argumento, q no caso é Comida e não Sanduiche.

Bom dia amigo,

Eu disse que o polimorfismo não funcionava para esse caso:Comida s = new Sanduiche("bauru");… no sentido de ele fazer a chamada ao método que possui o parametro do supertipo (Comida) e não do subtipo (Sanduiche). Para esse caso que você disse, funciona! =)

[]'s

Andersonrms

Eu entendi o q vc quis dizer getAdicted.
Mas o q tem d diferente nesse código, q é a dúvida do lucas_guj, e é algo novo pra mim, é q há uma mistura de métodos sobrepostos e sobrecarregados ao msm tempo.
Como eu disse, e o próprio lucas_guj pode verificar com testes, existe akela regra dos métodos sobrecarregados, q o q determina qual método será chamado é o tipo da variável d referência e não o tipo do objeto. Até ai td bem, nada d novo pra mim. Só que…

na classe pai tem o método:

fazer (Comida c)

e na classe filha tem os métodos:

fazer (Comida c)
fazer (Sanduiche s)

Quando vc faz usa essas classes da seguinte maneira, o q acontece?

Panela p = new Frigideira();
Comida s = new Sanduiche("bauru");

p.fazer(s);

Primeiro, o polimorfismo. Ou seja, mesmo o método sendo chamado por uma variável d referência do tipo pai, quem decide qual método será executado é a JVM, e esta verá q o tipo do objeto é d uma classe filha, portanto procurará na classe filha pelo método. Acontece q na classe filha existem dois métodos sobrecarregados, sendo q um método recebe como argumento um tipo genérico e o outro um tipo específico. Sendo assim, a lógica é aplicar a lei da sobrecarga (chamar o método do tipo da variável d referência e não do tipo do objeto). Mas será q é realmente isso o q acontece? O código acima executa o método da classe filha:

fazer (Comida c)

Bom, até ai a regra da sobrecarga e o polimorfismo funcionaram dentro da lógica esperada. Mas e se nós modificarmos o código da seguinte maneira:

Panela p = new Frigideira();
Sanduiche s = new Sanduiche("bauru");

p.fazer(s);

Agora a variável de referência passada como parâmetro é do tipo específico. Então a lógica seria executar o método da classe filha q recebe como argumento o tipo específico, certo? Errado!!! O método executado será o método da classe filha…

fazer (Comida c)

Isso sim é novo pra mim. A regra da sobrecarga não se aplica no polimorfismo. Se o método da classe pai tem como parâmetro um tipo Comida, a JVM não se importa se existem outros métodos sobrecarregados na classe filha q tem como parâmetro um tipo específico d Comida. A única coisa q a JVM se preocupa, quando realiza o polimorfismo, é se na classe filha existe um método fazer q tem como parâmetro um tipo Comida.

É por isso q quando o lucas_guj acrescentou o método:

fazer (Sanduiche s)

na classe pai, é executado o método fazer da classe filha q tem como parâmetro um tipo Sanduiche. Se esse método não existir na classe filha, é executado o método da classe pai. Não sei se ficou claro, então vou modificar um pouco a classe Panela e a classe Teste:

Panela:

class Panela
{
 public void fazer (Comida c)
 { System.out.println("Estou fazendo "+ c.getNome()); }
 
 public void fazer (Sanduiche s)
 { System.out.println("Estou toooooostando "+ s.getNome()); }
}

Teste:

public class Teste
{
 public static void main(String a[])
 {
  Panela q = new Frigideira();
  
  Comida s = new Sanduiche("bauru");
  Sanduiche x = new Sanduiche("x-tudo");

  q.fazer(s);
  q.fazer(x);
 }
}

Nesse caso, a saída é:

Estou fritando bauru
Estou tostando x-tudo

Acontece o seguinte; como existem dois métodos sobrecarregados na classe pai, primeiro é aplicada a regra da sobrecarga para escolher um dos métodos. Se a variável de referência passada como argumento for do tipo Comida, é escolhido o método q tem como parâmetro o tipo Comida, se a variável de referência for do tipo Sanduiche, é escolhido o método q tem como parâmetro o tipo Sanduiche; como a variável q chama o método fazer faz referência a uma Frigideira, a JVM procura na classe Frigideira pelo método fazer q tenha EXATAMENTE o msm parâmetro do método escolhido na classe pai.

Se eu tiver falado alguma besteira podem me corrigir sem problemas. Espero ter ajudado.

lucas_guj

Andersonrms

Percebi algo que não tinha percebido graças a sua observação

Entendi que você disse que em metodos sobrecarregados de classe pai e classe filha, ele primeiro verifica a variavel de referencia e depois? Uma coisa que reparei, falando sobre a variavel de referencia e sobre qual metodo chamar. Reparei que quando chamavamos o metodo fazer() de q, se nao tivesse o metodo na classe Panela, ele executava o da classe filha e se tivesse o metodo fazer na classe Panela ai ele executaria. Mais já parou para reparar que nossa variavel q é uma variavel de referencia de PANELA e que quando a gente faz assim no codigo:

Panela p = new Panela();    
Frigideira q = new Frigideira(); 

Comida c = new Comida("galinha");    
Sanduiche s = new Sanduiche("bauru");    
p.fazer(c);    
q.fazer(c);    
q.fazer(s);

A saída é a que deveria ter sido apresentada desde o começo?

Estou fazendo galinha
Estou fritando galinha
Estou tostando bauru

Isso tudo porque mudamos a variavel de referencia Panela para Frigideira, ai ele verifica a variavel de referencia do metodo sobrecarregado direto na classe filha e executa o metodo relacionado. Agora se deixarmos a variavel q referencia Panela (lembrando que PAnela está somente com um metodo fazer( Comida c ),
executar o metodo fazer passando tanto uma variavel de referencia Comida quanto Sanduiche ele executa o metodo da classe fazer(Comida c) de Frigideira.

Conseguiu entender o que quis dizer? Isso está ficando cada vez mais confuso. =/

lucas_guj

Vamos tentar exemplificar melhor as coisas. DE COMEÇO A CLASSE DO COLEGA NÂO EXECUTAVA O QUE ELE ACHAVA O QUE EXECUTAVA. Então fazendo as seguintes alterações na classe PANELA, FRIGIDEIRA e na classe TESTE, temos um resultado e vamos analisar esse resultado para poder explicar o que acontece nessa herança-polimorfismo e sobrecarga.

Panela

class Panela  
{  
    public void fazer (Comida c) {
        System.out.println("Estou fazendo na Panela o(a) "+ c.getNome());
    }  

    public void fazer (Sanduiche s) {
        System.out.println("Estou tostando na Panela o(a) "+ s.getNome());
    }  
}

Frigideira

class Frigideira extends Panela  
{  
	public void fazer (Sanduiche s) {
		System.out.println("Estou tostando na Frigideira o(a) "+ s.getNome());
	}  
      
	public void fazer (Comida c) {
		System.out.println("Estou fritando na Frigideira o(a) "+ c.getNome());
	}  
}

Teste

public class Teste {    
    public static void main(String a[]) {    
        Panela p = new Panela();    
        Panela q = new Frigideira();    
          
        Comida c = new Comida("galinha");  
        Sanduiche s = new Sanduiche( "Bauru"); 
        Comida c2 = new Sanduiche("X-TUDO");
        p.fazer(c);
        p.fazer(s);
        p.fazer(c2); 
        q.fazer(c);    
        q.fazer(s);    
        q.fazer(c2);    
    }    
}

As demais classes, deixa como estavam, e ai temos o seguinte resultado:

Estou fazendo na Panela o(a) galinha
Estou tostando na Panela o(a) Bauru
Estou fazendo na Panela o(a) X-TUDO
Estou fritando na Frigideira o(a) galinha
Estou tostando na Frigideira o(a) Bauru
Estou fritando na Frigideira o(a) X-TUDO

E se TIRAMOS o método de Panela:

public void fazer (Sanduiche s) {
    System.out.println("Estou tostando na Panela o(a) "+ s.getNome());
}

O resultado é esse:

Estou fazendo na Panela o(a) galinha
Estou fazendo na Panela o(a) Bauru
Estou fazendo na Panela o(a) X-TUDO
Estou fritando na Frigideira o(a) galinha
Estou fritando na Frigideira o(a) Bauru
Estou fritando na Frigideira o(a) X-TUDO

Pronto agora podemos analisar tudo pois tentei todos os exemplos ai emcima…

T

Enfim, óntem meu professor finalmente revelou a questão.

Para todos os efeitos Sanduiche é Comida, mas porque isso? Trata-se de algo chamado single dispatch, em que um objeto quando chamado em um método, é considerado como sendo um objeto do tipo que foi declarado.

Sanduiche foi declarado como Comida, portanto será tratado como comida em todo método que for chamada. É uma peculiaridade não só do Java, mas do C++ também.

Enfim, obrigado a todos pela força.

Criado 18 de março de 2012
Ultima resposta 20 de mar. de 2012
Respostas 13
Participantes 5