Arrays en java

De ChuWiki
Saltar a: navegación, buscar

Veamos lo básico de los arrays en java. En arrays examples tienes más o menos todo el código que se va viendo aquí.


Array de tipos primitivos

Podemos declarar un array de tipos primitivos, por ejemplo, char, boolean, byte, short, int, float o double, de la siguiente forma

int [] a = new int[10];

y tendremos un array de 10 int en este caso, inicializados con un valor por defecto (0 o false).

Una forma fácil de incializar y crear el array es con la siguiente sintaxis

int [] a = {1,33,55,33,22,55};

que crearía un array a con 6 elementos inicializados a los valores que se muestran.

Arrays de tipos no primitivos

Si no son tipos primitivos, sino que son clases, podemos declararlos de la misma forma, pero el problema es que las posiciones del array estarán inicializadas a null, es decir, no tendrán ningún objeto dentro. Debemos, una por una, asignarles un valor antes de intentar leerlo

Boolean [] b = new Boolean[10];  // Tenemos un array de 10 null

// Inicializacion
for (int i=0;i<b.length;i++){
   b[i] = Boolean.TRUE;
}


El atributo .length de un array nos da su longitud y podemos usarlo para un bucle. Cada elemento se inicializa con un Boolean.TRUE. El acceso a un elemento del array se hace con [], como hemos visto arriba b[i]. El primer índice es el 0, por lo que en un array de 10 elementos, los índices van de 0 a 9.

Es importante ser consciente de que si metemos el mismo elemento en el array en todas las posiciones, no se están haciendo copias de dicho elemento. Si modificamos el elemento en cualquier posición, los estamos modificando todos. Por ejemplo, imagina que tienes una clase Person con nombre, apellido y edad. Si haces esto

Person [] personArray = new Person[10];
Person aPerson = new Person();
aPerson.setName("name");
aPerson.setSurname("surname");
aPerson.setAge(34);
for (int i=0;i<personArray.length;i++) {
   personArray[i]=aPerson;
}

realmente estamos teniendo un array con 10 posiciones que tienen todos ellos la misma persona. Si cambiamos, por ejemplo, el valor de la edad de esa persona en cualquier posición del array, la estamos cambiando en todas las posiciones, porque realmente sólo hay una persona. La forma correcta de hacer esto si queremos tener realmente 10 personas distintas, es hacer un new por cada posición del array

Person [] personArray = new Person[10];
for (int i=0;i<personArray.length;i++) {
   Person aPerson = new Person();
   aPerson.setName(...);
   aPerson.setSurname(...);
   aPerson.setAge(..);
   personArray[i]=aPerson;
}

Así tendremos realemente 10 personas independientes.

Copia de arrays

La clase System de java tiene un método arrayCopy() que admite los siguientes parámetros

  • Array de origen
  • Posición inicial del array de origen desde la que vamos a empezar a copiar
  • Array de destino, debe ser del mismo tipo de elementos que el de origen, si no, obtendremos un error
  • Posicion incial del array de destino donde vamoa a empezar a copiar
  • Número de elementos a copiar.

Así, por ejemplo

// Array origen
Boolean [] b = new Boolean[10];
for (int i=0;i<b.length;i++){
   b[i]=Boolean.TRUE;
}

// Array de destino, no es necesario inicializar los
// elementos
Boolean [] c = new Boolean[4];

// Llamada a copia. 4 elementos desde el elemento 0 de origen 
// al elemento 0 en adelante del destino.
System.arraycopy(b, 0, c, 0, 4);

Es importante tener en cuenta que la copia de arrays no hace copias de los elementos en el array. Después de la copia, tendremos dos arrays que comparten las mismas instancias de los elementos. Si modificamos los atributos de uno de los elementos del array, estamos modificando también el otro. Esto no quiere decir que si asignamos un nuevo elemento al array, estemos modificando también el otro. Veamos lo que queremos decir. En el siguiente ejemplo vemos que el nombre de la persona cambia en los dos arrays

// Una instancia de Person
Person aPerson = new Person();
aPerson.name="Juan";
aPerson.surname="Lopez";
aPerson.age=31;

// Array a copiar, solo un elemento.
Person [] sourcePersonArray = {aPerson};

// Array donde se guaradrá la copia
Person [] destinationPersonArray = new Person[1];

// Se hace la copia
System.arraycopy(sourcePersonArray, 0, destinationPersonArray, 0, 1);

