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 sobre la otra? La elección depende de lo que estés intentando modelar.

Utilice una clase abstracta cuando las clases comparten una base común con estado o comportamiento compartido. Las clases abstractas pueden tener variables de instancia, constructores y una mezcla 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
}

Utiliza una interfaz cuando quieras definir una capacidad que clases no relacionadas puedan compartir. Las interfaces se centran en lo que un objeto puede hacer, 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 (único extends)Sí (múltiples implements)
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, una interfaz es probablemente la mejor opción.

challenge icon

Desafío

Fácil

Vamos a construir un sistema de vehículos que demuestre cuándo usar clases abstractas frente a interfaces. Modelarás vehículos que comparten un estado y comportamiento común a través de una clase abstracta, mientras añades capacidades opcionales a través de 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. También añade 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 (marca, año) y cierto comportamiento (getInfo), pero cada uno arranca su motor de manera diferente.
  • Convertible.java: Define una interfaz para vehículos que pueden convertir su techo. Esta capacidad no está ligada a lo que un vehículo es; es algo que ciertos vehículos pueden hacer. Declara dos métodos: openRoof() y closeRoof(), ambos devolviendo 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: una marca de coche, un año de coche, una marca de motocicleta y un año de 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. Luego imprime el resultado de startEngine() para cada uno; observa cómo cada tipo de vehículo implementa esto de manera diferente.

    Finalmente, demuestra la capacidad de la interfaz: dado que solo el Car implementa Convertible, llama e imprime openRoof() seguido de closeRoof() solo en 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 de descapotable—¡esto ilustra cuándo usar cada enfoque!

Hoja de referencia

Usa una clase abstracta cuando las clases comparten una base común con un estado o comportamiento compartido. Las clases abstractas pueden tener variables de instancia, constructores y una mezcla 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 clases no relacionadas puedan compartir. Las interfaces se centran en lo que un objeto puede hacer, 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 { ... }
CaracterísticaClase AbstractaInterfaz
Variables de instanciaSolo constantes
ConstructoresNo
Herencia múltipleNo (extensión única)Sí (múltiples implementaciones)
Modificadores de accesoCualquieraSolo públicos (para métodos abstractos)

Pauta: Si estás creando una clase abstracta con solo métodos abstractos y sin estado, es probable que una interfaz sea la mejor opción.

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 nueva línea
        String motorcycleBrand = scanner.nextLine();
        int motorcycleYear = scanner.nextInt();
        
        // TODO: Crear un Car con 4 puertas
        
        // TODO: Crear una Motorcycle sin sidecar (false)
        
        // TODO: Imprimir getInfo() para ambos vehículos (demuestra el comportamiento compartido de la clase abstracta)
        
        // TODO: Imprimir startEngine() para ambos vehículos (demuestra diferentes implementaciones)
        
        // TODO: Imprimir openRoof() y closeRoof() solo para el coche (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