"Amigo, você gosta de baleias?"

"Baleias? Não, nunca ouvi falar delas."

"É como uma vaca, só que maior e que nada. Aliás, as baleias vieram das vacas. Uh, ou pelo menos elas compartilham um ancestral comum. Não importa."

Polimorfismo e substituição - 1

"Ouça. Quero falar sobre outra ferramenta muito poderosa de OOP: o polimorfismo . Ele tem quatro recursos."

1) Substituição do método.

Imagine que você escreveu uma classe "Vaca" para um jogo. Ele tem muitas variáveis ​​e métodos membros. Objetos desta classe podem fazer várias coisas: andar, comer, dormir. As vacas também tocam um sino quando caminham. Digamos que você implementou tudo na classe até os mínimos detalhes.

Polimorfismo e substituição - 2

Então, de repente, o cliente diz que quer lançar um novo nível do jogo, onde todas as ações acontecem no mar, e o personagem principal é uma baleia.

Você começou a projetar a classe Whale e percebeu que ela é apenas um pouco diferente da classe Cow. Ambas as classes usam lógica muito semelhante e você decide usar herança.

A classe Cow é ideal para ser a classe pai: ela já possui todas as variáveis ​​e métodos necessários. Tudo o que você precisa fazer é adicionar a capacidade de nadar da baleia. Mas há um problema: sua baleia tem patas, chifres e um sino. Afinal, a classe Cow implementa essa funcionalidade. O que você pode fazer?

Polimorfismo e substituição - 3

A substituição de método vem em socorro. Se herdarmos um método que não faz exatamente o que precisamos em nossa nova classe, podemos substituir o método por outro.

Polimorfismo e substituição - 4

Como isso é feito? Em nossa classe descendente, declaramos o método que queremos alterar (com a mesma assinatura de método da classe pai) . Em seguida, escrevemos um novo código para o método. É isso. É como se o método antigo da classe pai não existisse.

Veja como funciona:

Código Descrição
class Cow
{
public void printColor()
{
System.out.println("I'm white");
}
public void printName()
{
System.out.println("I'm a cow");
}
}class Whale extends Cow
{
public void printName()
{
System.out.println("I'm a whale");
}
}
Aqui definimos duas classes:  Cow e  WhaleWhaleherda  Cow.

Whale classe substitui o  printName();método.

public static void main(String[] args)
{
Cow cow = new Cow();
cow.printName();
}
Este código exibe « I'm a cow » na tela.
public static void main(String[] args)
{
Whale whale = new Whale();
whale.printName();
}
Este código exibe « I'm a baleia » na tela

Após herdar Cowe sobrescrever printName, a Whaleclasse na verdade possui os seguintes dados e métodos:

Código Descrição
class Whale
{
public void printColor()
{
System.out.println("I'm white");
}
public void printName()
{
System.out.println("I'm a whale");
}
}
Não sabemos nada sobre nenhum método antigo.

"Honestamente, era isso que eu esperava."

2) Mas isso não é tudo.

"Suponha que a  Cow classe tenha um  printAllmétodo , que chama os outros dois métodos. Então o código funcionaria assim:"

A tela mostrará:
sou branco
sou uma baleia

Código Descrição
class Cow
{
public void printAll()
{
printColor();
printName();
}
public void printColor()
{
System.out.println("I'm white");
}
public void printName()
{
System.out.println("I'm a cow");
}
}

class Whale extends Cow
{
public void printName()
{
System.out.println("I'm a whale");
}
}
public static void main(String[] args)
{
Whale whale = new Whale();
whale.printAll();
}
A tela mostrará:
sou branco
sou uma baleia

Observe que quando o método printAll () da classe Cow é chamado em um objeto Whale, o método printName () da Whale será usado, não o da Cow.

O importante não é a classe em que o método está escrito, mas sim o tipo (classe) do objeto no qual o método é chamado.

"Eu vejo."

"Você só pode herdar e substituir métodos não estáticos. Métodos estáticos não são herdados e, portanto, não podem ser substituídos."

Aqui está a aparência da classe Whale depois que aplicamos a herança e sobrescrevemos os métodos:

Código Descrição
class Whale
{
public void printAll()
{
printColor();
printName();
}
public void printColor()
{
System.out.println("I'm white");
}
public void printName()
{
System.out.println("I'm a whale");
}
}
Aqui está a aparência da classe Whale depois que aplicamos a herança e sobrescrevemos o método. Não sabemos nada sobre nenhum printNamemétodo antigo.

