Polimorfismo
Polimorfismo em Java: Guia Completo para Iniciantes
Olá, futuro(a) programador(a) Java! Hoje vamos mergulhar em um dos conceitos mais poderosos e elegantes da programação orientada a objetos: o polimorfismo.
O que é Polimorfismo?
Polimorfismo vem do grego: poli (muitas) + morfos (formas). Em Java, significa que um mesmo método pode se comportar de diferentes maneiras dependendo do contexto.
Pense nisso como um controle remoto universal: o botão "ligar" funciona para TV, som e ar-condicionado, mas cada aparelho reage de forma diferente.
Tipos de Polimorfismo em Java
1. Polimorfismo de Sobrecarga (Compile-time)
A sobrecarga acontece quando criamos vários métodos com o mesmo nome mas com parâmetros diferentes. O Java decide qual método chamar durante a compilação.
public class Calculadora {
// Método para somar dois inteiros
public int somar(int a, int b) {
return a + b;
}
// Método para somar três inteiros
public int somar(int a, int b, int c) {
return a + b + c;
}
// Método para somar dois números decimais
public double somar(double a, double b) {
return a + b;
}
public static void main(String[] args) {
Calculadora calc = new Calculadora();
System.out.println(calc.somar(5, 3)); // Chama o primeiro método
System.out.println(calc.somar(5, 3, 2)); // Chama o segundo método
System.out.println(calc.somar(5.5, 3.2)); // Chama o terceiro método
}
}
Dica importante: A sobrecarga não depende apenas do tipo dos parâmetros, mas também da quantidade e ordem deles.
2. Polimorfismo de Sobrescrita (Runtime)
A sobrescrita acontece quando uma classe filha redefine um método da classe pai. O Java decide qual método executar durante a execução do programa.
// Classe pai
class Animal {
public void fazerSom() {
System.out.println("Animal fazendo som...");
}
}
// Classe filha
class Cachorro extends Animal {
@Override
public void fazerSom() {
System.out.println("Au au!");
}
}
// Outra classe filha
class Gato extends Animal {
@Override
public void fazerSom() {
System.out.println("Miau!");
}
}
Referência do Tipo Pai Apontando para Objeto Filho
Este é um conceito fundamental para entender polimorfismo:
public class TesteAnimais {
public static void main(String[] args) {
// Referência do tipo Animal apontando para um Cachorro
Animal meuAnimal = new Cachorro();
meuAnimal.fazerSom(); // Saída: "Au au!"
// Mesma referência, agora apontando para um Gato
meuAnimal = new Gato();
meuAnimal.fazerSom(); // Saída: "Miau!"
}
}
Por que isso é útil? Podemos criar métodos que aceitam qualquer tipo de Animal:
public class Veterinario {
public void examinar(Animal animal) {
System.out.println("Examinando animal...");
animal.fazerSom();
}
public static void main(String[] args) {
Veterinario vet = new Veterinario();
vet.examinar(new Cachorro()); // Funciona!
vet.examinar(new Gato()); // Funciona!
}
}
Classes Abstratas
Uma classe abstrata é como um "esqueleto" que não pode ser instanciado diretamente. Ela serve como modelo para outras classes.
// Classe abstrata - não pode ser instanciada!
abstract class Forma {
protected String cor;
// Construtor
public Forma(String cor) {
this.cor = cor;
}
// Método abstrato - sem implementação
public abstract double calcularArea();
// Método concreto - com implementação
public String getCor() {
return cor;
}
}
class Circulo extends Forma {
private double raio;
public Circulo(String cor, double raio) {
super(cor); // Chama o construtor da classe pai
this.raio = raio;
}
@Override
public double calcularArea() {
return Math.PI * raio * raio;
}
}
class Retangulo extends Forma {
private double largura;
private double altura;
public Retangulo(String cor, double largura, double altura) {
super(cor);
this.largura = largura;
this.altura = altura;
}
@Override
public double calcularArea() {
return largura * altura;
}
}
Interfaces
Uma interface é um contrato que define o que uma classe deve fazer, mas não como fazer.
// Interface
interface Voador {
void voar(); // Método abstrato (implícito)
void pousar();
}
interface Nadador {
void nadar();
}
// Classe que implementa múltiplas interfaces
class Pato extends Animal implements Voador, Nadador {
@Override
public void fazerSom() {
System.out.println("Quack!");
}
@Override
public void voar() {
System.out.println("Pato voando...");
}
@Override
public void pousar() {
System.out.println("Pato pousando...");
}
@Override
public void nadar() {
System.out.println("Pato nadando...");
}
}
Exemplo Prático Completo
Vamos criar um sistema de funcionários usando polimorfismo:
// Interface
interface Trabalhador {
void trabalhar();
double calcularSalario();
}
// Classe abstrata
abstract class Funcionario implements Trabalhador {
protected String nome;
protected int id;
public Funcionario(String nome, int id) {
this.nome = nome;
this.id = id;
}
public String getNome() {
return nome;
}
}
class Desenvolvedor extends Funcionario {
private double salarioBase = 5000;
public Desenvolvedor(String nome, int id) {
super(nome, id);
}
@Override
public void trabalhar() {
System.out.println(nome + " está programando...");
}
@Override
public double calcularSalario() {
return salarioBase;
}
}
class Gerente extends Funcionario {
private double salarioBase = 8000;
private double bonus = 2000;
public Gerente(String nome, int id) {
super(nome, id);
}
@Override
public void trabalhar() {
System.out.println(nome + " está gerenciando a equipe...");
}
@Override
public double calcularSalario() {
return salarioBase + bonus;
}
}
class Estagiario extends Funcionario {
public Estagiario(String nome, int id) {
super(nome, id);
}
@Override
public void trabalhar() {
System.out.println(nome + " está aprendendo...");
}
@Override
public double calcularSalario() {
return 1500;
}
}
public class Empresa {
public static void main(String[] args) {
// Array polimórfico
Funcionario[] funcionarios = {
new Desenvolvedor("Ana", 1),
new Gerente("Carlos", 2),
new Estagiario("Beatriz", 3)
};
// Processamento polimórfico
double totalSalarios = 0;
for (Funcionario f : funcionarios) {
f.trabalhar();
System.out.println("Salário: R$" + f.calcularSalario());
System.out.println("---");
totalSalarios += f.calcularSalario();
}
System.out.println("Total de salários: R$" + totalSalarios);
}
}
Operador instanceof
O instanceof verifica se um objeto é de um determinado tipo:
public class TesteTipo {
public static void main(String[] args) {
Animal animal = new Cachorro();
if (animal instanceof Cachorro) {
System.out.println("É um cachorro!");
// Podemos fazer cast seguro
Cachorro cachorro = (Cachorro) animal;
}
if (animal instanceof Gato) {
System.out.println("É um gato!");
}
// Todo objeto é instanceof de Object
System.out.println(animal instanceof Object); // true
}
}
Erros Comuns
❌ Tentar instanciar classe abstrata
// ERRO! Isso não compila
Forma forma = new Forma("azul"); // Erro de compilação!
❌ Esquecer de implementar métodos abstratos
abstract class Animal {
public abstract void fazerSom();
}
// ERRO! Classe não é abstrata mas não implementa o método
class Peixe extends Animal {
// Precisa implementar fazerSom()!
}
❌ Confundir sobrecarga com sobrescrita
class Pai {
public void metodo(int x) { }
}
class Filho extends Pai {
// Isso é SOBRESCRITA (mesma assinatura)
@Override
public void metodo(int x) { }
// Isso é SOBRECARGA (parâmetros diferentes)
public void metodo(String x) { }
}
Dicas Finais
-
Use @Override: Sempre use esta anotação ao sobrescrever métodos. O Java avisará se você cometer erros.
-
Prefira interfaces a classes abstratas: Interfaces são mais flexíveis (uma classe pode implementar várias).
-
Polimorfismo + Herança = Código elegante: Use polimorfismo para escrever código que funciona com vários tipos.
-
Cuidado com instanceof: Use com moderação. Muito uso pode indicar design problemático.
-
Pense em "é um": Um cachorro é um animal, um gerente é um funcionário. Isso ajuda a modelar suas classes.
Parabéns! Você agora entende um dos conceitos mais importantes da programação orientada a objetos. Pratique criando seus próprios exemplos e logo o polimorfismo se tornará natural para você.