Arrays estáticos en Java Parte I

COMPARTIR EN REDES SOCIALES

Representación gráfica de un Array

Los Arrays en Java son una de las estructuras de datos principales del lenguaje de programación. Mediante estas estructuras de datos podemos agrupar elementos de forma consecutiva en memoria y de esta forma trabajar con grandes conjuntos de datos de una forma totalmente estandarizada y muy cómoda.

Cuando nos referimos a Arrays hay que distinguir principalmente 2 tipos de Arrays:

  • Arrays de memoria estática
  • Arrays de memoria dinámica

Vamos a ver las diferencias a continuación:

Los Arrays de memoria estática son aquellos Arrays en los que en su definición se especifica el número de elementos máximo que podrá almacenar la estructura de datos, un claro ejemplo de esto en Java es el siguiente: String [] cadenas = new String[10]. Mediante esta declaración hemos acotado el número de elementos máximo del conjunto a 10.

Por el contrario, los Arrays de memoria dinámica son aquellos Arrays en los que no se especifica un número máximo de elementos y es la máquina virtual de Java quien se encarga por nosotros de gestionar la memoria de forma transparente.

Aclarados ambos tipos de Arrays en este post nos vamos a centrar en los Arrays estáticos, y veremos lo siguiente acerca de ellos:

  1. ¿ Cómo se declaran ?
  2. ¿ Qué hay de su instanciación ?
  3. Declaración e instanciación en una misma sentencia
  4. Su estructuración en memoria
  5. ¿ Cómo se indexan los elementos en un array ?
  6. Las diferentes formas de asignar elementos al Array
  7. Acceder a valores almacenados en un Array
  8. Pequeño programa en Java para ver la definición, instanciación, asignación y lectura de elementos
  9. Las diferentes formas de recorrer o iterar sobre un Array
  10. Algunos ejemplo prácticos
  11. Eficiencia de los Arrays estáticos

1. ¿ Cómo se declaran ?

La forma de declarar un Array estático siempre sigue el mismo patrón, que podemos resumir de la siguiente manera: tipo [] nombreArray. Vamos a ver varios ejemplos:

int [] misNumeros;
boolean [] misBooleanos;
float [] misNumeros;
double [] mis numeros;

No solo se pueden utilizar tipos primitivos de datos(int, boolean, float, double, etc…), también se pueden utilizar objetos:

String [] cadenas;
BigDecimal [] misNumeros;
MiClase [] alumnos;

Es importante entender la diferencia entre lo que se entiende por definición y lo que se entiende por instanciación. Mediante la declaración, que es lo que hemos visto hasta ahora, solo estamos diciendo que vamos a crear un Array de un determinado tipo y le vamos a asignar un nombre. Todavía no hemos reservado memoria y por lo tanto no hemos dicho el número de elementos que tendrá nuestro Array. Esto lo veremos a continuación y consiste en la etapa de instanciación.

2. ¿ Qué hay de su instanciación ?

Mediante la instanciación se reserva la memoria para poder almacenar el número de elementos del tipo que sea deseado, siguiendo algunos de los ejemplos anteriores, utilizaremos misNumeros de tipo int y cadenas de tipo String se haría de la siguiente forma:

misNumeros = new int[10];
cadenas = new String[10];

Como podemos apreciar estamos instanciando ambos Arrays para que puedan tener un máximo de 10 elementos. Es en este momento cuando se reserva la memoria para almacenar dichos elementos.

La declaración y la instanciación se puede hacer en una misma sentencia, es lo que veremos a continuación:

3. Declaración e instanciación en una misma sentencia

Esto es bastante fácil y bastante intuitivo, basta con juntar la declaración y la instanciación en una misma linea:

int [] misNumeros = new int[10];
String [] misCadenas = new int[10];
MiClase [] alumnos = new MiClase[10];

Es importante destacar que una vez se haya seleccionado el tamaño del array esté no se podrá dimensionar para almacenar más elementos, por eso lo llamamos memoria estática.

4. Su estructuración en memoria

Los elementos de un Array se estructuran en memoria de forma consecutiva, y esto aporta grandes ventajas de las cuales hablaremos a continuación, pero primero veamos un esquema:

Estructuración en memoria de los Arrays en Java
Estructuración en memoria de los Arrays en Java

Si suponemos que cada celda ocupa 4 bytes en una arquitectura x86 y que almacenamos enteros, la representación sería la de la figura anterior. Como se puede apreciar todos los números se encuentran en memoria RAM de forma consecutiva. Esto tiene varias ventajas que comentaremos en detalle en otras entradas:

  • Principios de localidad espacial y temporal. Son conceptos avanzados de los cuales hablaremos en otras entradas
  • El más importante, se puede calcular de forma muy sencilla la posición de cualquier elemento a través de una sencilla multiplicación: DIRECCIÓN DE MEMORIA = DIRECCIÓN BASE + NÚMERO DE ELEMENTO – 1 * TAMAÑO DEL TIPO DE ELEMENTO

