Interfaces y Clases Abstractas en Java: diferencias y cuándo usar cada una

D
DanisCh
(Actualizado: ) • 17 min de lectura
Interfaces y Clases Abstractas en Java: diferencias y cuándo usar cada una
Java

Es una de las preguntas más frecuentes en entrevistas técnicas de Java. Una que aparece en el 90% de los procesos de selección según datos de CodeGym. Y a pesar de eso, muchos desarrolladores la responden de forma superficial o confunden los casos de uso.

Interfaces y clases abstractas no son simplemente "dos formas de hacer lo mismo". Son herramientas con propósitos distintos que responden a preguntas distintas. Entender cuándo usar cada una es una señal clara de madurez en el diseño de software orientado a objetos.

Si ya tienes claros los 4 pilares de la Programación Orientada a Objetos, este artículo es el paso siguiente natural. Y si quieres ver estos conceptos aplicados en colecciones y procesamiento de datos, échale un vistazo a nuestro artículo sobre Streams en Java.


La pregunta clave antes de decidir

Antes de entrar en la técnica, hay una pregunta conceptual que lo resuelve todo:

¿Estás definiendo lo que un objeto ES, o lo que un objeto PUEDE HACER?

  • Si es lo que un objeto ES → clase abstracta
  • Si es lo que un objeto PUEDE HACER → interfaz
// Un Perro ES un Animal → clase abstracta
// Un Perro PUEDE nadar → interfaz Nadable
// Un Perro PUEDE ser entrenado → interfaz Entrenable

abstract class Animal { ... }          // Perro ES-UN Animal
interface Nadable { void nadar(); }    // Perro PUEDE nadar
interface Entrenable { void obedecer(); }

class Perro extends Animal implements Nadable, Entrenable {
    public void nadar() { ... }
    public void obedecer() { ... }
}

¿Qué es una Clase Abstracta?

Una clase abstracta es una clase que no puede instanciarse directamente — no puedes hacer new Animal(). Sirve como plantilla base para otras clases, pudiendo contener tanto métodos abstractos (sin implementación) como métodos concretos (con implementación compartida).

// Clase abstracta — el molde para una jerarquía de clases relacionadas
public abstract class Animal {

    // Atributos de instancia — las clases abstractas SÍ tienen estado
    private String nombre;
    private int    edad;
    protected double peso;  // accesible por subclases

    // Constructor — las clases abstractas SÍ tienen constructor
    // (lo llaman las subclases con super())
    public Animal(String nombre, int edad, double peso) {
        this.nombre = nombre;
        this.edad   = edad;
        this.peso   = peso;
    }

    // Método abstracto — OBLIGA a las subclases a implementarlo
    // No tiene cuerpo — las subclases deciden el "cómo"
    public abstract String hacerSonido();

    // Método abstracto — cada animal se mueve de forma diferente
    public abstract void moverse();

    // Método concreto — implementación compartida por TODAS las subclases
    // No necesitan sobreescribirlo (aunque pueden)
    public void comer() {
        System.out.println(nombre + " está comiendo");
    }

    public void dormir() {
        System.out.println(nombre + " está durmiendo");
    }

    public void describir() {
        System.out.printf("%s — %d años, %.1f kg%n", nombre, edad, peso);
    }

    // Getters
    public String getNombre() { return nombre; }
    public int    getEdad()   { return edad; }
    public double getPeso()   { return peso; }
}
// Subclases concretas — implementan los métodos abstractos
public class Perro extends Animal {

    private String raza;

    public Perro(String nombre, int edad, double peso, String raza) {
        super(nombre, edad, peso);  // llama al constructor del padre
        this.raza = raza;
    }

    @Override
    public String hacerSonido() {
        return "¡Guau!";  // implementación específica del perro
    }

    @Override
    public void moverse() {
        System.out.println(getNombre() + " corre en cuatro patas");
    }

    // Método específico del Perro (no existe en Animal)
    public void buscarPelota() {
        System.out.println(getNombre() + " busca la pelota");
    }
}

public class Aguila extends Animal {

    public Aguila(String nombre, int edad, double peso) {
        super(nombre, edad, peso);
    }

    @Override
    public String hacerSonido() {
        return "¡Squawk!";
    }

    @Override
    public void moverse() {
        System.out.println(getNombre() + " vuela a gran velocidad");
    }
}

