Programación Orientada a Objetos (POO): guía completa en Java

📅 Actualizado en marzo 2026 ✍️ Ángel López ⏱️ 18 min de lectura ✓ Nivel principiante ★ ★ ★ ★ ★ (5/5)

🌐 ¿Qué es la Programación Orientada a Objetos?

La Programación Orientada a Objetos (POO, o OOP en inglés — Object-Oriented Programming) es un paradigma de programación que organiza el software en torno a objetos en lugar de funciones y lógica secuencial. Cada objeto combina datos (llamados atributos o campos) y comportamientos (llamados métodos) en una única entidad cohesiva.

La idea central es sencilla pero poderosa: en lugar de pensar en un programa como una secuencia de instrucciones que manipulan datos sueltos, pensamos en él como un conjunto de objetos que colaboran entre sí enviándose mensajes. Este enfoque refleja la forma en que percibimos el mundo real, donde las entidades tienen propiedades y realizan acciones.

📘 Definición formal: La POO es un paradigma de programación basado en el concepto de objetos, que contienen datos en forma de campos (atributos) y código en forma de procedimientos (métodos). Los objetos interactúan entre sí mediante el envío de mensajes (invocaciones de métodos).

Java es uno de los lenguajes de programación orientados a objetos más utilizados en el mundo. Desde su creación en 1995, fue diseñado con la POO como filosofía central: en Java, ✅ todo el código se organiza en clases, y la unidad básica de trabajo es el objeto.

Concepto Descripción Analogía mundo real
Clase Plantilla que define atributos y métodos Plano de un edificio
Objeto Instancia concreta de una clase El edificio construido
Atributo Variable que almacena el estado del objeto Color, número de pisos
Método Función que define el comportamiento del objeto Abrir la puerta, encender luces
Mensaje Invocación de un método sobre un objeto Pedirle al portero que abra

📜 Breve historia de la POO

La evolución de los paradigmas de programación ha seguido una tendencia constante: descomponer los problemas en unidades cada vez más manejables. Comprender esta evolución ayuda a entender por qué la POO se convirtió en el paradigma dominante.

▶️ Programación lineal (años 50-60)

Los primeros programas eran secuencias de instrucciones ejecutadas una tras otra, sin estructura. Los lenguajes como FORTRAN y COBOL producían código difícil de mantener cuando los programas crecían — lo que se conocía como código espagueti por la maraña de saltos (GOTO) que hacían el flujo prácticamente imposible de seguir.

🔹 Programación estructurada (años 70)

La programación estructurada, popularizada por lenguajes como Pascal y C, introdujo la descomposición en funciones y procedimientos. El teorema de Böhm-Jacopini demostró que cualquier programa puede escribirse usando solo tres estructuras: secuencia, selección e iteración. Esto eliminó el GOTO y produjo código más legible, pero los datos y las funciones seguían siendo entidades separadas.

🔸 Nacimiento de la POO (años 60-80)

El concepto de objeto apareció por primera vez en Simula 67 (1967), creado por Ole-Johan Dahl y Kristen Nygaard en Noruega para simulaciones. En los años 70, Alan Kay desarrolló Smalltalk en Xerox PARC, el primer lenguaje puramente orientado a objetos, acuñando el término «programación orientada a objetos». En 1985, Bjarne Stroustrup creó C++ extendiendo C con clases y herencia, llevando la POO al mundo de la programación de sistemas.

➡️ Consolidación: Java y más allá (1995-hoy)

En 1995, James Gosling y su equipo en Sun Microsystems lanzaron Java, que combinaba la POO con la portabilidad de la JVM (Java Virtual Machine). Java simplificó C++ eliminando la gestión manual de memoria y la herencia múltiple de clases, ofreciendo un modelo más seguro. Hoy, la POO es el paradigma dominante: lenguajes como Java, C#, Python, JavaScript, Kotlin, Swift y TypeScript utilizan sus principios.

