Menu
Coddy logo textTech

Classes Abstratas vs Interfaces

Parte da seção Programação Orientada a Objetos do Journey de Java da Coddy — lição 38 de 87.

Agora que você entende tanto classes abstratas quanto interfaces, surge uma pergunta comum: quando você deve usar uma em vez da outra? A escolha depende do que você está tentando modelar.

Use uma classe abstrata quando as classes compartilham uma base comum com estado ou comportamento compartilhado. Classes abstratas podem ter variáveis de instância, construtores e uma mistura de métodos abstratos e concretos:

public abstract class Animal {
    protected String name;  // estado compartilhado
    
    public Animal(String name) {
        this.name = name;
    }
    
    public void sleep() {  // comportamento compartilhado
        System.out.println(name + " is sleeping");
    }
    
    public abstract void makeSound();  // deve ser implementado
}

Use uma interface quando quiser definir uma capacidade que classes não relacionadas possam compartilhar. As interfaces focam no que um objeto pode fazer, não no que ele é:

public interface Flyable {
    void fly();
}

// Classes não relacionadas podem compartilhar essa capacidade
class Bird extends Animal implements Flyable { ... }
class Airplane implements Flyable { ... }
class Drone implements Flyable { ... }

Aqui está uma comparação rápida:

RecursoClasse AbstrataInterface
Variáveis de instânciaSimApenas constantes
ConstrutoresSimNão
Herança múltiplaNão (single extends)Sim (multiple implements)
Modificadores de acessoQualquerApenas público (para métodos abstratos)

Uma diretriz prática: se você se encontrar criando uma classe abstrata com apenas métodos abstratos e sem estado, uma interface é provavelmente a melhor escolha.

challenge icon

Desafio

Fácil

Vamos construir um sistema de veículos que demonstra quando usar classes abstratas versus interfaces. Você modelará veículos que compartilham estado e comportamento comuns por meio de uma classe abstrata, enquanto adiciona capacidades opcionais por meio de interfaces.

Você organizará seu código em cinco arquivos:

  • Vehicle.java: Crie uma classe abstrata que sirva como base para todos os veículos. Todo veículo possui um campo brand (String) e um campo year (int). Inclua um construtor para inicializar ambos os campos, métodos getter para cada um e um método abstrato startEngine() que retorna uma String. Adicione também um método concreto getInfo() que retorna: [brand] ([year]). Este é um caso de uso perfeito para uma classe abstrata — os veículos compartilham estado (brand, year) e algum comportamento (getInfo), mas cada um liga seu motor de forma diferente.
  • Convertible.java: Defina uma interface para veículos que podem converter seu teto. Essa capacidade não está ligada ao que um veículo é — é algo que certos veículos podem fazer. Declare dois métodos: openRoof() e closeRoof(), ambos retornando String.
  • Car.java: Crie uma classe que estenda Vehicle e implemente Convertible. Um Car tem um campo adicional numDoors (int). Use super para inicializar os campos herdados. Implemente startEngine() para retornar: [brand] car engine started. Implemente openRoof() para retornar: [brand] roof opening e closeRoof() para retornar: [brand] roof closing.
  • Motorcycle.java: Crie uma classe que estenda Vehicle, mas NÃO implemente Convertible — motocicletas não têm teto! Uma Motorcycle tem um campo hasSidecar (boolean). Implemente startEngine() para retornar: [brand] motorcycle engine roaring.
  • Main.java: Reúna tudo para mostrar a diferença entre classes abstratas e interfaces. Você receberá quatro entradas: uma marca de carro, um ano de carro, uma marca de motocicleta e um ano de motocicleta.

    Crie um Car (com 4 portas) e uma Motorcycle (sem sidecar). Primeiro, demonstre o comportamento compartilhado da classe abstrata imprimindo getInfo() para ambos os veículos — ambos herdam isso de Vehicle. Em seguida, imprima o resultado de startEngine() para cada um — observe como cada tipo de veículo implementa isso de forma diferente.

    Finalmente, demonstre a capacidade da interface: como apenas o Car implementa Convertible, chame e imprima openRoof() seguido de closeRoof() apenas no carro.

Você receberá quatro entradas: marca do carro (String), ano do carro (int), marca da motocicleta (String) e ano da motocicleta (int).

Sua saída deve mostrar seis linhas no total. Observe como ambos os veículos compartilham o estado da classe abstrata e o método getInfo(), mas apenas o Car tem a capacidade conversível — isso ilustra quando usar cada abordagem!

Folha de consulta

Use uma classe abstrata quando as classes compartilham uma base comum com estado ou comportamento compartilhado. Classes abstratas podem ter variáveis de instância, construtores e uma mistura de métodos abstratos e concretos:

public abstract class Animal {
    protected String name;  // estado compartilhado
    
    public Animal(String name) {
        this.name = name;
    }
    
    public void sleep() {  // comportamento compartilhado
        System.out.println(name + " is sleeping");
    }
    
    public abstract void makeSound();  // deve ser implementado
}

Use uma interface quando quiser definir uma capacidade que classes não relacionadas podem compartilhar. Interfaces focam no que um objeto pode fazer, não no que ele é:

public interface Flyable {
    void fly();
}

// Classes não relacionadas podem compartilhar esta capacidade
class Bird extends Animal implements Flyable { ... }
class Airplane implements Flyable { ... }
class Drone implements Flyable { ... }
RecursoClasse AbstrataInterface
Variáveis de instânciaSimApenas constantes
ConstrutoresSimNão
Herança múltiplaNão (extends único)Sim (múltiplos implements)
Modificadores de acessoQualquer umApenas público (para métodos abstratos)

Diretriz: Se você estiver criando uma classe abstrata com apenas métodos abstratos e sem estado, uma interface provavelmente é a melhor escolha.

Experimente você mesmo

import java.util.Scanner;

class Main {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        
        // Ler as entradas
        String carBrand = scanner.nextLine();
        int carYear = scanner.nextInt();
        scanner.nextLine(); // consumir a quebra de linha
        String motorcycleBrand = scanner.nextLine();
        int motorcycleYear = scanner.nextInt();
        
        // TODO: Criar um Car com 4 portas
        
        // TODO: Criar uma Motorcycle sem sidecar (false)
        
        // TODO: Imprimir getInfo() para ambos os veículos (demonstra o comportamento compartilhado da classe abstrata)
        
        // TODO: Imprimir startEngine() para ambos os veículos (demonstra diferentes implementações)
        
        // TODO: Imprimir openRoof() e closeRoof() apenas para o carro (demonstra a capacidade da interface)
    }
}
quiz iconTeste seus conhecimentos

Esta lição inclui um quiz rápido. Comece a lição para respondê-lo e acompanhar seu progresso.

Todas as lições de Programação Orientada a Objetos