Declaración, Construcción e Inicialización de Arrays en Java

Buenas tardes, en esta entrada veremos lo referente a la Declaración, Construcción e Inicialización de un Array.


Los Arrays son objetos en Java que almacenan múltiples variables del mismo tipo. Los arrays pueden almacenar tanto primitivos como referencias a objetos, pero un array por sí mismo siempre será un objeto localizado en el Heap, incluso si el array es declarado para almacenar elementos primitivos. En otras palabras, no hay nada parecido a un array primitivo, pero podemos hacer un array de primitivos. Para ver esto bien, tenemos que saber 3 cosas diferentes:

  • Como hacer una variable de referencia a un array (Declarar).
  • Como hacer un objeto Array (Construir).
  • Como poblar el array con elementos (Inicializar).

Hay múltiples maneras diferentes de hacer cada cosa, y necesitamos saber como se hacen todas ellas.

  • Declarando un Array

    Los arrays son declarados poniendo el tipo de elemento que el array almacenará, el cual puede ser un objeto o un primitivo, seguido por 2 corchetes a la izquierda o derecha del identificados.

    Declarando un array de primitivos:

    		int[] key; // Corchetes antes del nombre (recomendado)
    		int key2[]; // Corchetes detrás del nombre (legal pero menos legible)
    		

    Declarando u n array de referencias de objeto:

    		Thread[] threads; 	// Recomendado
    		Thread threads[]; 	// Legal pero menos legible
    		

    Cuando estamos declarando una referencia a un array, deberíamos poner siempre los corchetes del array inmediatamente después del tipo declarado, en vez de ponerlo depués del identificador (nombre de al variable). De esta manera, cualquiera que lea nuestro código puede fácilmente decir, por ejemplo, que key es una referencia a un objeto array de int, y no un tipo de dato primitivo int.

    Podemos también declarar arrays multidimensionales, los cuales no son otra cosa que arrays de arrays. Esto puede hacerse de la siguiente manera:

    		String[][][] occupantName; 	// Recomendado
    		String[] ManagerName;	// Legal
    		

    El primer ejemplo es un array de tres dimensiones (Un array de arrays de arrays) y el segundo es un array de dos dimensiones. Vemos que en el segundo ejemplo tenemos unos corchetes antes del nombre de la variable y otros corchetes detrás del nombre de la variable. Esto es perfectamente legal para el compilador, pero de nuevo volvemos a recordar que lo legal no tiene porque estar bien.

    No es legal incluir el tamaño de un array en nuestra declaración. Si, sabemos que en otros lenguajes se puede hacer:

    		int[5] scores;
    		

    El código anterior no logrará pasar la prueba del compilador. Recordemos, que la JVM no asigna memoria hasta que hayamos instanciado el objeto array. Ahí es cuando el tamaño importa.

  • Construyendo un Array

    Construir un array significa crear el objeto array en el Heap (Donde todos los objetos viven). Para crear un objeto array, Java debe saber cuando espacio debe asignar en el Heap, por lo que debemos especificar el tamaño del array en tiempo de creación. El tamaño de un array es el número de elementos que el array almacenará.

    • Construyendo Arrays de 1 Dimensión

      La manera mas fácil de construir un array es usar la palabra clave new seguida del tipo del array, con corchetes especificando cuantos elementos de ese tipo almacenará el array. Lo siguiente es un ejemplo de la construcción de un array de tipo int:

      				int[] testScores;			// Declara un array de enteros
      				testScores = new int[4];	// Construye un array y lo asigna a la variable testScores
      				

      El código anterior pone un nuevo objeto en el Heap (un objeto array que almacena 4 elementos) y cada elemento contiene un int con el valor por defecto 0. Pensemos en este código como si le estuviera diciendo al compilador “Crea un objeto array que almacenará 4 ints, y asignalo a la variable de referencia testScores. También, sigue adelante y inicia cada elemento int a 0. Gracias”.

      También podemos declarar y construir un array en una misma sentencia como en el siguiente caso:

      				int[] testScores = new int[4];
      				

      Una misma sentencia produce el mismo resultado que las 2 sentencias del ejemplo anterior. Un array de tipos de objeto puede ser construido de la misma manera:

      				Thread[] threads = new Thread[5];
      				

      Recordemos que el constructor de Thread en el anterior ejemplo no está siendo invocado. No estamos creando una instancia de Thread, sino un objeto de array de Thread singular. Después de la anterior sentencia, no hay aún ningún objeto Thread.

      Volvemos a recordar, que debemos siempre darle un tamaño a los arrays cuando están construyendose. La JVM necesita el tamaño para asignar el espacio apropiado en el Heap para el nuevo objeto Array. No será nunca legal, por ejemplo, hacer lo siguiente:

      				int[] carList = new int[];	// No compilará, necesita un tamaño
      				

      En adición a ser construidos con new, los arrays pueden también ser creados usando 2 tipos de sintaxis que crea un array mientras simultáneamente inicializa los elementos con valores en el código (en vez de valores por defecto). Veremos esto un poco mas adelante. Por ahora, vamos a entender el porque usar esta sintaxis, los objetos pueden también ser creados incluso sin usar la palabra new.

    • Construyendo Arrays Multidimensionales

      Los arrays multidimensionales, recordemos, son simplemente ararys de arrays. Por lo que un array de 2 dimensiones del tipo int es realmente un objeto del tipo int array (int []), que cada elemento almacena una referencia a otro array de int. La segunda dimensión almacena int primitivos. El siguiente código declara y construye un array de 2 dimensiones de tipo int:

      				int[][] myArray = new int[3][];
      				

      Vemos que solo los primeros corchetes tienen un tamaño. Esto es aceptable en Java, ya que la JVM necesita saber solo el tamaño del objeto asignado a la variable myArray.

  • Inicializando un Array

    Inicializar un array significa meter cosas dentro de el. Estas “cosas” en un array son los elementos del array, y pueden ser valores primitivos (2, x, false), o referencias a objetos por variables de referencia en el array. Si tenemos un array de objetos, el array no almacena los objetos, al igual que ninguna otra variable no primitiva nunca almacena el objeto, pero en vez de esto almacena la “referencia” al objeto. Pero si hablamos sobre arrays, por ejemplo, un “array de 5 Strings”, aunque lo que realmente significa es “un array de 5 referencias a objetos String”. Entonces la gran pregunta es si las referencias están referenciando a objetos String realmente, o son simplemente null. Recordemos que una referencia que no tiene un objeto asignado a el es una referencia a null. Y si intentamos usar esa referencia a null con el operador (.) para invocar algún método en el, obtendremos un NullPointerException.

    Los elementos individuales en un array pueden ser accedidos con un número como índice. El índice siempre empieza con 0, por lo que en un array de 10 elementos, empezaríamos desde el 0 hasta el 9. Supongamos que creamos un array de 3 animales como el siguiente:

    		Animal[] pets = new Animal[3]
    		

    Tenemos un objeto Array en el Heap, con 3 referencias a null del tipo Animal, pero no tenemos 3 objetos Animal. El próximo paso es crear los objetos Animal y asignarles una posición en el array referenciado por pets:

    		pets[0] = new Animal();
    		pets[1] = new Animal();
    		pets[2] = new Animal();
    		

    Este código pone 3 nuevos objetos Animal en el Heap y los asigna a tres posiciones en el array de pets.

    Un array de 2 dimensiones puede ser inicializado de la siguiente manera:

    		int[][] scores = new int[3][];
    		// Declara y crea un array que tiene 3 referencias a 3 arrays de int
    		
    		scores[]0 = new int[4];
    		// El primero elemento en el array scores es un array de int que almacena 4 elementos int
    		
    		scores[1] = new int[4];
    		// El segundo elemento en el array scores es un array de int de 6 elementos int
    		
    		scores[2] = new int[1];
    		// El tercer elemento en el array scores es un array de int de un solo elemento int
    		
    • Inicializando Elementos en un Bucle

      Los objetos Array tienen variable pública, length que nos dá el número de elementos en el array. El último valor del índice, entonces, es siempre uno menos que length. Por ejemplo, si length en un array es 4, los índices serán desde 0 hasta 3. A menudo veremos elementos de un array inicializados en un bucle como el siguiente:

      				Dog[] myDogs = new Dog[6];	// Crea un array de 6 referencias a Dog
      				
      				for (int x = 0; x < myDogs.length; x++){
      					myDogs[x] = new Dog();	// Asignar un nuevo Dog a la posición x
      				}
      				

      La variable length nos dice cuantos elementos almacena el array, pero no nos dice cuales de estos elementos han sido inicializados.

    • Declarando, Construyendo e Inicializando en una linea

      Podemos usar diferentes atajos en la sintaxis específica de los arrays para inicializar (poner valores explícitos en los elementos de un array) y construir (instanciar el objeto array por sí mismo) en una misma sentencia:

      				int x = 9;
      				int[] dots = {6, x, 9};
      				

      En la línea 2 del código anterior se hacen 4 cosas:

      • Declara la variable de referencia de un array de int llamado dots.
      • Crea un array de int con un tamaño de 3 (3 elementos).
      • Carga el array de elementos con valores de 6, 9 y 8.
      • Asigna el nuevo objeto array a la variable de referencia dots.

      El tamaño (length) es determinado por el número de items separados por coma entre las llaves. El código es funcionalmente equivalente al siguiente código algo mas largo:

      				int[] dots;
      				dots = new int[3];
      				int x = 9;
      				dots[0] = 6;
      				dots[1] = x;
      				dots[2] = 8;
      				

      Esto plantea la cuestión, “¿Por qué entonces se usa la forma mas larga?”. Por una sola razón, puede que no sepamos en el momento en el que creamos el array los valores que asignaremos a los elementos del array. Con referencias a objetos en vez de primitivos, funciona exactamente de la misma manera:

      				Dog puppy = new Dog("Frodo");
      				Dog[] myDogs = {puppy, New Dog("Clover"), new Dog("Aiko")};
      				

      En el ejemplo anterior se crea un array de Dog, referenciado por la variable myDogs, con un alongitud de 3 elementos. Este asigna un objeto Dog creado previamente al primer elemento en el array. También crea 2 nuevos objetos Dog, y los añade a las 2 variables de referencia Dog localizados en el array myDogs.

      Podemos tambien usar esta sintaxis como atajo con arrays multidimensionales, como lo siguiente:

      				int[][] scores = {{5, 2, 4, 7}, {9, 2}, {3, 4}};
      				

      El código anterior crea un total de 4 objetos en el Heap. Primero, un array de arrays de int es construido. El array scores tienen una longitud de 3, derivado por el número de items separados por coma dentro de las llaves. Cada uno de estos 3 elementos en el array scores tiene una variable de referencia a un array de int, por lo que hay 3 array de int que se construyen y se asignan a los 3 elementos en el array scrores.

      El tamaño de cada uno de los 3 arrays de int es derivado por el número de items dentro de sus correspondientes llames. Por ejemplo, el primer array tiene una longitud de 4, el segundo array tiene una longitud de 2 y el tercer array tiene una longitud de 2. Hasta aquí, tenemos 4 objetos: un array de arrays de int, y 3 arrays de enteros. Finalmente, los 3 arrays de enteros son inicializados con los valores enteros dentro de las llaves. El siguiente código muestra los valores de algunos de los elementos en este array de 2 dimensiones:

      				scores[0]	// Array de 4 enteros
      				scores[1]	// Array de 2 enteros
      				scores[2]	// Array de 2 enteros
      				scores[0][1]	// El valor entero 2
      				scores[2][1]	// El valor entero 4
      				
    • Construyendo e Inicializando un Array Anónimo

      Es el nombre que se le dá al segundo atajo de la creación de arrays y puieden ser usados para construir e inicializar un array, y entonces asignar el array a una variable de referencia previamente declarada:

      				int[] testScores;
      				testScores = new int[] {4, 7, 2};
      				

      El código anterior crea un nuevo array de enteros con 3 elementos, inicializa estos 3 elementos con los valores 4, 7 y 2, y entonces asigna el nuevo array a la variable de referencia del array de enteros declarada como testScores. Llamamos a esta manera creación de un array anónimo porque con esta sintaxis no necesitamos asignar el nuevo array a nada.Podemos usar esto para crear un array justo a tiempo, por ejemplo, como un argumento a un método que coge los parámetros del array. El siguiente código demuestra esto:

      				public class Foof{
      					void takesAnArray(int[] someArray){
      						// Usamos los parámetros del array
      					}
      					
      					public static void main (String[] args){
      						Foof f = new Foof();
      						f.takesAnArray(new int[] {7, 7, 8, 2, 5};)	// Necesitamos argumento de un array
      					}
      				}
      				
    • Asignaciones Legales de Elementos de un Array

      ¿Qué podemos poner en un array particular? No solo podemos poner objetos o primitivos del tipo declarado como elementos de un array.

      • Array de Primitivos

        Los arrays de primitivos pueden aceptar cualquier valor que pueda ser implícitamente promovidas al tipo declarado del array. Por ejemplo, un array de enteros puede almacenar cualquier valor que pueda meterse en una variable entera de 32-bit. Así, el siguiente código sería legal:

        						int[] weightList = new int[5];
        						byte b = 4;
        						char c = 'c';
        						short s = 7;
        						weightList[0] = b;	// Ok, un byte es mas pequeño que un int
        						weightList[1] = c;	// Ok, un byte es mas pequeño que un int
        						weightList[2] = s;	// Ok, un byte es mas pequeño que un int
        						
      • Array de Referencias a Objetos

        Si el tipo de array declarado es de una clase, podemos poner objetos de cualquier subclase del tipo declarado dentro del array. Por ejemplo, si Subaru es una subclase de Car, podemos poner objetos Subaru y objetos Car en un array del tipo Car como en el siguiente ejemplo:

        						class Car{}
        						class Subaru extends Car {}
        						class Ferrari extends Car {}
        						...
        						Car[] myCars = {new Subaru(), new Car(), new Ferrari()};
        						

        Esto ayuda a recordar que los elementos en un array Car no son mas que variables de referencia de Car. Por lo que lo que pueda ser asignado a una variable de referencia de Car puede también ser legalmente asignado a un elemento array de Car.

        Si el array es declarado como un tipo de Interface, los elementos del array pueden referir a cualquier instancia de cualquier clase que implemente la Interface declarada. El siguiente código muestra el uso de una interface como un tipo de array:

        						interface Sporty{
        							void beSporty();
        						}
        						
        						class Ferrari extends Car implements Sporty{
        						
        							public void beSporty() {
        								// Implementa el método de Sporty en la clase Ferrari
        							}
        							
        						}
        						
        						class RacingFlats extends AthleticShoe implements Sporty{
        						
        							public void beSporty() {
        								// Implementa el método Sporty
        							}
        							
        						}
        						
        						class GolfClub{ }
        						class TestSportyThings{
        							public static void main(String[] args){
        								Sporty[] sportyThings = new Sporty[3];
        								sportyThings[0] = new Ferrari();	//OK, Ferrari implementa Sporty
        								sportyThings[1] = new RacingFlats();	// Ok, RacingFlats implementa Sporty
        								sportyThings[2] = new GolfClub();	// NO!, GolfClub no implementa Sporty
        							}
        						}
        						

        El punto clave de este es que cualquier objeto que pase el test “IS-A” o “ES-UN” para el tipo de array declarado puede ser asignado a un elemento de ese array.

      • Asignaciones de Referencias de Arrays para Arrays de una dimensión

        No estamos hablando sobre referencias en el array (En otras palabras, elementos del array), sino de referencias al objeto array. Por ejemplo, si declaramos un array de enteros, la variable de referencia que hemos declarado puede ser reasignada a cualquier array de enteros, pero no puede ser reasignada a cualqueir otra cosa que no sea un array de enteros, incluyendo un valor entero. Recordemos, todos los arrays son objetos, por lo que una referencia a un array de enteros no puede referirse a un tipo de dato primitivo int. El siguiente código demuestra asignaciones legales y no legales para los arrays de primitivos:

        						int[] splats;
        						int[] dats = new int[4];
        						char[] letters = new char[5];
        						splats = dats;	//Ok, dats se refiere a un array de enteros
        						splats = letters;	//NO, letters se refiere a un array de caracteres
        						

        Es tentador asumir que como una variable del tipo byte, short o char puede ser explícitamente promovida y asignada a un entero, un array de cualquiera de estos tipos pueda ser asignado a un array de enteros. No podemos hacer esto en Java.

        Los arrays que mantienen referencias a objetos, en oposición a los primitivos, no son tan restrictivos. Al igual que podemos poner un objeto Honda en un array de Car (ya que Honda hereda de Car), podemos también asignar un array del tipo Honda a una variable de referencia Car como en el siguiente ejemplo:

        						Car[] cars;
        						Honda[] cuteCars = new Honda[5];
        						cars = cuteCars;	// Ok, ya que Honda es del tipo Car
        						Beer[] beers = new Beer[99];
        						cars = beers;	// NO, Beer no es del tipo Car
        						

        Aplicamos el test “IS-A” o “ES-UN” para ayudarnos y ver lo que es legal y lo que es ilegal. Honda es un Car, por lo que un array de Honda puede ser asignado a un array de Car. Beer no es un Car, ya que Beer no hereda nada de Car.

        Las reglas para las asignaciones de array se aplican igual a interfaces como clases. Un array declarado como un tipo de Interface puede referenciar a un array de cualquier tipo que implemente la interface. Recordemos, cualquier objeto de una clase que esté implementando una interface en particular pasará el test IS-A para la interface. Por ejemplo, si Box implementa Foldable, lo siguiente sería legal:

        						Foldable[] foldingThings;
        						Box[] boxThings = new Box[3];
        						foldingThings = boxThings[];	//	OK, Box implementa Foldable, por lo que Box es Foldable
        						
      • Asignaciones de Referencia de Arrays para Arrays Multidimensionales

        Cuando asignamos un array a una referencia de array declarada previamente, el array que estamos asignando debe de tener la misma dimension que la referencia a la que lo estamos asignando. Por ejemplo, un array de 2 dimensiones de enteros no puede ser asignado a una referencia de array regular, como lo siguiente:

        						int[] blots;
        						int[][] squeegees = new int[3][];
        						blots = squeegees;	// NO, squeegees es un array de 2 dimensiones de arrays de enteros
        						
        						int[] blocks = new int[6];
        						blots = blocks;		// Ok, blocks es un array de enteros
        						

        Pongamos particular atención a las asignaciones del array usando diferentes dimensiones:

        						int[][] books = new int[3][];
        						int[] numbers = new int[6];
        						int aNumber = 7;
        						books[0] = aNumber;		// NO, se espera un array de enteros no un entero
        						books[0] = numbers;		// OK, ya que numbers es un array de enteros.
        						
  • Bloques de Inicialización

    Hemos hablado de 2 lugares en una clase donde podemos poner código que realice operaciones: métodos y constructores. Los bloques de Inicialización son la tercera parte en un programa en Java donde las operaciones pueden ser realizadas. Los bloque de inicialización se ejecutan cuando la clase es por primera vez cargada o cuando una instancia es creada. Vamos a ver un ejemplo:

    		class SmallInit{
    			static int x;
    			int y;
    			
    			static{		// Blocuq de inicialización static
    				x = 7;
    			}
    			
    			{			// Bloque de inicialización de instancia
    				y = 8;
    			}
    		}
    		

    Como podemos ver, la sintaxis de los bloques de inicialización es muy breve. No tienen nombres, no pueden coger argumentos, y no retornan nada. Un bloque de inicialización static se ejecuta una vez, cuando la clase es por primera vez cargada. Un bloque de inicialización de instancia se ejecuta cada vez que una nueva instancia es creada. Recordemos lo que hablamos sobre el orden en el cual los constructores son ejecutados, en este caso el bloque de inicialización de instancia se ejecuta despues de la llamada de super() en un constructor, en otras palabras, después de que el super-constructor haya sido ejecutado.

    Podemos tener varios bloques de inicialización en una clase. Es importante tomar nota que a diferencia de los constructores o los métodos, el orden en el cual los bloques de inicialización aparecen en la clase importa. Cuando es la hora de ejecutar un bloque de inicialización, si una clase tiene mas de uno, se ejecutarán en el orden en el cual aparece en el archivo de la clase…en otras palabras, desde arriba hacia abajo. Basados en las reglas que acabamos de discutir, vamos a ver un ejemplo de esto:

    		class Init{
    			Init (int x) {
    				System.out.println("1-arg const");
    			}
    			
    			Init(){
    				System.out.println("no-arg const");
    			}
    			
    			static {
    				System.out.println("1st static init");
    			}
    			
    			{
    				System.out.println("1st instance init");
    			}
    			
    			{
    				System.out.println("2st instance init");
    			}
    			
    			static{
    				System.out.println("2nd static init");
    			}
    			
    			public static void main (String[] args){
    				new Init();
    				new Init(7);
    			}
    		}
    		

    Recordemos estas reglas:

    • Los bloques de inicialización se ejecutan en el orden en el que aparecen.
    • Los bloques de inicialización static se ejecutan una vez, cuando la clase es por primera vez cargada.
    • Los bloques de inicialización de instancia se ejecutan cada vez que una isntancia de la clase es creada.
    • Los bloques de inicialización de instancia se ejecutan después de la llamada a super() del constructor.

    Con estas reglas en la mente, la siguiente salida debería tener sentido:

    		1st static init
    		2nd static init
    		1st instance init
    		2st instance init
    		no-arg const
    		1st instance init
    		2st instance init
    		1-arg const
    		

    Como podemos ver, el bloque de inicialización de instancia son ejecutados 2 veces. Es a menudo usado para poner código que todos los constructores en una clase deberían compartir. De esta manera, el código no tiene que estar duplicado en los diferentes constructores. Finalmente si cometemos un error en nustro bloque de inicialización static, la JVM puede lanzar una excepción ExceptionInInitializationError. Vamos a ver un ejemplo:

    		class InitError {
    			static int[] x = new int[4];
    			static{
    				x[4] = 5;		// Índice del array inválido
    			}
    			public static void main (String[] args){ }
    		


Con esto hemos visto un poco mas sobre los arrays en Java, tenemos que tener en mente que no son tipos de dato primitivos, aunque los almacenen, sino que son objetos, y debemos tratarlos como tal.

Sin más, cualquier aporte o corrección es bienvenido.

Saludos!!!