// Uso:
Perro miPerro = new Perro("Rex", 3, 25.5, "Labrador");
miPerro.comer();         // heredado de Animal — "Rex está comiendo"
miPerro.hacerSonido();   // implementado en Perro — "¡Guau!"
miPerro.buscarPelota();  // específico de Perro

// Animal miAnimal = new Animal("X", 1, 1.0); ← Error de compilación
// No se puede instanciar una clase abstracta

¿Qué es una Interfaz?

Una interfaz define un contrato de comportamiento: especifica qué debe poder hacer cualquier clase que la implemente, sin importar de qué jerarquía provenga esa clase. Antes de Java 8, las interfaces solo podían tener métodos abstractos y constantes. Desde Java 8, también pueden tener métodos default y static.

// Interfaz — define un contrato de comportamiento
// No impone jerarquía — cualquier clase puede implementarla

public interface Nadable {
    // Método abstracto — cualquier implementador DEBE definirlo
    void nadar();

    // Método default (Java 8+) — implementación opcional que puede sobreescribirse
    default void bucear() {
        System.out.println("Buceando a poca profundidad");
    }

    // Método static — no se sobreescribe, se llama con el nombre de la interfaz
    static String obtenerMedioNatural() {
        return "agua";
    }
}

public interface Volable {
    void volar();
    double obtenerAlturaMaxima();

    default void planear() {
        System.out.println("Planeando con las corrientes de aire");
    }
}

public interface Entrenable {
    void aprender(String comando);
    void ejecutar(String comando);

    default boolean puedeAprender() {
        return true;
    }
}

// Una clase puede implementar MÚLTIPLES interfaces — herencia múltiple de tipo
public class Pato extends Animal implements Nadable, Volable {

    public Pato(String nombre, int edad, double peso) {
        super(nombre, edad, peso);
    }

    @Override
    public String hacerSonido() { return "¡Cuac!"; }

    @Override
    public void moverse() { System.out.println("El pato camina bamboleándose"); }

    @Override
    public void nadar() {
        System.out.println(getNombre() + " nada en el estanque");
    }

    @Override
    public void volar() {
        System.out.println(getNombre() + " vuela en formación en V");
    }

    @Override
    public double obtenerAlturaMaxima() { return 100.0; } // metros

    // bucear() heredado del default de Nadable — no necesita sobreescribirse
    // planear() heredado del default de Volable — no necesita sobreescribirse
}

// Una clase completamente diferente puede implementar la misma interfaz
// Un Pez no tiene NADA en común con un Pato en su jerarquía, pero ambos nadan
public class Pez implements Nadable {
    @Override
    public void nadar() {
        System.out.println("El pez nada con aletas");
    }

    @Override
    public void bucear() {  // sobreescribe el default
        System.out.println("El pez bucea hasta el fondo del océano");
    }
}

// Uso del polimorfismo con interfaces:
List<Nadable> seresQueNadan = Arrays.asList(
    new Pato("Donald", 5, 1.5),
    new Pez(),
    new Perro("Rex", 3, 25.5)  // si Perro implementa Nadable
);
seresQueNadan.forEach(Nadable::nadar); // cada uno nada a su manera

Las diferencias técnicas — tabla completa

CaracterísticaClase AbstractaInterfaz
Instanciación❌ No se puede instanciar directamente❌ No se puede instanciar directamente
HerenciaUna clase solo puede extender UNA clase abstractaUna clase puede implementar MÚLTIPLES interfaces
Estado (atributos)✅ Puede tener atributos de instancia con cualquier valorSolo constantes (public static final)
Constructor✅ Puede tener constructores❌ No puede tener constructores
Métodos abstractos✅ Puede tener (con abstract)✅ Todos los métodos son abstractos por defecto
Métodos concretos✅ Puede tener (con implementación)Solo con default o static (Java 8+)
Modificadores de accesopublic, protected, private, defaultSolo public (implícito)
Palabra claveextendsimplements
RelaciónES-UN (is-a) — jerarquíaPUEDE-HACER (can-do) — capacidad
PropósitoCompartir estado y lógica en una jerarquíaDefinir un contrato de comportamiento

Evolución de las Interfaces: Java 8, 9 y 21

Las interfaces cambiaron drásticamente con Java 8. Muchos artículos desactualizados dicen que "las interfaces solo tienen métodos abstractos" — eso era verdad antes de 2014.

public interface InterfazModerna {

    // ── Antes de Java 8 — solo esto ─────────────────────────
    // Constante (implícitamente public static final)
    int MAX_INTENTOS = 3;