💡 Dato: Alan Kay, creador de Smalltalk, definió la POO así: «Todo es un objeto. Los objetos se comunican enviando y recibiendo mensajes. Los objetos tienen su propia memoria».

🏛️ Los cuatro pilares de la POO

La Programación Orientada a Objetos se sustenta sobre cuatro principios fundamentales, conocidos como los «pilares» de la POO. Estos principios no son independientes entre sí: trabajan de forma coordinada para producir software modular, reutilizable y mantenible.

Pilar ¿Qué hace? Beneficio principal
🛡️ Encapsulamiento Oculta los detalles internos del objeto Protección y control del estado
🧬 Herencia Permite que una clase extienda a otra Reutilización de código
🎭 Polimorfismo Un mismo método se comporta de forma diferente Flexibilidad y extensibilidad
🔮 Abstracción Modela solo las características esenciales Simplicidad y enfoque

En las siguientes secciones desarrollaremos cada pilar en detalle con ejemplos en Java. Antes, necesitamos comprender los conceptos de clase y objeto, que son la base sobre la que se construyen los cuatro pilares.

📦 Clases y objetos en Java

▶️ ¿Qué es una clase?

Una clase es una plantilla o molde que define la estructura y el comportamiento que tendrán los objetos creados a partir de ella. La clase especifica qué atributos (datos) tendrá cada objeto y qué métodos (acciones) podrá realizar. En Java, toda la lógica del programa se organiza dentro de clases.

Java
public class Automovil {
    // Atributos (estado del objeto)
    String marca;
    String modelo;
    String color;
    int velocidadActual;

    // Constructor
    public Automovil(String marca, String modelo, String color) {
        this.marca = marca;
        this.modelo = modelo;
        this.color = color;
        this.velocidadActual = 0;
    }

    // Métodos (comportamiento del objeto)
    public void acelerar(int incremento) {
        this.velocidadActual += incremento;
        System.out.println(marca + " " + modelo + " acelera a " + velocidadActual + " km/h");
    }

    public void frenar() {
        this.velocidadActual = 0;
        System.out.println(marca + " " + modelo + " se detiene");
    }
}

🔹 ¿Qué es un objeto?

Un objeto es una instancia concreta de una clase. Mientras la clase es el molde, el objeto es la pieza fabricada a partir de ese molde, con valores específicos en sus atributos. A partir de una misma clase se pueden crear múltiples objetos, cada uno con su propio estado independiente.

Java
public class Main {
    public static void main(String[] args) {
        // Crear dos objetos a partir de la misma clase
        Automovil coche1 = new Automovil("Ford", "Focus", "Azul");
        Automovil coche2 = new Automovil("Toyota", "Corolla", "Rojo");

        // Cada objeto tiene su propio estado
        coche1.acelerar(80);   // Ford Focus acelera a 80 km/h
        coche2.acelerar(60);   // Toyota Corolla acelera a 60 km/h

        coche1.frenar();       // Ford Focus se detiene
        // coche2 sigue a 60 km/h — estados independientes
    }
}

📘 Terminología: «Crear un objeto», «instanciar una clase» y «construir una instancia» son expresiones equivalentes. El operador new en Java invoca al constructor de la clase y reserva memoria para el nuevo objeto en el heap.

🔸 Componentes de un objeto

Todo objeto tiene tres componentes fundamentales:

Estado: los valores actuales de sus atributos. Por ejemplo, un automóvil con marca="Ford", color="Azul" y velocidadActual=80.

Comportamiento: las acciones que puede realizar, definidas por sus métodos. Por ejemplo, acelerar(), frenar().

Identidad: cada objeto es único, incluso si tiene el mismo estado que otro. Java distingue dos objetos diferentes aunque sus atributos sean idénticos porque ocupan posiciones distintas en memoria.

🛡️ Encapsulamiento

