W3docs

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");       // string

Isso é 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 — implicit

Ir 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 runtime

A 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 polymorphic

Essa é 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 called

Adicionar @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.greet

Mesma 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

java— editable, runs on the server

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.

Prática

Prática
Animal a = new Cat(); a.speak(); — qual speak() realmente é executado?
Animal a = new Cat(); a.speak(); — qual speak() realmente é executado?
Was this page helpful?