    // Método abstracto (implícitamente public abstract)
    void metodoObligatorio();

    // ── Java 8 — default y static ────────────────────────────

    // default: implementación opcional que las clases pueden sobreescribir
    // Creados para añadir métodos a interfaces existentes sin romper código
    default void metodoConImplementacionOpcional() {
        System.out.println("Implementación por defecto");
    }

    // static: pertenece a la interfaz, no a la implementación
    // No se hereda ni se sobreescribe
    static String obtenerVersion() {
        return "1.0";
    }

    // ── Java 9 — private ──────────────────────────────────────

    // private: métodos auxiliares para los default, no expuestos al exterior
    private void logInterno(String mensaje) {
        System.out.println("[LOG] " + mensaje);
    }

    default void operacionCompleja() {
        logInterno("Iniciando operación");   // uso del private
        realizarOperacion();
        logInterno("Operación completada");
    }

    private void realizarOperacion() {
        System.out.println("Ejecutando...");
    }
}

// ── Interfaces funcionales (Java 8) ──────────────────────────
// Una interfaz con exactamente UN método abstracto
// Puede usarse con lambdas y referencias a métodos

@FunctionalInterface
public interface Validador<T> {
    boolean validar(T valor);  // único método abstracto

    // Puede tener default y static — sigue siendo funcional
    default Validador<T> y(Validador<T> otro) {
        return valor -> this.validar(valor) && otro.validar(valor);
    }
}

// Usar con lambda:
Validador<String> noVacio  = s -> !s.isEmpty();
Validador<String> longOk   = s -> s.length() >= 3;
Validador<String> emailOk  = s -> s.contains("@");

Validador<String> emailValido = noVacio.y(longOk).y(emailOk);
System.out.println(emailValido.validar("ana@mail.com")); // true
System.out.println(emailValido.validar(""));             // false

// Las interfaces funcionales más usadas del JDK:
// Predicate<T>    → boolean test(T t)
// Function<T,R>   → R apply(T t)
// Consumer<T>     → void accept(T t)
// Supplier<T>     → T get()
// Comparator<T>   → int compare(T a, T b)

Cuándo usar Clase Abstracta

✅ 1. Cuando las subclases comparten estado (atributos)

// Si varias clases necesitan los mismos atributos → clase abstracta
public abstract class CuentaBancaria {
    // Estado compartido — no se puede poner en una interfaz
    private final String numeroCuenta;
    private double saldo;
    private final String titular;
    private final List<String> historial = new ArrayList<>();

    public CuentaBancaria(String numeroCuenta, String titular, double saldoInicial) {
        this.numeroCuenta = numeroCuenta;
        this.titular      = titular;
        this.saldo        = saldoInicial;
    }

    // Lógica compartida — no se repite en cada subclase
    public void depositar(double monto) {
        if (monto <= 0) throw new IllegalArgumentException("Monto inválido");
        saldo += monto;
        historial.add("Depósito: +" + monto);
    }

    // Método abstracto — cada tipo de cuenta retira de forma diferente
    public abstract boolean retirar(double monto);

    // Método abstracto — cada cuenta calcula su comisión diferente
    public abstract double calcularComisionMensual();

    public double getSaldo() { return saldo; }
    protected void setSaldo(double saldo) { this.saldo = saldo; }
    public List<String> getHistorial() { return Collections.unmodifiableList(historial); }
}

public class CuentaAhorros extends CuentaBancaria {
    private final double tasaInteres;

    public CuentaAhorros(String numero, String titular, double saldo, double tasa) {
        super(numero, titular, saldo);
        this.tasaInteres = tasa;
    }

    @Override
    public boolean retirar(double monto) {
        if (monto > getSaldo()) {
            System.out.println("Saldo insuficiente");
            return false;
        }
        setSaldo(getSaldo() - monto);
        return true;
    }

    @Override
    public double calcularComisionMensual() { return 0.0; } // sin comisión
}

public class CuentaCorriente extends CuentaBancaria {
    private final double limiteSobregiro;

    public CuentaCorriente(String numero, String titular, double saldo, double sobregiro) {
        super(numero, titular, saldo);
        this.limiteSobregiro = sobregiro;
    }

    @Override
    public boolean retirar(double monto) {
        if (monto > getSaldo() + limiteSobregiro) return false;
        setSaldo(getSaldo() - monto);
        return true;
    }

    @Override
    public double calcularComisionMensual() { return 5.99; }
}

✅ 2. Cuando quieres compartir lógica común (Template Method Pattern)

// El Template Method es un patrón donde la clase abstracta define
// el algoritmo general y las subclases implementan los pasos específicos

public abstract class GeneradorInforme {

    // El método plantilla — define el algoritmo general
    // Es final para que las subclases no cambien el flujo
    public final void generarInforme() {
        String titulo    = obtenerTitulo();         // paso 1: abstracto
        List<String> datos = recopilarDatos();     // paso 2: abstracto
        String formato   = formatear(datos);        // paso 3: concreto (compartido)
        guardar(titulo, formato);                   // paso 4: concreto (compartido)
        System.out.println("Informe '" + titulo + "' generado");
    }

    // Los pasos específicos — cada subclase los implementa diferente
    protected abstract String obtenerTitulo();
    protected abstract List<String> recopilarDatos();

    // Pasos compartidos — implementación en la clase base
    private String formatear(List<String> datos) {
        return String.join("\n", datos);
    }

    private void guardar(String titulo, String contenido) {
        // lógica de guardado común
        System.out.println("Guardando: " + titulo);
    }
}

public class InformeVentas extends GeneradorInforme {
    @Override
    protected String obtenerTitulo() { return "Informe de Ventas Q2 2026"; }

    @Override
    protected List<String> recopilarDatos() {
        return Arrays.asList("Ventas: $45,000", "Unidades: 320", "Margen: 32%");
    }
}

public class InformeInventario extends GeneradorInforme {
    @Override
    protected String obtenerTitulo() { return "Estado del Inventario"; }

    @Override
    protected List<String> recopilarDatos() {
        return Arrays.asList("Total: 1,250 productos", "Sin stock: 12", "Crítico: 45");
    }
}

Cuándo usar Interfaz

✅ 1. Cuando clases no relacionadas necesitan el mismo comportamiento

// Comparable — la interfaz más usada del JDK
// Perro, Producto y Factura no tienen NADA en común
// pero todos pueden ser comparables

public interface Comparable<T> {
    int compareTo(T otro);
}

public class Producto implements Comparable<Producto> {
    private String nombre;
    private double precio;

    @Override
    public int compareTo(Producto otro) {
        return Double.compare(this.precio, otro.precio);
    }
}

public class Empleado implements Comparable<Empleado> {
    private String nombre;
    private double salario;

    @Override
    public int compareTo(Empleado otro) {
        return this.nombre.compareTo(otro.nombre);
    }
}

// Ahora ambos funcionan con Collections.sort(), TreeSet, etc.
List<Producto> productos = new ArrayList<>(...);
Collections.sort(productos);  // usa compareTo() de Producto

TreeSet<Empleado> empleados = new TreeSet<>(...);  // ordenados automáticamente

✅ 2. Cuando una clase necesita cumplir múltiples contratos

// Java no permite herencia múltiple de clases
// pero sí de interfaces — clave para el diseño flexible

public interface Serializable { }           // puede serializarse
public interface Cloneable { }              // puede clonarse
public interface Comparable<T> { ... }     // puede compararse
public interface AutoCloseable {            // puede cerrarse automáticamente
    void close() throws Exception;
}

// Una clase puede cumplir todos los contratos a la vez
public class ConexionBD implements AutoCloseable, Serializable {
    @Override
    public void close() {
        System.out.println("Conexión cerrada");
    }
}

// Usado con try-with-resources gracias a AutoCloseable:
try (ConexionBD conn = new ConexionBD()) {
    // usar la conexión
} // conn.close() se llama automáticamente

✅ 3. Para definir APIs — el contrato entre módulos

// Las interfaces son el contrato entre el que provee y el que consume

// Contrato del repositorio — el servicio solo conoce esta interfaz
public interface ProductoRepositorio {
    Optional<Producto> findById(int id);
    List<Producto>     findAll();
    List<Producto>     findByCategoria(String categoria);
    Producto           save(Producto producto);
    void               deleteById(int id);
}

// Implementación MySQL — el servicio no sabe que existe
public class ProductoRepositorioMySQL implements ProductoRepositorio {
    // implementación con MySQL
}

// Implementación en memoria — para tests
public class ProductoRepositorioMemoria implements ProductoRepositorio {
    private final Map<Integer, Producto> datos = new HashMap<>();
    // implementación con HashMap — sin BD
}

// El servicio solo depende de la interfaz — no de la implementación
public class ProductoServicio {
    private final ProductoRepositorio repositorio;

    public ProductoServicio(ProductoRepositorio repositorio) {
        this.repositorio = repositorio;  // inyección de dependencia
    }

    public List<Producto> obtenerActivos() {
        return repositorio.findAll().stream()
            .filter(Producto::isActivo)
            .collect(Collectors.toList());
    }
}

// En producción: usa MySQL
new ProductoServicio(new ProductoRepositorioMySQL());

// En tests: usa la implementación en memoria (rápido, sin BD real)
new ProductoServicio(new ProductoRepositorioMemoria());

El patrón combinado — lo mejor de ambos mundos

En la práctica profesional, las soluciones más elegantes combinan interfaces con clases abstractas. La interfaz define el contrato y la clase abstracta proporciona una implementación parcial reutilizable.

// PATRÓN: Interface + AbstractClass + Concrete class

// 1. La INTERFAZ define el contrato completo
public interface ColeccionOrdenada<T extends Comparable<T>> {
    void agregar(T elemento);
    void eliminar(T elemento);
    T    obtenerMinimo();
    T    obtenerMaximo();
    List<T> obtenerOrdenados();
    int  tamaño();
    boolean contiene(T elemento);
}

// 2. La CLASE ABSTRACTA implementa lo que es común y deja lo específico
public abstract class ColeccionOrdenadaBase<T extends Comparable<T>>
    implements ColeccionOrdenada<T> {

    // Estado compartido
    protected final List<T> elementos = new ArrayList<>();

    // Implementación compartida de los métodos "fáciles"
    @Override
    public int tamaño() { return elementos.size(); }

    @Override
    public boolean contiene(T elemento) { return elementos.contains(elemento); }

    @Override
    public List<T> obtenerOrdenados() {
        List<T> copia = new ArrayList<>(elementos);
        Collections.sort(copia);
        return copia;
    }

    @Override
    public T obtenerMinimo() {
        if (elementos.isEmpty()) throw new NoSuchElementException();
        return Collections.min(elementos);
    }

    @Override
    public T obtenerMaximo() {
        if (elementos.isEmpty()) throw new NoSuchElementException();
        return Collections.max(elementos);
    }

    // Deja para las subclases solo lo que cambia
    @Override
    public abstract void agregar(T elemento);

    @Override
    public abstract void eliminar(T elemento);
}

// 3. Las CLASES CONCRETAS solo implementan lo diferente
public class ColeccionSinDuplicados<T extends Comparable<T>>
    extends ColeccionOrdenadaBase<T> {

    @Override
    public void agregar(T elemento) {
        if (!contiene(elemento)) {  // sin duplicados
            elementos.add(elemento);
        }
    }

    @Override
    public void eliminar(T elemento) {
        elementos.remove(elemento);
    }
}

public class ColeccionConDuplicados<T extends Comparable<T>>
    extends ColeccionOrdenadaBase<T> {

    @Override
    public void agregar(T elemento) {
        elementos.add(elemento);  // permite duplicados
    }

    @Override
    public void eliminar(T elemento) {
        elementos.remove(elemento);  // elimina la primera ocurrencia
    }
}

// Resultado: código mínimo en las clases concretas
// La interfaz garantiza el contrato
// La clase abstracta evita repetición
// Las concretas solo definen lo que las diferencia

Interfaces del JDK que debes conocer

// Las interfaces más usadas en el ecosistema Java

// Comparable<T> — ordenamiento natural
// Implementar cuando los objetos tienen un orden natural (precio, fecha...)
class Producto implements Comparable<Producto> {
    public int compareTo(Producto otro) { ... }
}

// Comparator<T> — ordenamiento externo sin tocar la clase
// Útil cuando no controlas la clase o quieres múltiples criterios de orden
Comparator<Producto> porNombre = Comparator.comparing(Producto::getNombre);
Comparator<Producto> porPrecio = Comparator.comparingDouble(Producto::getPrecio);

// Iterable<T> — permite usar el for-each
// Implementar para que tu colección sea iterable con for (X x : miColeccion)
class MiColeccion<T> implements Iterable<T> {
    public Iterator<T> iterator() { ... }
}

// AutoCloseable — recursos que se cierran automáticamente
// Usar con try-with-resources
class MiRecurso implements AutoCloseable {
    public void close() { ... }  // se llama automáticamente al salir del try
}

