,

Desarrollo de clase en Java

·

Aprender clases en Java es uno de esos pasos que marcan un antes y un después cuando empiezas a programar en serio. Al principio puedes resolver ejercicios con variables sueltas, arrays, condicionales y métodos sencillos. Pero llega un momento en el que el código empieza a crecer, los datos se mezclan, el main se convierte en un cajón desastre y cuesta entender qué representa cada parte del programa.

Ahí entran las clases en Java.

Una clase permite crear tus propios tipos de datos. En vez de tener variables como nombre1, edad1, nota1, nombre2, edad2 y nota2, puedes crear una clase Alumno. En vez de manejar arrays separados para nombres, precios y stock, puedes crear una clase Producto. Y en vez de tener todo el código de una cuenta bancaria mezclado en el programa principal, puedes diseñar una clase CuentaBancaria con sus propios datos y operaciones.

En mi caso, después de trabajar en desarrollo software, consultoría tecnológica y docencia en FP Informática, tengo bastante claro que programar con clases no va de repartir código en archivos porque sí. Va de pensar mejor el problema. Una buena clase ayuda a que el programa hable el mismo idioma que la realidad que quiere representar.

En esta guía vamos a ver qué son las clases en Java, cómo se crean, qué diferencia hay entre clase y objeto, cómo funcionan los atributos, métodos, constructores, encapsulación, métodos estáticos, paquetes, herencia e interfaces. Todo con ejemplos sencillos y progresivos.

Qué es una clase en Java y por qué cambia tu forma de programar

Una clase en Java es una plantilla o modelo que describe cómo serán los objetos de un determinado tipo. Dicho de forma más cercana: una clase define qué datos tendrá algo y qué acciones podrá realizar.

Por ejemplo, imagina que quieres gestionar productos en una tienda. Un producto puede tener:

  • nombre;
  • precio;
  • stock.

Y también puede hacer cosas como:

  • mostrar su información;
  • comprobar si hay stock;
  • aplicar un descuento;
  • vender una cantidad determinada.

Eso encaja perfectamente con una clase Producto.

public class Producto {
String nombre;
double precio;
int stock;

void mostrarInformacion() {
System.out.println(nombre + " - " + precio + " euros - Stock: " + stock);
}
}

Esta primera versión de la clase es sencilla, pero ya muestra la idea principal de las clases en Java: agrupar datos y comportamiento dentro de una misma estructura.

De variables sueltas a tipos propios

Antes de usar clases en Java, es habitual resolver problemas con variables independientes. Por ejemplo:

String nombreProducto = "Teclado";
double precioProducto = 24.99;
int stockProducto = 10;

Esto puede funcionar en ejercicios pequeños. El problema llega cuando tienes varios productos:

String nombre1 = "Teclado";
double precio1 = 24.99;
int stock1 = 10;

String nombre2 = "Ratón";
double precio2 = 12.50;
int stock2 = 20;

El código empieza a repetirse y cada vez es más fácil equivocarse. Con una clase Producto, en cambio, puedes representar cada producto como un objeto propio.

Producto p1 = new Producto();
p1.nombre = "Teclado";
p1.precio = 24.99;
p1.stock = 10;

Producto p2 = new Producto();
p2.nombre = "Ratón";
p2.precio = 12.50;
p2.stock = 20;

La diferencia parece pequeña al principio, pero es enorme: el código deja de ser una colección de datos sueltos y empieza a organizarse alrededor de conceptos reales.

Una clase representa un concepto del problema

Cuando explico clases en Java, una idea que suelo repetir mucho es esta: una clase debería representar algo que tenga sentido dentro del problema.

Si estás trabajando con estudiantes, probablemente tendrá sentido crear una clase Alumno. Si estás trabajando con reservas, una clase Reserva. Si estás haciendo una aplicación de biblioteca, una clase Libro.

Esto ayuda a que el código sea más legible. No solo para la máquina, sino para ti, para tus compañeros y para tu yo del futuro cuando vuelva a leer el código dentro de unas semanas.

Un programa bien organizado en clases no se entiende solo por sus instrucciones, sino también por sus nombres. Ver clases como Producto, CuentaBancaria, Libro o Alumno ya te da pistas sobre qué está haciendo el programa.

Diferencia entre clase, objeto, instancia y referencia

Uno de los bloqueos más habituales al aprender clases en Java es confundir clase, objeto, instancia y referencia. Es normal. Son conceptos muy cercanos, pero no significan exactamente lo mismo.

Clase: la plantilla

La clase es la plantilla. Define cómo serán los objetos, pero no es un objeto concreto.

public class Alumno {
String nombre;
int edad;
double notaMedia;
}

Aquí Alumno es una clase. Todavía no tenemos ningún alumno real en memoria. Solo hemos definido el molde.

Una forma sencilla de verlo es pensar en un plano de una casa. El plano describe cómo será la casa, pero no puedes vivir dentro del plano. Para eso necesitas construir una casa real. Con las clases en Java ocurre algo parecido: la clase describe, pero el objeto es lo que se crea a partir de ella.

Objeto e instancia: el elemento real creado con new

Un objeto es un elemento concreto creado a partir de una clase. También se le suele llamar instancia.

Alumno a1 = new Alumno();
a1.nombre = "Lucía";
a1.edad = 18;
a1.notaMedia = 7.4;

En este caso, a1 permite trabajar con un objeto real de la clase Alumno.

Podemos crear otro alumno diferente:

Alumno a2 = new Alumno();
a2.nombre = "Carlos";
a2.edad = 19;
a2.notaMedia = 6.2;

Los dos objetos pertenecen a la misma clase, pero tienen estados distintos. a1 representa a Lucía y a2 representa a Carlos.

Esta es una de las grandes ventajas de las clases en Java: puedes crear muchos objetos del mismo tipo, cada uno con sus propios datos.

Referencia: la variable que apunta al objeto

Cuando declaras una variable de tipo clase, esa variable no guarda directamente todo el objeto. Guarda una referencia al objeto en memoria.

Producto p1 = new Producto();
Producto p2 = p1;

En este ejemplo, p1 y p2 apuntan al mismo objeto. Si modificas el objeto usando p2, también verás el cambio desde p1, porque las dos referencias llevan al mismo sitio.

p1.nombre = "Teclado";
p2.nombre = "Ratón";

System.out.println(p1.nombre); // Ratón

Este comportamiento puede sorprender al principio. En clase suele ser uno de esos momentos en los que se enciende la bombilla: no estás copiando el objeto completo, estás copiando la referencia.

Entender esto es clave para trabajar bien con clases en Java, especialmente cuando empiezas a pasar objetos como parámetros o a guardarlos dentro de listas.

Estructura básica de una clase en Java

Una clase en Java suele seguir una estructura bastante clara. No todos los elementos son obligatorios, pero mantener un orden ayuda mucho a leer y mantener el código.

Una estructura típica sería esta:

public class NombreClase {

// 1. Atributos
private String atributo;

// 2. Constructores
public NombreClase(String atributo) {
this.atributo = atributo;
}

// 3. Métodos de acceso
public String getAtributo() {
return atributo;
}

public void setAtributo(String atributo) {
this.atributo = atributo;
}

// 4. Métodos de comportamiento
public void hacerAlgo() {
System.out.println("Comportamiento de la clase");
}
}

Esta estructura no es una ley universal, pero sí una buena costumbre.

Nombre de la clase y convenciones

En Java, los nombres de las clases suelen escribirse en PascalCase. Eso significa que cada palabra empieza por mayúscula y no se usan guiones bajos.

Ejemplos correctos:

Producto
CuentaBancaria
AlumnoCurso
ReservaHotel

Los atributos y métodos, en cambio, suelen escribirse en camelCase:

nombreProducto
calcularPrecioFinal()
mostrarInformacion()

Puede parecer un detalle menor, pero no lo es. Las convenciones hacen que el código sea más fácil de leer y ayudan a que cualquier programador Java se sienta cómodo al revisar tu proyecto.

Atributos

Los atributos son los datos internos de la clase. Representan el estado de cada objeto.

public class Producto {
private String nombre;
private double precio;
private int stock;
}

Cada objeto Producto tendrá su propio nombre, su propio precio y su propio stock.

Constructores

Los constructores sirven para crear objetos con un estado inicial.

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

Gracias al constructor, puedes crear un producto así:

Producto teclado = new Producto("Teclado", 24.99, 10);

Esto es mucho más limpio que crear el objeto vacío y asignar después cada atributo uno por uno.

Getters, setters y métodos de comportamiento

Los getters permiten consultar atributos privados. Los setters permiten modificarlos de forma controlada. Y los métodos de comportamiento representan acciones propias del objeto.

public String getNombre() {
return nombre;
}

public boolean hayStock() {
return stock > 0;
}

Una buena clase en Java no debería limitarse a guardar datos. También debería incluir operaciones relacionadas con esos datos.

Atributos y estado de un objeto

Los atributos representan el estado interno de un objeto. También se conocen como campos o propiedades, aunque en Java es muy habitual hablar de atributos o campos.

Por ejemplo:

public class Alumno {
String nombre;
int edad;
double notaMedia;
}

Cada objeto Alumno tiene su propio estado. Un alumno puede llamarse Lucía, tener 18 años y una nota media de 7.4. Otro puede llamarse Carlos, tener 19 años y una nota media de 6.2.

Alumno a1 = new Alumno();
a1.nombre = "Lucía";
a1.edad = 18;
a1.notaMedia = 7.4;

Alumno a2 = new Alumno();
a2.nombre = "Carlos";
a2.edad = 19;
a2.notaMedia = 6.2;

Los dos son alumnos, pero no son el mismo alumno. Pertenecen a la misma clase, pero tienen datos diferentes.

Estado y comportamiento

En programación orientada a objetos, un objeto combina estado y comportamiento.

El estado son sus datos actuales. El comportamiento son las operaciones que puede realizar.

Por ejemplo:

ClaseEstado posibleComportamiento posible
Alumnonombre, edad, notaMediamostrarDatos(), haAprobado()
CuentaBancariatitular, saldoingresar(), retirar(), consultarSaldo()
Productonombre, precio, stockaplicarDescuento(), hayStock()
Librotitulo, autor, disponibleprestar(), devolver(), mostrarFicha()

Esta tabla resume muy bien la esencia de las clases en Java. Una clase no solo debe responder a “qué datos guarda”, sino también a “qué sabe hacer”.

En proyectos reales, una clase que solo tiene atributos públicos y ninguna lógica suele acabar generando problemas. Toda la lógica queda repartida por fuera y el objeto pierde sentido. Por eso, una de las claves al diseñar clases en Java es unir datos y comportamiento de forma coherente.