Supongamos que queremos calcular la dirección de memoria del tercer elemento: Sería DIRECCIÓN DE MEMORIA = 0x00 + (0x02 * 0x04) = 0x08, en decimal 8. Esto permite que el acceso a cada elemento se haga de una forma muy eficiente.

5. ¿ Cómo se indexan los elementos en un Array ?

Los elementos en un Array se indexan a través de un índice de la siguiente forma:

Indexación de los elementos en los Arrays en Java
Indexación de los elementos en los Arrays en Java

Cada elemento tiene asociado un índice, por ejemplo, el número 40 tienen asociado el índice 0, el 22 el índice 4, el 89 el 8, y así sucesivamente. Los índices siempre son consecutivos y va de 1 en 1, es decir, para pasar de un elemento al siguiente será necesario sumar 1 al índice del elemento actual.

6. Las diferentes formas de asignar elementos al Array

Ahora que ya hemos visto como se indexan los elementos en un Array podemos explicar como se insertan elementos dentro de un Array, existen principalmente 2 formas, pero vamos a ver la más convencional ya que también es la más flexible.

Supongamos que tenemos un Array definido de la siguiente forma:

int [] misNumeros = new int [10];

Vamos a asignar un número en cada posición del Array de la siguiente forma:

misNumeros[0] = 10;
misNumeros[1] = 20;
misNumeros[2] = 30;
misNumeros[3] = 40;
misNumeros[4] = 50;
misNumeros[5] = 60;
misNumeros[6] = 70;
misNumeros[7] = 80;
misNumeros[8] = 90;
misNumeros[9] = 100;

Siempre se hace de la misma forma: nombreDelArray[indice] = valor;
Es importante destacar que los índices en Java siempre empiezan en 0 y su último valor es el número de elementos que hemos declarado -1, en este caso como hemos declarado 10 elementos, el último indice al cuál podemos acceder es el 9.

7. Acceder a valores almacenados en un Array

La forma de acceder a los elementos de un Array sigue la misma dinámica que se sigue en su asignación, por ejemplo, si queremos acceder al elemento cuyo valor es 60, lo haríamos mediante misNumeros[5], si queremos acceder al elemento cuyo valor es 30 sería mediante misNúmeros[2].

8. Pequeño programa en Java para ver la definición, instanciación, asignación y lectura de elementos

//Definicion e Instanciacion
int [] misNumeros = new int[10];
		
//Asignacion
misNumeros[0] = 10;
misNumeros[1] = 20;
misNumeros[2] = 30;
misNumeros[3] = 40;
misNumeros[4] = 50;
misNumeros[5] = 60;
misNumeros[6] = 70;
misNumeros[7] = 80;
misNumeros[8] = 90;
misNumeros[9] = 100;
		
//Mostramos todos los valores
System.out.println(misNumeros[0]);
System.out.println(misNumeros[1]);
System.out.println(misNumeros[2]);
System.out.println(misNumeros[3]);
System.out.println(misNumeros[4]);
System.out.println(misNumeros[5]);
System.out.println(misNumeros[6]);
System.out.println(misNumeros[7]);
System.out.println(misNumeros[8]);
System.out.println(misNumeros[9]);

La salida del programa será la siguiente:

10
20
30
40
50
60
70
80
90
100

9. Las diferentes formas de recorrer o iterar sobre un Array

Para iterar sobre un Array, es decir, recorrer todos los elementos almacenados en el Array existen 3 formas principalmente:

  • Mediante un bucle for
  • A través de un bucle while
  • Utilizando la estructura de control forEach

Para todas las formas se utilizan bucles, veamos cada una de las formas por separado

Partimos de la siguiente base:

int [] misNumeros = new int[10];

misNumeros[0] = 10;
misNumeros[1] = 20;
misNumeros[2] = 30;
misNumeros[3] = 40;
misNumeros[4] = 50;
misNumeros[5] = 60;
misNumeros[6] = 70;
misNumeros[7] = 80;
misNumeros[8] = 90;
misNumeros[9] = 100;

Mediante un bucle for:

for (int i = 0; i < misNumeros.length; i++){
    System.out.println(misNumeros[i]);
}

La sintaxis del for es muy sencilla, for (creacion del índice; condición de iteración; incremento de la variable). Por lo tanto, creamos una variable llamada i, que nos servirá como índice, iteramos hasta que i tenga un valor de misNumeros.length -1, es decir, hasta que i valga 9 y en cada iteración incrementamos el valor de i en 1.

La salida del programa sería la siguiente:

10
20
30
40
50
60
70
80
90
100

A través de un bucle while:

int i = 0;

while (i < misNumeros.length){
    System.out.println(misNumeros[i]);
    i++;
}

Declaramos la variable fuera del bucle while, la condición se pone entre los paréntesis del bucle y en la sección de código mostramos el contenido del Array en la posición i e incrementamos en 1 dicha variable.

La salida del programa sería la siguiente:

10
20
30
40
50
60
70
80
90
100

Utilizando la estructura de control forEach

for (int numero: misNumeros) {
    System.out.println(numero);
}

Mediante esta última forma nos olvidamos de los índices, e iteramos sobre todo el Array con la siguiente sintaxis: for (tipo_de_dato nombre_de_la_variable: Array). Esta última forma tiene ventajas y desventajas:

  • Ventaja: Muy útil para recorrer un Array entero de principio a fin
  • Desventaja: No tiene condición de parada, por lo tanto, recorremos sí o sí el Array de principio a fin, esto a veces no es muy eficiente cuando por ejemplo estamos buscando algún elemento del Array ya que nos interesaría salir del bucle lo antes posible una vez ya hayamos encontrado el elemento.

10. Algunos ejemplo prácticos

Buscar un determinado elemento dentro de un Array

//Definicion e Instanciacion
int [] misNumeros = new int[10];
		
//Asignacion
misNumeros[0] = 10;
misNumeros[1] = 20;
misNumeros[2] = 30;
misNumeros[3] = 40;
misNumeros[4] = 50;
misNumeros[5] = 60;
misNumeros[6] = 70;
misNumeros[7] = 80;
misNumeros[8] = 90;
misNumeros[9] = 100;
		
//variable para indicar que numero vamos a buscar
int numeroBuscado = 60;
//Variable para parar de iterar una vez encontrado el número buscado
boolean encontrado = false;
//Variable para almacenar el indice del número que estamos buscando en //caso de encontrarlo
int posicionNumeroBuscado = -1;
		
for (int i = 0; i < misNumeros.length && encontrado == false; i++) {
	if (misNumeros[i] == numeroBuscado) {
		System.out.println("Encontrado!!!");
		encontrado = true;
		posicionNumeroBuscado = i;
	}
}
		
if (posicionNumeroBuscado != -1) {
	System.out.println(misNumeros[posicionNumeroBuscado]);
}else {
	System.out.println("El número no se ha encontrado");
}

Buscamos el número mediante el bucle for, que a diferencia de los ejemplos anteriores tiene 2 condiciones:

  • la condición de que i nunca pase del último elemento del Array
  • otra condición que sale del bucle una vez hayamos encontrado el elemento

Por eso, cuando hemos encontrado el elemento asignamos la variable encontrado a true.

Por último, si hemos encontrado el elemento, el valor de la variable posicionNumeroBuscado habrá cambiado y por lo tanto podemos acceder sin temor al elemento ya fuera del bucle.

Sumar todos los número de un Array

//Definicion e Instanciacion
int [] misNumeros = new int[10];
		
//Asignacion
misNumeros[0] = 10;
misNumeros[1] = 20;
misNumeros[2] = 30;
misNumeros[3] = 40;
misNumeros[4] = 50;
misNumeros[5] = 60;
misNumeros[6] = 70;
misNumeros[7] = 80;
misNumeros[8] = 90;
misNumeros[9] = 100;
		
//Variable para almacenar el valor del sumatorio
int sumatorio = 0;
		
for (int numero: misNumeros) {
	sumatorio = sumatorio + numero;
}
		
System.out.println("El sumatorio es: " + sumatorio);

11. Eficiencia de los Arrays estáticos

Los Arrays estáticos son muy eficientes debido a que su acceso es muy eficiente, es decir, el acceso a todos y cada uno de los elementos del Array es eficiente por las siguientes razones:

  • Calcular la dirección de memoria de un elemento en la posición i es muy sencillo, recordad la formula DIRECCIÓN DE MEMORIA = DIRECCIÓN_BASE + (INDICE_ELEMENTO * TAM_ELEMENTO).
  • Principios de localidad: Lo normal cuando se accede a un dato en memoria es que se vaya a acceder a datos que están en posiciones contiguas y que se acceda a los mismos varias veces. Por ello, y debido a que los elementos de un Array están estructurados en memoria de forma consecutiva, esto permite que al acceder a un elemento se carguen en memoria caché los elementos que están contiguos a dicho elemento con el objetivo de que sus accesos en el futuro sean mucho más rápidos ya que salen de memoria caché.

Si has disfrutado este artículo no dudes en leer la segunda parte de Arrays en Java.

Para ver más información sobre los Arrays podéis consultar wikipedia a través del anterior enlace.

Si te ha gustado el artículo de «Arrays en Java» deja un comentario en la caja de comentarios para apoyar el blog!


COMPARTIR EN REDES SOCIALES

1 Comment

  1. ¡Qué interesante me ha parecido la manera de explicar la estructura en memoria de los Array estáticos! Estoy aprendiendo Java y me cuesta encontrar esas cosas explicadas de manera tan didáctica. Se ve rápidamente lo sencillo que es calcular las direcciones de memoria gracias a ese «diseño» y me es fácil imaginar lo potente que puede resultar eso.

Deja una respuesta

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