Guía de la clase java.util.Arrays

1. Introducción

En este tutorial, veremos java.util.Arrays , una clase de utilidad que ha sido parte de Java desde Java 1.2.

Usando Arrays, podemos crear, comparar, ordenar, buscar, transmitir y transformar arreglos.

2. Creando

Echemos un vistazo a algunas de las formas en que podemos crear matrices: copyOf , copyOfRange y fill.

2.1. copyOf y copyOfRange

Para usar copyOfRange , necesitamos nuestra matriz original y el índice inicial (inclusive) y el índice final (exclusivo) que queremos copiar:

String[] intro = new String[] { "once", "upon", "a", "time" }; String[] abridgement = Arrays.copyOfRange(storyIntro, 0, 3); assertArrayEquals(new String[] { "once", "upon", "a" }, abridgement); assertFalse(Arrays.equals(intro, abridgement));

Y para usar copyOf , tomaríamos intro y un tamaño de matriz de destino y obtendríamos una nueva matriz de esa longitud:

String[] revised = Arrays.copyOf(intro, 3); String[] expanded = Arrays.copyOf(intro, 5); assertArrayEquals(Arrays.copyOfRange(intro, 0, 3), revised); assertNull(expanded[4]);

Tenga en cuenta que copyOf rellena la matriz con nulos si nuestro tamaño de destino es mayor que el tamaño original.

2.2. llenar

Otra forma, podemos crear una matriz de longitud fija, es llenar, que es útil cuando queremos una matriz donde todos los elementos son iguales:

String[] stutter = new String[3]; Arrays.fill(stutter, "once"); assertTrue(Stream.of(stutter) .allMatch(el -> "once".equals(el));

Consulte setAll para crear una matriz donde los elementos son diferentes.

Tenga en cuenta que necesitamos crear una instancia de la matriz nosotros mismos de antemano, a diferencia de algo como Cadena [] llena = Arrays.fill (“once” , 3) ; –Desde que esta característica se introdujo antes de que los genéricos estuvieran disponibles en el idioma.

3. Comparando

Ahora cambiemos a métodos para comparar matrices.

3.1. equals y deepEquals

Podemos usar iguales para una comparación simple de matrices por tamaño y contenido. Si agregamos un nulo como uno de los elementos, la verificación de contenido falla:

assertTrue( Arrays.equals(new String[] { "once", "upon", "a", "time" }, intro)); assertFalse( Arrays.equals(new String[] { "once", "upon", "a", null }, intro));

Cuando tenemos matrices anidadas o multidimensionales, podemos usar deepEquals no solo para verificar los elementos de nivel superior, sino también para realizar la verificación de forma recursiva:

Object[] story = new Object[] { intro, new String[] { "chapter one", "chapter two" }, end }; Object[] copy = new Object[] { intro, new String[] { "chapter one", "chapter two" }, end }; assertTrue(Arrays.deepEquals(story, copy)); assertFalse(Arrays.equals(story, copy));

Observe cuán profundo E quals pasa pero igual falla .

Esto se debe a que deepEquals finalmente se llama a sí mismo cada vez que encuentra una matriz , mientras que equals simplemente comparará las referencias de las sub-matrices.

Además, ¡esto hace que sea peligroso llamar a una matriz con una autorreferencia!

3.2. hashCode y deepHashCode

La implementación de hashCode nos dará la otra parte del contrato equals / hashCode que se recomienda para los objetos Java. Usamos hashCode para calcular un número entero basado en el contenido de la matriz:

Object[] looping = new Object[]{ intro, intro }; int hashBefore = Arrays.hashCode(looping); int deepHashBefore = Arrays.deepHashCode(looping);

Ahora, establecemos un elemento de la matriz original en nulo y volvemos a calcular los valores hash:

intro[3] = null; int hashAfter = Arrays.hashCode(looping); 

Alternativamente, deepHashCode verifica las matrices anidadas en busca de números coincidentes de elementos y contenidos. Si recalculamos con deepHashCode :

int deepHashAfter = Arrays.deepHashCode(looping);

Ahora, podemos ver la diferencia en los dos métodos:

assertEquals(hashAfter, hashBefore); assertNotEquals(deepHashAfter, deepHashBefore); 

deepHashCode es el cálculo subyacente que se utiliza cuando trabajamos con estructuras de datos como HashMap y HashSet en matrices .

4. Ordenar y buscar

A continuación, echemos un vistazo a la clasificación y búsqueda de matrices.

4.1. ordenar

Si nuestros elementos son primitivos o implementan Comparable , podemos usar sort para realizar una ordenación en línea:

String[] sorted = Arrays.copyOf(intro, 4); Arrays.sort(sorted); assertArrayEquals( new String[]{ "a", "once", "time", "upon" }, sorted);

Tenga cuidado de que la ordenación mute la referencia original , por eso realizamos una copia aquí.

sort utilizará un algoritmo diferente para diferentes tipos de elementos de matriz. Los tipos primitivos usan una ordenación rápida de doble pivote y los tipos de objeto usan Timsort. Ambos tienen el caso promedio de O (n log (n)) para una matriz ordenada aleatoriamente.

A partir de Java 8, parallelSort está disponible para una combinación de ordenación en paralelo. Ofrece un método de clasificación concurrente utilizando varias tareas Arrays.sort .

4.2. búsqueda binaria

Buscar en una matriz no ordenada es lineal, pero si tenemos una matriz ordenada, podemos hacerlo en O (log n) , que es lo que podemos hacer con binarySearch:

int exact = Arrays.binarySearch(sorted, "time"); int caseInsensitive = Arrays.binarySearch(sorted, "TiMe", String::compareToIgnoreCase); assertEquals("time", sorted[exact]); assertEquals(2, exact); assertEquals(exact, caseInsensitive);

If we don't provide a Comparator as a third parameter, then binarySearch counts on our element type being of type Comparable.

And again, note that if our array isn't first sorted, then binarySearch won't work as we expect!

5. Streaming

As we saw earlier, Arrays was updated in Java 8 to include methods using the Stream API such as parallelSort (mentioned above), stream and setAll.

5.1. stream

stream gives us full access to the Stream API for our array:

Assert.assertEquals(Arrays.stream(intro).count(), 4); exception.expect(ArrayIndexOutOfBoundsException.class); Arrays.stream(intro, 2, 1).count();

We can provide inclusive and exclusive indices for the stream however we should expect an ArrayIndexOutOfBoundsException if the indices are out of order, negative, or out of range.

6. Transforming

Finally, toString,asList, and setAll give us a couple different ways to transform arrays.

6.1. toString and deepToString

A great way we can get a readable version of our original array is with toString:

assertEquals("[once, upon, a, time]", Arrays.toString(storyIntro)); 

Again we must use the deep version to print the contents of nested arrays:

assertEquals( "[[once, upon, a, time], [chapter one, chapter two], [the, end]]", Arrays.deepToString(story));

6.2. asList

Most convenient of all the Arrays methods for us to use is the asList. We have an easy way to turn an array into a list:

List rets = Arrays.asList(storyIntro); assertTrue(rets.contains("upon")); assertTrue(rets.contains("time")); assertEquals(rets.size(), 4);

However, the returned List will be a fixed length so we won't be able to add or remove elements.

Note also that, curiously, java.util.Arrays has its own ArrayList subclass, which asList returns. This can be very deceptive when debugging!

6.3. setAll

With setAll, we can set all of the elements of an array with a functional interface. The generator implementation takes the positional index as a parameter:

String[] longAgo = new String[4]; Arrays.setAll(longAgo, i -> this.getWord(i)); assertArrayEquals(longAgo, new String[]{"a","long","time","ago"});

And, of course, exception handling is one of the more dicey parts of using lambdas. So remember that here, if the lambda throws an exception, then Java doesn't define the final state of the array.

7. Parallel Prefix

Another new method in Arrays introduced since Java 8 is parallelPrefix. With parallelPrefix, we can operate on each element of the input array in a cumulative fashion.

7.1. parallelPrefix

If the operator performs addition like in the following sample, [1, 2, 3, 4] will result in [1, 3, 6, 10]:

int[] arr = new int[] { 1, 2, 3, 4}; Arrays.parallelPrefix(arr, (left, right) -> left + right); assertThat(arr, is(new int[] { 1, 3, 6, 10}));

Also, we can specify a subrange for the operation:

int[] arri = new int[] { 1, 2, 3, 4, 5 }; Arrays.parallelPrefix(arri, 1, 4, (left, right) -> left + right); assertThat(arri, is(new int[] { 1, 2, 5, 9, 5 }));

Notice that the method is performed in parallel, so the cumulative operation should be side-effect-free and associative.

For a non-associative function:

int nonassociativeFunc(int left, int right) { return left + right*left; }

using parallelPrefix would yield inconsistent results:

@Test public void whenPrefixNonAssociative_thenError() { boolean consistent = true; Random r = new Random(); for (int k = 0; k < 100_000; k++) { int[] arrA = r.ints(100, 1, 5).toArray(); int[] arrB = Arrays.copyOf(arrA, arrA.length); Arrays.parallelPrefix(arrA, this::nonassociativeFunc); for (int i = 1; i < arrB.length; i++) { arrB[i] = nonassociativeFunc(arrB[i - 1], arrB[i]); } consistent = Arrays.equals(arrA, arrB); if(!consistent) break; } assertFalse(consistent); }

7.2. Performance

Parallel prefix computation is usually more efficient than sequential loops, especially for large arrays. When running micro-benchmark on an Intel Xeon machine(6 cores) with JMH, we can see a great performance improvement:

Benchmark Mode Cnt Score Error Units largeArrayLoopSum thrpt 5 9.428 ± 0.075 ops/s largeArrayParallelPrefixSum thrpt 5 15.235 ± 0.075 ops/s Benchmark Mode Cnt Score Error Units largeArrayLoopSum avgt 5 105.825 ± 0.846 ops/s largeArrayParallelPrefixSum avgt 5 65.676 ± 0.828 ops/s

Here is the benchmark code:

@Benchmark public void largeArrayLoopSum(BigArray bigArray, Blackhole blackhole) { for (int i = 0; i  left + right); blackhole.consume(bigArray.data); }

7. Conclusion

In this article, we learned how some methods for creating, searching, sorting and transforming arrays using the java.util.Arrays class.

Esta clase se ha ampliado en versiones de Java más recientes con la inclusión de métodos de producción y consumo de secuencias en Java 8 y métodos de desajuste en Java 9.

La fuente de este artículo está, como siempre, en Github.