Valores por defecto y cuidado con null

Cuando creas un objeto en Java, sus atributos reciben valores por defecto si no los inicializas.

Algunos valores habituales son:

TipoValor por defecto
int0
double0.0
booleanfalse
charcarácter nulo
Referencias como Stringnull

Esto significa que si tienes un atributo String nombre y no le das valor, será null.

Alumno alumno = new Alumno();
System.out.println(alumno.nombre); // null

El problema aparece cuando intentas llamar a un método sobre una referencia que vale null.

System.out.println(alumno.nombre.toUpperCase());

Esto puede provocar una NullPointerException.

Por eso los constructores son tan importantes: ayudan a que los objetos nazcan con datos válidos desde el principio.

Métodos en Java: comportamiento dentro de una clase

Los métodos representan las acciones que puede realizar un objeto o las operaciones que podemos pedirle a una clase.

Si los atributos responden a “qué datos tiene este objeto”, los métodos responden a “qué puede hacer este objeto”.

Por ejemplo:

public class Producto {
private String nombre;
private double precio;

public double calcularPrecioConIVA() {
return precio * 1.21;
}
}

Aquí calcularPrecioConIVA() es un método. Usa el atributo precio para calcular y devolver un resultado.

Métodos void

Un método void no devuelve ningún valor. Eso no significa que no haga nada. Significa que realiza una acción, pero no entrega un resultado al código que lo llama.

public void mostrarInformacion() {
System.out.println(nombre + " - " + precio + " euros");
}

Este método muestra información por pantalla. No necesita devolver nada.

Métodos que devuelven valor

Otros métodos sí devuelven un resultado usando return.

public boolean hayStock() {
return stock > 0;
}

Este método devuelve true si hay stock y false si no lo hay.

También podemos devolver un número:

public double calcularPrecioConDescuento(double porcentaje) {
return precio - (precio * porcentaje / 100);
}

Cuando un método declara que devuelve boolean, int, double, String u otro tipo, debe devolver un valor compatible. Si no lo hace, el programa no compilará.

Parámetros

Los parámetros son datos que un método recibe para trabajar.

public void aplicarDescuento(double porcentaje) {
precio = precio - (precio * porcentaje / 100);
}

Gracias al parámetro porcentaje, el método sirve para aplicar un 10%, un 20% o cualquier otro descuento.

producto.aplicarDescuento(10);
producto.aplicarDescuento(20);

Esto hace que los métodos sean flexibles y reutilizables.

Responsabilidad única de un método

Una buena práctica al trabajar con clases en Java es que cada método tenga una responsabilidad clara.

Un método llamado calcularTotal() debería calcular un total. Uno llamado mostrarResumen() debería mostrar un resumen. Uno llamado guardar() debería guardar.

Lo que conviene evitar son métodos como estos:

gestionarTodo()
calcularYMostrarYGuardar()
metodo1()

El problema no es solo el nombre. Normalmente esos métodos hacen demasiadas cosas. Y cuando un método hace demasiadas cosas, cuesta probarlo, reutilizarlo y mantenerlo.

En desarrollo software real, esto se nota muchísimo. Un método largo y confuso puede funcionar hoy, pero dentro de dos meses se convierte en una trampa. Por eso, cuando diseño o explico clases en Java, insisto mucho en usar métodos pequeños, nombres descriptivos y responsabilidades claras.

Constructores en Java: cómo inicializar objetos correctamente

Un constructor es un bloque especial que se ejecuta cuando creas un objeto con new.

Sirve para inicializar el estado inicial del objeto.

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;
}
}

El constructor tiene el mismo nombre que la clase y no declara tipo de retorno, ni siquiera void.

Producto teclado = new Producto("Teclado", 24.99, 10);

Esta línea crea un nuevo objeto Producto con nombre, precio y stock.

Constructor por defecto

Si una clase no define ningún constructor, Java proporciona automáticamente un constructor sin parámetros.

public class Alumno {
private String nombre;
}

En ese caso podrías hacer:

Alumno alumno = new Alumno();

Pero hay un detalle importante: si defines un constructor propio, Java deja de crear automáticamente el constructor por defecto.

