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ística | Clase abstracta | Interfaz |
|---|---|---|
| Variables de instancia | Sí | Solo constantes |
| Constructores | Sí | No |
| Herencia múltiple | No (único extends) | Sí (múltiples implements) |
| Modificadores de acceso | Cualquiera | Solo 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.
Desafío
FácilVamos 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 campobrand(String) y un campoyear(int). Incluye un constructor para inicializar ambos campos, métodos getter para cada uno y un método abstractostartEngine()que devuelva un String. También añade un método concretogetInfo()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()ycloseRoof(), ambos devolviendo String.Car.java: Crea una clase que extiendaVehiclee implementeConvertible. Un Car tiene un campo adicionalnumDoors(int). Usasuperpara inicializar los campos heredados. ImplementastartEngine()para que devuelva:[brand] car engine started. ImplementaopenRoof()para que devuelva:[brand] roof openingycloseRoof()para que devuelva:[brand] roof closing.Motorcycle.java: Crea una clase que extiendaVehiclepero NO implementeConvertible—¡las motocicletas no tienen techo! Una Motorcycle tiene un campohasSidecar(boolean). ImplementastartEngine()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 destartEngine()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 imprimeopenRoof()seguido decloseRoof()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ística | Clase Abstracta | Interfaz |
|---|---|---|
| Variables de instancia | Sí | Solo constantes |
| Constructores | Sí | No |
| Herencia múltiple | No (extensión única) | Sí (múltiples implementaciones) |
| Modificadores de acceso | Cualquiera | Solo 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)
}
}
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
1Fundamentos de OOP
Archivos externosIntroducción a la OOPClases vs. ObjetosLa palabra clave thisMétodosCampos (Atributos)Método constructorSobrecarga de constructoresResumen - Calculadora simple4Herencia
Herencia básica (extends)La palabra clave superSobrescritura de métodos (@Override)Encadenamiento de constructoresLa clase ObjectHerencia simple y multinivelPor qué no hay herencia múltiple de clasesResumen - Jerarquía de empleados7Métodos especiales y la clase Object
Método toString()equals() y hashCode()Método clone()compareTo() y ComparableInterfaz ComparatorResumen - Ordenamiento personalizado2Modificadores de acceso y encapsulamiento
Visión general de los niveles de accesoMétodos Getter y SetterOcultamiento de informaciónLa palabra clave finalResumen - Gestor de cuentas bancarias5Polimorfismo
Fundamentos de sobrecarga de métodosSobrescritura de métodos (tiempo de ejecución)Upcasting y DowncastingEl operador instanceofClases y métodos abstractosResumen - Calculadora de figuras8Conceptos avanzados de POO
Composición vs. HerenciaAgregación vs. ComposiciónClases internas, anidadas y anónimasEnums y métodos de EnumRecords (Java 16+)Clases selladas (Java 17+)11Patrones de diseño - Parte 1
Introducción a los patrones de diseñoPatrón SingletonPatrón FactoryPatrón BuilderPatrón ObserverPatrón Strategy3Propiedades de clase y miembros estáticos
Variables de instancia vs. estáticasMétodos estáticosBloques estáticosConstantes (static final)Repaso - Contador y utilidad6Interfaces y Clases Abstractas
Introducción a las InterfacesImplementación de InterfacesImplementación de Múltiples InterfacesMétodos Default y Static en InterfacesClases Abstractas vs InterfacesInterfaces FuncionalesResumen - Sistema de Pagos9Genéricos
Introducción a los genéricosClases genéricasMétodos genéricosParámetros de tipo delimitadosComodines (?, extends, super)Resumen - Contenedor genérico12Patrones de diseño Parte 2
Patrón CommandPatrón AdapterPatrón DecoratorPatrón Template MethodPatrón StatePatrón CompositePatrón Iterator