El encapsulamiento es el principio que consiste en agrupar datos y métodos dentro de una clase y restringir el acceso directo a los datos internos del objeto. En lugar de permitir que cualquier parte del programa modifique los atributos directamente, el encapsulamiento obliga a usar métodos controlados (llamados getters y setters) que pueden incluir validaciones.

En Java, el encapsulamiento se implementa mediante los modificadores de acceso:

Modificador Clase Paquete Subclase Mundo
private
default (sin modificador)
protected
public
Java — Encapsulamiento con validación
public class CuentaBancaria {
    private String titular;
    private double saldo;

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

    // Getter: acceso controlado al saldo
    public double getSaldo() {
        return saldo;
    }

    // Setter con validación: no permite depósitos negativos
    public void depositar(double cantidad) {
        if (cantidad <= 0) {
            System.out.println("⚠️ La cantidad debe ser positiva");
            return;
        }
        saldo += cantidad;
    }

    // Método con lógica de negocio
    public boolean retirar(double cantidad) {
        if (cantidad <= 0 || cantidad > saldo) {
            System.out.println("⚠️ Retiro inválido");
            return false;
        }
        saldo -= cantidad;
        return true;
    }
}

💡 Buena práctica: Declara siempre los atributos como private y proporciona métodos public para acceder y modificar el estado. Esto permite añadir validaciones, registros de auditoría o notificaciones sin que el código cliente se vea afectado.

El encapsulamiento también incluye el concepto de ocultamiento de información (information hiding): los detalles de implementación interna de una clase quedan ocultos al exterior. El usuario de la clase solo necesita conocer la interfaz pública (los métodos disponibles), no cómo funcionan internamente. Esto convierte cada clase en una ✅ «caja negra» que expone solo lo necesario.

🧬 Herencia

La herencia es el mecanismo por el cual una clase (subclase o clase hija) puede heredar atributos y métodos de otra clase (superclase o clase padre). La subclase reutiliza el código de la superclase y puede añadir nuevos atributos o métodos, o modificar (sobrescribir) los heredados.

En Java, la herencia se declara con la palabra reservada extends. Una clase solo puede heredar de una única superclase (herencia simple), aunque puede implementar múltiples interfaces.

Java — Herencia: superclase y subclase
// Superclase
public class Animal {
    protected String nombre;
    protected int edad;

    public Animal(String nombre, int edad) {
        this.nombre = nombre;
        this.edad = edad;
    }

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

    public String toString() {
        return nombre + " (" + edad + " años)";
    }
}

// Subclase — hereda de Animal
public class Perro extends Animal {
    private String raza;

    public Perro(String nombre, int edad, String raza) {
        super(nombre, edad);  // Llama al constructor de Animal
        this.raza = raza;
    }

    // Método propio de Perro
    public void ladrar() {
        System.out.println(nombre + " dice: ¡Guau!");
    }

    // Sobrescritura del método heredado
    @Override
    public String toString() {
        return nombre + " (" + raza + ", " + edad + " años)";
    }
}

🔹 Jerarquía de clases

La herencia permite crear jerarquías de clases donde las clases más generales están arriba y las más especializadas abajo. En Java, todas las clases heredan implícitamente de java.lang.Object, que es la raíz de toda la jerarquía.

⚠️ Precaución: No abuses de la herencia. Si la relación entre dos clases no es claramente «es un/a» (un Perro es un Animal), considera usar composición en lugar de herencia. La regla es: hereda comportamiento, no solo para reutilizar código.

🎭 Polimorfismo

El polimorfismo (del griego poly = muchos, morphé = forma) es la capacidad de un objeto de adoptar múltiples formas. En la práctica, significa que un mismo método puede comportarse de manera diferente según el objeto que lo invoque.

Java implementa dos tipos de polimorfismo:

▶️ Polimorfismo en tiempo de compilación (sobrecarga)

Varios métodos en la misma clase comparten el mismo nombre pero difieren en sus parámetros (tipo, número u orden). El compilador determina cuál invocar basándose en los argumentos.

Java — Sobrecarga de métodos
public class Calculadora {
    public int sumar(int a, int b) {
        return a + b;
    }

    public double sumar(double a, double b) {
        return a + b;
    }

    public int sumar(int a, int b, int c) {
        return a + b + c;
    }
}

🔸 Polimorfismo en tiempo de ejecución (sobrescritura)

Una subclase proporciona su propia implementación de un método heredado de la superclase. La JVM determina en tiempo de ejecución qué versión del método invocar, basándose en el tipo real del objeto (no en el tipo de la referencia).

Java — Polimorfismo en acción
public class Figura {
    public double calcularArea() {
        return 0;
    }
}

public class Circulo extends Figura {
    private double radio;

    public Circulo(double radio) { this.radio = radio; }

    @Override
    public double calcularArea() {
        return Math.PI * radio * radio;
    }
}

public class Rectangulo extends Figura {
    private double ancho, alto;

    public Rectangulo(double ancho, double alto) {
        this.ancho = ancho;
        this.alto = alto;
    }

    @Override
    public double calcularArea() {
        return ancho * alto;
    }
}

// Uso polimórfico
public class Main {
    public static void main(String[] args) {
        Figura[] figuras = {
            new Circulo(5),
            new Rectangulo(4, 6),
            new Circulo(3)
        };

        for (Figura f : figuras) {
            // La JVM llama al método correcto según el tipo real
            System.out.println("Área: " + f.calcularArea());
        }
    }
}

💡 Clave: El polimorfismo permite escribir código genérico que trabaja con la superclase, pero ejecuta el comportamiento específico de cada subclase. Esto es la base de los principios SOLID y del diseño de frameworks profesionales.

🔮 Abstracción

La abstracción es el proceso de identificar las características esenciales de un objeto e ignorar los detalles irrelevantes para el contexto del problema. En programación, abstraer significa crear modelos simplificados de entidades complejas del mundo real, capturando solo lo que importa para el sistema que estamos construyendo.

Java implementa la abstracción mediante dos mecanismos:

▶️ Clases abstractas

Una clase abstracta es una clase que ❌ no puede instanciarse directamente. Puede contener métodos abstractos (sin implementación) que las subclases están obligadas a implementar, y también métodos concretos con implementación completa.

Java — Clase abstracta
public abstract class Vehiculo {
    protected String matricula;

    public Vehiculo(String matricula) {
        this.matricula = matricula;
    }

    // Método abstracto: cada subclase define cómo arranca
    public abstract void arrancar();

    // Método concreto: compartido por todas las subclases
    public void mostrarMatricula() {
        System.out.println("Matrícula: " + matricula);
    }
}

🔹 Interfaces

Una interfaz define un contrato: un conjunto de métodos que la clase que la implemente debe proporcionar. A diferencia de las clases, una clase puede implementar múltiples interfaces, lo que permite una forma de «herencia múltiple» de comportamiento.

Java — Interface
public interface Conducible {
    void acelerar(int velocidad);
    void frenar();
    int getVelocidad();
}

public interface ElectricamenteRecargable {
    void recargar(int porcentaje);
    int getNivelBateria();
}

// Una clase puede implementar múltiples interfaces
public class CocheElectrico extends Vehiculo
        implements Conducible, ElectricamenteRecargable {

    private int velocidad = 0;
    private int bateria = 100;

    public CocheElectrico(String matricula) { super(matricula); }

    @Override
    public void arrancar() { System.out.println("Motor eléctrico activado"); }

    @Override
    public void acelerar(int v) { this.velocidad += v; }

    @Override
    public void frenar() { this.velocidad = 0; }

    @Override
    public int getVelocidad() { return velocidad; }

    @Override
    public void recargar(int porcentaje) { bateria = Math.min(100, bateria + porcentaje); }

    @Override
    public int getNivelBateria() { return bateria; }
}
Característica Clase abstracta Interface
¿Se puede instanciar? ❌ No ❌ No
¿Puede tener atributos? ✅ Sí Solo constantes (static final)
¿Puede tener métodos concretos? ✅ Sí ✅ Sí (desde Java 8, con default)
¿Herencia/implementación múltiple? ❌ Solo una superclase ✅ Múltiples interfaces
¿Constructores? ✅ Sí ❌ No

🔗 Relaciones entre objetos

Los objetos no existen de forma aislada: se relacionan entre sí de distintas formas. Comprender estas relaciones es fundamental para diseñar sistemas orientados a objetos bien estructurados.

Relación Descripción Ejemplo Palabra clave
Herencia (es un/a) Una clase extiende a otra Un Perro es un Animal extends
Implementación Una clase cumple un contrato CocheElectrico implementa Conducible implements
Composición (tiene un/a, fuerte) Un objeto contiene a otro y controla su ciclo de vida Un Motor pertenece a un Coche Atributo creado internamente
Agregación (tiene un/a, débil) Un objeto referencia a otro que existe independientemente Un Equipo tiene Jugadores Atributo recibido por parámetro
Asociación Relación general entre objetos Un Profesor enseña a Alumnos Referencia entre clases
Dependencia Un objeto usa temporalmente a otro Un Servicio usa un Logger Parámetro de método

💡 Principio de diseño: «Favorece la composición sobre la herencia» es uno de los principios más importantes del libro Design Patterns (Gang of Four). La composición ofrece mayor flexibilidad porque las relaciones se pueden cambiar en tiempo de ejecución.

⚡ Ventajas de la POO frente a otros paradigmas

La POO no es el único paradigma de programación, pero ha demostrado ser especialmente eficaz para el desarrollo de sistemas complejos. Estas son sus principales ventajas:

🎯 Modelado natural: la POO permite representar entidades del mundo real de forma directa. Un sistema de gestión hospitalaria puede tener clases como Paciente, Médico, Consulta y Receta, reflejando la estructura del dominio.

🎯 Reutilización de código: la herencia y la composición permiten reutilizar código existente sin duplicarlo. Una clase base bien diseñada puede ser extendida por decenas de subclases especializadas.

🎯 Mantenibilidad: el encapsulamiento permite modificar la implementación interna de una clase sin afectar al código que la utiliza, siempre que la interfaz pública no cambie.

🎯 Escalabilidad: los sistemas orientados a objetos se escalan añadiendo nuevas clases, no modificando las existentes (principio abierto/cerrado — OCP).

🎯 Trabajo en equipo: cada equipo puede trabajar en clases diferentes de forma independiente, integrándose a través de interfaces bien definidas.

🎯 Testabilidad: las clases bien encapsuladas se pueden probar de forma unitaria, verificando su comportamiento de forma aislada con herramientas como JUnit.

📚 Ejemplo integrador: sistema de biblioteca

Veamos cómo los cuatro pilares de la POO trabajan juntos en un sistema real. Diseñaremos un sistema simplificado de gestión de una biblioteca que demuestra encapsulamiento, herencia, polimorfismo y abstracción.

Java — Sistema de biblioteca (abstracción + herencia)
// ABSTRACCIÓN: clase abstracta para todo material de biblioteca
public abstract class MaterialBiblioteca {
    // ENCAPSULAMIENTO: atributos privados
    private String titulo;
    private String codigo;
    private boolean prestado;

    public MaterialBiblioteca(String titulo, String codigo) {
        this.titulo = titulo;
        this.codigo = codigo;
        this.prestado = false;
    }

    // Método abstracto: cada tipo de material define su plazo
    public abstract int getDiasPrestamo();