public class Alumno {
private String nombre;

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

Ahora ya no podrías hacer esto, salvo que definas también un constructor sin parámetros:

Alumno alumno = new Alumno(); // Error si no existe constructor sin parámetros

Sobrecarga de constructores

Una clase puede tener varios constructores, siempre que tengan distinta lista de parámetros. Esto se llama sobrecarga de constructores.

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

public Alumno() {
this.nombre = "Sin nombre";
this.edad = 0;
}

public Alumno(String nombre) {
this.nombre = nombre;
this.edad = 0;
}

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

Esto te permite crear objetos de varias formas:

Alumno a1 = new Alumno();
Alumno a2 = new Alumno("Lucía");
Alumno a3 = new Alumno("Carlos", 19);

La sobrecarga de constructores es útil cuando tiene sentido permitir diferentes formas de inicializar un objeto.

Uso de this

La palabra this hace referencia al objeto actual.

Se usa mucho cuando un parámetro tiene el mismo nombre que un atributo.

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

En this.nombre, nos referimos al atributo del objeto. En nombre, nos referimos al parámetro recibido por el constructor.

Sin this, el código sería ambiguo o directamente no haría lo que esperamos.

Mi recomendación práctica: usa this en constructores y setters cuando el parámetro se llame igual que el atributo. Es una convención clara y muy habitual en clases en Java.

Encapsulación en Java: protege los datos de tus objetos

La encapsulación consiste en proteger los datos internos de un objeto y ofrecer métodos controlados para acceder a ellos o modificarlos.

En Java se consigue principalmente con modificadores de acceso:

ModificadorAcceso permitidoUso habitual
privateSolo dentro de la propia claseAtributos internos
publicDesde cualquier parte del programaMétodos de uso externo
protectedClase, paquete y clases heredadasHerencia
Sin modificadorAcceso de paqueteClases del mismo paquete

La norma general al crear clases en Java es sencilla: los atributos deberían ser private.

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

¿Por qué? Porque así evitamos que cualquier parte del programa modifique directamente el estado interno del objeto.

Por qué evitar atributos públicos

Imagina esta clase:

public class CuentaBancaria {
public double saldo;
}

Y ahora este uso:

CuentaBancaria cuenta = new CuentaBancaria();
cuenta.saldo = -5000;

El programa permite dejar una cuenta bancaria con saldo negativo sin ningún control. En una aplicación real esto sería peligrosísimo.

Con atributos privados, puedes obligar a que los cambios pasen por métodos que validen lo que ocurre.

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

public void ingresar(double cantidad) {
if (cantidad > 0) {
saldo += cantidad;
}
}

public boolean retirar(double cantidad) {
if (cantidad > 0 && cantidad <= saldo) {
saldo -= cantidad;
return true;
}
return false;
}

public double getSaldo() {
return saldo;
}
}

Ahora el saldo no se cambia directamente. Para modificarlo hay que usar ingresar() o retirar().

Eso es encapsulación bien aplicada.

Getters y setters bien usados

Un getter permite consultar un atributo privado.

public double getSaldo() {
return saldo;
}

Un setter permite modificarlo.

public void setTitular(String titular) {
if (titular != null && !titular.isBlank()) {
this.titular = titular;
}
}

Pero cuidado: no todos los atributos necesitan setter.

En una cuenta bancaria puede tener sentido consultar el saldo, pero no permitir cambiarlo directamente. Para modificar el saldo deberían existir métodos como ingresar() y retirar().

Esta diferencia es importante. Encapsular no significa crear getters y setters automáticamente para todo. Encapsular significa controlar cómo se usa el estado interno del objeto.

Encapsular no es complicar el código

A veces, cuando alguien empieza con clases en Java, piensa que poner atributos privados y crear métodos es “dar más vueltas”. Pero no es eso.

Encapsular es diseñar una puerta de entrada segura al objeto.

Si un objeto puede quedar en un estado inválido, tarde o temprano ese error aparecerá en otra parte del programa. Y cuando el proyecto crece, encontrar ese fallo puede ser complicado.

En mi experiencia, tanto desarrollando como enseñando, la encapsulación es uno de los conceptos que más ayudan a pasar de “mi código funciona” a “mi código está bien diseñado”.

Cómo crear y usar objetos propios desde main

Una vez definida una clase, podemos crear objetos desde otra clase. Muchas veces lo haremos desde una clase principal con main.

Por ejemplo:

public class Main {
public static void main(String[] args) {
CuentaBancaria cuenta = new CuentaBancaria("Ana", 100);
cuenta.ingresar(50);

boolean retiradaCorrecta = cuenta.retirar(30);

if (retiradaCorrecta) {
System.out.println("Retirada realizada");
}

System.out.println("Saldo actual: " + cuenta.getSaldo());
}
}

Este ejemplo muestra una idea muy importante: el main no debería hacerlo todo.

La cuenta sabe ingresar, retirar y consultar saldo. El programa principal simplemente usa esos comportamientos.

Instanciar objetos con new

Para crear un objeto usamos new.

Producto producto = new Producto("Ratón", 12.5, 20);

Aquí pasan varias cosas:

  1. Se reserva memoria para un nuevo objeto.
  2. Se llama al constructor de Producto.
  3. La variable producto guarda una referencia al objeto creado.

Esto es una de las bases de las clases en Java: defines una clase y después puedes crear objetos a partir de ella tantas veces como necesites.

Llamar a métodos de instancia

Una vez creado el objeto, puedes llamar a sus métodos.

producto.mostrarInformacion();
producto.aplicarDescuento(10);

Si el método pertenece al objeto y trabaja con su estado interno, lo normal es que sea un método de instancia.

Por ejemplo, aplicarDescuento() depende del precio de ese producto concreto. Por tanto, tiene sentido que sea un método del objeto producto.

Qué ocurre cuando dos referencias apuntan al mismo objeto

Este punto merece atención.

Producto p1 = new Producto("Ratón", 12.5, 20);
Producto p2 = p1;

p1 y p2 no son dos objetos distintos. Son dos referencias al mismo objeto.

Si modificas el objeto desde p2, también lo verás modificado desde p1.

p2.aplicarDescuento(10);

El descuento se aplica al objeto compartido.

Este detalle es fundamental cuando trabajas con listas, métodos que reciben objetos o estructuras más complejas. Entender referencias evita muchos errores al usar clases en Java.

Métodos y atributos estáticos en Java