// Devuelve true, el elemento 0 de cada array contiene la única instancia que hemos hecho de Person
System.out.println("It's same instance = "+(sourcePersonArray[0]==destinationPersonArray[0]));

// Modificamos el nombre de la Person en uno de los arrays
sourcePersonArray[0].name="Pedro";

// Vemos que el otro array también ha sido modificado.
System.out.println(destinationPersonArray[0].name);

Los comentarios son suficientemente claros, si se modifica el nombre de la persona en el primer array, también se modifica en el segundo. Sin embargo, si una vez hecha la copia, hacemos esto

sourcePersonArray[0] = new Person();

estamos metiendo una instancia nueva en la posición 0 de sourcePersonArray. Eso no hace que la instancia nueva se meta también en destinationPersonArray, así que destinationPersonArray[0] seguirá siendo la que teníamos antes con sus mismos atributos.

Clase Arrays

La clase Arrays de java tiene múltiples métodos que nos ayudan a manejar arrays. Veamos algunos de ellos

toString()

Este método convierte el array en un String legible para un humano. El String está formado por un corchete, los elementos separados por comas y un cierre de corchete. Para escribir cada elemento, el método llamará al método toString() de cada uno de los elementos, por lo que si no lo tienen ya, debemos poner un método toString() adecuado para el elemento. Todos los elementos heredan toString() de la clase Object, pero este toString() por defecto solo escribe una especie de identificador del objeto, algo como esto

