Classes Abstratas em Java
Defina implementações parciais em Java com classes e métodos abstratos que as subclasses devem completar.
Uma classe abstrata é uma classe que não pode ser instanciada diretamente. Ela existe para ser estendida. Pode combinar métodos concretos (com corpo) e métodos abstratos (sem corpo, que a subclasse precisa implementar) — essa combinação é o que a diferencia tanto de uma classe comum quanto de uma interface.
Esta página explica como declarar uma classe abstrata, como as subclasses a completam, por que classes abstratas podem armazenar estado, o padrão de método template e como escolher entre uma classe abstrata e uma interface.
Use uma classe abstrata quando subclasses concretas precisam compartilhar estado e infraestrutura, não apenas um contrato de API. Se você precisa apenas de um contrato, uma interface é a escolha mais adequada. Classes abstratas são construídas sobre herança e polimorfismo, então é útil estar confortável com esses conceitos antes.
Declarando uma
Adicione abstract ao cabeçalho da classe. Adicione abstract a qualquer método que não tenha corpo:
public abstract class Shape {
protected final String name;
protected Shape(String name) { this.name = name; }
public abstract double area(); // no body — subclass must provide one
public String describe() { // concrete — inherited as-is
return name + " area=" + area();
}
}Algumas consequências disso:
new Shape("circle")é um erro de compilação — classes abstratas não podem ser instanciadas.- Uma subclasse que não implementa todos os métodos abstratos herdados deve, ela mesma, ser declarada
abstract. É possível ter subclasses abstratas de classes abstratas. - Uma classe abstrata pode ter um construtor — as subclasses o chamam com
super(...)assim como um pai comum.
Implementando os métodos abstratos
Uma subclasse concreta deve fornecer um corpo para cada método abstrato herdado:
public class Circle extends Shape {
private final double r;
public Circle(double r) {
super("circle");
this.r = r;
}
@Override
public double area() { return Math.PI * r * r; }
}Agora new Circle(2) funciona, e describe() (herdado de Shape) chama o area() da subclasse via despacho dinâmico.
Classes abstratas podem armazenar estado
Essa é a principal razão para escolher uma classe abstrata em vez de uma interface. O pai pode declarar campos, escrever um construtor que os inicializa e oferecer métodos que operam sobre esse estado compartilhado:
public abstract class HttpHandler {
private final String path;
protected HttpHandler(String path) { this.path = path; }
public final String path() { return path; }
public abstract Response handle(Request r); // subclass-specific behavior
}Todo handler concreto tem um path; o pai o armazena e o expõe; cada subclasse escreve apenas a lógica por requisição. Interfaces não conseguem fazer isso por conta própria (elas não têm campos de instância).
Combinando métodos abstratos e concretos — o método template
Um padrão comum: a classe abstrata implementa o fluxo geral como um método concreto e deixa os pontos de variação como abstratos. As subclasses preenchem apenas as partes que diferem:
public abstract class Beverage {
// Template — the algorithm, written once.
public final void prepare() {
boilWater();
brew(); // varies
pourIntoCup();
addCondiments(); // varies
}
protected abstract void brew();
protected abstract void addCondiments();
private void boilWater() { System.out.println("boiling water"); }
private void pourIntoCup() { System.out.println("pouring into cup"); }
}
public class Tea extends Beverage {
protected void brew() { System.out.println("steeping tea"); }
protected void addCondiments() { System.out.println("adding lemon"); }
}Tea.prepare() executa o template do pai, que chama de volta os métodos brew e addCondiments de Tea via polimorfismo. Adicionar uma subclasse Coffee requer apenas os dois métodos abstratos.
Esse é o padrão de método template e é o motivo mais comum para escolher uma classe abstrata.
Abstrato vs final
abstract e final são opostos e o compilador impõe isso:
abstract class— deve ser subclassificada.final class— não pode ser subclassificada.
O mesmo vale para métodos: métodos abstract devem ser sobrescritos; métodos final não podem ser. Usar os dois ao mesmo tempo é um erro de compilação.
Abstrato vs interface
| Classe abstrata | Interface | |
|---|---|---|
| Construtores | Sim | Não |
| Campos de instância | Sim | Não (apenas constantes public static final) |
| Corpos de métodos | Sim (qualquer número) | Sim via default (usado com parcimônia) |
| Herança | Simples — apenas um pai | Múltipla — várias interfaces |
| Usar quando | Subclasses compartilham estado e infraestrutura | Subclasses compartilham apenas um contrato de API |
Uma regra prática: comece com uma interface. Evolua para (ou adicione) uma classe abstrata somente se perceber que código compartilhado está se acumulando entre implementações. Os métodos default do Java 8+ em interfaces absorveram parte do território que as classes abstratas costumavam dominar, mas o caso de uso de "estado mutável compartilhado" ainda pertence ao domínio das classes abstratas.
Métodos abstratos não podem ser private ou static
privatetornaria o método invisível para as subclasses — elas não poderiam sobrescrevê-lo, então a abstração seria inaplicável.- Métodos
staticnão são despachados dinamicamente — não podem ser sobrescritos, apenas ocultados — portanto, um método estático abstrato seria sem sentido.
Essas duas combinações são erros de compilação.
Erros comuns
- Esquecer
abstractna classe. Um método sem corpo dentro de uma classe não abstrata não compila — o compilador exige que a classe envolvente também sejaabstract. - Tentar instanciá-la.
new Shape("x")é rejeitado em tempo de compilação. Instancie uma subclasse concreta. - Deixar um método não implementado em uma subclasse concreta. Se ao menos um método abstrato herdado não tiver corpo, a subclasse também deve ser declarada
abstract. - Esperar herança múltipla. Uma classe pode estender apenas um pai (abstrato ou não). Se precisar combinar vários contratos, recorra a interfaces.
Um exemplo completo
O que vem a seguir
Você viu agora a versão de abstração que vem com estado e código compartilhado. A versão que dispensa ambos — contrato puro, sem implementação — é a interface. Continue para interfaces em Java.