CodeGym /Cursos /JAVA 25 SELF /O conceito de polimorfismo: por que ele é necessário

O conceito de polimorfismo: por que ele é necessário

JAVA 25 SELF
Nível 18 , Lição 0
Disponível

1. Introdução

Polimorfismo — é um dos três conceitos fundamentais da programação orientada a objetos (ao lado de herança e encapsulamento). Literalmente, a palavra vem do grego “poly” (muitos) e “morph” (forma). Em programação isso significa: uma interface — muitas implementações.

Definição

Polimorfismo — é a capacidade de objetos de classes diferentes responderem às mesmas mensagens (chamadas de métodos) de maneiras distintas.

Ou seja, se você tem um método makeSound(), pode chamá‑lo em qualquer animal, mas o gato vai miar, o cachorro vai latir e a vaca vai mugir. Para o programador — é apenas a chamada animal.makeSound(), mas o que acontecerá de fato depende de qual objeto está por trás dessa variável.

Analogia do dia a dia

Imagine que você tenha em casa um controle remoto da TV, e com esse mesmo controle possa operar as caixas de som, o projetor e até a cafeteira. Você aperta o botão “Ligar” — turnOn(), e cada aparelho reage à sua maneira. O importante é: todos têm um “botão” de ligar, mas a implementação é diferente.

Exemplo em Java

class Animal {
    void makeSound() {
        System.out.println("Algum som...");
    }
}

class Dog extends Animal {
    @Override
    void makeSound() {
        System.out.println("Au!");
    }
}

class Cat extends Animal {
    @Override
    void makeSound() {
        System.out.println("Miau!");
    }
}

class Cow extends Animal {
    @Override
    void makeSound() {
        System.out.println("Muuu!");
    }
}

Agora podemos fazer o seguinte:

Animal animal1 = new Dog();
Animal animal2 = new Cat();
Animal animal3 = new Cow();

animal1.makeSound(); // Au!
animal2.makeSound(); // Miau!
animal3.makeSound(); // Muuu!

Observe que: todas as variáveis têm o tipo Animal, mas o resultado da chamada depende do tipo efetivo do objeto.

2. Tipos de polimorfismo

Em Java (e na maioria das linguagens orientadas a objetos) distinguem-se dois tipos principais de polimorfismo:

Polimorfismo de compilação (estático) — sobrecarga de métodos (overloading)

É quando, em uma mesma classe, existem vários métodos com o mesmo nome, mas com parâmetros diferentes. O compilador decide qual método chamar com base nos argumentos fornecidos.

Exemplo (vamos espiar — mais detalhes na próxima aula):

class Printer {
    void print(int x) {
        System.out.println("Número: " + x);
    }

    void print(String s) {
        System.out.println("String: " + s);
    }
}

Polimorfismo de execução (dinâmico) — sobrescrita de métodos (overriding)

É quando um método é definido na classe base e depois sobrescrito nas subclasses. Qual método será chamado — é decidido durante a execução do programa (runtime), dependendo do tipo efetivo do objeto.

Exemplo — veja acima com os animais.

3. Para que serve o polimorfismo?

Polimorfismo — não é apenas uma palavra bonita para entrevistas. É uma ferramenta que torna seu código flexível, extensível e fácil de manter.

Generalidade do código

Você pode escrever código que trabalhe com objetos do tipo base, sem se preocupar com os detalhes da implementação. Por exemplo, se você tem uma lista de animais, pode percorrê‑la e chamar makeSound() em cada um, sem precisar pensar se é um gato ou um cachorro.

Animal[] animals = { new Dog(), new Cat(), new Cow() };

for (Animal animal : animals) {
    animal.makeSound(); // Sempre chamará o método "correto"
}

Facilidade de extensão

Se amanhã seu chefe chegar e disser: “Vamos adicionar um papagaio!”, você simplesmente escreve uma nova classe Parrot extends Animal e a adiciona ao array. Todo o restante do código permanece como está. Isso é — aberto para extensão e fechado para modificação (princípio OCP do SOLID).

Simplificação da arquitetura

Você pode construir sistemas complexos, em que partes separadas interagem entre si por meio de abstrações (classes base ou interfaces), sem se preocupar com as implementações concretas. Isso economiza tempo, nervos e café.

4. Conceitos-chave: tipo de referência e tipo efetivo

Tipo de referência da variável
Quando você escreve Animal animal = new Dog();, a variável animal tem o tipo de referência Animal, ou seja, o compilador “acha” que é um animal e permite apenas os métodos declarados na classe Animal.

Tipo efetivo (real) do objeto
Mas na memória você tem de fato um objeto do tipo Dog. É ele quem define qual método será chamado ao acessar makeSound().