    // Métodos concretos compartidos
    public boolean prestar() {
        if (prestado) {
            System.out.println("'" + titulo + "' ya está prestado");
            return false;
        }
        prestado = true;
        System.out.println("'" + titulo + "' prestado por " + getDiasPrestamo() + " días");
        return true;
    }

    public void devolver() {
        prestado = false;
        System.out.println("'" + titulo + "' devuelto correctamente");
    }

    // Getters
    public String getTitulo() { return titulo; }
    public String getCodigo() { return codigo; }
    public boolean isPrestado() { return prestado; }
}

// HERENCIA: clases especializadas
public class Libro extends MaterialBiblioteca {
    private String autor;
    private int paginas;

    public Libro(String titulo, String codigo, String autor, int paginas) {
        super(titulo, codigo);
        this.autor = autor;
        this.paginas = paginas;
    }

    @Override
    public int getDiasPrestamo() { return 14; }  // 2 semanas

    public String getAutor() { return autor; }
}

public class Revista extends MaterialBiblioteca {
    private int numero;
    private String mes;

    public Revista(String titulo, String codigo, int numero, String mes) {
        super(titulo, codigo);
        this.numero = numero;
        this.mes = mes;
    }

    @Override
    public int getDiasPrestamo() { return 7; }  // 1 semana
}

public class DVD extends MaterialBiblioteca {
    private int duracionMinutos;

    public DVD(String titulo, String codigo, int duracionMinutos) {
        super(titulo, codigo);
        this.duracionMinutos = duracionMinutos;
    }

    @Override
    public int getDiasPrestamo() { return 3; }  // 3 días
}
Java — Sistema de biblioteca (polimorfismo en acción)
import java.util.ArrayList;
import java.util.List;

public class Biblioteca {
    private List<MaterialBiblioteca> catalogo = new ArrayList<>();

    public void agregarMaterial(MaterialBiblioteca material) {
        catalogo.add(material);
    }

    // POLIMORFISMO: el método trabaja con la clase base
    // pero ejecuta el comportamiento específico de cada subclase
    public void prestarMaterial(String codigo) {
        for (MaterialBiblioteca material : catalogo) {
            if (material.getCodigo().equals(codigo)) {
                material.prestar();  // Cada tipo tiene su plazo
                return;
            }
        }
        System.out.println("Material no encontrado: " + codigo);
    }

    public void mostrarDisponibles() {
        System.out.println("=== Materiales disponibles ===");
        for (MaterialBiblioteca m : catalogo) {
            if (!m.isPrestado()) {
                System.out.println("[" + m.getCodigo() + "] " + m.getTitulo()
                    + " (préstamo: " + m.getDiasPrestamo() + " días)");
            }
        }
    }

    // Uso del sistema
    public static void main(String[] args) {
        Biblioteca bib = new Biblioteca();

        bib.agregarMaterial(new Libro("Clean Code", "LIB-001", "Robert C. Martin", 464));
        bib.agregarMaterial(new Revista("National Geographic", "REV-042", 256, "Febrero"));
        bib.agregarMaterial(new DVD("Documental Java", "DVD-007", 90));

        bib.mostrarDisponibles();

        bib.prestarMaterial("LIB-001");  // 'Clean Code' prestado por 14 días
        bib.prestarMaterial("DVD-007");  // 'Documental Java' prestado por 3 días

        bib.mostrarDisponibles();        // Solo queda la revista
    }
}

🐛 Errores comunes en POO

Error Problema Solución
Atributos public Rompe el encapsulamiento, cualquiera modifica el estado sin control Usar private + getters/setters
Herencia excesiva Jerarquías profundas difíciles de mantener Preferir composición; máximo 3-4 niveles
Clases «Dios» Una clase que hace demasiadas cosas Principio de responsabilidad única (SRP)
Olvidar @Override Error silencioso si el método no coincide exactamente Usar siempre la anotación @Override
Comparar objetos con == Compara referencias, no contenido Usar .equals() y sobrescribir si es necesario
No inicializar atributos Valores por defecto inesperados o NullPointerException Inicializar en el constructor

