Guide de la classe java.util.Arrays

1. Introduction

Dans ce tutoriel, nous allons jeter un œil à java.util.Arrays , une classe utilitaire qui fait partie de Java depuis Java 1.2.

À l'aide de tableaux, nous pouvons créer, comparer, trier, rechercher, diffuser et transformer des tableaux.

2. Création

Jetons un coup d'œil à certaines des façons dont nous pouvons créer des tableaux: copyOf , copyOfRange et fill.

2.1. copyOf et copyOfRange

Pour utiliser copyOfRange , nous avons besoin de notre tableau d'origine et de l'index de début (inclus) et de l'index de fin (exclusif) que nous voulons copier:

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));

Et pour utiliser copyOf , nous prendrions une intro et une taille de tableau cible et nous récupérerions un nouveau tableau de cette longueur:

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

Notez que copyOf remplit le tableau avec un s nul si notre taille cible est plus grande que la taille d'origine.

2.2. remplir

Une autre façon, nous pouvons créer un tableau de longueur fixe, est fill, ce qui est utile lorsque nous voulons un tableau où tous les éléments sont les mêmes:

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

Consultez setAll pour créer un tableau où les éléments sont différents.

Notez que nous devons instancier le tableau nous-mêmes au préalable - par opposition à quelque chose comme String [] fill = Arrays.fill («once» , 3) ; –Depuis que cette fonctionnalité a été introduite avant que les génériques ne soient disponibles dans la langue.

3. Comparaison

Passons maintenant aux méthodes de comparaison de tableaux.

3.1. égal et deepEquals

Nous pouvons utiliser des égaux pour une simple comparaison de tableaux par taille et contenu. Si nous ajoutons un null comme l'un des éléments, la vérification du contenu échoue:

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

Lorsque nous avons des tableaux imbriqués ou multidimensionnels, nous pouvons utiliser deepEquals non seulement pour vérifier les éléments de niveau supérieur, mais également pour effectuer la vérification de manière récursive:

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));

Notez comment deepE quals passe mais égal échoue .

En effet, deepEquals s'appelle finalement à chaque fois qu'il rencontre un tableau , tandis que equals comparera simplement les références des sous-tableaux.

De plus, cela rend dangereux d'appeler un tableau avec une auto-référence!

3.2. hashCode et deepHashCode

L'implémentation de hashCode nous donnera l'autre partie du contrat equals / hashCode qui est recommandé pour les objets Java. Nous utilisons hashCode pour calculer un entier en fonction du contenu du tableau:

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

Maintenant, nous définissons un élément du tableau d'origine sur null et recalculons les valeurs de hachage:

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

Sinon, deepHashCode vérifie les tableaux imbriqués pour les numéros correspondants d'éléments et de contenus. Si nous recalculons avec deepHashCode :

int deepHashAfter = Arrays.deepHashCode(looping);

Maintenant, nous pouvons voir la différence entre les deux méthodes:

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

deepHashCode est le calcul sous-jacent utilisé lorsque nous travaillons avec des structures de données telles que HashMap et HashSet sur des tableaux .

4. Tri et recherche

Ensuite, jetons un coup d'œil au tri et à la recherche de tableaux.

4.1. Trier

Si nos éléments sont des primitifs ou implémentent Comparable , nous pouvons utiliser sort pour effectuer un tri en ligne:

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

Veillez à ce que le tri mute la référence d'origine , c'est pourquoi nous effectuons une copie ici.

sort utilisera un algorithme différent pour différents types d'éléments de tableau. Les types primitifs utilisent un tri rapide à double pivot et les types d'objets utilisent Timsort. Les deux ont le cas moyen de O (n log (n)) pour un tableau trié aléatoirement.

Depuis Java 8, parallelSort est disponible pour un tri-fusion parallèle. Il offre une méthode de tri simultané à l'aide de plusieurs tâches Arrays.sort .

4.2. recherche binaire

La recherche dans un tableau non trié est linéaire, mais si nous avons un tableau trié, nous pouvons le faire en O (log n) , ce que nous pouvons faire avec 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.

Cette classe a été étendue dans les versions Java plus récentes avec l'inclusion de méthodes de production et de consommation de flux dans Java 8 et de méthodes d'incompatibilité dans Java 9.

La source de cet article est, comme toujours, sur Github.