📦 ¿Qué es un objeto en programación?
En la Programación Orientada a Objetos (POO), un objeto es la entidad fundamental sobre la que gira todo el paradigma. Un objeto es un ejemplar concreto de una clase: si la clase Coche define las características generales de cualquier coche, un objeto sería un coche específico —por ejemplo, un Toyota Corolla rojo con 45.000 km—. Cada objeto posee datos propios (sus atributos) y puede ejecutar acciones (sus métodos).
La POO modela problemas del mundo real representándolos como colecciones de objetos que interactúan entre sí a través de mensajes. Este enfoque facilita la organización del código, la reutilización de componentes y el mantenimiento de sistemas complejos. En Java, prácticamente todo es un objeto: las cadenas de texto, los arrays, las ventanas gráficas, las conexiones de red y, por supuesto, las entidades de negocio que el programador define.
int, double, boolean, char, etc.), aunque cada uno dispone de una clase envolvente (wrapper) como Integer o Double que sí son objetos.
🌍 Objetos del mundo real vs. objetos en software
Para entender los objetos en programación, resulta útil partir de una analogía con el mundo físico. Un perro, un libro, una factura o un pedido de comercio electrónico son entidades con características observables y acciones que pueden realizar o recibir. En la POO, esa misma idea se traslada al código:
| Concepto del mundo real | Equivalencia en POO (Java) | Ejemplo |
|---|---|---|
| Categoría o tipo de cosa | Clase | class Coche { ... } |
| Cosa concreta e individual | Objeto (instancia) | Coche miCoche = new Coche(); |
| Características (color, peso, marca) | Atributos (campos, propiedades) | miCoche.color = "rojo"; |
| Acciones (arrancar, frenar) | Métodos | miCoche.arrancar(); |
Esta correspondencia directa entre el dominio del problema y el código es una de las grandes ventajas de la POO: el software refleja la realidad de manera natural, lo que facilita tanto el diseño como la comunicación entre desarrolladores y usuarios de negocio.
🔑 Estado, comportamiento e identidad
Todo objeto en Java se define por tres características esenciales que lo distinguen de cualquier otro elemento del programa:
▶️ Estado
El estado de un objeto es el conjunto de valores que almacenan sus atributos en un momento dado. Por ejemplo, un objeto CuentaBancaria puede tener un saldo de 1.500,00 € y un titular llamado «Ana García». El estado cambia cuando un método modifica alguno de esos valores (por ejemplo, al realizar un ingreso o una retirada).
🔹 Comportamiento
El comportamiento es el conjunto de operaciones que el objeto puede ejecutar, definidas por los métodos de su clase. En el ejemplo de la cuenta bancaria, el comportamiento incluye ingresar(), retirar() y consultarSaldo(). El comportamiento puede modificar el estado propio del objeto o el de otros objetos con los que interactúe.
🔸 Identidad
La identidad es lo que distingue a un objeto de cualquier otro, incluso si ambos tienen exactamente el mismo estado. En Java, la identidad viene dada por la referencia en memoria: dos objetos pueden tener los mismos valores en todos sus atributos y, sin embargo, ser objetos distintos porque ocupan posiciones diferentes en el heap.
// Dos objetos con el mismo estado pero distinta identidad
Coche cocheA = new Coche("Toyota", "Rojo");
Coche cocheB = new Coche("Toyota", "Rojo");
System.out.println(cocheA == cocheB); // false → distinta identidad
System.out.println(cocheA.equals(cocheB)); // true → si equals() está sobrescrito
🏗️ Cómo crear objetos en Java con new
En Java, la creación de un objeto requiere dos pasos conceptuales que habitualmente se realizan en una sola línea:
▶️ Paso 1: Declarar la variable de referencia
Se indica el tipo (la clase) y el nombre de la variable. En este punto, la variable existe pero aún no apunta a ningún objeto; su valor es null.
🔹 Paso 2: Instanciar el objeto con new
El operador new realiza tres acciones internas: reserva memoria en el heap para el nuevo objeto, invoca el constructor de la clase para inicializar los atributos y devuelve la referencia (dirección de memoria) al objeto recién creado.
// Paso a paso
Coche miCoche; // 1. Declaración (miCoche vale null)
miCoche = new Coche("Seat", "Blanco"); // 2. Instanciación (ahora apunta al objeto)
// Todo en una línea (forma más habitual)
Coche miCoche = new Coche("Seat", "Blanco");
null, que provocaría un NullPointerException.
🔹 Uso de constructores con parámetros
Una clase puede definir múltiples constructores (sobrecarga de constructores), cada uno con diferentes parámetros. Esto permite crear objetos con distintos grados de inicialización:
public class Producto {
private String nombre;
private double precio;
private int stock;
// Constructor sin parámetros (valores por defecto)
public Producto() {
this.nombre = "Sin nombre";
this.precio = 0.0;
this.stock = 0;
}
// Constructor con todos los parámetros
public Producto(String nombre, double precio, int stock) {
this.nombre = nombre;
this.precio = precio;
this.stock = stock;
}
// Constructor parcial (solo nombre y precio)
public Producto(String nombre, double precio) {
this(nombre, precio, 0); // Reutiliza el constructor completo
}
}
// Tres formas de crear un Producto
Producto p1 = new Producto(); // "Sin nombre", 0.0, 0
Producto p2 = new Producto("Teclado", 49.99, 150); // Todos los datos
Producto p3 = new Producto("Ratón", 19.99); // Sin stock inicial
🔬 Anatomía de un objeto: atributos y métodos
Un objeto es, en esencia, una cápsula que agrupa datos y las operaciones que actúan sobre esos datos. Esta separación en dos partes —atributos y métodos— es la base del encapsulamiento, uno de los cuatro pilares de la POO.
▶️ Atributos (campos o propiedades)
Los atributos son las variables de instancia que almacenan el estado del objeto. Cada objeto tiene su propia copia de estos valores. Por convención, los atributos se declaran como private para proteger el estado interno:
public class Empleado {
// Atributos (estado del objeto)
private String nombre;
private String departamento;
private double salario;
private boolean activo;
}
🔹 Métodos (operaciones)
Los métodos definen el comportamiento del objeto. A través de ellos se puede consultar o modificar el estado de forma controlada. Los métodos más comunes son los getters (lectura), setters (escritura) y los métodos de lógica de negocio:
public class Empleado {
private String nombre;
private double salario;
private boolean activo;
// Constructor
public Empleado(String nombre, double salario) {
this.nombre = nombre;
this.salario = salario;
this.activo = true;
}
// Getter
public String getNombre() {
return nombre;
}
// Setter con validación
public void setSalario(double nuevoSalario) {
if (nuevoSalario >= 0) {
this.salario = nuevoSalario;
}
}
// Método de lógica de negocio
public double calcularSalarioAnual() {
return activo ? salario * 14 : 0; // 14 pagas en España
}
// Representación textual del objeto
@Override
public String toString() {
return nombre + " | Salario: " + salario + "€ | Activo: " + activo;
}
}
miEmpleado.salario = -500). Utiliza siempre setters con validación para garantizar que el estado del objeto sea consistente en todo momento.
🧠 Referencias, memoria heap y stack
Comprender cómo Java gestiona los objetos en memoria es esencial para evitar errores sutiles. A diferencia de los tipos primitivos, los objetos no se almacenan directamente en las variables: lo que la variable contiene es una referencia (una especie de dirección postal) al objeto real, que reside en el heap.
| Zona de memoria | Almacena | Gestión |
|---|---|---|
| Stack (pila) | Variables locales, referencias a objetos, parámetros de métodos | Automática (LIFO): se libera al salir del método |
| Heap (montículo) | Los objetos propiamente dichos (atributos + datos) | Garbage Collector: libera objetos sin referencias |
// Ejemplo de referencias compartidas
Coche a = new Coche("BMW", "Negro"); // 'a' apunta al objeto en heap
Coche b = a; // 'b' apunta al MISMO objeto
b.setColor("Azul"); // Modifica el objeto compartido
System.out.println(a.getColor()); // "Azul" → ambas referencias ven el cambio
// Ahora 'a' apunta a un objeto nuevo
a = new Coche("Audi", "Gris"); // 'a' cambia; 'b' sigue apuntando al BMW azul
System.out.println(b.getMarca()); // "BMW" → el objeto original sigue vivo
b = a), no se copia el objeto, sino únicamente la referencia. Ambas variables apuntan al mismo objeto. Para obtener una copia independiente necesitas implementar el método clone() o un constructor de copia.
♻️ Ciclo de vida de un objeto
Los objetos en Java nacen, viven y mueren siguiendo un ciclo bien definido que el programador debe comprender para escribir código eficiente y libre de fugas de memoria:
▶️ 1. Creación (instanciación)
El operador new reserva memoria en el heap e invoca el constructor. El objeto queda listo para su uso, con sus atributos inicializados.
🔹 2. Uso (vida activa)
El objeto recibe mensajes (llamadas a métodos), su estado cambia y puede interactuar con otros objetos. Mientras al menos una variable mantenga una referencia al objeto, este permanece «vivo».
🔸 3. Inaccesibilidad
Cuando ninguna variable del programa mantiene una referencia al objeto (por ejemplo, porque la variable salió de ámbito o se le asignó null), el objeto se convierte en elegible para la recolección de basura.
➡️ 4. Recolección de basura (Garbage Collection)
El Garbage Collector (GC) de la JVM detecta automáticamente los objetos inaccesibles y libera su memoria. El programador no necesita (ni puede) destruir objetos manualmente. Opcionalmente, antes de ser recolectado, la JVM invoca el método finalize() del objeto, aunque su uso está desaconsejado desde Java 9.
public class CicloVidaDemo {
public static void main(String[] args) {
// 1. Creación
Producto p = new Producto("Monitor", 299.99, 50);
// 2. Uso
p.vender(5);
System.out.println(p);
// 3. Inaccesibilidad
p = null; // La referencia se pierde → el objeto es elegible para GC
// 4. Sugerir recolección (la JVM decide cuándo ejecutarla)
System.gc();
}
}
System.gc() en código de producción. La JVM optimiza la recolección de basura de forma automática y una llamada explícita puede degradar el rendimiento. Confía en el GC.
⚖️ Igualdad vs. identidad: == y equals()
Uno de los errores más frecuentes entre programadores Java principiantes es confundir la igualdad (mismo contenido) con la identidad (mismo objeto en memoria). Java proporciona dos mecanismos diferentes para cada caso:
| Mecanismo | Compara | Uso típico |
|---|---|---|
== |
Referencias (direcciones de memoria) | Verificar si dos variables apuntan al mismo objeto |
equals() |
Contenido lógico (si se sobrescribe) | Verificar si dos objetos son equivalentes en valor |
String s1 = new String("Hola");
String s2 = new String("Hola");
System.out.println(s1 == s2); // false → son objetos distintos en memoria
System.out.println(s1.equals(s2)); // true → mismo contenido "Hola"
// Caso especial: literales String (pool de cadenas)
String s3 = "Hola";
String s4 = "Hola";
System.out.println(s3 == s4); // true → la JVM reutiliza el mismo literal
Para que equals() funcione correctamente en tus propias clases, debes sobrescribirlo (junto con hashCode()). La implementación por defecto heredada de Object simplemente compara referencias, comportamiento idéntico a ==.
public class Producto {
private String nombre;
private double precio;
// ... constructor, getters, setters ...
@Override
public boolean equals(Object obj) {
if (this == obj) return true; // Misma referencia
if (obj == null || getClass() != obj.getClass()) return false;
Producto otro = (Producto) obj;
return Double.compare(otro.precio, precio) == 0
&& Objects.equals(nombre, otro.nombre);
}
@Override
public int hashCode() {
return Objects.hash(nombre, precio);
}
}
🛒 Ejemplo integrador: sistema de productos
Veamos un ejemplo completo que reúne todos los conceptos vistos: creación de objetos, atributos, métodos, referencias, toString() y equals(). Simularemos un pequeño sistema de gestión de productos de un comercio electrónico:
import java.util.Objects;
public class Producto {
private String codigo;
private String nombre;
private double precio;
private int stock;
public Producto(String codigo, String nombre, double precio, int stock) {
this.codigo = codigo;
this.nombre = nombre;
this.precio = precio;
this.stock = stock;
}
// --- Getters ---
public String getCodigo() { return codigo; }
public String getNombre() { return nombre; }
public double getPrecio() { return precio; }
public int getStock() { return stock; }
// --- Lógica de negocio ---
public boolean hayStock() {
return stock > 0;
}
public boolean vender(int cantidad) {
if (cantidad <= 0 || cantidad > stock) return false;
stock -= cantidad;
return true;
}
public void reponer(int cantidad) {
if (cantidad > 0) stock += cantidad;
}
public double valorInventario() {
return precio * stock;
}
// --- equals, hashCode, toString ---
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null || getClass() != obj.getClass()) return false;
Producto otro = (Producto) obj;
return Objects.equals(codigo, otro.codigo);
}
@Override
public int hashCode() {
return Objects.hash(codigo);
}
@Override
public String toString() {
return String.format("[%s] %s — %.2f€ (stock: %d)",
codigo, nombre, precio, stock);
}
}
public class TiendaDemo {
public static void main(String[] args) {
// Crear objetos
Producto teclado = new Producto("TEC-001", "Teclado mecánico", 79.99, 30);
Producto raton = new Producto("RAT-002", "Ratón ergonómico", 34.50, 100);
Producto monitor = new Producto("MON-003", "Monitor 27\" 4K", 349.00, 15);
// Usar métodos
System.out.println("=== INVENTARIO INICIAL ===");
System.out.println(teclado);
System.out.println(raton);
System.out.println(monitor);
// Vender productos
teclado.vender(5);
raton.vender(20);
monitor.vender(2);
System.out.println("\n=== TRAS VENTAS ===");
System.out.println(teclado); // stock: 25
System.out.println(raton); // stock: 80
System.out.println(monitor); // stock: 13
// Valor total del inventario
double total = teclado.valorInventario()
+ raton.valorInventario()
+ monitor.valorInventario();
System.out.printf("\nValor total del inventario: %.2f€%n", total);
// Comparar objetos
Producto copia = new Producto("TEC-001", "Teclado mecánico", 79.99, 30);
System.out.println("\n¿teclado == copia? " + (teclado == copia)); // false
System.out.println("¿teclado.equals(copia)? " + teclado.equals(copia)); // true
}
}
🐛 Errores frecuentes al trabajar con objetos
A continuación se presentan los errores más habituales que cometen los programadores al trabajar con objetos en Java, junto con su causa y solución:
| Error | Causa | Solución |
|---|---|---|
NullPointerException |
Usar un objeto que nunca se instanció o cuya referencia es null |
Siempre instanciar con new antes de usar; comprobar null cuando sea necesario |
Comparar con == en vez de equals() |
Se comparan direcciones de memoria, no el contenido | Usar equals() para comparar valores; sobrescribirlo en clases propias |
| Modificar objeto compartido sin querer | Varias variables apuntan al mismo objeto en el heap | Crear copias defensivas (constructor de copia o clone()) |
No sobrescribir toString() |
Al imprimir el objeto aparece Clase@hashcode |
Implementar toString() con información legible |
| Atributos públicos sin protección | Cualquier código puede establecer valores inválidos | Declarar atributos private y exponer setters con validación |
✏️ Ejercicios prácticos
Ejercicio 1 — Crear la clase Libro
Crea una clase Libro con atributos titulo (String), autor (String), paginas (int) y precio (double). Incluye un constructor con todos los parámetros, getters, toString() y un método aplicarDescuento(double porcentaje) que reduzca el precio. En el main, crea dos libros, aplica un 15% de descuento al primero e imprime ambos.
Ver solución
public class Libro {
private String titulo;
private String autor;
private int paginas;
private double precio;
public Libro(String titulo, String autor, int paginas, double precio) {
this.titulo = titulo;
this.autor = autor;
this.paginas = paginas;
this.precio = precio;
}
public String getTitulo() { return titulo; }
public String getAutor() { return autor; }
public int getPaginas() { return paginas; }
public double getPrecio() { return precio; }
public void aplicarDescuento(double porcentaje) {
if (porcentaje > 0 && porcentaje < 100) {
precio -= precio * porcentaje / 100;
}
}
@Override
public String toString() {
return String.format("\"%s\" de %s — %d págs. — %.2f€",
titulo, autor, paginas, precio);
}
public static void main(String[] args) {
Libro libro1 = new Libro("El Quijote", "Cervantes", 863, 24.90);
Libro libro2 = new Libro("1984", "George Orwell", 326, 15.50);
libro1.aplicarDescuento(15);
System.out.println(libro1); // "El Quijote" de Cervantes — 863 págs. — 21.17€
System.out.println(libro2); // "1984" de George Orwell — 326 págs. — 15.50€
}
}
Ejercicio 2 — Referencias y aliasing
Sin ejecutar el código, predice la salida del siguiente programa. Después, compruébalo en tu IDE:
Producto a = new Producto("X01", "USB", 9.99, 200);
Producto b = a;
Producto c = new Producto("X01", "USB", 9.99, 200);
b.vender(50);
System.out.println(a.getStock()); // ¿?
System.out.println(c.getStock()); // ¿?
System.out.println(a == b); // ¿?
System.out.println(a == c); // ¿?
System.out.println(a.equals(c)); // ¿?
Ver solución
150 // b.vender(50) afecta a 'a' porque apuntan al MISMO objeto
200 // 'c' es un objeto independiente, no se ve afectado
true // 'a' y 'b' son la misma referencia
false // 'a' y 'c' son objetos distintos en memoria
true // equals() compara por código ("X01") → son equivalentes
Ejercicio 3 — Sistema de gestión de alumnos
Crea la clase Alumno con atributos nombre, nota1, nota2 y nota3 (todos double). Incluye: un constructor, un método calcularMedia(), un método estaAprobado() (media ≥ 5.0) y toString(). En el main, crea un array de 3 alumnos, calcula la media de cada uno e indica cuántos aprobaron.
Ver solución
public class Alumno {
private String nombre;
private double nota1, nota2, nota3;
public Alumno(String nombre, double nota1, double nota2, double nota3) {
this.nombre = nombre;
this.nota1 = nota1;
this.nota2 = nota2;
this.nota3 = nota3;
}
public double calcularMedia() {
return (nota1 + nota2 + nota3) / 3.0;
}
public boolean estaAprobado() {
return calcularMedia() >= 5.0;
}
@Override
public String toString() {
return String.format("%s → Media: %.2f → %s",
nombre, calcularMedia(), estaAprobado() ? "APROBADO" : "SUSPENSO");
}
public static void main(String[] args) {
Alumno[] alumnos = {
new Alumno("María López", 7.5, 8.0, 6.5),
new Alumno("Carlos Ruiz", 4.0, 3.5, 5.0),
new Alumno("Lucía Martín", 9.0, 8.5, 9.5)
};
int aprobados = 0;
for (Alumno a : alumnos) {
System.out.println(a);
if (a.estaAprobado()) aprobados++;
}
System.out.printf("%nTotal aprobados: %d de %d%n", aprobados, alumnos.length);
}
}
Ejercicio 4 — Constructor de copia
Añade a la clase Producto del ejemplo integrador un constructor de copia que reciba otro Producto y cree una copia independiente. Demuestra en el main que modificar la copia no afecta al original.
Ver solución
// Añadir a la clase Producto:
public Producto(Producto otro) {
this.codigo = otro.codigo;
this.nombre = otro.nombre;
this.precio = otro.precio;
this.stock = otro.stock;
}
// En el main:
Producto original = new Producto("TEC-001", "Teclado", 79.99, 30);
Producto copia = new Producto(original); // Constructor de copia
copia.vender(10);
System.out.println("Original: " + original.getStock()); // 30 (no afectado)
System.out.println("Copia: " + copia.getStock()); // 20
❓ Preguntas frecuentes sobre El Objeto en POO: qué es y cómo se crea en Java
Las dudas más comunes respondidas de forma clara y directa.
💬 Foro de discusión
¿Tienes dudas sobre El Objeto en POO: qué es y cómo se crea en Java? Comparte tu pregunta con la comunidad.
Todavía no hay mensajes. ¡Sé el primero en participar!