Polimorfismo em Java
Escreva código Java flexível com polimorfismo em tempo de compilação (sobrecarga) e em tempo de execução (sobrescrita).
Polimorfismo é "uma interface, muitas implementações." Em Java ele tem duas formas:
- Polimorfismo em tempo de compilação (sobrecarga): o compilador escolhe entre métodos que compartilham um nome com base nos tipos de argumento passados.
- Polimorfismo em tempo de execução (sobrescrita): a JVM escolhe entre implementações de métodos com base no objeto real sobre o qual a chamada é feita.
O tipo em tempo de execução é o que as pessoas geralmente querem dizer com "polimorfismo" em um contexto de POO, e é o que torna a herança valiosa. Sem ele, uma referência Cat seria a única forma de chamar Cat.speak() — você não poderia escrever código que percorre uma lista mista de animais e pede a cada um que fale.
Polimorfismo em tempo de compilação — sobrecarga
Dois métodos na mesma classe podem compartilhar um nome desde que suas listas de parâmetros sejam diferentes. O compilador escolhe qual chamar com base nos tipos de argumento no ponto de chamada:
public class Printer {
void print(int n) { System.out.println("int: " + n); }
void print(double d) { System.out.println("double: " + d); }
void print(String s) { System.out.println("string: " + s); }
}
Printer p = new Printer();
p.print(5); // int
p.print(5.0); // double
p.print("hi"); // stringIsso é decidido inteiramente em tempo de compilação. O método escolhido fica gravado no bytecode; nada muda em tempo de execução. A sobrecarga de métodos foi abordada em detalhes em sobrecarga de métodos na Parte 5.
Polimorfismo em tempo de execução — sobrescrita e despacho dinâmico
O tipo interessante. Quando uma subclasse sobrescreve um método, chamadas por meio de uma referência tipada pelo pai ainda despacham para a versão da subclasse:
class Animal {
String speak() { return "(noise)"; }
}
class Cat extends Animal {
@Override String speak() { return "meow"; }
}
class Dog extends Animal {
@Override String speak() { return "woof"; }
}
Animal[] zoo = { new Cat(), new Dog(), new Animal() };
for (Animal a : zoo) {
System.out.println(a.speak());
}
// meow
// woof
// (noise)Em cada iteração a é tipado como Animal, mas o objeto real é um Cat, um Dog ou um Animal. A chamada a.speak() não escolhe o método em tempo de compilação — em tempo de compilação, o compilador só sabe que a é algum Animal. Em tempo de execução, a JVM olha para o objeto real e despacha para o speak da classe daquele objeto.
Isso é o despacho dinâmico (às vezes chamado de despacho virtual). É o que torna o laço acima interessante: ele é escrito genericamente contra Animal, e funciona para qualquer subclasse — incluindo as que não existiam quando o laço foi escrito.
Por que isso importa
Polimorfismo é o recurso de POO que torna o código aberto para extensão sem modificação. Uma função que recebe uma Shape e chama area() nela funciona para toda forma que existe hoje e toda forma que alguém adicionar amanhã. A função não precisa de uma cadeia if (shape instanceof Circle).
double totalArea(List<Shape> shapes) {
double sum = 0;
for (Shape s : shapes) sum += s.area(); // dispatches to each subclass
return sum;
}Adicione Triangle extends Shape, e totalArea funciona em listas de triângulos de graça. Essa é a essência do Princípio Aberto/Fechado — aberto para extensão, fechado para modificação.
Upcasting e downcasting
Ir de um tipo de subclasse para um tipo pai é um upcast. É implícito e sempre seguro:
Cat c = new Cat();
Animal a = c; // upcast — implicitIr na outra direção — atribuir uma referência tipada pelo pai de volta a um tipo de subclasse — é um downcast. Requer uma expressão de cast, e a JVM verifica em tempo de execução que o objeto é realmente daquele subtipo:
Animal a = new Cat();
Cat c = (Cat) a; // downcast — runtime check
Animal a2 = new Dog();
Cat c2 = (Cat) a2; // ClassCastException at runtimeA alternativa amigável ao tempo de compilação é a verificação instanceof, frequentemente combinada com correspondência de padrão no Java moderno:
if (a instanceof Cat c) {
c.purr();
}Campos não são polimórficos
O despacho dinâmico se aplica somente a métodos de instância. Campos, métodos static e métodos private são vinculados em tempo de compilação com base no tipo declarado da referência:
class A {
String label = "A";
static String klass() { return "A"; }
}
class B extends A {
String label = "B";
static String klass() { return "B"; }
}
A a = new B();
System.out.println(a.label); // "A" — field, not polymorphic
System.out.println(a.klass()); // "A" — static, not polymorphicEssa é uma das razões pelas quais você mantém campos privados e os acessa por meio de métodos — métodos participam do polimorfismo; campos não.
@Override e bugs silenciosos
Sempre anote sobrescritas com @Override. A anotação diz ao compilador "isto é uma intenção de sobrescrever um método pai — falhe se não for." Sem ela, um pequeno erro de digitação cria um novo método que parece uma sobrescrita mas não é:
class Animal {
String speak() { return "(noise)"; }
}
class Cat extends Animal {
String Speak() { return "meow"; } // capital S — typo, new method
}
Animal a = new Cat();
System.out.println(a.speak()); // "(noise)" — Cat.Speak was never calledAdicionar @Override faz o compilador detectar isso imediatamente.
Polimorfismo com interfaces
Herança não é o único caminho. Uma interface também é um tipo pai — diferentes classes concretas a implementam, e código que recebe o tipo de interface funciona com todas elas:
interface Greeter {
String greet();
}
class English implements Greeter {
public String greet() { return "Hello"; }
}
class French implements Greeter {
public String greet() { return "Bonjour"; }
}
Greeter g = new French();
System.out.println(g.greet()); // "Bonjour" — dispatched to French.greetMesma ideia — escreva código contra a abstração, deixe o tempo de execução escolher a implementação. O capítulo sobre interfaces aprofunda a mecânica.
Um exemplo prático
O que vem a seguir
O polimorfismo se apoia em um mecanismo: uma subclasse substituindo um método herdado. Esse mecanismo — o que é permitido, o que não é, e a anotação @Override que mantém a honestidade — é o tema do próximo capítulo. Continue para sobrescrita de métodos.