Hasta ahora hemos hablado sobre todo de atributos y métodos de instancia. Es decir, elementos que pertenecen a cada objeto concreto.

Pero Java también permite definir miembros estáticos con static.

Los miembros estáticos pertenecen a la clase, no a cada objeto.

Método estático

Un método estático se puede llamar usando el nombre de la clase, sin crear un objeto.

public class Calculadora {
public static int sumar(int a, int b) {
return a + b;
}
}

Uso:

int resultado = Calculadora.sumar(3, 4);

Esto tiene sentido porque sumar() no depende del estado de una calculadora concreta. Solo recibe dos números y devuelve un resultado.

Atributo estático

Un atributo estático se comparte entre todos los objetos de la clase.

public class Alumno {
private static int totalAlumnos = 0;
private String nombre;

public Alumno(String nombre) {
this.nombre = nombre;
totalAlumnos++;
}

public static int getTotalAlumnos() {
return totalAlumnos;
}
}

Cada vez que se crea un objeto Alumno, aumenta totalAlumnos.

Alumno a1 = new Alumno("Lucía");
Alumno a2 = new Alumno("Carlos");

System.out.println(Alumno.getTotalAlumnos()); // 2

Ese contador no pertenece a Lucía ni a Carlos. Pertenece a la clase Alumno.

Por qué no conviene convertir todo en estático

Un error frecuente al aprender clases en Java es usar static para todo porque parece más cómodo.

Pero si una operación depende del estado de un objeto, no debería ser estática.

Por ejemplo, este método debería ser de instancia:

producto.aplicarDescuento(10);

Porque depende del precio del producto concreto.

En cambio, este método puede ser estático:

Calculadora.sumar(3, 4);

Porque no necesita recordar ningún estado interno.

La regla práctica es simple: si el método necesita datos de un objeto concreto, método de instancia. Si representa una operación general que no depende de ningún objeto, puede ser estático.

Paquetes y librerías de clases

Cuando un proyecto crece, no conviene tener todas las clases mezcladas en la misma carpeta.

Java permite organizar las clases en paquetes. Un paquete agrupa clases relacionadas y ayuda a evitar conflictos de nombres.

package modelo;

public class Producto {
private String nombre;
private double precio;
}

Después, desde otra clase, puedes importar Producto si está en otro paquete.

import modelo.Producto;

public class Main {
public static void main(String[] args) {
Producto p = new Producto();
}
}

Cómo organizar clases en paquetes

Una estructura sencilla podría ser:

PaqueteContenido posible
modeloClases del dominio: Producto, Cliente, Pedido
servicioLógica de negocio: ProductoService, PedidoService
vistaInterfaz o salida de datos
utilClases auxiliares reutilizables

Esta organización ayuda a que el proyecto sea más mantenible.

En proyectos reales, tener criterio al organizar paquetes es tan importante como saber escribir clases. Puedes tener clases en Java técnicamente correctas, pero si el proyecto está mal organizado, mantenerlo se vuelve incómodo.

Qué es una librería de clases

Una librería de clases es un conjunto de clases reutilizables que puedes incorporar en distintos proyectos.

Java incluye muchas librerías estándar. Algunas muy habituales son:

Librería o paqueteUso
java.langClases básicas como String, Math, System
java.utilUtilidades como Scanner, ArrayList, Random, HashMap
java.timeFechas y horas modernas
java.ioEntrada y salida clásica de datos
java.nioManejo moderno de archivos y entrada/salida

Cuando usas String, Scanner o ArrayList, ya estás usando clases creadas por otras personas. El siguiente paso es aprender a crear tus propias clases en Java para resolver tus propios problemas.

Herencia e interfaces: primeros pasos

La herencia y las interfaces son conceptos más avanzados, pero conviene entender su idea básica desde el principio.

Cuándo tiene sentido usar herencia

La herencia permite crear una clase nueva a partir de otra clase existente.

La clase original se llama superclase o clase padre. La nueva se llama subclase o clase hija.

public class Persona {
private String nombre;

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

public void saludar() {
System.out.println("Hola, soy " + nombre);
}
}

Ahora podemos crear una clase Alumno que herede de Persona.

public class Alumno extends Persona {
private String ciclo;

public Alumno(String nombre, String ciclo) {
super(nombre);
this.ciclo = ciclo;
}
}

La palabra extends indica herencia. La llamada super(nombre) llama al constructor de la superclase.

La herencia debe usarse cuando existe una relación clara de tipo “es un”.

Ejemplos correctos:

  • un Alumno es una Persona;
  • un Profesor es una Persona;
  • un Libro puede ser un RecursoBiblioteca.

Ejemplos incorrectos:

  • un Producto tiene un precio, pero no es un precio;
  • un Pedido contiene productos, pero no es un producto.

No uses herencia solo para reutilizar código. Primero debe existir una relación conceptual correcta.

Sobrescritura de métodos con @Override

Una subclase puede redefinir un método heredado. Eso se llama sobrescritura de métodos.

public class Profesor extends Persona {
private String modulo;

public Profesor(String nombre, String modulo) {
super(nombre);
this.modulo = modulo;
}

@Override
public void saludar() {
System.out.println("Hola, soy profesor de " + modulo);
}
}

La anotación @Override ayuda al compilador a comprobar que realmente estamos sobrescribiendo un método de la clase padre.

Qué es una interfaz y cuándo usarla