Ilustração

Animal animal = new Dog();
animal.makeSound(); // Chamará Dog.makeSound(), e não Animal.makeSound()

Importante! Por meio de uma referência do tipo base (Animal) você não conseguirá chamar métodos que existam apenas em Dog, se eles não estiverem declarados na classe base.

Vinculação tardia (dinâmica)
É a “mágica” que acontece durante a execução: quando você chama um método por uma referência do tipo base, a JVM olha para o tipo efetivo do objeto e chama a implementação “correta”. Isso é o polimorfismo em ação.

5. Exemplo prático: polimorfismo em um aplicativo

Vamos continuar o desenvolvimento do nosso aplicativo didático. Suponha que estejamos escrevendo uma simulação simples de um zoológico. Temos a classe base Animal e alguns de seus descendentes. Queremos que todos os animais possam “emitir um som”, mas não queremos escrever código separado para cada tipo de animal toda vez.

Etapa 1: Classe base e descendentes

class Animal {
    void makeSound() {
        System.out.println("Algum som...");
    }
}

class Dog extends Animal {
    @Override
    void makeSound() {
        System.out.println("Au!");
    }
}

class Cat extends Animal {
    @Override
    void makeSound() {
        System.out.println("Miau!");
    }
}

Etapa 2: Array de animais

Animal[] zoo = { new Dog(), new Cat(), new Animal() };

Etapa 3: Iteração e chamada do método

for (Animal animal : zoo) {
    animal.makeSound();
}

Resultado da execução:

Au!
Miau!
Algum som...

Observe: não sabemos de antemão quem exatamente está no array — mas o programa resolve isso e chama o método necessário.

6. Representação esquemática do polimorfismo


.        Animal (makeSound)
           /        \
        Dog        Cat
     (makeSound) (makeSound)

Animal animal = new Dog();
animal.makeSound(); // --> Dog.makeSound()

Animal animal = new Cat();
animal.makeSound(); // --> Cat.makeSound()

7. Mais um exemplo: polimorfismo em um caso real

Suponha que você esteja escrevendo um programa para gerenciar os funcionários de uma empresa. Você tem a classe base Employee e dois descendentes: Manager e Developer. Todos os funcionários podem trabalhar — work(), mas fazem isso de maneiras diferentes.

class Employee {
    void work() {
        System.out.println("O funcionário está trabalhando.");
    }
}

class Manager extends Employee {
    @Override
    void work() {
        System.out.println("O gerente conduz uma reunião.");
    }
}

class Developer extends Employee {
    @Override
    void work() {
        System.out.println("O desenvolvedor escreve código.");
    }
}

Agora você pode fazer o seguinte:

Employee[] staff = { new Manager(), new Developer(), new Employee() };

for (Employee emp : staff) {
    emp.work();
}

Resultado:

O gerente conduz uma reunião.
O desenvolvedor escreve código.
O funcionário está trabalhando.

8. Quando o polimorfismo NÃO funciona

O polimorfismo funciona apenas para métodos declarados na classe base. Se uma subclasse tiver um método exclusivo, por meio da referência do tipo base você não o verá.

class Dog extends Animal {
    void fetchStick() {
        System.out.println("O cachorro traz o graveto!");
    }
}

Animal animal = new Dog();
// animal.fetchStick(); // Erro de compilação! Esse método não é visível via Animal

Para chamar um método específico, é necessário converter a variável para o tipo desejado:

if (animal instanceof Dog) {
    ((Dog) animal).fetchStick();
}

Mas isso já é outra história — o principal: por meio do polimorfismo estão disponíveis apenas os métodos declarados na classe base.

9. Erros comuns ao trabalhar com polimorfismo

Erro nº 1: Esperar que, por meio de uma referência do tipo base, todos os métodos da subclasse estejam disponíveis. Na verdade, estão disponíveis apenas aqueles que são declarados na classe base.

Erro nº 2: Não usar a anotação @Override ao sobrescrever um método. Sem ela, você pode acabar escrevendo um método com assinatura incorreta e, então, o polimorfismo não funcionará (o método da classe base não será sobrescrito).

Erro nº 3: Tentar chamar um método específico da subclasse sem fazer casting. O compilador não permitirá, porque não sabe quem exatamente está por trás da referência do tipo base.

Erro nº 4: Confundir sobrecarga (overloading) com sobrescrita (overriding). Sobrecarga — são vários métodos com o mesmo nome e parâmetros diferentes em uma mesma classe. Sobrescrita — é alterar o comportamento de um método na subclasse.

Comentários
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION