Arrays en Java — de las listas de Python a tamaño fijo, índices y referencias
Recuerdo la primera vez que un programa mío en Java se paró de golpe con un mensaje en rojo que decía ArrayIndexOutOfBoundsException. Era un bucle que recorría un array de notas y yo estaba convencido de que el código estaba perfecto: había escrito i <= notas.length. Venía de Python, donde un fallo así me habría dado un IndexError y listo, pero Java me obligó a parar y entender por qué el último índice es length - 1. Tardé un buen rato en darme cuenta de que el problema no era el bucle, sino que no había entendido qué es un array por dentro. Este artículo es para que a ti no te pase.
Vamos a ver qué es un array, cómo se crea, cómo se recorre, por qué dos arrays «iguales» a veces no lo son y qué hace la clase Arrays. Si vienes de Python, iremos comparando con listas y tuplas, porque ahí está la mitad de las confusiones. Y si todavía no has visto cómo funcionan los métodos, te conviene pasar antes por Métodos en Java: lo usaremos al final.
Tabla de Contenidos
Qué es un array (y en qué se parece a una lista de Python)
Un array es una colección de valores del mismo tipo, guardados uno detrás de otro, con un tamaño fijo que se decide al crearlo. Piensa en una caja de huevos: tiene un número concreto de huecos, todos sirven para lo mismo (huevos), y cada hueco tiene una posición.
Comparado con Python, quédate con estas tres diferencias:
- Todos los elementos son del mismo tipo. Un
int[]solo guarda enteros. En una lista de Python puedes mezclar números y textos; aquí no. - El tamaño es fijo. No existe
appendniremove. Si necesitas más huecos, tienes que crear otro array más grande. (Para tamaño variable, Java tieneArrayList, que verás más adelante en colecciones.) - Los elementos sí se pueden modificar. En eso se parece a una lista, y no a una tupla.
Un resumen para tener a mano:
| Lista de Python | Tupla de Python | Array de Java | |
|---|---|---|---|
| Tipos mezclados | Sí | Sí | No, un solo tipo |
| Cambia de tamaño | Sí | No | No |
| Se pueden modificar elementos | Sí | No | Sí |
Declarar y crear un array
En Java hay que distinguir dos pasos que en Python no se ven: declarar la variable (decir qué tipo de array es) y crear el array (reservar los huecos). El tipo va seguido de corchetes:
int[] notas; // declaración: "notas" será un array de enteros (todavía no existe)
Hay dos formas de crearlo. La primera es darle los valores directamente, entre llaves:
int[] notas = {7, 5, 9, 4, 10}; // 5 huecos, ya con valores
La segunda es indicar solo el tamaño con new, y rellenarlo después:
int[] notas = new int[5]; // 5 huecos, todos valen 0 notas[0] = 7; notas[1] = 5;
¿Y qué valen los huecos si no los relleno?
Java nunca deja un hueco «sin valor»: al crear el array con new, cada elemento recibe el valor por defecto de su tipo:
| Tipo de los elementos | Valor por defecto |
|---|---|
int, long, short, byte | 0 |
double, float | 0.0 |
boolean | false |
char | el carácter nulo ('\u0000') |
String y cualquier objeto | null («no apunta a nada») |
int[] enteros = new int[3]; boolean[] marcas = new boolean[3]; String[] nombres = new String[3]; System.out.println(enteros[0]); // 0 System.out.println(marcas[0]); // false System.out.println(nombres[0]); // null
Un array de String recién creado no contiene textos vacíos: contiene null. Si intentas hacer nombres[0].length() antes de asignarle algo, obtendrás un NullPointerException. Es un error clásico.
Acceder a los elementos: índices y length
Igual que en Python, los índices empiezan en 0. En un array de 5 elementos, las posiciones válidas son 0, 1, 2, 3 y 4.
int[] notas = {7, 5, 9, 4, 10};
int primera = notas[0]; // 7
notas[3] = 6; // ahora el array es {7, 5, 9, 6, 10}
int cuantas = notas.length; // 5
int ultima = notas[notas.length - 1]; // 10
Fíjate en que length no lleva paréntesis: en Python escribías len(lista), aquí es notas.length. Es un atributo del array, no un método. (Con los String pasa al revés: texto.length() sí lleva paréntesis. Una de las pequeñas trampas de Java.)
Y una diferencia importante con Python: los índices negativos no existen. En Python lista[-1] es el último elemento; en Java, notas[-1] da error. Para el último hay que escribir notas[notas.length - 1].
ArrayIndexOutOfBoundsException. Si pides una posición que no existe, el programa se detiene:
notas[5] → Index 5 out of bounds for length 5
Con 5 elementos, el último índice es 4, no 5. La regla: el último índice siempre es length - 1. Es justo lo que me pasó a mí con aquel bucle; lo verás con calma en la sección siguiente.
Recorrer un array
Hay dos formas, igual que en Python: por índices o por valores.
Por índices: el for clásico
int[] notas = {7, 5, 9, 4, 10};
for (int i = 0; i < notas.length; i++) {
System.out.println("Posición " + i + ": " + notas[i]);
}
Lo importante es la condición: i < notas.length, con menor estricto. La última vuelta ocurre con i = 4 y, al sumar uno, i vale 5 y el bucle se corta. Si escribes i <= notas.length, hay una vuelta de más con i = 5 y aparece la excepción de antes.
El fallo más común al recorrer arrays: escribir <= en lugar de < en la condición del for. El programa compila sin problemas y falla al ejecutarse, justo en la última vuelta.
Por valores: el for-each
Si solo necesitas leer cada valor y no te importa su posición, Java tiene una versión más corta, equivalente a for valor in lista de Python:
int suma = 0;
for (int nota : notas) { // "para cada nota en notas"
suma += nota;
}
System.out.println("Suma: " + suma);
Se lee así: «para cada int llamado nota dentro de notas«. Los dos puntos : sustituyen a la palabra in de Python.
¿Cuál uso? Si necesitas el índice (para saber la posición, para recorrer dos arrays a la vez, o para modificar elementos), usa el for clásico. Si solo vas a leer los valores, usa el for-each. Tiene una trampa: en un for-each de int, la variable nota es una copia de cada valor, así que hacer nota = 0 dentro del bucle no cambia el array.
El punto que más confunde: un array es una referencia
Esto es lo que más cuesta al principio y, a la vez, lo que más te va a ahorrar errores si lo entiendes bien. Mira este código y piensa qué crees que imprime:
int[] notas = {7, 5, 9};
int[] copia = notas; // ¿esto copia el array?
copia[0] = 99;
System.out.println(notas[0]); // ¿7 o 99?
Imprime 99. Y no es un fallo: es cómo funcionan los arrays. Una variable de tipo int[] no contiene los números: contiene la dirección donde está el array en memoria. Es como la dirección de una casa. Cuando escribes int[] copia = notas;, copias la dirección, no la casa. Ahora hay dos variables que apuntan a la misma casa, y si alguien cambia los muebles, lo ve cualquiera de los dos.
Con los números sueltos no ocurre: int b = a; copia el valor y a partir de ahí son independientes. Los arrays (y todos los objetos) se comportan distinto. Si quieres una copia de verdad, hay que crear otro array:
import java.util.Arrays;
int[] notas = {7, 5, 9};
int[] copia = Arrays.copyOf(notas, notas.length); // array nuevo e independiente
copia[0] = 99;
System.out.println(notas[0]); // 7 (el original no cambia)
System.out.println(copia[0]); // 99
Por qué == no sirve para comparar arrays
La misma idea explica otro tropiezo. El operador == compara direcciones, no contenidos:
int[] a = {1, 2, 3};
int[] b = {1, 2, 3};
System.out.println(a == b); // false (son dos arrays distintos)
System.out.println(Arrays.equals(a, b)); // true (mismo contenido)
Es la misma historia que == contra .equals() con los String: para comparar contenido, se usa un método. Para arrays, Arrays.equals(a, b).
Y si imprimo un array directamente, ¿qué sale?
int[] notas = {7, 5, 9};
System.out.println(notas); // [I@49476842 (¡no son los números!)
Sale algo como [I@49476842: el tipo del array ([I significa «array de int») y una dirección de memoria. Para ver el contenido, usa Arrays.toString(notas), que da [7, 5, 9].
Imprimir un array con println(array). Si ves algo del estilo [I@6d06d69c, no es un fallo de tu programa: simplemente te falta Arrays.toString(...).
Esta idea de «la variable guarda una dirección» se volverá a ver con clases y objetos: String, y cualquier objeto que crees, funcionan igual. Si lo entiendes aquí con arrays, ese tema ya lo llevas medio hecho.
Arrays y métodos
Lo que acabamos de ver tiene una consecuencia directa si ya conoces los métodos: cuando pasas un array a un método, se pasa la dirección, no una copia. Es decir, el método puede modificar tu array original.
static void subirUnPunto(int[] datos) {
for (int i = 0; i < datos.length; i++) {
datos[i]++; // modifica el array original
}
}
public static void main(String[] args) {
int[] notas = {7, 5, 9};
subirUnPunto(notas);
System.out.println(Arrays.toString(notas)); // [8, 6, 10]
}
Con un int suelto esto no pasa (el método recibe una copia del valor). Con un array, sí. Es muy útil, pero también una fuente de errores: si un método «ordena» o «cambia» tu array sin avisar, luego te sorprendes. Una buena costumbre es dejar claro en el nombre del método si modifica o si solo calcula (subirUnPunto modifica; media solo calcula).
Arrays de varias dimensiones
En Python, una tabla se representa con una lista de listas. En Java, con un array de arrays, y se declara con un par de corchetes por dimensión:
int[][] tabla = {
{10, 15, 20, 30, 40},
{12, 14, 16, 18},
{11, 17, 23, 29, 31}
};
System.out.println(tabla[1][3]); // 18 → fila 1, columna 3
La forma de pensarlo: tabla es un array de 3 elementos, y cada elemento es a su vez un array de enteros. Por eso tabla[1] es la segunda fila entera, y tabla[1][3] es el elemento 3 de esa fila.
También puedes crear una tabla vacía indicando filas y columnas:
int[][] tabla = new int[2][3]; // 2 filas, 3 columnas, todo a 0 tabla[1][2] = 5; System.out.println(Arrays.deepToString(tabla)); // [[0, 0, 0], [0, 0, 5]]
Fíjate en que para imprimir una tabla se usa deepToString («a fondo»), porque toString normal solo sabría mostrar las direcciones de las filas.
Como cada fila es un array independiente, las filas pueden tener longitudes distintas (en el ejemplo de arriba, 5, 4 y 5 elementos). Por eso, al recorrer, la longitud de cada fila se pide con tabla[i].length y no con una única longitud común.
Recorrer una tabla
Hacen falta dos bucles anidados: el de fuera recorre las filas, el de dentro recorre los elementos de cada fila.
// Por índices
for (int fila = 0; fila < tabla.length; fila++) {
for (int col = 0; col < tabla[fila].length; col++) {
System.out.print(tabla[fila][col] + " ");
}
System.out.println(); // salto de línea al terminar cada fila
}
// Por valores
for (int[] fila : tabla) { // cada "fila" es un int[]
for (int valor : fila) {
System.out.print(valor + " ");
}
System.out.println();
}
En el for-each, la variable exterior debe ser del tipo de un nivel menos: si tabla es int[][], cada fila es un int[]. Es la única regla que hay que recordar para no equivocarse con los tipos.
La clase Arrays: herramientas ya hechas
Java trae una clase llamada Arrays (en el paquete java.util, por eso hay que escribir import java.util.Arrays; arriba del archivo) con operaciones muy usadas que no merece la pena programar a mano. Las principales:
| Método | Qué hace |
|---|---|
Arrays.toString(a) | Devuelve el contenido como texto: "[10, 15, 20]" |
Arrays.sort(a) | Ordena el array de menor a mayor (modifica el original) |
Arrays.binarySearch(a, x) | Busca x en un array ya ordenado y devuelve su posición |
Arrays.copyOf(a, n) | Crea un array nuevo de longitud n con los datos de a |
Arrays.copyOfRange(a, desde, hasta) | Copia un trozo (el hasta no se incluye) |
Arrays.equals(a, b) | Compara el contenido de dos arrays |
Arrays.fill(a, x) | Rellena todo el array con el valor x |
import java.util.Arrays;
public class Main {
public static void main(String[] args) {
int[] datos = {80, 70, 60, 50, 40, 30, 20, 10};
Arrays.sort(datos);
System.out.println(Arrays.toString(datos));
// [10, 20, 30, 40, 50, 60, 70, 80]
System.out.println(Arrays.binarySearch(datos, 30)); // 2 (está en la posición 2)
System.out.println(Arrays.binarySearch(datos, 35)); // -4 (no está)
int[] trozo = Arrays.copyOfRange(datos, 2, 5);
System.out.println(Arrays.toString(trozo)); // [30, 40, 50]
int[] masGrande = Arrays.copyOf(datos, 10); // los huecos nuevos se rellenan con 0
System.out.println(Arrays.toString(masGrande));
// [10, 20, 30, 40, 50, 60, 70, 80, 0, 0]
int[] sietes = new int[5];
Arrays.fill(sietes, 7);
System.out.println(Arrays.toString(sietes)); // [7, 7, 7, 7, 7]
}
}
¿Por qué binarySearch devuelve -4?
Cuando el valor no está, binarySearch devuelve -(punto de inserción) - 1, donde el «punto de inserción» es la posición donde debería estar para mantener el orden. Buscando 35 en [10, 20, 30, 40, ...], le tocaría la posición 3 (entre el 30 y el 40): -3 - 1 = -4. Lo práctico: si el resultado es negativo, no está; si es 0 o más, es su posición.
binarySearch solo funciona si el array ya está ordenado. Si lo usas sobre un array desordenado, no da error, pero el resultado no significa nada. Ordena primero con Arrays.sort.
Otra pega: Arrays.equals compara en un solo nivel. Si los elementos son a su vez arrays (una tabla), se usa Arrays.deepEquals, igual que deepToString para imprimir.
Es habitual preguntarse…
¿Puedo añadir un elemento a un array?
No: el tamaño es fijo. Lo que se hace es crear un array más grande (con Arrays.copyOf(a, a.length + 1)) y colocar el nuevo valor en el último hueco. Si necesitas hacerlo con frecuencia, es la señal de que te conviene un ArrayList.
¿Por qué hay que decir el tamaño al crearlo?
Porque Java reserva un bloque de memoria contiguo de ese tamaño. Es lo que hace que acceder a notas[3] sea tan rápido: sabe exactamente dónde está sin buscar.
¿Qué diferencia hay entre un array vacío y null?
new int[0] es un array real, con cero elementos (su length es 0 y se puede recorrer sin problemas). null significa que la variable no apunta a ningún array: si haces .length sobre ella, se produce un NullPointerException.
¿Puedo crear un array de tamaño variable, por ejemplo new int[n]?
Sí. El tamaño puede ser cualquier expresión, como una variable leída con Scanner. Lo que no puede cambiar es el tamaño después de crearlo. Si n es negativo, falla con NegativeArraySizeException.
Un ejemplo completo
Juntemos todo en un programa pequeño: calcula el máximo y la media de las notas de un grupo con métodos, y luego la media de cada grupo de una tabla. Ve leyendo los comentarios, que explican el porqué de cada decisión.
import java.util.Arrays;
public class Notas {
// Devuelve la nota más alta. Parte del primer elemento y compara con el resto.
static int maximo(int[] datos) {
int max = datos[0]; // asumimos que el primero es el mayor
for (int i = 1; i < datos.length; i++) { // empezamos en 1: el 0 ya lo tenemos
if (datos[i] > max) {
max = datos[i];
}
}
return max;
}
// Devuelve la media. El (double) evita la división entera: 41 / 6 daría 6, no 6.83
static double media(int[] datos) {
int suma = 0;
for (int n : datos) { // for-each: solo leemos, no modificamos
suma += n;
}
return (double) suma / datos.length;
}
// Este método SÍ modifica el array original (por eso usa el for clásico)
static void subirUnPunto(int[] datos) {
for (int i = 0; i < datos.length; i++) {
datos[i]++;
}
}
public static void main(String[] args) {
int[] notas = {7, 5, 9, 4, 10, 6};
System.out.println("Notas: " + Arrays.toString(notas));
System.out.println("Máxima: " + maximo(notas));
System.out.println("Media: " + media(notas));
subirUnPunto(notas);
System.out.println("Tras subir: " + Arrays.toString(notas));
// Tres grupos con distinto número de alumnos (filas de distinta longitud)
int[][] grupos = {
{7, 5, 9},
{4, 10},
{6, 8, 8, 3}
};
for (int g = 0; g < grupos.length; g++) {
// grupos[g] es un int[]: se lo pasamos directamente al método media
System.out.println("Grupo " + g + " (" + grupos[g].length + " alumnos): " + media(grupos[g]));
}
}
}
Salida:
Notas: [7, 5, 9, 4, 10, 6] Máxima: 10 Media: 6.833333333333333 Tras subir: [8, 6, 10, 5, 11, 7] Grupo 0 (3 alumnos): 7.0 Grupo 1 (2 alumnos): 7.0 Grupo 2 (4 alumnos): 6.25
Fíjate en lo bien que encajan las piezas: media funciona igual con el array de notas que con grupos[g], porque cada fila de la tabla es un array de enteros. Eso es lo que significa que una tabla sea «un array de arrays».
Visualízalo con Python Tutor
Lo de las referencias se entiende mucho mejor viéndolo que leyéndolo. Entra en pythontutor.com/java.html, pega este código y avanza paso a paso con «Next»:
public class Main {
public static void main(String[] args) {
int[] notas = {7, 5, 9};
int[] alias = notas; // ¿copia o misma casa?
alias[0] = 99;
int[] copia = new int[notas.length];
for (int i = 0; i < notas.length; i++) {
copia[i] = notas[i]; // copia real, elemento a elemento
}
copia[1] = 0;
System.out.println(notas[0]);
System.out.println(copia[1]);
}
}
Qué observar:
- Tras
int[] notas = {7, 5, 9};, a la derecha aparece un array y la variablenotascon una flecha hacia él: esa flecha es la «dirección». - Tras
int[] alias = notas;, aparece una segunda flecha que apunta al mismo array, no un array nuevo. - Al ejecutar
alias[0] = 99;, el 7 cambia a 99 en el único array que existe:notas[0]también vale 99. - Con
new int[notas.length]aparece un segundo array independiente, y el bucle copia los valores uno a uno. Ahoracopia[1] = 0no afecta anotas.
Resumen
- Un array guarda valores del mismo tipo y tiene tamaño fijo; los elementos sí se pueden modificar.
- Se crea con llaves (
{1, 2, 3}) o connew tipo[tamaño]; sin rellenar, los huecos valen0,0.0,falseonullsegún el tipo. - Los índices van de
0alength - 1;lengthno lleva paréntesis y no hay índices negativos. - Se recorre con
forclásico (necesitas índice o modificar) o con for-each (solo leer). Condición delfor:i < array.length. - Una variable de array guarda una referencia: asignar (
b = a) no copia, y==no compara contenido. UsaArrays.copyOfyArrays.equals. - Al pasar un array a un método, el método puede modificarlo.
- Una tabla es un array de arrays (
int[][]), se recorre con dos bucles y sus filas pueden tener distinta longitud. - La clase
Arrays(toString,sort,binarySearch,copyOf,copyOfRange,equals,fill) evita reinventar la rueda.
Checklist: ¿lo tienes claro?
- ¿Sabes declarar un array y crearlo de las dos formas (llaves y
new)? - ¿Sabes decir qué valor tendrá cada hueco si no lo rellenas?
- ¿Puedes explicar por qué
notas[notas.length]falla? - ¿Sabes por qué
int[] b = a;no hace una copia? - ¿Sabes comparar dos arrays por contenido y mostrar uno por pantalla?
- ¿Sabes recorrer una tabla con dos bucles, por índices y por valores?
Si algún punto te ha dejado dudas, vuelve a su sección antes de seguir. En el siguiente artículo lo pondremos en práctica con programas completos.
Sigue aprendiendoTodos los artículos de Java¿Te ha servido este artículo? Así puedes apoyar SergioLearns →
