Volver a Labs
Principiante

Programación Orientada a Objetos en Java

2–3 horas Actualizado Junio 2026
Java POO Backend Fundamentos

Introducción

La Programación Orientada a Objetos (POO) es un paradigma que organiza el software como una colección de objetos que combinan datos y comportamiento. En lugar de escribir una lista de instrucciones, modelas el mundo real en clases que luego se instancian como objetos.

Java es uno de los lenguajes más populares para aprender POO porque fuerza este paradigma desde el primer archivo: todo en Java vive dentro de una clase.

¿Qué vas a aprender en este lab?

  • Definir clases y crear objetos
  • Proteger el estado interno con encapsulamiento
  • Reutilizar código con herencia
  • Escribir código flexible con polimorfismo
  • Usar clases abstractas e interfaces
  • Identificar y corregir errores comunes

Requisitos previos: conocimiento básico de Java (variables, tipos de datos, condicionales y bucles). No necesitas experiencia previa con POO.


1. Clases y Objetos

Una clase es la plantilla. Un objeto es una instancia concreta de esa plantilla.

Piénsalo así: Persona es la clase (el molde), y julian es un objeto (una persona real hecha con ese molde).

Sintaxis de una clase en Java

public class Persona {
    // Atributos (estado)
    String nombre;
    int edad;

    // Método (comportamiento)
    public void saludar() {
        System.out.println("Hola, soy " + nombre + " y tengo " + edad + " años.");
    }
}

Crear e instanciar un objeto

public class Main {
    public static void main(String[] args) {
        // Crear un objeto de tipo Persona
        Persona julian = new Persona();
        julian.nombre = "Julian";
        julian.edad   = 25;

        julian.saludar(); // Hola, soy Julian y tengo 25 años.
    }
}

La palabra clave new reserva espacio en memoria y devuelve una referencia al nuevo objeto.

Ejercicio 1

Crea una clase Producto con los atributos nombre (String), precio (double) y stock (int). Añade un método mostrarInfo() que imprima los tres valores. Instancia dos productos distintos en main y llama al método en cada uno.


2. Encapsulamiento

El encapsulamiento consiste en ocultar el estado interno de un objeto y exponer solo lo necesario a través de métodos. Esto evita que cualquier parte del código modifique los datos de forma incorrecta.

Modificadores de acceso

Modificador Visible desde
private Solo la misma clase
public Cualquier clase
protected La clase y sus subclases
(sin modificador) El mismo paquete

Getters y Setters

public class Persona {
    private String nombre;
    private int edad;

    // Getter
    public String getNombre() {
        return nombre;
    }

    // Setter con validación
    public void setEdad(int edad) {
        if (edad < 0 || edad > 150) {
            System.out.println("Edad no válida.");
            return;
        }
        this.edad = edad;
    }

    public int getEdad() {
        return edad;
    }
}

La palabra clave this diferencia el atributo de la clase (this.edad) del parámetro del método (edad).

Persona p = new Persona();
p.setEdad(25);   // OK
p.setEdad(-5);   // Imprime "Edad no válida."
// p.edad = -5;  // Error de compilación: edad es private

Ejercicio 2

Modifica la clase Producto del ejercicio anterior: declara todos los atributos como private. Añade getters para los tres y un setter para precio que rechace valores negativos. Prueba que no puedes asignar precio directamente desde main.


3. Constructores

Un constructor es un método especial que se llama automáticamente al crear un objeto con new. No tiene tipo de retorno y lleva el mismo nombre que la clase.

Constructor por defecto

Si no declaras ningún constructor, Java crea uno vacío automáticamente. En cuanto declares uno propio, el automático desaparece.

Constructor parametrizado

public class CuentaBancaria {
    private String titular;
    private double saldo;

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

    public void depositar(double monto) {
        if (monto > 0) saldo += monto;
    }

    public void mostrarSaldo() {
        System.out.println(titular + " — Saldo: $" + saldo);
    }
}
CuentaBancaria cuenta = new CuentaBancaria("Julian", 1000.0);
cuenta.depositar(500.0);
cuenta.mostrarSaldo(); // Julian — Saldo: $1500.0

Sobrecarga de constructores

Puedes tener múltiples constructores con diferentes parámetros:

public class CuentaBancaria {
    private String titular;
    private double saldo;

    public CuentaBancaria(String titular) {
        this(titular, 0.0); // llama al otro constructor
    }

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

Ejercicio 3

Añade dos constructores a Producto: uno que reciba solo nombre (precio y stock quedan en 0) y otro que reciba los tres atributos. Crea instancias usando ambos constructores y verifica que los valores sean correctos.


4. Herencia

La herencia permite que una clase hija reutilice atributos y métodos de una clase padre, y los extienda o modifique según sus necesidades.

public class Animal {
    protected String nombre;

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

    public void hacerSonido() {
        System.out.println(nombre + " hace un sonido.");
    }
}
public class Perro extends Animal {
    public Perro(String nombre) {
        super(nombre); // llama al constructor del padre
    }

    @Override
    public void hacerSonido() {
        System.out.println(nombre + " ladra: ¡Guau!");
    }
}

public class Gato extends Animal {
    public Gato(String nombre) {
        super(nombre);
    }

    @Override
    public void hacerSonido() {
        System.out.println(nombre + " maúlla: ¡Miau!");
    }
}
Animal miPerro = new Perro("Rex");
Animal miGato  = new Gato("Luna");

miPerro.hacerSonido(); // Rex ladra: ¡Guau!
miGato.hacerSonido();  // Luna maúlla: ¡Miau!

Puntos clave:

  • extends declara la relación de herencia
  • super() llama al constructor del padre (debe ser la primera línea)
  • @Override indica que se está redefiniendo un método del padre

Ejercicio 4

Crea una clase padre Vehiculo con atributos marca y velocidadMaxima. Crea dos subclases: Coche (añade atributo numeroPuertas) y Moto (añade atributo tieneSidecar). Cada una debe sobreescribir un método describir().


5. Polimorfismo

El polimorfismo permite tratar objetos de distintas clases hijas como si fueran del tipo del padre, y que cada uno responda a los métodos a su manera.

public class Main {
    public static void main(String[] args) {
        Animal[] animales = {
            new Perro("Rex"),
            new Gato("Luna"),
            new Perro("Max")
        };

        for (Animal a : animales) {
            a.hacerSonido(); // cada objeto responde según su clase real
        }
    }
}

Salida:

Rex ladra: ¡Guau!
Luna maúlla: ¡Miau!
Max ladra: ¡Guau!

Java decide qué versión de hacerSonido() ejecutar en tiempo de ejecución según el tipo real del objeto, no según el tipo de la referencia. Esto se llama despacho dinámico.

Ejercicio 5

Crea una lista de tipo List<Vehiculo> con instancias de Coche y Moto. Itera la lista y llama a describir() en cada elemento. Agrega una tercera subclase Camion sin modificar el código del bucle.


6. Clases Abstractas e Interfaces

Clases abstractas

Una clase abstracta no puede instanciarse directamente. Define métodos abstractos (sin cuerpo) que las subclases deben implementar obligatoriamente.

public abstract class Figura {
    protected String color;

    public Figura(String color) {
        this.color = color;
    }

    // Método abstracto: sin cuerpo, obliga a las subclases a implementarlo
    public abstract double calcularArea();

    // Método concreto: se hereda tal cual
    public void mostrarColor() {
        System.out.println("Color: " + color);
    }
}
public class Circulo extends Figura {
    private double radio;

    public Circulo(String color, double radio) {
        super(color);
        this.radio = radio;
    }

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

Interfaces

Una interfaz define un contrato de comportamiento. Todas las clases que la implementen deben proveer los métodos declarados.

public interface Pagable {
    double calcularPago();
    String obtenerTipoPago();
}
public class Empleado implements Pagable {
    private String nombre;
    private double salarioMensual;

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

    @Override
    public double calcularPago() {
        return salarioMensual;
    }

    @Override
    public String obtenerTipoPago() {
        return "Salario fijo mensual";
    }
}

public class Freelancer implements Pagable {
    private String nombre;
    private double tarifaHora;
    private int    horasTrabajadas;

    public Freelancer(String nombre, double tarifa, int horas) {
        this.nombre          = nombre;
        this.tarifaHora      = tarifa;
        this.horasTrabajadas = horas;
    }

    @Override
    public double calcularPago() {
        return tarifaHora * horasTrabajadas;
    }

    @Override
    public String obtenerTipoPago() {
        return "Por horas";
    }
}

¿Cuándo usar cada uno?

Clase abstracta Interfaz
Herencia Solo una Múltiples
Estado (atributos) No (solo constantes)
Métodos concretos Sí (desde Java 8, default)
Uso ideal Compartir código base Definir contrato de comportamiento

Ejercicio 6

Crea una interfaz Exportable con un método exportarCSV(): String. Implementa esta interfaz en las clases Empleado y Producto. Luego usa una lista de tipo List<Exportable> para exportar ambos sin conocer su tipo concreto.


7. Errores Comunes

Error 1 — NullPointerException al olvidar new

// Incorrecto
Persona p;
p.saludar(); // NullPointerException: p no apunta a ningún objeto

// Correcto
Persona p = new Persona();
p.saludar();

Una variable de tipo objeto en Java contiene una referencia, no el objeto en sí. Sin new, la referencia es null.


Error 2 — Comparar objetos con == en lugar de .equals()

String a = new String("hola");
String b = new String("hola");

System.out.println(a == b);       // false — compara referencias (direcciones en memoria)
System.out.println(a.equals(b));  // true  — compara el contenido

Usa == solo con primitivos (int, boolean, etc.). Para objetos, siempre .equals().


Error 3 — Olvidar @Override y crear un método nuevo sin querer

public class Gato extends Animal {
    // Incorrecto: el parámetro es diferente al del padre
    public void hacerSonido(String tono) {
        System.out.println("Miau con tono " + tono);
    }
    // Esto NO sobreescribe el método del padre: crea uno nuevo.
}
public class Gato extends Animal {
    // Con @Override el compilador verifica que realmente sobreescribes
    @Override
    public void hacerSonido() {
        System.out.println(nombre + " maúlla.");
    }
}

@Override no es obligatorio pero siempre debes usarlo: si el nombre o la firma no coinciden con ningún método del padre, el compilador lanza un error inmediatamente.


Error 4 — Llamar a super() fuera del constructor o no como primera línea

public class Perro extends Animal {
    public Perro(String nombre) {
        System.out.println("Creando perro..."); // Error de compilación
        super(nombre); // super() debe ser la PRIMERA línea del constructor
    }
}

Resumen

Pilar ¿Qué hace? Palabra clave en Java
Abstracción Modela entidades del mundo real como clases class
Encapsulamiento Protege el estado interno del objeto private, getters/setters
Herencia Reutiliza y extiende el comportamiento de otra clase extends, super
Polimorfismo Trata distintos objetos de forma uniforme @Override, despacho dinámico

Próximos pasos

  • Colecciones: ArrayList, HashMap y la API de Streams para trabajar con grupos de objetos
  • Excepciones: manejo de errores con try-catch, excepciones personalizadas
  • Genéricos: clases y métodos que funcionan con cualquier tipo (List<T>)
  • Patrones de diseño: Singleton, Factory, Observer — soluciones probadas a problemas comunes