Menu
Coddy logo textTech

Clases Abstractas vs Interfaces

Parte de la sección Programación Orientada a Objetos del Journey de Java de Coddy. Lección 38 de 87.

Ahora que entiendes tanto las clases abstractas como las interfaces, surge una pregunta común: ¿cuándo deberías usar una en lugar de la otra? La elección depende de lo que intentas modelar.

Usa una clase abstracta cuando las clases compartan una base común con estado o comportamiento compartidos. Las clases abstractas pueden tener variables de instancia, constructores y una combinación de métodos abstractos y concretos:

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

Usa una interfaz cuando quieras definir una capacidad que puedan compartir clases no relacionadas. Las interfaces se centran en lo que puede hacer un objeto, no en lo que es:

public interface Flyable {
    void fly();
}

// Clases no relacionadas pueden compartir esta capacidad
class Bird extends Animal implements Flyable { ... }
class Airplane implements Flyable { ... }
class Drone implements Flyable { ... }

Aquí tienes una comparación rápida:

CaracterísticaClase abstractaInterfaz
Variables de instanciaSolo constantes
ConstructoresNo
Herencia múltipleNo (una sola extensión)Sí (múltiples implementaciones)
Modificadores de accesoCualquieraSolo públicos (para métodos abstractos)

Una guía práctica: si te encuentras creando una clase abstracta con solo métodos abstractos y sin estado, probablemente una interfaz sea la mejor opción.

challenge icon

Desafío

Fácil

Construyamos un sistema de vehículos que demuestre cuándo usar clases abstractas frente a interfaces. Modelarás vehículos que comparten estado y comportamiento comunes mediante una clase abstracta, mientras añades capacidades opcionales mediante interfaces.

Organizarás tu código en cinco archivos:

  • Vehicle.java: Crea una clase abstracta que sirva como base para todos los vehículos. Cada vehículo tiene un campo brand (String) y un campo year (int). Incluye un constructor para inicializar ambos campos, métodos getter para cada uno y un método abstracto startEngine() que devuelva un String. Añade también un método concreto getInfo() que devuelva: [brand] ([year]). Este es un caso de uso perfecto para una clase abstracta: los vehículos comparten estado (brand, year) y parte del comportamiento (getInfo), pero cada uno arranca su motor de una manera diferente.
  • Convertible.java: Define una interfaz para los vehículos que pueden abrir o cerrar su techo. Esta capacidad no está vinculada a lo que un vehículo es: es algo que ciertos vehículos pueden hacer. Declara dos métodos: openRoof() y closeRoof(), ambos con un valor de retorno String.
  • Car.java: Crea una clase que extienda Vehicle e implemente Convertible. Un Car tiene un campo adicional numDoors (int). Usa super para inicializar los campos heredados. Implementa startEngine() para que devuelva: [brand] car engine started. Implementa openRoof() para que devuelva: [brand] roof opening y closeRoof() para que devuelva: [brand] roof closing.
  • Motorcycle.java: Crea una clase que extienda Vehicle pero NO implemente Convertible: ¡las motocicletas no tienen techo! Una Motorcycle tiene un campo hasSidecar (boolean). Implementa startEngine() para que devuelva: [brand] motorcycle engine roaring.
  • Main.java: Reúne todo para mostrar la diferencia entre clases abstractas e interfaces. Recibirás cuatro entradas: la marca de un coche, el año de un coche, la marca de una motocicleta y el año de una motocicleta.

    Crea un Car (con 4 puertas) y una Motorcycle (sin sidecar). Primero, demuestra el comportamiento compartido de la clase abstracta imprimiendo getInfo() para ambos vehículos. Ambos heredan esto de Vehicle. Después, imprime el resultado de startEngine() para cada uno: observa cómo cada tipo de vehículo lo implementa de manera diferente.

    Por último, demuestra la capacidad de la interfaz: como solo el Car implementa Convertible, llama e imprime openRoof() seguido de closeRoof() únicamente para el coche.

Recibirás cuatro entradas: marca del coche (String), año del coche (int), marca de la motocicleta (String) y año de la motocicleta (int).

Tu salida debe mostrar seis líneas en total. Observa cómo ambos vehículos comparten el estado de la clase abstracta y el método getInfo(), pero solo el Car tiene la capacidad convertible. ¡Esto ilustra cuándo usar cada enfoque!

Pruébalo tú mismo

import java.util.Scanner;

class Main {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        
        // Leer entradas
        String carBrand = scanner.nextLine();
        int carYear = scanner.nextInt();
        scanner.nextLine(); // consumir salto de línea
        String motorcycleBrand = scanner.nextLine();
        int motorcycleYear = scanner.nextInt();
        
        // TODO: Crear un Car con 4 puertas
        
        // TODO: Crear un Motorcycle sin sidecar (false)
        
        // TODO: Imprimir getInfo() para ambos vehículos (demuestra el comportamiento de la clase abstracta compartida)
        
        // TODO: Imprimir startEngine() para ambos vehículos (demuestra diferentes implementaciones)
        
        // TODO: Imprimir openRoof() y closeRoof() solo para el car (demuestra la capacidad de la interfaz)
    }
}
quiz iconPonte a prueba

Esta lección incluye un breve cuestionario. Empieza la lección para responderlo y registrar tu progreso.

Todas las lecciones de Programación Orientada a Objetos

Practica por tu cuenta: Compilador de Java online