Una interfaz define un contrato. Indica qué métodos debe implementar una clase.

public interface Imprimible {
void imprimir();
}

Una clase puede implementar esa interfaz:

public class Factura implements Imprimible {
private String numero;

public Factura(String numero) {
this.numero = numero;
}

@Override
public void imprimir() {
System.out.println("Factura número: " + numero);
}
}

La clase Factura se compromete a implementar el método imprimir().

Una forma sencilla de diferenciar herencia e interfaz es esta:

  • la herencia representa una relación “es un”;
  • la interfaz representa una capacidad o contrato.

Por ejemplo, un Perro puede heredar de Animal, porque un perro es un animal. Además, podría implementar una interfaz Entrenable, porque puede tener esa capacidad.

Ejemplo completo: clase Libro para una biblioteca sencilla

Vamos a unir varias ideas de clases en Java con un ejemplo práctico: una aplicación sencilla para gestionar libros.

No buscamos crear una biblioteca real completa, sino practicar conceptos como:

  • clase propia;
  • atributos privados;
  • constructor;
  • métodos de instancia;
  • atributo estático;
  • método estático;
  • interfaz;
  • uso desde una clase principal.

Análisis del caso

Queremos representar libros. Cada libro tendrá:

  • título;
  • autor;
  • ISBN;
  • disponibilidad.

Y podrá realizar operaciones como:

  • prestarse;
  • devolverse;
  • mostrar o imprimir su información.

Además, queremos contar cuántos libros se han creado.

Interfaz Imprimible

Primero podemos crear una interfaz:

public interface Imprimible {
void imprimir();
}

Esta interfaz obliga a que cualquier clase que la implemente tenga un método imprimir().

Clase Libro

Ahora creamos la clase Libro.

public class Libro implements Imprimible {
private static int totalLibros = 0;

private String titulo;
private String autor;
private String isbn;
private boolean disponible;

public Libro(String titulo, String autor, String isbn) {
this.titulo = titulo;
this.autor = autor;
this.isbn = isbn;
this.disponible = true;
totalLibros++;
}

public boolean prestar() {
if (disponible) {
disponible = false;
return true;
}
return false;
}

public void devolver() {
disponible = true;
}

public boolean isDisponible() {
return disponible;
}

public static int getTotalLibros() {
return totalLibros;
}

@Override
public void imprimir() {
System.out.println("Título: " + titulo);
System.out.println("Autor: " + autor);
System.out.println("ISBN: " + isbn);
System.out.println("Disponible: " + disponible);
}
}

Esta clase reúne muchos elementos importantes:

  • totalLibros es un atributo estático;
  • titulo, autor, isbn y disponible son atributos privados;
  • el constructor inicializa el libro;
  • prestar() modifica el estado si el libro está disponible;
  • devolver() vuelve a marcarlo como disponible;
  • getTotalLibros() permite consultar el contador;
  • imprimir() implementa el método de la interfaz Imprimible.

Clase principal

Ahora podemos usar la clase desde main.

public class MainBiblioteca {
public static void main(String[] args) {
Libro libro1 = new Libro("El Quijote", "Miguel de Cervantes", "123-A");
Libro libro2 = new Libro("Clean Code", "Robert C. Martin", "456-B");

libro1.imprimir();

if (libro1.prestar()) {
System.out.println("Libro prestado correctamente");
} else {
System.out.println("El libro no está disponible");
}

System.out.println("Total de libros creados: " + Libro.getTotalLibros());
}
}

Fíjate en el diseño: el main no necesita saber cómo se presta internamente un libro. Solo llama a prestar() y recibe un resultado.

Esto es diseñar con clases en Java: cada clase se encarga de su parte.

Mejoras posibles

Este ejemplo se puede ampliar de muchas formas:

  • validar que título, autor e ISBN no estén vacíos;
  • crear una clase Usuario;
  • crear una clase Biblioteca;
  • guardar una colección de libros;
  • añadir fecha de préstamo;
  • separar clases en paquetes como modelo y app;
  • crear pruebas manuales para comprobar prestar() y devolver().

Aquí es donde las clases en Java empiezan a tener todavía más sentido. Cuando el programa crece, cada nueva responsabilidad puede convertirse en una clase o método bien diseñado, en vez de acabar mezclada dentro del main.

Buenas prácticas al diseñar clases en Java

Crear clases en Java no consiste solo en saber escribir public class. También hay que diseñarlas bien.

Estas buenas prácticas ayudan mucho:

Buena prácticaPor qué importa
Un archivo por clase públicaFacilita localizar y mantener el código
Nombre de clase claroRepresenta una entidad o concepto del problema
Atributos privadosProtegen el estado interno del objeto
Métodos con nombres descriptivosEl código se entiende mejor
Constructores válidosEl objeto nace en un estado coherente
Evitar clases gigantesCada clase debería tener una responsabilidad clara
Evitar un main enormeEl main debería coordinar, no contener toda la lógica
Usar paquetesAyuda a organizar proyectos grandes
Documentar lo importanteAclara decisiones, no repite lo evidente

Una clase debe tener un propósito claro

Si una clase necesita explicar demasiadas cosas diferentes, probablemente tiene demasiadas responsabilidades.

Por ejemplo, una clase Producto debería representar un producto. No debería gestionar usuarios, imprimir facturas, conectarse a una base de datos y calcular estadísticas de ventas al mismo tiempo.

