Classes Abstratas em Java
No Java, além das classes comuns, existem as classes abstratas. Uma classe abstrata pode conter atributos e métodos como qualquer outra, mas não pode ser instanciada diretamente. Seu papel é servir como modelo base para outras classes, oferecendo uma estrutura e, muitas vezes, parte da implementação que será compartilhada pelas subclasses.
Definição
O modificador abstract
é usado na declaração da classe:
public abstract class Human {
private String name;
public String getName() {
return name;
}
}
A principal diferença em relação a uma classe comum é que não é possível criar um objeto dela diretamente:
Human h = new Human(); // Erro: não é possível instanciar uma classe abstrata
Métodos abstratos
Além de métodos comuns (com corpo), uma classe abstrata pode declarar métodos abstratos, que não têm implementação e terminam com ponto e vírgula:
public abstract void display();
⚠️ Regras importantes:
- Qualquer classe concreta que herde de uma classe abstrata deve implementar todos os métodos abstratos herdados.
- Se uma classe declarar pelo menos um método abstrato, ela própria precisa ser marcada como
abstract
.
Exemplo prático: Sistema bancário
Imagine um sistema para gerenciar operações bancárias.
Há três classes principais: Person
(pessoa), Employee
(funcionário do banco) e Client
(cliente do banco).
As duas últimas compartilham atributos e comportamentos comuns de Person
, mas não existe uma instância genérica de pessoa no sistema. Nesse caso, faz sentido tornar Person
abstrata.
public class Program {
public static void main(String[] args) {
Employee sam = new Employee("Sam", "Lehman Brothers");
sam.display();
Client bob = new Client("Bob", "Lehman Brothers");
bob.display();
}
}
abstract class Person {
private String name;
public String getName() {
return name;
}
public Person(String name) {
this.name = name;
}
public abstract void display();
}
class Employee extends Person {
private String bank;
public Employee(String name, String company) {
super(name);
this.bank = company;
}
@Override
public void display() {
System.out.printf("Employee Name: %s \t Bank: %s%n", getName(), bank);
}
}
class Client extends Person {
private String bank;
public Client(String name, String company) {
super(name);
this.bank = company;
}
@Override
public void display() {
System.out.printf("Client Name: %s \t Bank: %s%n", getName(), bank);
}
}
Resumo do exemplo:
Person
define atributos e comportamentos comuns, mas não pode ser instanciada.Employee
eClient
herdam dePerson
e implementam o método abstratodisplay()
de forma específica.
Exemplo clássico: Geometria
No mundo real, “figura” é um conceito genérico — não existe uma figura sem forma definida.
Mas figuras concretas como círculos, retângulos e quadrados compartilham características comuns, que podem ser representadas em uma classe abstrata Figure
.
// Classe abstrata para figuras
abstract class Figure {
float x; // coordenada X
float y; // coordenada Y
Figure(float x, float y) {
this.x = x;
this.y = y;
}
// Método abstrato para perímetro
public abstract float getPerimeter();
// Método abstrato para área
public abstract float getArea();
}
// Subclasse concreta: Retângulo
class Rectangle extends Figure {
private float width;
private float height;
Rectangle(float x, float y, float width, float height) {
super(x, y);
this.width = width;
this.height = height;
}
@Override
public float getPerimeter() {
return (width * 2) + (height * 2);
}
@Override
public float getArea() {
return width * height;
}
}
Resumo do exemplo:
Figure
define os atributos de posição e os métodos abstratosgetPerimeter()
egetArea()
.Rectangle
fornece implementações concretas para calcular perímetro e área.- Outras figuras, como
Circle
ouSquare
, podem herdar deFigure
e implementar os mesmos métodos, mantendo consistência na API.
📝 Exercícios
Tarefa 1 — Identificar uso incorreto de classe abstrata
Descrição: Analise o código abaixo e indique o motivo do erro de compilação.
abstract class Shape {
public abstract double getArea();
}
public class Program {
public static void main(String[] args) {
Shape s = new Shape();
}
}
Resposta
Shape
, é necessário criar uma subclasse concreta que implemente o método getArea()
.
abstract
impede a criação de objetos dessa classe. Ele indica que a implementação está incompleta e precisa ser finalizada por subclasses concretas.
Tarefa 2 — Implementar métodos abstratos
Descrição:
Implemente a classe Circle
como subclasse de Figure
para calcular área e perímetro.
abstract class Figure {
public abstract double getPerimeter();
public abstract double getArea();
}
Resposta
class Circle extends Figure {
private double radius;
public Circle(double radius) {
this.radius = radius;
}
@Override
public double getPerimeter() {
return 2 * Math.PI * radius;
}
@Override
public double getArea() {
return Math.PI * radius * radius;
}
}
Circle
é concreta, portanto precisa implementar todos os métodos abstratos de Figure
. Cada método retorna o cálculo correspondente usando o valor do raio.
Tarefa 3 — Polimorfismo com classes abstratas
Descrição: Dado o código a seguir, identifique a saída no console.
abstract class Person {
String name;
public Person(String name) {
this.name = name;
}
public abstract void display();
}
class Employee extends Person {
String company;
public Employee(String name, String company) {
super(name);
this.company = company;
}
@Override
public void display() {
System.out.println("Employee " + name + " works at " + company);
}
}
public class Program {
public static void main(String[] args) {
Person p = new Employee("Sam", "Oracle");
p.display();
}
}
Resposta
Employee Sam works at Oracle
p
seja do tipo Person
, a instância real é de Employee
. O método display()
é sobrescrito na subclasse e chamado dinamicamente em tempo de execução (polimorfismo).