1. Polimorfismo: o que é e para que serve
Se você acha que polimorfismo é algo do mundo dos mutantes da Marvel, lamento desapontar: em programação tudo é bem mais tranquilo — mas não menos mágico. Polimorfismo é a capacidade de objetos com implementações diferentes responderem de maneiras distintas às mesmas chamadas de métodos.
Exemplo do dia a dia:
Você tem a classe Book e a classe Magazine, ambas herdando da classe abstrata LibraryItem. Você quer poder chamar o método printInfo() para qualquer item da biblioteca e que ele exiba as informações certas — para livro, autor e título; para revista, número da edição e data.
Exemplo de código:
abstract class LibraryItem {
String title;
LibraryItem(String title) {
this.title = title;
}
abstract void printInfo();
}
class Book extends LibraryItem {
String author;
Book(String title, String author) {
super(title);
this.author = author;
}
@Override
void printInfo() {
System.out.println("Livro: " + title + ", autor: " + author);
}
}
class Magazine extends LibraryItem {
int issueNumber;
Magazine(String title, int issueNumber) {
super(title);
this.issueNumber = issueNumber;
}
@Override
void printInfo() {
System.out.println("Revista: " + title + ", edição: " + issueNumber);
}
}
Agora é possível criar um array com itens diferentes e chamar printInfo() para cada um:
LibraryItem[] items = {
new Book("O Senhor das Moscas", "William Golding"),
new Magazine("Ciência e Vida", 5)
};
for (LibraryItem item : items) {
item.printInfo();
}
// Será impresso:
// Livro: O Senhor das Moscas, autor: William Golding
// Revista: Ciência e Vida, edição: 5
É assim que o polimorfismo funciona!
2. Erros comuns com polimorfismo
Tentar chamar métodos que não existem no tipo base
Um dos erros mais comuns é tentar acessar um método que só foi declarado na classe filha por meio de uma referência do tipo base.
LibraryItem item = new Book("Harry Potter", "J. K. Rowling");
// item.getAuthor(); // Erro de compilação! Em LibraryItem não existe o método getAuthor()
O Java compila o código com base no que vê no tipo da variável (LibraryItem), não no objeto real (Book). Portanto, se você precisa chamar um método específico de livro, é necessário fazer o cast:
if (item instanceof Book) {
Book book = (Book) item;
// Agora é possível chamar book.getAuthor()
}
Conversão de tipo sem verificação
Se você tem certeza de que o objeto é um Book, mas na verdade não é, terá um ClassCastException em tempo de execução. Por exemplo:
LibraryItem item = new Magazine("Forbes", 12);
Book book = (Book) item; // BUM! ClassCastException
A forma correta — sempre verificar o tipo:
if (item instanceof Book) {
Book book = (Book) item;
// OK
} else {
System.out.println("Isto não é um livro!");
}
Não aproveitar as vantagens do polimorfismo
Às vezes, desenvolvedores escrevem código de forma que ele fique rigidamente acoplado a tipos específicos, quando poderiam usar abstrações. Por exemplo, se você escreve:
Book[] books = ...;
for (Book book : books) {
book.printInfo();
}
Isso só funciona para livros. E se amanhã surgirem revistas, jornais, quadrinhos? É melhor usar um array de LibraryItem[] e trabalhar com métodos da classe base ou da interface.
3. Abstrações: para que servem e como não errar
Classes abstratas e interfaces
Abstração é a arte de destacar o essencial e esconder os detalhes. Em Java, para isso existem classes abstratas e interfaces.
- Classe abstrata — é uma classe que não pode ser instanciada diretamente, apenas estendida.
- Interface — é um contrato: o que a classe deve fazer, não como ela faz.
Erro 1: Criar uma classe abstrata sem métodos abstratos
Se na sua classe abstrata não há nenhum método abstrato, pense bem — será que ela realmente precisa ser abstrata? Talvez seja mais simples fazer uma classe comum.
abstract class UselessAbstract {
void sayHello() {
System.out.println("Hello!");
}
}
// É melhor fazer uma classe comum se não houver métodos abstratos
Erro 2: Ausência de implementação de métodos obrigatórios nas classes filhas
Se uma classe herda de uma classe abstrata ou implementa uma interface, ela é obrigada a implementar todos os métodos abstratos. Se esquecer — o compilador vai avisar, mas às vezes os métodos são implementados “para constar” e não fazem nada. Isso é ruim para a manutenção do código.
class Magazine extends LibraryItem {
Magazine(String title, int issueNumber) {
super(title);
// ...
}
@Override
void printInfo() {
// Vazio! Ruim!
}
}
Erro 3: Hierarquia de abstrações muito profunda ou confusa
Quando classes herdam umas das outras em cinco a dez níveis, torna-se muito difícil entendê-las. É melhor criar hierarquias “planas”, onde tudo é claro.
Exemplo ruim:
LibraryItem
|
BookItem
|
PrintedBook
|
IllustratedBook
|
ChildrenIllustratedBook
Complicado, não é? Melhor limitar-se a dois ou três níveis.
4. Prática: aplicando polimorfismo e abstrações no aplicativo didático
Vamos evoluir seu aplicativo didático para biblioteca. Antes, você tinha apenas livros. Agora vamos adicionar revistas e implementar uma interface comum para publicações impressas.
Vamos declarar a classe abstrata:
abstract class LibraryItem {
protected String title;
public LibraryItem(String title) {
this.title = title;
}
public abstract void printInfo();
}
Vamos adicionar classes filhas:
class Book extends LibraryItem {
private String author;
public Book(String title, String author) {
super(title);
this.author = author;
}
@Override
public void printInfo() {
System.out.println("Livro: " + title + ", autor: " + author);
}
}
class Magazine extends LibraryItem {
private int issueNumber;
public Magazine(String title, int issueNumber) {
super(title);
this.issueNumber = issueNumber;
}
@Override
public void printInfo() {
System.out.println("Revista: " + title + ", edição: " + issueNumber);
}
}
Usando polimorfismo:
LibraryItem[] items = {
new Book("Código Limpo", "Robert Martin"),
new Magazine("Java World", 3)
};
for (LibraryItem item : items) {
item.printInfo();
}
Adicionar uma interface para publicações eletrônicas
Suponha que algumas publicações possam ser lidas online. Vamos introduzir uma interface:
interface ReadableOnline {
void openOnline();
}
class EBook extends Book implements ReadableOnline {
private String url;
public EBook(String title, String author, String url) {
super(title, author);
this.url = url;
}
@Override
public void openOnline() {
System.out.println("Abrindo o livro eletrônico no endereço: " + url);
}
}
Agora é possível trabalhar com livros eletrônicos por meio da interface:
ReadableOnline ebook = new EBook("Java para Leigos", "Barry Burd", "https://example.com/java");
ebook.openOnline();
5. Como evitar problemas com polimorfismo e abstrações: boas práticas
- Use interfaces e classes abstratas para descrever comportamento, não estado.
Por exemplo, a interface Printable descreve bem a capacidade de “imprimir”, mas manter no interface um campo String title já é uma má ideia. - Verifique o tipo do objeto com instanceof antes de converter.
Especialmente se o objeto puder ser de tipos diferentes. Isso evita ClassCastException. - Busque hierarquias “planas” e fáceis de entender.
Quanto mais simples a árvore de herança — mais fácil manter e evoluir o código. - Evite criar abstrações “sem sentido”.
Se a classe não contém métodos abstratos e não se destina à herança, não a torne abstrata. - Use a anotação @Override sempre que sobrescrever um método.
Isso ajuda o compilador a detectar erros na assinatura.
6. Erros típicos ao trabalhar com polimorfismo e abstrações
Erro nº 1: Fazer cast sem verificação
Às vezes dá vontade de “encurtar caminho” e fazer o cast sem verificar. Isso pode até funcionar, mas também pode causar uma queda inesperada do programa. Sempre use instanceof:
if (item instanceof Book) {
Book book = (Book) item;
// ...
}
Erro nº 2: Tentar chamar método da classe filha por meio de referência do tipo base
LibraryItem item = new Book("Java", "Autor");
item.getAuthor(); // Erro de compilação: em LibraryItem não existe esse método!
Solução — ou faça o cast, ou adicione o método necessário à classe base (se isso fizer sentido).
Erro nº 3: Implementação incompleta de interface ou classe abstrata
Se esquecer de implementar todos os métodos de uma interface — o compilador não deixará montar o projeto. Mas se implementar “stubs” que não fazem nada, isso levará a comportamentos inesperados.
Erro nº 4: Hierarquia de herança muito profunda
Se você tem mais de três níveis de herança — pense se não é possível simplificar a arquitetura.
Erro nº 5: Violação do princípio da responsabilidade única
Se a abstração descreve responsabilidades demais, ela se torna difícil de manter. É melhor dividir em várias interfaces ou classes.
GO TO FULL VERSION