Arrays en Java — práctica con una tienda, una matriz y las notas de clase (y cómo no estropear el original)
Hace un tiempo hice un método para invertir un array. Lo probé, me imprimió el resultado perfecto y yo, contentísimo, imprimí también el array original para comparar… y estaba invertido. Mi array «de antes» había desaparecido. Tardé un rato en entenderlo: mi método no había trabajado con una copia, había dado la vuelta al mismo array que le pasé. Ese tropiezo me enseñó más sobre arrays que cualquier definición, y aparece varias veces en esta práctica.
Si ya has leído la teoría de arrays, este artículo es para aplicarla con tres programas completos: un inventario de tienda, operaciones con matrices y estadísticas de unas notas. Todos son de dificultad media y están pensados para que los copies, los ejecutes y los rompas a propósito para ver qué pasa. Cada uno trae el código comentado, la salida y las dudas que suelen surgir.
Tabla de Contenidos
Antes de empezar
Para seguir la práctica conviene tener claro:
- Cómo se declara y crea un array (
{...}ynew) y quelengthno lleva paréntesis. - Que un array se pasa a un método por referencia: el método puede modificar el original.
- Cómo se recorre con
fory con for-each, y cómo se recorre una matriz con dos bucles.
Si alguno de estos puntos te suena a chino, vuelve un momento a la teoría. Lo agradecerás en el segundo programa.
Arrays en Java práctica — Programa 1: la tienda (arrays paralelos)
Vamos a llevar el inventario de una papelería. Tenemos cinco productos, cada uno con su nombre, su precio y sus unidades en stock. El programa tiene que listar el inventario, vender productos (comprobando que existen y que hay stock), calcular el valor total y avisar de los agotados.
La idea: arrays paralelos
Usamos tres arrays del mismo tamaño en los que la posición i describe siempre el mismo producto: productos[i], precios[i] y stock[i]. Es una técnica muy común cuando todavía no se han visto las clases (más adelante se resolverá con objetos, pero primero conviene dominar esto).
Antes de programar, piensa qué hace falta saber para cada operación. Para vender «Mochila», lo primero es saber en qué posición está. Por eso empezamos por un método buscar que devuelve esa posición, y todos los demás lo reutilizan.
El código
public class Tienda {
// Devuelve la posición del producto o -1 si no existe
static int buscar(String[] productos, String nombre) {
for (int i = 0; i < productos.length; i++) {
if (productos[i].equals(nombre)) { // equals, no ==: son String
return i;
}
}
return -1; // -1 nunca es una posición válida
}
// Intenta vender. Devuelve true si la venta se hizo.
static boolean vender(String[] productos, int[] stock, String nombre, int unidades) {
int pos = buscar(productos, nombre);
if (pos == -1) {
System.out.println(" No existe el producto: " + nombre);
return false;
}
if (stock[pos] < unidades) {
System.out.println(" Stock insuficiente de " + nombre + ": quedan " + stock[pos]);
return false;
}
stock[pos] -= unidades; // modifica el array original: no hace falta devolverlo
return true;
}
// Suma precio * unidades de cada producto
static double valorInventario(double[] precios, int[] stock) {
double total = 0;
for (int i = 0; i < precios.length; i++) {
total += precios[i] * stock[i];
}
return total;
}
static void listar(String[] productos, double[] precios, int[] stock) {
for (int i = 0; i < productos.length; i++) {
System.out.printf(" %-10s %6.2f € %3d uds%n", productos[i], precios[i], stock[i]);
}
}
static void mostrarAgotados(String[] productos, int[] stock) {
boolean hayAgotados = false;
for (int i = 0; i < stock.length; i++) {
if (stock[i] == 0) {
System.out.println(" Agotado: " + productos[i]);
hayAgotados = true;
}
}
if (!hayAgotados) {
System.out.println(" No hay productos agotados");
}
}
public static void main(String[] args) {
// Arrays paralelos: la posición i describe el mismo producto en los tres
String[] productos = {"Cuaderno", "Boli", "Mochila", "Regla", "Carpeta"};
double[] precios = {2.50, 0.90, 24.00, 1.20, 3.75};
int[] stock = {10, 50, 3, 0, 8};
System.out.println("Inventario inicial:");
listar(productos, precios, stock);
System.out.printf("Valor total: %.2f €%n%n", valorInventario(precios, stock));
System.out.println("Ventas:");
vender(productos, stock, "Mochila", 2);
vender(productos, stock, "Mochila", 2);
vender(productos, stock, "Compás", 1);
vender(productos, stock, "Cuaderno", 10);
System.out.println("\nInventario final:");
listar(productos, precios, stock);
System.out.printf("Valor total: %.2f €%n", valorInventario(precios, stock));
mostrarAgotados(productos, stock);
}
}
Salida
Inventario inicial: Cuaderno 2,50 € 10 uds Boli 0,90 € 50 uds Mochila 24,00 € 3 uds Regla 1,20 € 0 uds Carpeta 3,75 € 8 uds Valor total: 172,00 € Ventas: Stock insuficiente de Mochila: quedan 1 No existe el producto: Compás Inventario final: Cuaderno 2,50 € 0 uds Boli 0,90 € 50 uds Mochila 24,00 € 1 uds Regla 1,20 € 0 uds Carpeta 3,75 € 8 uds Valor total: 99,00 € Agotado: Cuaderno Agotado: Regla
Fíjate en las ventas: la primera de Mochila (2 unidades) sale bien y no imprime nada; la segunda falla porque solo quedaba 1; «Compás» no existe; y la de Cuaderno vende las 10 unidades y lo deja agotado.
printf usa el formato de tu idioma: con el sistema en español verás 2,50; con el sistema en inglés, 2.50. No es un fallo, es el separador decimal de tu configuración.
Preguntas que te puedes hacer
¿Por qué equals y no == en buscar? Porque == compara direcciones, no contenido, igual que pasaba con los arrays. Con los String hay que comparar con equals.
¿Por qué devuelve -1 si no lo encuentra? Porque -1 no es nunca un índice válido, así que quien llame al método puede distinguir «está en la posición 0» de «no está». Es una convención muy habitual en Java (binarySearch hace algo parecido).
¿Por qué vender no devuelve el stock actualizado? No hace falta: recibe la dirección del array stock, así que stock[pos] -= unidades cambia el array original. Esto es justo lo que vimos en «Arrays y métodos».
¿Y si hay dos productos con el mismo nombre? buscar devolvería siempre el primero. En una tienda real eso sería un problema y se usarían códigos únicos; aquí asumimos que los nombres no se repiten.
El fallo típico de este programa: tener los tres arrays con distinto tamaño (por ejemplo, un precio de más o de menos). Ningún error te avisa al compilar; en cuanto recorres con precios.length y accedes a stock[i], saltará una ArrayIndexOutOfBoundsException. Si vas a usar arrays paralelos, comprueba siempre que miden lo mismo.
Pruébalo tú
- Añade un método
reponer(productos, stock, nombre, unidades)que sume unidades al stock. - Añade
productoMasCaro(productos, precios)que devuelva el nombre del más caro (pista: busca la posición del máximo, como en la teoría).
Arrays en Java práctica — Programa 2: operaciones con matrices
Ahora, tablas. Vamos a trabajar con una matriz de enteros (imagina las ventas de tres tiendas durante cuatro semanas) y a calcular la suma por filas, la suma por columnas, la suma de la diagonal y la transpuesta. De paso, un método que comprueba si una matriz es rectangular, porque en Java una «tabla» no tiene por qué serlo.
La idea
Una matriz es un array de arrays: m.length es el número de filas y m[i].length el de columnas de la fila i. La suma por filas guarda su resultado en un array con una posición por fila; la de columnas, en uno con una posición por columna. Entender qué índice manda en cada caso es la clave del programa.
El código
import java.util.Arrays;
public class Matriz {
// Comprueba que todas las filas tienen la misma longitud
static boolean esRectangular(int[][] m) {
for (int i = 1; i < m.length; i++) {
if (m[i].length != m[0].length) {
return false;
}
}
return true;
}
static int[] sumaFilas(int[][] m) {
int[] sumas = new int[m.length]; // una suma por fila
for (int f = 0; f < m.length; f++) {
for (int c = 0; c < m[f].length; c++) {
sumas[f] += m[f];
}
}
return sumas;
}
static int[] sumaColumnas(int[][] m) {
int[] sumas = new int[m[0].length]; // una suma por columna: usamos la longitud de la fila 0
for (int f = 0; f < m.length; f++) {
for (int c = 0; c < m[f].length; c++) {
sumas += m[f]; // la suma va a la posición de la COLUMNA
}
}
return sumas;
}
// Solo tiene sentido en matrices cuadradas
static int sumaDiagonal(int[][] m) {
int suma = 0;
for (int i = 0; i < m.length; i++) {
suma += m[i][i];
}
return suma;
}
// Filas y columnas intercambiadas: la original no se toca
static int[][] transpuesta(int[][] m) {
int[][] t = new int[m[0].length][m.length]; // ¡al revés! columnas x filas
for (int f = 0; f < m.length; f++) {
for (int c = 0; c < m[f].length; c++) {
t[f] = m[f];
}
}
return t;
}
static void imprimir(int[][] m) {
for (int[] fila : m) {
for (int valor : fila) {
System.out.printf("%4d", valor);
}
System.out.println();
}
}
public static void main(String[] args) {
int[][] ventas = {
{ 1, 2, 3, 4},
{ 5, 6, 7, 8},
{ 9, 10, 11, 12}
};
System.out.println("Matriz 3x4:");
imprimir(ventas);
System.out.println("¿Rectangular? " + esRectangular(ventas));
System.out.println("Suma por filas: " + Arrays.toString(sumaFilas(ventas)));
System.out.println("Suma por columnas: " + Arrays.toString(sumaColumnas(ventas)));
System.out.println("\nTranspuesta (4x3):");
imprimir(transpuesta(ventas));
int[][] cuadrada = {
{2, 0, 1},
{3, 5, 4},
{1, 6, 9}
};
System.out.println("\nSuma de la diagonal de la cuadrada: " + sumaDiagonal(cuadrada));
int[][] irregular = {
{1, 2, 3},
{4, 5},
{6}
};
System.out.println("¿Rectangular la irregular? " + esRectangular(irregular));
}
}
Salida
Matriz 3x4: 1 2 3 4 5 6 7 8 9 10 11 12 ¿Rectangular? true Suma por filas: [10, 26, 42] Suma por columnas: [15, 18, 21, 24] Transpuesta (4x3): 1 5 9 2 6 10 3 7 11 4 8 12 Suma de la diagonal de la cuadrada: 16 ¿Rectangular la irregular? false
Comprueba a mano la primera fila: 1 + 2 + 3 + 4 = 10 ✔. Y la primera columna: 1 + 5 + 9 = 15 ✔. Y la diagonal de la cuadrada: 2 + 5 + 9 = 16 ✔.
Preguntas que te puedes hacer
¿Por qué en sumaFilas el resultado va en sumas[f] y en sumaColumnas en sumas? Es la clave de todo el programa. Cada suma debe acumularse en la posición que corresponde a lo que estás sumando: si sumas por filas, mandas la fila f; si sumas por columnas, manda la columna c. Si te equivocas de índice, no hay error: el programa da números mal y ya está.
¿Por qué sumaColumnas crea el array con m[0].length? Porque necesita saber cuántas columnas hay antes de empezar, y la forma de saberlo es mirar la longitud de una fila. Por eso funciona bien con matrices rectangulares, y por eso existe esRectangular: con la matriz irregular, ese método fallaría o daría resultados sin sentido.
¿Por qué en transpuesta el array se crea con [m[0].length][m.length]? Porque la transpuesta intercambia filas y columnas: una matriz de 3 filas × 4 columnas se convierte en una de 4 filas × 3 columnas. Y por eso la asignación es t[f] = m[f]: lo que estaba en la fila f, columna c, pasa a la fila c, columna f.
¿Por qué transpuesta crea una matriz nueva en vez de modificar la original? Porque con matrices no cuadradas no se puede transponer «en el sitio»: el tamaño cambia (3×4 pasa a 4×3), y un array no puede cambiar de tamaño. Además, así la original sigue intacta, que es justo lo que te enseñaba mi anécdota.
Probar sumaDiagonal con la matriz de 3×4. Como m[i][i] con i = 3 no existe (la fila 3 no existe en una matriz de 3 filas), o bien se sale de columnas o bien da un resultado que no significa nada. La diagonal solo tiene sentido en matrices cuadradas: si quieres hacerlo bien, comprueba antes que m.length == m[0].length.
Cuando escribas dos bucles anidados, nombra las variables f (fila) y c (columna), o fila y col, en vez de i y j. Parece un detalle, pero es la forma más rápida de no intercambiarlas sin darte cuenta.
Pruébalo tú
- Escribe
sumaTotal(m)que devuelva la suma de todos los elementos. - Escribe
filaMayorSuma(m)que devuelva el índice de la fila con mayor suma (en nuestro ejemplo, la 2).
Arrays en Java práctica — Programa 3: estadísticas de unas notas
El último programa es el más «de examen»: dado un array con las notas de un grupo (de 0 a 10), calcular la media, la mediana, la moda, cuántos alumnos están por encima de la media y dibujar un histograma con asteriscos.
La idea
Hay dos cosas interesantes aquí:
- La mediana necesita el array ordenado, pero no queremos ordenar el original (acuérdate de mi anécdota). Solución: trabajar sobre una copia.
- La moda se calcula con un array de frecuencias. Como las notas van de 0 a 10, podemos crear un array de 11 posiciones donde la posición
ncuenta cuántas veces aparece la notan. Es decir, la nota se usa como índice. Es un truco muy elegante y sirve también para el histograma.
El código
import java.util.Arrays;
public class Estadisticas {
static double media(int[] datos) {
int suma = 0;
for (int n : datos) {
suma += n;
}
return (double) suma / datos.length;
}
// Trabaja sobre una COPIA: ordenar el original sería un efecto secundario no deseado
static double mediana(int[] datos) {
int[] orden = Arrays.copyOf(datos, datos.length);
Arrays.sort(orden);
int n = orden.length;
if (n % 2 == 1) {
return orden[n / 2]; // impar: el del medio
}
return (orden[n / 2 - 1] + orden[n / 2]) / 2.0; // par: media de los dos del medio
}
// Notas de 0 a 10: frecuencias[n] cuenta cuántas veces aparece la nota n
static int[] frecuencias(int[] notas) {
int[] frec = new int[11];
for (int nota : notas) {
frec[nota]++; // la nota ES el índice
}
return frec;
}
// Devuelve la nota más repetida (si hay empate, la menor)
static int moda(int[] notas) {
int[] frec = frecuencias(notas);
int mejor = 0;
for (int nota = 1; nota < frec.length; nota++) {
if (frec[nota] > frec[mejor]) { // > estricto: en empate gana la menor
mejor = nota;
}
}
return mejor;
}
static int contarSuperiores(int[] datos, double umbral) {
int cuenta = 0;
for (int n : datos) {
if (n > umbral) {
cuenta++;
}
}
return cuenta;
}
static void histograma(int[] notas) {
int[] frec = frecuencias(notas);
for (int nota = 0; nota < frec.length; nota++) {
System.out.printf("%2d | ", nota);
for (int i = 0; i < frec[nota]; i++) {
System.out.print("*");
}
System.out.println();
}
}
public static void main(String[] args) {
int[] notas = {7, 5, 9, 4, 10, 6, 7, 3, 8, 7, 5, 10, 2, 7, 6};
System.out.println("Notas: " + Arrays.toString(notas));
System.out.printf("Media: %.2f%n", media(notas));
System.out.println("Mediana: " + mediana(notas));
System.out.println("Moda: " + moda(notas));
System.out.println("Por encima de la media: " + contarSuperiores(notas, media(notas)));
// Prueba de que mediana no ha tocado el original
System.out.println("Notas: " + Arrays.toString(notas));
System.out.println("\nHistograma:");
histograma(notas);
// Con número par de datos
int[] pares = {4, 8, 6, 2};
System.out.println("\nMediana de " + Arrays.toString(pares) + " = " + mediana(pares));
}
}
Salida
Notas: [7, 5, 9, 4, 10, 6, 7, 3, 8, 7, 5, 10, 2, 7, 6] Media: 6,40 Mediana: 7.0 Moda: 7 Por encima de la media: 8 Notas: [7, 5, 9, 4, 10, 6, 7, 3, 8, 7, 5, 10, 2, 7, 6] Histograma: 0 | 1 | 2 | * 3 | * 4 | * 5 | ** 6 | ** 7 | **** 8 | * 9 | * 10 | ** Mediana de [4, 8, 6, 2] = 5.0
Mira la línea de «Notas» antes y después de calcular la mediana: idénticas. Eso es lo que buscábamos al trabajar con una copia. Puedes comprobarlo a mano: la suma de las 15 notas es 96, y 96 / 15 = 6,4 ✔. Ordenadas, la octava (la del medio) es un 7 ✔.
Preguntas que te puedes hacer
¿Qué pasaría si mediana ordenara directamente datos? Que Arrays.sort(datos) modificaría el array original, y en main la línea «Notas» mostraría los datos ya ordenados. Es exactamente el fallo de mi anécdota: un método que cambia algo que no debía. La versión incorrecta sería esta:
// ❌ MAL: ordena el array que le pasan, el original queda alterado
static double mediana(int[] datos) {
Arrays.sort(datos);
...
}
¿Por qué frec[nota]++ funciona? Porque la nota (un int entre 0 y 10) es un índice válido de un array de 11 posiciones. Cada vez que aparece una nota, sumamos uno en su casilla. Fíjate que el array tiene 11 posiciones, no 10: para incluir el 0 y el 10 hacen falta once.
¿Por qué moda usa > y no >=? Con > estricto, si dos notas empatan en frecuencia, nos quedamos con la primera que encontramos, que es la menor. Si cambias a >=, ganaría la mayor. Probar esto es un buen ejercicio para entender cómo afecta el operador.
¿Y si una nota está fuera de rango, por ejemplo un 11? frec[11] no existe y saltaría ArrayIndexOutOfBoundsException. En un programa real validarías las notas antes (o harías frec más grande). Aquí asumimos datos correctos para centrarnos en la técnica.
¿Por qué la mediana sale 7.0 y no 7? Porque el método devuelve double: en el caso par (4, 8, 6, 2 → 5.0) la mediana puede tener decimales, y un método debe devolver siempre el mismo tipo.
Calcular la media con división entera. Si suma y datos.length son int, suma / datos.length da 6 en vez de 6,4. Por eso el programa convierte con (double) suma. (Ya lo viste en la teoría con el ejemplo de media.)
La moda con un array de frecuencias solo funciona cuando los valores son pequeños y enteros (como las notas de 0 a 10). Si los datos pudieran ser cualquier número, necesitarías otras estructuras, como las colecciones que verás más adelante.
Pruébalo tú
- Añade
minimo(datos)ymaximo(datos)y muestra el rango (máximo − mínimo). - Cuenta cuántos alumnos han suspendido (menos de 5) y cuántos han sacado sobresaliente (9 o más).
- Modifica
histogramapara que pinte las filas de la nota más alta a la más baja.
Errores típicos en esta práctica
- Arrays paralelos de distinto tamaño. Compila y falla al ejecutar con
ArrayIndexOutOfBoundsException. - Intercambiar índices de fila y columna al sumar o transponer. No da error; da resultados mal.
- Olvidar que un método modifica el array que recibe. Si no quieres alterar el original, trabaja sobre una copia con
Arrays.copyOf. - Comparar con
==Stringo arrays en lugar deequalsoArrays.equals. - División entera al calcular medias.
Resumen
- Los arrays paralelos permiten guardar datos relacionados con el mismo índice, pero deben medir lo mismo.
- Una matriz es un array de arrays:
m.lengthson las filas ym[f].lengthlas columnas de la filaf; en operaciones por filas o por columnas, el índice que manda es distinto. - Si un método necesita ordenar o modificar datos sin tocar el original, trabaja sobre una copia.
- Usar el valor como índice (
frec[nota]++) es una técnica muy potente para contar frecuencias. - Devolver
-1para «no encontrado» es una convención habitual y segura.
Checklist: ¿lo has practicado?
- ¿Has ejecutado los tres programas y comprobado las salidas a mano?
- ¿Sabes explicar por qué
vendermodifica el stock sin devolverlo? - ¿Sabes qué índice manda al sumar por filas y cuál al sumar por columnas?
- ¿Sabes por qué
medianatrabaja sobre una copia? - ¿Has intentado resolver los «Pruébalo tú» sin mirar?
Si has respondido que sí a casi todo, estás listo para el siguiente paso: los ejercicios de arrays, donde ya no te doy el código hecho. ¡Nos vemos allí!
Siguiente paso: ejerciciosArrays en Java — ejercicios con invertir, rotar, quitar duplicados y fusionar (y un error que se esconde donde menos lo esperas)¿Te ha servido este artículo? Así puedes apoyar SergioLearns →
