UD2 · Sección 1 — Los cuatro pilares de la POO: visión panorámica
| Dato | Valor |
|---|---|
| Unidad Didáctica | UD2 — Programación Orientada a Objetos con la API de Java |
| Resultado de Aprendizaje | RA2 — Escribe y prueba programas sencillos, reconociendo y aplicando los fundamentos de la programación orientada a objetos. |
| Sección | 1 de 10 — Los cuatro pilares de la POO (visión panorámica) |
| Evaluación | 1.ª Evaluación |
| Duración estimada | 2,5 horas lectivas |
| Criterios de evaluación cubiertos | CE 2.1 (identifica los fundamentos de la POO), CE 2.3 (instancia objetos a partir de clases predefinidas — nivel conceptual), CE 2.4 (utiliza métodos y propiedades — nivel introductorio), CE 2.5 (llamadas a métodos estáticos — nivel introductorio) |
| Prerrequisitos | UD1 completa |
| Paquete del código de ejemplo | dam.programacion.ud2 |
| Herramientas | JDK 25, IntelliJ IDEA Community, Maven 3.9.x |
1. Punto de partida
En la Unidad 1 escribisteis vuestro primer programa Java, instalasteis IntelliJ con Maven, dominasteis los tipos primitivos, jugasteis con String y hasta os atrevisteis con un if como aperitivo. Pero hay algo que seguramente os ha estado rondando la cabeza: la primera línea que escribisteis fue public class Main { ... } y aceptasteis que esa envoltura era necesaria sin entender del todo qué significaba. La palabra class estaba ahí, presente pero muda. Va a cobrar vida en esta unidad.
El cambio que vamos a dar ahora marca un antes y un después en cómo piensas cuando programas. Hasta ahora trabajamos con mentalidad procedimental: piensa un paso, escribe una instrucción, ejecútala. Funciona, pero cuando un programa crece, esa aproximación se vuelve un infierno de variables sueltas y funciones dispersas. La Programación Orientada a Objetos (POO) propone organizar el código alrededor de "quién lo hace" en lugar de "qué hay que hacer". Los protagonistas dejan de ser los verbos y pasan a ser los sustantivos.
Esta sección es deliberadamente panorámica. No crearemos clases propias (eso llega en UD4), ni profundizaremos en herencia (UD4 y UD7). Aquí sentamos las bases conceptuales: el plano del centro comercial antes de entrar en cada tienda.
2. POO: de verbos a sustantivos
Imagina que tienes que modelar el proceso de pedir un café en una cafetería. En estilo procedimental escribirías algo así:
Todo son verbos. Funciona, pero ¿qué pasa si tienes varios clientes a la vez? ¿Y si el monedero cambia de cash a tarjeta? Tocas varias funciones sueltas.
En POO identificas los sustantivos y les das responsabilidades:
Cada sustantivo será un objeto. Si mañana cambia la lógica del cobro, solo tocas el cajero. El resto del sistema sigue intacto.
Analogía del restaurante que usaremos a lo largo de la sección:
| Elemento del restaurante | Equivalente en POO |
|---|---|
| La receta escrita del risotto | La clase |
| Cada plato de risotto que sale al comedor | Un objeto (instancia) |
| Los ingredientes del plato | El estado del objeto (atributos) |
| Las operaciones que el chef aplica al plato | Los métodos del objeto |
| El comensal pide "un risotto" sin saber qué pasa en la cocina | Abstracción |
| El chef esconde los detalles de cocción tras la barra | Encapsulación |
| El risotto hereda características del plato "arroz" | Herencia |
| "Una ración" significa cosas distintas según pidas risotto, paella o macarrones | Polimorfismo |
Java es un lenguaje POO puro: todo vive dentro de una clase. No hay funciones sueltas como en C o Python. Cada función es un método de alguna clase, y cada dato que no es primitivo es un objeto.
3. Clase y objeto: el molde y la galleta
Esta es la distinción más importante de la sección. Si la entiendes, el 80% del trabajo está hecho.
- Clase: plantilla, molde o plano que define qué atributos y qué métodos tendrán todas las instancias de un cierto tipo. No ocupa memoria para datos concretos, solo define la estructura.
- Objeto: instancia concreta de una clase, creada en memoria, con valores específicos para sus atributos. Ocupa memoria y tiene identidad propia.
Analogía del molde de galletas
Tienes un molde con forma de estrella. El molde es la clase: define que todas las galletas tendrán forma de estrella, cinco puntas. Pero el molde no es comestible. Cada vez que estiras la masa y presionas, obtienes una galleta. Esa galleta es un objeto. Puedes hacer cien galletas con el mismo molde: todas comparten la forma, pero cada una es independiente. Modificar el molde afecta a las galletas futuras, no a las ya hechas.
flowchart LR
M["MOLDE\n(clase)"] -->|presionas| G1["Galleta 1\n(objeto)"]
M -->|presionas| G2["Galleta 2\n(objeto)"]
M -->|presionas| G3["Galleta 3\n(objeto)"]
| Aspecto | Clase | Objeto |
|---|---|---|
| Naturaleza | Plantilla, definición | Instancia concreta |
| Memoria | No ocupa datos | Ocupa memoria en el heap |
| Cantidad | Una por tipo | Infinitos posibles |
| Cuándo se crea | En tiempo de compilación | En tiempo de ejecución, con new |
| Analogía | Molde de galletas | Cada galleta |
| Ejemplo Java | String |
"Maria", "Carlos", "Java 25" |
Lo que ya sabes sin saberlo: String es una clase
Desde la UD1 usas objetos sin saberlo:
Sin darte cuenta estabas: declarando una variable de tipo String (la clase), asignándole una instancia (el objeto "Maria"), llamando a un método (length()) y usando el operador + sobrecargado para concatenar. Java hace que los literales entre comillas dobles sean automáticamente instancias de String. Conceptualmente, "Maria" y "Carlos" son dos objetos distintos de la misma clase.
La palabra new permite crear objetos a propósito:
Para otras clases, sin embargo, new será la única manera de crear instancias. Lo verás con Random, StringBuilder y otras en secciones posteriores.
4. Los cuatro pilares de la POO
La POO se sostiene sobre cuatro ideas fuerza. Aquí los presentamos como visión panorámica: definitions, analogías y un ejemplo de cada uno con elementos del API de Java que ya conoces.
graph TD
POO["Programación Orientada a Objetos"]
POO --> E["Encapsulación<br/>Esconde el estado, expone interfaz"]
POO --> A["Abstracción<br/>Oculta la complejidad"]
POO --> H["Herencia<br/>Una clase hereda de otra"]
POO --> P["Polimorfismo<br/>Una operación, varias formas"]
style POO fill:#1565c0,color:#fff,stroke:#0d47a1
style E fill:#2e7d32,color:#fff,stroke:#1b5e20
style A fill:#ef6c00,color:#fff,stroke:#e65100
style H fill:#6a1b9a,color:#fff,stroke:#4a148c
style P fill:#c62828,color:#fff,stroke:#b71c1c
Encapsulación
Agrupar datos y métodos en una misma unidad (la clase) y proteger los datos del acceso externo directo, exponiendo solo lo necesario.
Analogía del cajero automático. No abres el cajero con destornillador para sacar dinero: pulsas unos botones y el cajero, por dentro, hace lo que tenga que hacer. Te da una interfaz limpia y esconde el resto.
Ejemplo. String encapsula su estado interno. Sabemos que guarda los caracteres en un array (desde Java 9, en un byte[] con codec). ¿Puedes acceder a ese array? No. Solo puedes usar métodos públicos como length() o charAt(int). Si los desarrolladores de Java cambiaran mañana la implementación interna, tu código seguiría funcionando porque solo dependes de la interfaz pública.
Abstracción
Centrarse en los aspectos esenciales, ignorando los detalles irrelevantes. Mientras la encapsulación es una técnica (cómo se esconde), la abstracción es una actitud mental (qué elegimos modelar).
Analogía del coche. Cuando aprendes a conducir, nadie te explica el ciclo termodinámico del motor. Aprendes que hay un pedal de acelerador, uno de freno, un volante. Toda la complejidad mecánica queda oculta.
Ejemplo. System.out.println("Hola, mundo"). No necesitas saber que hay un PrintStream, ni buffers, ni llamadas al sistema operativo. La abstracción es "dame un texto y yo me encargo de mostrarlo por consola".
Herencia
Mecanismo por el cual una clase (subclase) hereda atributos y métodos de otra (superclase), pudiendo añadir los suyos o redefinir los heredados.
Analogía. Un plato base llamado "arroz cocido". A partir de él, mediante especialización, surgen el risotto (con caldo y queso), la paella (con azafrán y marisco) y el arroz con leche (con azúcar y leche). Todos comparten la base, cada uno añade o modifica algo.
Ejemplo. La clase Integer (wrapper que viste en UD1) hereda de la clase abstracta Number. Esto significa que Integer tiene todo lo que tiene Number (por ejemplo, intValue()) más sus propios métodos (parseInt, toBinaryString). Aquí solo presentamos el concepto; en la UD4 crearás tus propias jerarquías con extends.
Polimorfismo
Capacidad de que una misma operación se comporte de manera distinta según el objeto sobre el que se invoque. Del griego "muchas formas".
Analogía de "imprimir". Si te digo "imprime esto", el verbo es el mismo, pero la acción concreta depende del contexto: si te lo digo a ti, lo escribes a mano; a una impresora láser, sale por la bandeja; a una impresora 3D, fabrica una pieza de plástico. Una palabra, cuatro comportamientos.
Ejemplo. System.out.println es polimórfico por sobrecarga. Si escribes println(42), println(3.14), println("hola") o println(true), cada llamada usa un println distinto (uno recibe int, otro double, otro String, otro boolean). El nombre es siempre println, el comportamiento interno varía según el tipo.
| Pilar | Idea central | Ejemplo Java ya visto | Se profundiza en |
|---|---|---|---|
| Encapsulación | Estado protegido, interfaz pública | String oculta su array interno |
UD4 (private/public) |
| Abstracción | Modelo solo lo esencial | System.out.println() |
UD4 (clases abstractas) |
| Herencia | Una clase hereda de otra | Integer extends Number |
UD4, UD7 |
| Polimorfismo | Una operación, muchas formas | println(int) vs println(String) |
UD7 |
5. new y la memoria: heap vs. stack
La Máquina Virtual de Java divide la memoria en varias zonas; nos interesan dos: la pila (stack) y el montículo (heap).
flowchart TB
subgraph STACK["STACK (Pila)"]
S1["Frame main()<br/>args, nombre, edad"]
S2["Frame saludar()<br/>saludo"]
end
subgraph HEAP["HEAP (Montículo)"]
H1["Objeto String 'Maria'<br/>@0x1a4f"]
H2["Objeto String 'Carlos'<br/>@0x1b8c"]
end
S1 -.referencia.-> H1
S1 -.referencia.-> H2
La pila (stack) es una zona de memoria por hilo, privada, que funciona como una pila de platos: el último en entrar es el primero en salir. Cada vez que se llama a un método, la JVM reserva un frame con los parámetros, las variables locales y la dirección de retorno. Cuando el método termina, el frame se destruye. Aquí viven los primitivos locales y las referencias a objetos.
El montículo (heap) es una zona compartida por todos los hilos. Aquí viven los objetos. Cada vez que haces new, la JVM reserva espacio en el heap, inicializa el objeto y te devuelve una referencia que se guarda en la pila. Los objetos pueden vivir más allá del método que los creó.
| Característica | Stack | Heap |
|---|---|---|
| Qué guarda | Primitivos locales, referencias, frames | Objetos |
| Ámbito | Por hilo, privado | Compartido por todos los hilos |
| Ciclo de vida | Automático, ligado a la llamada al método | Gestionado por el garbage collector |
| Velocidad | Muy rápida | Más lenta |
| Errores típicos | StackOverflowError |
OutOfMemoryError, NullPointerException |
Analogía del polideportivo. La pila es como las taquillas del vestuario: cada usuario tiene la suya, con su llave; se asignan al entrar y se liberan al salir; son pequeñas y privadas. El heap es como el almacén grande del polideportivo: un espacio compartido donde se guardan colchonetas, redes, pelotas; cualquier monitor puede meter o sacar cosas, y pueden quedarse mucho tiempo. Cuando una pelota ya no se usa y nadie sabe dónde está, eventualmente alguien la tira (eso es el garbage collector).
En la pila, una variable local int es como una toalla en tu taquilla. Una referencia a un objeto es como un papelito en tu taquilla con la dirección de dónde está la pelota en el almacén. Si pierdes el papelito, la pelota sigue en el almacén pero ya no sabes dónde está.
Caso especial: String y el pool de strings. Cuando escribes un literal como "Maria", Java no crea necesariamente un objeto nuevo en el heap: primero mira si ya existe un String con ese contenido en una zona especial llamada string pool. Si existe, te devuelve la referencia al existente. Por eso la comparación con == puede dar resultados sorprendentes: == compara referencias, no contenidos. De ahí que en UD1 insistiéramos en usar .equals() para comparar strings.
Implicaciones prácticas que recordarás:
- Pasar un objeto a un método pasa la referencia, no el objeto. Si el método lo modifica, el cambio se ve fuera.
- Pasar un primitivo pasa una copia del valor. El llamador no ve los cambios.
- Asignar un objeto a otra variable crea otra referencia al mismo objeto, no una copia.
- Llamar a un método sobre
nullprovocaNullPointerException, el error más común de Java.
6. El operador punto .: tu mejor amigo
El operador . es probablemente el más utilizado en Java después del punto y coma. Su misión es acceder a miembros de un objeto o de una clase: atributos y métodos. La sintaxis general es referencia.miembro.
Métodos de instancia vs. métodos estáticos
- Método de instancia: pertenece a un objeto concreto. Se llama con
objeto.metodo(...). - Método estático: pertenece a la clase, no necesita instancia. Se llama con
Clase.metodo(...).
La regla mental para decidir: "¿Necesito un objeto concreto para que este método tenga sentido?". Si la respuesta es sí, es de instancia. Si es no, es estático.
Encadenamiento
Si un método devuelve un objeto, puedes inmediatamente llamar a un método de ese objeto devuelto:
Se evalúa de izquierda a derecha. Es elegante, pero cuidado: si una etapa devuelve null, la siguiente lanza NullPointerException.
Desmenuzando System.out.println()
La llamada que más veces has escrito contiene, en una sola línea, toda la teoría de esta sección:
| Fragmento | Qué es |
|---|---|
System |
Una clase del paquete java.lang |
System.out |
Un atributo estático y público; referencia a un objeto de tipo PrintStream |
System.out.println(...) |
Llamada al método de instancia println sobre ese PrintStream |
Una sola línea usa miembros estáticos, miembros de instancia, referencias y el operador punto. Si la entiendes, tienes los fundamentos de POO asentados.
7. Ejemplo integrador
Programa que usa exclusivamente elementos ya vistos en UD1 y los conceptos nuevos de esta sección. No introduce nada de secciones posteriores.
Salida esperada:
Antes de ejecutarlo, intenta predecir la salida. Si aciertas, has entendido la sección.
8. Errores comunes
| Error | Qué pasa | Cómo evitarlo |
|---|---|---|
| Confundir clase con objeto | Pensar que String y "Maria" son lo mismo |
String es la plantilla; "Maria" es una instancia concreta |
Usar == con objetos |
Compara referencias, no contenidos | Usa .equals() por defecto para comparar strings u objetos |
Olvidar new cuando es necesario |
Error de compilación | Para clases que no son String, normalmente hace falta new o un método factoría |
Llamar a un método sobre null |
NullPointerException |
Inicializa las referencias y comprueba null cuando haya duda |
| Pensar que asignar un objeto lo copia | Crea otra referencia al mismo objeto | Recuerda: las referencias son como papelitos con la dirección del objeto en el heap |
9. Resumen
| Concepto | Idea clave | Ejemplo |
|---|---|---|
| Clase | Plantilla que define estructura y comportamiento | String |
| Objeto | Instancia concreta, con su propio estado | "Maria" |
new |
Crea un objeto en el heap | new String("Maria") |
| Heap | Memoria compartida donde viven los objetos | El objeto "Maria" vive aquí |
| Stack | Memoria por hilo con variables locales y referencias | La variable nombre vive aquí |
Operador . |
Acceso a miembros | nombre.length() |
| Método de instancia | Pertenece a un objeto concreto | nombre.length() |
| Método estático | Pertenece a la clase | Integer.parseInt("42") |
| Encapsulación | Estado interno protegido | String oculta su array |
| Abstracción | Modelo solo lo esencial | System.out.println() |
| Herencia | Una clase hereda de otra | Integer extends Number |
| Polimorfismo | Una operación, varias formas | println(int) vs println(String) |
== vs .equals() |
Referencias vs. contenido | a == b / a.equals(b) |
10. Información ampliada y próximos pasos
Si te ha sabido a poco y quieres profundizar en algún concepto antes de seguir avanzando, aquí tienes recursos seleccionados:
- Documentación oficial de Oracle sobre POO en Java: https://docs.oracle.com/javase/tutorial/java/concepts/
- El modelo de memoria de la JVM explicado en detalle: https://dev.java/learn/jvm/tool/memory-management/
- Artículo "Why String is Immutable in Java" (Baeldung, en inglés): https://www.baeldung.com/java-string-immutable
- Capítulo "Classes and Objects" del Java Language Specification (JLS), para los más curiosos: https://docs.oracle.com/javase/specs/jls/se25/html/jls-8.html
Próximos pasos sugeridos:
- Probar el código de ejemplo en IntelliJ. Crea un proyecto Maven con paquete
dam.programacion.ud2, copiaSintesisPilaresPOO, ejecútalo y comprueba que la salida coincide con la predicha. - Experimentar con
==y.equals(). Crea un pequeño programa que declare varias variablesString(unas con literales, otras connew String(...)) y compáralas. Predice el resultado antes de ejecutar. - Avanzar a la Sección 2 de UD2. Te enseñará a leer la documentación oficial (Javadoc) y a explorar el API de Java (
java.lang,java.util,java.math,java.time). Es el puente natural entre la teoría que acabas de aprender y las clases concretas que verás en las secciones 3 a 8.