Cuando una clase empieza a crecer demasiado, suele ser buena señal de que hay que dividir responsabilidades.

En consultoría y desarrollo real, esto es muy habitual: al principio se crea una clase “rápida” para resolver algo, luego se le añade otra cosa, luego otra, y cuando quieres darte cuenta tienes una clase enorme que nadie quiere tocar.

Por eso, cuanto antes aprendas a diseñar clases en Java con responsabilidades claras, más fácil será mantener tus programas.

El main no debería hacerlo todo

Uno de los errores más típicos en principiantes es meter toda la lógica dentro del método main.

El main debería coordinar el programa, no contenerlo todo.

Mal enfoque:

public class Main {
public static void main(String[] args) {
// Crear datos
// Validar datos
// Calcular precios
// Mostrar resultados
// Gestionar stock
// Guardar información
}
}

Mejor enfoque:

Producto producto = new Producto("Teclado", 24.99, 10);
producto.aplicarDescuento(10);
producto.mostrarInformacion();

La diferencia es que ahora el comportamiento está dentro de la clase que corresponde.

Nombres que explican el código

Los nombres importan mucho.

No es lo mismo esto:

public void metodo1() {
}

Que esto:

public boolean hayStock() {
return stock > 0;
}

Un buen nombre reduce la necesidad de comentarios. Cuando una clase, atributo o método tiene un nombre claro, el código se vuelve mucho más fácil de leer.

Errores frecuentes al crear clases en Java

Aprender clases en Java implica cometer errores. Lo importante es detectarlos pronto.

Poner todos los atributos como public

Este es uno de los errores más habituales.

public class Producto {
public String nombre;
public double precio;
public int stock;
}

Funciona, sí. Pero permite modificar el objeto desde cualquier parte y con cualquier valor.

producto.precio = -100;
producto.stock = -50;

Mejor usar atributos privados y métodos controlados.

Crear clases sin comportamiento

Otro error típico es crear clases que solo tienen datos.

public class Producto {
private String nombre;
private double precio;
private int stock;
}

No siempre está mal, pero si toda la lógica del producto queda fuera, se pierde parte del sentido de trabajar con objetos.

Una clase Producto podría tener métodos como:

hayStock()
aplicarDescuento()
vender()
mostrarInformacion()

Así el comportamiento relacionado con el producto vive dentro del propio producto.

Usar static para todo

static puede parecer cómodo, pero abusar de él hace que el programa deje de aprovechar objetos reales.

Si todo es estático, no estás modelando objetos con estado. Estás escribiendo funciones globales agrupadas en clases.

Usa static cuando algo pertenezca a la clase en general, no a un objeto concreto.

Constructores incompletos

Un constructor debería dejar el objeto en un estado válido.

Evita crear objetos con datos importantes sin inicializar, especialmente si después pueden provocar errores con null o valores inválidos.

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

Y si quieres mejorar todavía más, puedes validar:

public Producto(String nombre, double precio, int stock) {
if (nombre == null || nombre.isBlank()) {
throw new IllegalArgumentException("El nombre no puede estar vacío");
}

if (precio < 0) {
throw new IllegalArgumentException("El precio no puede ser negativo");
}

if (stock < 0) {
throw new IllegalArgumentException("El stock no puede ser negativo");
}

this.nombre = nombre;
this.precio = precio;
this.stock = stock;
}

Herencia mal planteada

No uses herencia solo porque quieres reutilizar código.

Antes de usar extends, pregúntate si existe una relación “es un”.

  • Alumno es una Persona: bien.
  • Pedido es un Producto: mal.
  • Pedido contiene productos: eso no es herencia.

Muchas veces, la composición es mejor que la herencia. Es decir, una clase puede tener objetos de otras clases como atributos.

Métodos demasiado largos

Un método largo suele esconder varias responsabilidades.

Si un método calcula, valida, muestra, guarda y modifica datos, probablemente deberías dividirlo.

calcular()
validar()
mostrar()
guardar()

Los métodos pequeños son más fáciles de probar, corregir y entender.

Ejercicios para practicar clases en Java

La mejor forma de aprender clases en Java es practicar. Aquí tienes varios ejercicios progresivos.

Ejercicio 1: clase Producto

Crea una clase Producto con:

  • nombre;
  • precio;
  • stock.

Debe incluir:

  • constructor;
  • getters;
  • método hayStock();
  • método vender(int cantidad);
  • método mostrarInformacion().

Ejemplo:

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 hayStock() {
return stock > 0;
}

public boolean vender(int cantidad) {
if (cantidad > 0 && cantidad <= stock) {
stock -= cantidad;
return true;
}
return false;
}

public void mostrarInformacion() {
System.out.println(nombre + " - " + precio + " euros - Stock: " + stock);
}
}

Ejercicio 2: clase Alumno

Crea una clase Alumno con:

  • nombre;
  • edad;
  • notaMedia.

Añade un método haAprobado() que devuelva true si la nota media es mayor o igual que 5.

public boolean haAprobado() {
return notaMedia >= 5;
}

Después, crea varios alumnos desde una clase Main y muestra si han aprobado o no.

Ejercicio 3: clase CuentaBancaria

Diseña una clase CuentaBancaria con:

  • titular;
  • IBAN;
  • saldo.

Los atributos deben ser privados.