[D@1833955

por lo que no veríamos nada legible. Un ejemplo con una clase Boolean que tiene un método toString() propio

Boolean[] anArray = {Boolean.TRUE, Boolean.FALSE, Boolean.FALSE, Boolean.TRUE};
System.out.println(Arrays.toString(anArray));

El código anterior muestra

[true, false, false, true]

sort()

El método Arrays.sort() permite ordenar un array. Si los elementos del array tienen un orden que java sepa (los elementos implementan la interfaz Comparable), se puede hacer de esta manera

Double [] doubles = new Double[5];
for (int i=0;i<doubles.length;i++){
   doubles[i] = Math.random();
}
Arrays.sort(doubles);

y el array de Double queda ordenado de menor a mayor.

Si java no sabe cómo ordenar el tipo de elemento del array, por ejemplo, porque es una clase nuestra que no implementa Comparable, o bien queremos que el orden no sea el indicado por esa interface, por ejemplo, queremos ordenar de mayor a menor, podemos pasar un Comparator al método sort(). Tenemos que hacer una clase java que implemente la interface Comparator, que tiene un método int compare(o1,o2). Debemos devolver:

  • 1 si queremos que o1 vaya detrás de o2 en el array. En realidad vale cualquier número positivo.
  • -1 si queremos que o1 vaya delante de o2 en el array. En realidad vale cualquier número negativo.
  • 0 si ambos elementos son iguales (nos da igual cual vaya delante o detrás en el array).

El siguiente código ordena de mayor a menor

Arrays.sort(doubles,new Comparator<Double>() {

   @Override
   public int compare(Double o1, Double o2) {
      if (o1<o2){
         return 1;
      }
      if (o1>o2){
         return -1;
      }
      return 0;
   }
});

Devolvemos 1 cuando queramos que o1 vaya detrás de o2 en el array. Como queremos orden descendente y estamos hablando de números double, o1 debe ir detrás de o2 en el array si o1 es más pequeño que o2, es decir, si o1<o2.

Devolvemos -1 cuando queramos que o1 vaya delante de o2 en el array. Como queremos orden descendente y estamos hablando de números double, esto sucede si o1 es más grande que o2, es decir, si o1>o2

Y Devolvemos 0 si no es ninguno de ambos casos, es decir, o1 y o2 son iguales.

Aunque se sale un poco de la temática de arrays, si quisieramos ordenar cadenas de texto, podríamos tener problemas con caracteres acentuados o extraños, como ñ, á, é, etc, ya que siguiendo los códigos ASCII estrictamente irían detrás de las letras normales, por lo que la ñ, por ejemplo, iría detrás de la z. Para poder hacer bien la comparación con cadenas de texto, deberíamos usar la clase Collator de java.

binarySearch()

Si el array está ordenado, este método nos ayuda a buscar la posición de un elemento usando un algoritmo de búsqueda binaria, más eficiente que si tenemos que recorrer todos los elementos uno a uno buscándolo. Si los elementos son los tipos de java que tienen un orden natural (números, cadenas, o cualquier clase que implemente la interface Comparable), el método sabrá buscarlo sin más. Si el tipo es una clase que no tiene un orden natural (una clase nuestra o de java que no implemente Comparable) o el orden no es el natural (número ordenados de mayor a menor por ejemplo), debemos suministrarle nuevamente un Comparator, siguiendo la misma regla de antes.

Vamos a hacer un ejemplo un poco más complejo. Imagina nuestra clase Persona (Person) con nombre (name), apellido (surname) y edad (age). Ponemos los atributos públicos para no alargar el código del ejemplo más de lo estrictamente necesario

package com.chuidiang.ejemplos.arrays;

public class Person {
   public String name;
   public String surname;
   public int age;
}

Queremos que el orden sea por apellido, así que hacemos el siguiente Comparator

package com.chuidiang.ejemplos.arrays;

import java.util.Comparator;

public class PersonComparator implements Comparator<Person>{

   @Override
   public int compare(Person o1, Person o2) {
      return (o1.surname.compareTo(o2.surname));
   }

}

Aprovechamos la función compareTo() de los String y que devuelve negativo, cero o positivo según sea o1 es anterior, igual o posterior a o2

Creamos y rellenamos un array de personas, poniendo unos apellidos concretos y poniendo más o menos aleatorios el nombre y edad, ya que no son de utilidad para nuestro ejemplo, y ordenamos el array, puesto que es necesario para poder hacer la búsqueda binaria

String [] surname = {"Garcia","Gomez","Lopez","Rubio","Fernandez","Roxas","Morales"};
Person [] p = new Person[surname.length];
for (int i=0;i<surname.length;i++){
   p[i]=new Person();
   p[i].name="Name"+i;
   p[i].surname=surname[i];
   p[i].age=(int)(Math.random()*20+10);
}
Arrays.sort(p,new PersonComparator());

Con esto tenemos un array de personas ordenado por apellidos. Los apellidos ordenados son :

Fernandez,Garcia,Gomez,Lopez,Morales,Roxas,Rubio

Ahora vamos a buscar uno, por ejemplo, "Rubio"

      
Person searchedPerson = new Person();
searchedPerson.surname="Rubio";
System.out.println(Arrays.binarySearch(p, searchedPerson, new PersonComparator()));

Hemos creado artificialemnte una persona nueva searchedPerson de apellido "Rubio" y usamos Arrays.binarySearch() para la búsqueda, pasando el array, la persona a buscar y el Comparator. La llamada devuelve un 6, que corresponde a la posición 7 del array (los índices del array empiezan en 0, por lo que la posición 7 del array tiene índice 6).

copyOf() y copyOfRange()

Son método similares al de System.arrayCopy(), pero con la diferencia principal de que mientras que en System.arrayCopy() teníamos que crear previamente el array de destino, estos métodos los crean directamente. Veamos un par de ejemplos

double [] sourceArray = {1.1,2.2,3.3,4.4};
double [] destinationArray = Arrays.copyOf(sourceArray, 5);
System.out.println(Arrays.toString(destinationArray));

Un array con cuatro elementos y hacemos una copia indicando que queremos que la copia tenga 5 elementos. Arrays.copyOf() hace la copia rellenando con 0 o null (según el tipo) los elementos de más en el array destino, o bien ignoraría los elementos de más en el array origen. El ejemplo anterior sacaría el siguiente resultado

[1.1, 2.2, 3.3, 4.4, 0.0]

es decir, 5 elementos en el que el quinto elemento no existente en el array original se ha relleando con un 0.0

Ahora un ejemplo con Arrays.copyOfRange()

double[] sourceArray = { 1.1, 2.2, 3.3, 4.4 };
double [] anotherDestinationArray = Arrays.copyOfRange(sourceArray, 3, 10);
System.out.println(Arrays.toString(anotherDestinationArray));

En este caso queremos copia del array original desde la posición 3 hasta la pocisión 10 (7 elementos en total). Como en el caso anterior, el método Arrays.copyOfRange() ignorará elementos sobrantes en el array original y rellenará con 0.0 los faltantes en el array destion. El ejemplo anterior sacaría por pantalla

[4.4, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]

Igual que en System.arrayCopy(), no se crean nuevas instancias de los elementos en el array destino, son compartidas con el array original.

equals() y deepEquals()

Arrays.equals() y Arrays.deepEquals() comparan si dos arrays con iguales. Para ello, verifican que contienen el mismo número de elemntos y que cada elemento es igual a su correpondiente en el otro array usando el método equals() del elemento. Veamos un ejemmplo

String [] objectArray = {new String("uno"),new String("dos")};
String [] anotherObjectArray = {new String("uno"),new String("dos")};
System.out.println(Arrays.equals(objectArray, anotherObjectArray));

En vez de poner directamente los String, hacemos new String para asegurarnos que son instancias distintas en uno y otro array. Si lo hicieramos de esta otra forma

String [] objectArray = {"uno","dos"};
String [] anotherObjectArray = {"uno","dos"};

el compilador de java sería lo suficientemente listo como para crear una única instancia del String "uno" y meterla en ambos arrays. La salida del ejemplo anterior sería true, ya que ambos arrays tienen la misma longitud y elementos cuyo equals() es true, es decir "uno".equals("uno") es true y "dos".equals("dos") es true.

La diferencia entre Arrays.equals() y Arrays.deepEquals() es que si alguno de los elementos es a su vez un array, equals() se conforma con llamar al equals() del array interior, que devolverá true sólo si el array interior es la misma instancia en ambos arrays externos. Array.deepEquals() sin embargo, si encuentra que uno de los elementos es un array, empezará a llamar a los equals() de cada uno de los elementos. Veamos un ejemplo para aclarar esto

// Dos sub-arrays con los mismos elementos
Object [] oneSubArray = {"cuatro-1","cuatro-2"};
Object [] anotherSubArray = {"cuatro-1","cuatro-2"};

// Dos arrays con los mismos elementos, y el cuarto elemento es uno
// de los su-barrays
Object [] oneArray={"uno","dos","tres",oneSubArray};
Object [] anotherArray = {"uno","dos","tres",anotherSubArray};

// equal() devuelve false, ya que oneSubArray.equals(anotherSubArray) es false al no ser
// ambos la misma instancia.
System.out.println("equals "+Arrays.equals(oneArray, anotherArray));

// deepEquals() devuelve true, ya que llamará a los equals() de los elementos dentro de 
// los subArray, es decir "cuatro-1".equals("cuatro-1") y "cuatro-2".equals("cuatro-2")
System.out.println("equals "+Arrays.deepEquals(oneArray, anotherArray));

Hemos creado dos sub arrays con la intención de meterlos como un elemento dentro de los arrays principales. Ambos subarrays son instancias distintas, pero contienen los mismos elementos.

Luego creamos los dos arrays principiales y hacemos que el cuarto elemento de cada array sea uno de los sub-arrays.

Arrays.equals() va haciendo un equals() de los elementos del array. Cuando llega a los sub-arrays, hace el equals() del sub-array como si fuera un elemento más, es decir, hace oneSubArray.equals(anotherSubArray) que devuelve false, ya que los arrays no definen ningún método equals() y heredan el de Object, que devuelve true si y solo si ambos elementos son la misma instancia, que no es el caso. En el ejemplo, la salida sería false.

Arrays.deepEquals() sin embargo, cuando llega a los sub-arrays, comienza a llamar a los equals() de cada uno de sus elementos. Esto valdría para cualquier nivel de profundidad de arrays anidados. En el ejemplo, la salida sería true.

fill()

Este método es sencillo, permite rellenar un array con elementos iguales. Tenemos la opción de rellenar todo el array, o bien sólo un rango de elementos dentro del array. Veamos ambos ejemplos

String [] array = new String[10];
Arrays.fill(array,2,5,"hello");
System.out.println(Arrays.toString(array));

Arrays.fill(array,"good bye");
System.out.println(Arrays.toString(array));

La primera llamada Arrays.fill(array,2,5,"hello") rellena los elementos del array con la cadena "hello" desde el índice 2 del array incluido (tercer elemento) hasta el 5 índice del array excluido. La salida sería

[null, null, hello, hello, hello, null, null, null, null, null]

donde vemos rellenos los índices 2, 3 y 4.

La llamada Arrays.fill(array,"good bye") rellena todos los elementos del array, al no haber especificado índices de principio y fin. La salida sería

[good bye, good bye, good bye, good bye, good bye, good bye, good bye, good bye, good bye, good bye]