✏️ Ejercicios prácticos

📘 Ejercicio 1 — Clase con encapsulamiento: Crea una clase Producto con atributos privados nombre (String), precio (double) y stock (int). Implementa un método vender(int cantidad) que reduzca el stock solo si hay suficiente, y un método aplicarDescuento(double porcentaje) que valide que el porcentaje esté entre 0 y 50.

Ver solución del Ejercicio 1
Java
public class Producto {
    private String nombre;
    private double precio;
    private int stock;

    public Producto(String nombre, double precio, int stock) {
        this.nombre = nombre;
        this.precio = precio;
        this.stock = stock;
    }

    public boolean vender(int cantidad) {
        if (cantidad <= 0 || cantidad > stock) {
            System.out.println("Venta inválida: stock insuficiente");
            return false;
        }
        stock -= cantidad;
        System.out.println("Vendidas " + cantidad + " unidades de " + nombre);
        return true;
    }

    public void aplicarDescuento(double porcentaje) {
        if (porcentaje < 0 || porcentaje > 50) {
            System.out.println("Descuento inválido (0-50%)");
            return;
        }
        precio -= precio * (porcentaje / 100);
        System.out.println(nombre + " nuevo precio: " + precio + " €");
    }

    // Getters
    public String getNombre() { return nombre; }
    public double getPrecio() { return precio; }
    public int getStock() { return stock; }
}

📘 Ejercicio 2 — Herencia y polimorfismo: Crea una jerarquía con una clase abstracta Empleado (nombre, salarioBase) y dos subclases: EmpleadoFijo (con bonus anual) y EmpleadoTemporal (con multiplicador por horas extra). Sobrescribe un método calcularSalario() en cada subclase. Crea un array de Empleado[] y calcula la nómina total usando polimorfismo.

Ver solución del Ejercicio 2
Java
public abstract class Empleado {
    protected String nombre;
    protected double salarioBase;

    public Empleado(String nombre, double salarioBase) {
        this.nombre = nombre;
        this.salarioBase = salarioBase;
    }

    public abstract double calcularSalario();

    @Override
    public String toString() {
        return nombre + " → " + calcularSalario() + " €";
    }
}

public class EmpleadoFijo extends Empleado {
    private double bonusAnual;

    public EmpleadoFijo(String nombre, double salarioBase, double bonusAnual) {
        super(nombre, salarioBase);
        this.bonusAnual = bonusAnual;
    }

    @Override
    public double calcularSalario() {
        return salarioBase + (bonusAnual / 12);
    }
}

public class EmpleadoTemporal extends Empleado {
    private int horasExtra;
    private double precioHora;

    public EmpleadoTemporal(String nombre, double salarioBase, int horasExtra, double precioHora) {
        super(nombre, salarioBase);
        this.horasExtra = horasExtra;
        this.precioHora = precioHora;
    }

    @Override
    public double calcularSalario() {
        return salarioBase + (horasExtra * precioHora);
    }
}

// Main: polimorfismo en acción
public class Nomina {
    public static void main(String[] args) {
        Empleado[] plantilla = {
            new EmpleadoFijo("Ana García", 2500, 3000),
            new EmpleadoTemporal("Luis Martín", 1800, 20, 15),
            new EmpleadoFijo("María López", 3000, 6000)
        };

        double totalNomina = 0;
        for (Empleado emp : plantilla) {
            System.out.println(emp);
            totalNomina += emp.calcularSalario();
        }
        System.out.println("Nómina total: " + totalNomina + " €");
    }
}

📘 Ejercicio 3 — Interfaces y composición: Crea una interfaz Imprimible con un método imprimir(). Haz que las clases Factura y Informe la implementen. Luego crea una clase Impresora que reciba cualquier objeto Imprimible y lo imprima. Demuestra que la impresora puede trabajar con cualquier tipo imprimible sin conocer su clase concreta.

