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.

Antes de empezar

Para seguir la práctica conviene tener claro:

  • Cómo se declara y crea un array ({...} y new) y que length no 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 for y 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í:

  1. La mediana necesita el array ordenado, pero no queremos ordenar el original (acuérdate de mi anécdota). Solución: trabajar sobre una copia.
  2. 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 n cuenta cuántas veces aparece la nota n. 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) y maximo(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 histograma para 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 == String o arrays en lugar de equals o Arrays.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.length son las filas y m[f].length las columnas de la fila f; 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 -1 para «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é vender modifica el stock sin devolverlo?
  • ¿Sabes qué índice manda al sumar por filas y cuál al sumar por columnas?
  • ¿Sabes por qué mediana trabaja 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 →

Sigue aprendiendo

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *