Clases abstractas pueden contener métodos abstractos y concretos (con implementación), así como estados (campos de datos). Una clase solo puede heredar de una clase abstracta. Las clases abstractas se utilizan cuando es necesario generalizar alguna parte de la lógica y el estado para una jerarquía de herederos.
Interfaces solo definen las firmas de los métodos (hasta Java 8), a partir de Java 8 pueden contener métodos con implementación por defecto (default) y métodos estáticos, pero no tienen estado. Una clase puede implementar múltiples interfaces. Use interfaces para definir un conjunto de comportamientos, independiente de la jerarquía de herencia.
Ejemplo:
abstract class Animal { String name; public Animal(String name) { this.name = name; } abstract void makeSound(); } interface Movable { void move(); } class Dog extends Animal implements Movable { public Dog(String name) { super(name); } void makeSound() { System.out.println("¡Guau!"); } public void move() { System.out.println("Corre"); } }
¿Cuál es la diferencia entre una clase abstracta con solo métodos abstractos y una interfaz hasta Java 8? ¿Se pueden usar de manera intercambiable?
Respuesta: No, no siempre. Una interfaz no puede contener estado (campos de instancia) y admite implementación múltiple, mientras que la herencia de una clase abstracta es única. Además, las firmas de los métodos de la interfaz siempre son públicas.
Historia
En el proyecto era necesario implementar muchos objetos de comportamiento similar, pero pertenecientes a diferentes jerarquías, interfaces. El desarrollador eligió usar una clase abstracta, lo que hizo imposible usar herencia múltiple. Esto llevó a una significativa refactorización y duplicación de código.
Historia
En un gran proyecto de API REST se creó una interfaz con campos (constantes), suponiendo que serían valores mutables, como en una clase normal. Como resultado, intentar modificar estos campos condujo a un Silent Failure: los valores permanecieron iguales y el error no se localizó durante mucho tiempo.
Historia
Durante el proceso de migración a Java 8, los desarrolladores añadieron métodos con implementación a la interfaz, pero olvidaron considerar que algunas clases también heredaban métodos con el mismo nombre a través de una clase abstracta. Esto condujo a conflictos de métodos y resultados inesperados al resolver la herencia.