Ver solución del Ejercicio 3
Java
public interface Imprimible {
    String imprimir();
}

public class Factura implements Imprimible {
    private String cliente;
    private double total;

    public Factura(String cliente, double total) {
        this.cliente = cliente;
        this.total = total;
    }

    @Override
    public String imprimir() {
        return "FACTURA → Cliente: " + cliente + " | Total: " + total + " €";
    }
}

public class Informe implements Imprimible {
    private String titulo;
    private String contenido;

    public Informe(String titulo, String contenido) {
        this.titulo = titulo;
        this.contenido = contenido;
    }

    @Override
    public String imprimir() {
        return "INFORME → " + titulo + ": " + contenido;
    }
}

public class Impresora {
    // Acepta cualquier Imprimible — polimorfismo por interfaz
    public void procesarDocumento(Imprimible doc) {
        System.out.println("Imprimiendo: " + doc.imprimir());
    }
}

public class Main {
    public static void main(String[] args) {
        Impresora impresora = new Impresora();
        impresora.procesarDocumento(new Factura("Ciberaula S.L.", 1250.00));
        impresora.procesarDocumento(new Informe("Ventas Q1", "Crecimiento del 15%"));
    }
}

❓ Preguntas frecuentes sobre Programación Orientada a Objetos (POO): guía completa en Java

Las dudas más comunes respondidas de forma clara y directa.

Los cuatro pilares de la POO son: encapsulamiento (ocultar los detalles internos de un objeto), herencia (permitir que una clase herede atributos y métodos de otra), polimorfismo (capacidad de un objeto de adoptar múltiples formas según el contexto) y abstracción (modelar entidades del mundo real enfocándose solo en las características relevantes). Estos principios trabajan juntos para crear software modular, reutilizable y fácil de mantener.
Una clase es un molde o plantilla que define la estructura (atributos) y el comportamiento (métodos) que tendrán los objetos creados a partir de ella. Un objeto es una instancia concreta de una clase, con valores específicos en sus atributos. Por ejemplo, la clase Automovil define que todo automóvil tiene marca y color, mientras que un objeto concreto podría ser un Ford Focus azul. Se pueden crear múltiples objetos a partir de la misma clase.
Java se considera orientado a objetos porque su diseño se basa en los principios de la POO: todo el código se organiza en clases, soporta herencia, polimorfismo, encapsulamiento y abstracción. Sin embargo, no es puramente orientado a objetos porque mantiene tipos primitivos (int, double, boolean, etc.) que no son objetos. Lenguajes como Smalltalk o Ruby, donde absolutamente todo es un objeto, se consideran puramente orientados a objetos.
La programación estructurada organiza el código en funciones y procedimientos que operan sobre datos separados, siguiendo un flujo secuencial. La POO agrupa datos y funciones en unidades llamadas objetos, que encapsulan estado y comportamiento. Esto permite modelar problemas del mundo real de forma más natural, facilita la reutilización de código mediante herencia, y hace que los sistemas grandes sean más mantenibles gracias a la modularidad y el bajo acoplamiento entre componentes.
El paso de mensajes es el mecanismo por el cual los objetos se comunican entre sí. En Java, enviar un mensaje a un objeto equivale a invocar uno de sus métodos. Por ejemplo, si tenemos un objeto coche de tipo Automovil, la instrucción coche.acelerar(50) envía el mensaje "acelerar con valor 50" al objeto. El objeto receptor ejecuta el método correspondiente y puede devolver un resultado o modificar su estado interno.
Valora este artículo

💬 Foro de discusión

¿Tienes dudas sobre Programación Orientada a Objetos (POO): guía completa en Java? Comparte tu pregunta con la comunidad.

¿Tienes cuenta? o comenta como invitado ↓

Todavía no hay mensajes. ¡Sé el primero en participar!