Incluye:

  • constructor;
  • getters;
  • ingresar();
  • retirar();
  • mostrarResumen().

La retirada solo debe permitirse si la cantidad es positiva y no supera el saldo.

Este ejercicio es perfecto para practicar encapsulación en clases en Java.

Ejercicio 4: clase Videojuego

Crea una clase Videojuego con:

  • título;
  • plataforma;
  • PEGI;
  • precio;
  • completado.

Añade métodos:

  • aplicarDescuento();
  • marcarCompletado();
  • mostrarFicha().

Crea tres objetos desde main y prueba sus métodos.

Ejercicio 5: herencia básica

Crea una clase Persona con:

  • nombre;
  • email.

Después crea dos clases heredadas:

  • Alumno;
  • Profesor.

Cada subclase debe añadir al menos un atributo propio y sobrescribir un método mostrarPerfil().

Ejercicio 6: interfaz Exportable

Define una interfaz Exportable con un método:

String exportarTexto();

Después crea una clase Producto que implemente la interfaz y devuelva una línea de texto con sus datos principales.

public interface Exportable {
String exportarTexto();
}
public class Producto implements Exportable {
private String nombre;
private double precio;

@Override
public String exportarTexto() {
return nombre + ";" + precio;
}
}

Este ejercicio ayuda a entender que una interfaz define un contrato que una clase debe cumplir.

Conclusión: programar con clases es diseñar mejor

Aprender clases en Java no es solo aprender una sintaxis nueva. Es aprender a organizar mejor los programas.

Una clase permite representar conceptos del problema mediante atributos, métodos y constructores. Los objetos son instancias concretas de esas clases y permiten trabajar con datos y comportamientos relacionados.

También has visto la importancia de la encapsulación. Usar atributos private y métodos públicos adecuados ayuda a evitar estados inválidos y mejora el mantenimiento del código. Además, has dado los primeros pasos con métodos estáticos, atributos estáticos, paquetes, librerías, herencia e interfaces.

Si tuviera que resumirlo en una idea, sería esta: programar con clases en Java no consiste en repartir código al azar en varios archivos. Consiste en diseñar tipos propios con responsabilidades claras, datos protegidos y métodos que representen operaciones reales del problema.

Y esto, cuando empiezas, puede parecer una capa extra de complejidad. Pero cuando el programa crece, es justo lo contrario: las clases bien diseñadas hacen que el código sea más claro, más mantenible y más profesional.

Preguntas frecuentes sobre clases en Java

¿Qué es una clase en Java?

Una clase en Java es una plantilla que define cómo serán los objetos de un determinado tipo. Incluye atributos, métodos y, normalmente, constructores.

¿Qué diferencia hay entre clase y objeto?

La clase es el modelo. El objeto es un elemento concreto creado a partir de ese modelo.

Por ejemplo, Producto es una clase. Un producto concreto llamado “Teclado” con precio 24.99 y stock 10 es un objeto.

¿Qué es una instancia?

Una instancia es un objeto concreto creado a partir de una clase. En la práctica, objeto e instancia suelen usarse como términos muy parecidos.

¿Qué es un atributo en Java?

Un atributo es un dato interno de una clase. Representa parte del estado de un objeto.

Por ejemplo, en una clase Alumno, los atributos podrían ser nombre, edad y notaMedia.

¿Qué es un método en Java?

Un método es un bloque de código que representa una acción u operación. Los métodos definen el comportamiento de una clase.

Por ejemplo, mostrarInformacion(), hayStock() o aplicarDescuento().

¿Para qué sirve un constructor?

Un constructor sirve para inicializar un objeto cuando se crea con new. Tiene el mismo nombre que la clase y no declara tipo de retorno.

¿Qué es this en Java?

this hace referencia al objeto actual. Se usa mucho para diferenciar atributos y parámetros cuando tienen el mismo nombre.

this.nombre = nombre;

¿Por qué los atributos deberían ser private?

Porque así se protege el estado interno del objeto. Si los atributos son públicos, cualquier parte del programa puede modificarlos sin control.

¿Cuándo usar getters y setters?

Usa getters cuando tenga sentido consultar un atributo. Usa setters solo cuando tenga sentido permitir modificarlo directamente y puedas controlar los valores recibidos.

No todos los atributos necesitan setter.

¿Qué diferencia hay entre método de instancia y método estático?

Un método de instancia pertenece a un objeto concreto y puede usar su estado interno. Un método estático pertenece a la clase y puede llamarse sin crear un objeto.

¿Cuándo conviene usar herencia?

Conviene usar herencia cuando existe una relación clara de tipo “es un”. Por ejemplo, un Alumno es una Persona.

No conviene usar herencia solo para reutilizar código.

¿Para qué sirve una interfaz?

Una interfaz sirve para definir un contrato. Indica qué métodos debe implementar una clase.

Por ejemplo, una interfaz Imprimible puede obligar a que las clases que la implementen tengan un método imprimir().

julian lopez jimenez

Hola, encantado de conocerte.

Regístrate para recibir las últimas entradas, cada domingo.

¡No hago spam!

Recibe nuevas entradas cada semana

Una seleccion de articulos, recursos y novedades sobre informatica, FP y tecnologia aplicada.

julian lopez jimenez

Hola, encantado de conocerte.

Regístrate para recibir las últimas entradas, cada domingo.

¡No hago spam!

Tambien te puede interesar