3) Tipo de fundição.

Aqui está um ponto ainda mais interessante. Como uma classe herda todos os métodos e dados de sua classe pai, um objeto dessa classe pode ser referenciado por variáveis ​​da classe pai (e o pai do pai, etc., até a classe Object). Considere este exemplo:

Código Descrição
public static void main(String[] args)
{
Whale whale = new Whale();
whale.printColor();
}
A tela mostrará:
eu sou branco.
public static void main(String[] args)
{
Cow cow = new Whale();
cow.printColor();
}
A tela mostrará:
eu sou branco.
public static void main(String[] args)
{
Object o = new Whale();
System.out.println(o.toString());
}
A tela mostrará:
Whale@da435a.
O método toString() é herdado da classe Object.

"Coisa boa. Mas por que você precisa disso?"

"É um recurso valioso. Você entenderá mais tarde que é muito, muito valioso."

4) Ligação tardia (despacho dinâmico).

Aqui está o que parece:

Código Descrição
public static void main(String[] args)
{
Whale whale = new Whale();
whale.printName();
}
A tela mostrará:
eu sou uma baleia.
public static void main(String[] args)
{
Cow cow = new Whale();
cow.printName();
}
A tela mostrará:
eu sou uma baleia.

Observe que não é o tipo da variável que determina qual método printName específico chamamos (o da classe Cow ou Whale), mas sim o tipo de objeto referenciado pela variável.

A variável Cow armazena uma referência a um objeto Whale , e o método printName definido na classe Whale será chamado.

"Bem, eles não acrescentaram isso por uma questão de clareza."

"Sim, não é tão óbvio. Lembre-se desta regra importante:"

O conjunto de métodos que você pode chamar em uma variável é determinado pelo tipo da variável. Mas qual método/implementação específico é chamado é determinado pelo tipo/classe do objeto referenciado pela variável.

"Vou tentar."

"Você vai se deparar com isso constantemente, então você vai entender rapidamente e nunca esquecer."

5) Tipo de fundição.

A conversão funciona de maneira diferente para tipos de referência, ou seja, classes, do que para tipos primitivos. No entanto, as conversões de alargamento e estreitamento também se aplicam a tipos de referência. Considere este exemplo:

Ampliando a conversão Descrição
Cow cow = new Whale();

Uma conversão de ampliação clássica. Agora você só pode chamar métodos definidos na classe Cow no objeto Whale.

O compilador permitirá que você use a variável cow apenas para chamar os métodos definidos pelo tipo Cow.

Conversão estreita Descrição
Cow cow = new Whale();
if (cow instanceof Whale)
{
Whale whale = (Whale) cow;
}
Uma conversão de restrição clássica com uma verificação de tipo. A variável cow do tipo Cow armazena uma referência a um objeto Whale.
Verificamos se esse é o caso e, em seguida, realizamos a conversão de tipo (alargamento). Isso também é chamado de conversão de tipo .
Cow cow = new Cow();
Whale whale = (Whale) cow; //exception
Você também pode executar uma conversão de restrição de um tipo de referência sem verificar o tipo do objeto.
Nesse caso, se a variável cow estiver apontando para algo diferente de um objeto Whale, uma exceção (InvalidClassCastException) será lançada.

6) E agora algo saboroso. Chamando o método original.

Às vezes, ao substituir um método herdado, você não deseja substituí-lo totalmente. Às vezes você só quer adicionar um pouco a ele.

Nesse caso, você realmente deseja que o código do novo método chame o mesmo método, mas na classe base. E o Java permite que você faça isso. É assim que se faz:  super.method().

aqui estão alguns exemplos:

Código Descrição
class Cow
{
public void printAll()
{
printColor();
printName();
}
public void printColor()
{
System.out.println("I'm white");
}
public void printName()
{
System.out.println("I'm a cow");
}
}

class Whale extends Cow
{
public void printName()
{
System.out.print("This is false: ");
super.printName();

System.out.println("I'm a whale");
}
}
public static void main(String[] args)
{
Whale whale = new Whale();
whale.printAll();
}
A tela vai mostrar:
eu sou branco
Isso é falso: eu sou uma vaca
eu sou uma baleia

"Hmm. Bem, isso foi uma lição. Minhas orelhas de robô quase derreteram."

"Sim, isso não é algo simples. É um dos materiais mais difíceis que você encontrará. O professor prometeu fornecer links para materiais de outros autores, para que, se você ainda não entender alguma coisa, possa preencher o formulário lacunas."