// Runnable y Callable — para concurrencia
Runnable tarea = () -> System.out.println("Ejecutando en otro hilo");
Callable<Integer> tareaConResultado = () -> calcularAlgo();

// Serializable — para persistencia y transferencia
class MiObjeto implements Serializable {
    private static final long serialVersionUID = 1L;
    // campos...
}

Errores comunes

❌ Usar clase abstracta cuando no hay jerarquía real

// ❌ Clase abstracta para "agrupar" clases que no comparten estado ni lógica
abstract class Forma { abstract double area(); }
class Cuadrado extends Forma { ... }
class Triangulo extends Forma { ... }

// Si Cuadrado y Triangulo no comparten NADA (ni estado ni lógica),
// una interfaz es más apropiada:
// ✅
interface Forma { double area(); double perimetro(); }

❌ Ignorar los default methods de las interfaces

// ❌ Duplicar lógica en cada implementación
interface Notificador {
    void enviar(String mensaje);
    // sin default — cada implementación repite el mismo log
}

// ✅ Usar default para lógica común
interface Notificador {
    void enviar(String mensaje);

    default void enviarConLog(String mensaje) {
        System.out.println("[LOG] Enviando: " + mensaje);
        enviar(mensaje);
        System.out.println("[LOG] Enviado correctamente");
    }
}

❌ Confundir "no puedo instanciar" con "no tiene constructor"

// Las clases abstractas SÍ tienen constructor
// Las subclases lo llaman con super()
abstract class Vehiculo {
    private String matricula;

    public Vehiculo(String matricula) {  // ← constructor en clase abstracta
        this.matricula = matricula;
    }
}

class Coche extends Vehiculo {
    public Coche(String matricula) {
        super(matricula);  // ← llama al constructor del padre
    }
}

El árbol de decisión — cómo elegir en 4 preguntas

¿Las clases van a compartir ATRIBUTOS DE INSTANCIA (estado)?
  SÍ → Clase abstracta (las interfaces no tienen estado)
  NO → continúa →

¿Las clases pertenecen a la MISMA JERARQUÍA conceptual?
  SÍ → Clase abstracta (relación ES-UN: Perro ES-UN Animal)
  NO → continúa →

¿Las clases necesitan cumplir MÚLTIPLES CONTRATOS de comportamiento?
  SÍ → Interfaces (una clase puede implementar muchas)
  NO → continúa →

¿El comportamiento es INDEPENDIENTE de la jerarquía?
  SÍ → Interfaz (Nadable puede aplicar a Pato, Pez, Perro)
  NO → Clase abstracta

Resumen: lo que aprendiste hoy

  • ✅ La pregunta clave: ¿el objeto ES algo (clase abstracta) o PUEDE HACER algo (interfaz)?
  • ✅ Las clases abstractas pueden tener atributos, constructores y métodos concretos — las interfaces no
  • ✅ Una clase puede extender solo UNA clase abstracta pero implementar MÚLTIPLES interfaces
  • ✅ Desde Java 8, las interfaces pueden tener métodos default y static
  • ✅ Desde Java 9, las interfaces pueden tener métodos private para uso interno
  • ✅ Las interfaces funcionales tienen un solo método abstracto y se usan con lambdas
  • ✅ Usa clase abstracta cuando compartes estado o lógica en una jerarquía de clases relacionadas
  • ✅ Usa interfaz para definir contratos que clases no relacionadas pueden cumplir
  • ✅ El patrón Interface + AbstractClass + Concrete es el más potente — cada nivel aporta lo suyo
  • ✅ Las interfaces son la base del polimorfismo desacoplado y la inyección de dependencias

🧪 ¿Tienes los fundamentos de Java y POO bien sólidos?

Interfaces y clases abstractas son Java intermedio. Para dominarlos necesitas tener claros los conceptos de herencia, polimorfismo y encapsulamiento. Comprueba dónde estás:

👉 Test: Java Básico 👉 Test: POO Conceptos Base 

¿Cuál de los dos te resultaba más confuso antes de leer este artículo: las interfaces o las clases abstractas? ¿Ya has tenido que elegir entre los dos en algún proyecto real? Cuéntanos en los comentarios 👇 — respondemos todos. ☕🚀

¿Te ha gustado esta entrada?

Compártela con tus compañeros para que también sigan aprendiendo.

Comunidad y Comentarios

0 COMENTARIOS

No hay comentarios todavía. Sé el primero en compartir tu opinión.

Escribe tu opinión
Respondiendo a