Anleitung zur java.util.Arrays-Klasse

1. Einleitung

In diesem Tutorial werfen wir einen Blick auf java.util.Arrays , eine Dienstprogrammklasse, die seit Java 1.2 Teil von Java ist.

Mit Arrays können wir Arrays erstellen, vergleichen, sortieren, suchen, streamen und transformieren.

2. Erstellen

Schauen wir uns einige Möglichkeiten an, wie wir Arrays erstellen können: copyOf , copyOfRange und fill.

2.1. copyOf und copyOfRange

Um copyOfRange verwenden zu können , benötigen wir unser ursprüngliches Array sowie den Anfangsindex (einschließlich) und den Endindex (exklusiv), die wir kopieren möchten:

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

Und um copyOf zu verwenden , würden wir ein Intro und eine Ziel-Array-Größe verwenden und ein neues Array dieser Länge zurückerhalten:

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

Beachten Sie, dass copyOf das Array mit Pads null s , wenn unsere Zielgröße größer als die ursprüngliche Größe.

2.2. füllen

Eine andere Möglichkeit, ein Array mit fester Länge zu erstellen, ist das Füllen. Dies ist nützlich, wenn wir ein Array möchten, bei dem alle Elemente gleich sind:

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

Schauen Sie sich setAll an, um ein Array zu erstellen, in dem die Elemente unterschiedlich sind.

Beachten Sie, dass wir das Array vorher selbst instanziieren müssen - im Gegensatz zu String [] fill = Arrays.fill („einmal“ , 3) ; –Seit diese Funktion eingeführt wurde, bevor Generika in der Sprache verfügbar waren.

3. Vergleichen

Wechseln wir nun zu Methoden zum Vergleichen von Arrays.

3.1. equals und deepEquals

Wir können Equals für einen einfachen Array-Vergleich nach Größe und Inhalt verwenden. Wenn wir als eines der Elemente eine Null hinzufügen, schlägt die Inhaltsprüfung fehl:

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

Wenn wir verschachtelte oder mehrdimensionale Arrays haben, können wir deepEquals verwenden, um nicht nur die Elemente der obersten Ebene zu überprüfen, sondern auch die Prüfung rekursiv durchzuführen:

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

Beachten Sie, wie deepE quals erfolgreich ist, aber gleich fehlschlägt .

Dies liegt daran, dass deepEquals sich letztendlich jedes Mal aufruft, wenn es auf ein Array trifft , während equals einfach die Referenzen von Sub-Arrays vergleicht.

Dies macht es auch gefährlich, ein Array mit einer Selbstreferenz aufzurufen!

3.2. hashCode und deepHashCode

Die Implementierung von hashCode gibt uns den anderen Teil des equals / hashCode- Vertrags, der für Java-Objekte empfohlen wird. Wir verwenden hashCode , um eine Ganzzahl basierend auf dem Inhalt des Arrays zu berechnen:

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

Jetzt setzen wir ein Element des ursprünglichen Arrays auf null und berechnen die Hashwerte neu:

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

Alternativ überprüft deepHashCode die verschachtelten Arrays auf übereinstimmende Anzahlen von Elementen und Inhalten. Wenn wir mit deepHashCode neu berechnen :

int deepHashAfter = Arrays.deepHashCode(looping);

Jetzt können wir den Unterschied zwischen den beiden Methoden sehen:

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

deepHashCode ist die zugrunde liegende Berechnung, die verwendet wird, wenn wir mit Datenstrukturen wie HashMap und HashSet auf Arrays arbeiten .

4. Sortieren und Suchen

Schauen wir uns als nächstes das Sortieren und Suchen von Arrays an.

4.1. Sortieren

Wenn unsere Elemente entweder Primitive sind oder sie implementieren Comparable , können wir verwenden Art eine Inline - Art auszuführen:

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

Achten Sie darauf, dass diese Sortierung die ursprüngliche Referenz mutiert. Deshalb führen wir hier eine Kopie durch.

sort verwendet einen anderen Algorithmus für verschiedene Array-Elementtypen. Primitive Typen verwenden einen QuickSort mit zwei Pivots und Objekttypen verwenden Timsort. Beide haben den Durchschnittsfall von O (n log (n)) für ein zufällig sortiertes Array.

Ab Java 8 ist parallelSort für eine parallele Sortierzusammenführung verfügbar. Es bietet eine gleichzeitige Sortiermethode mit mehreren Arrays.sort- Aufgaben.

4.2. binäre Suche

Die Suche in einem unsortierten Array ist linear, aber wenn wir ein sortiertes Array haben, können wir dies in O (log n) tun, was wir mit binarySearch tun können:

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.

Diese Klasse wurde in neueren Java-Versionen um Stream-produzierende und konsumierende Methoden in Java 8 und Mismatch-Methoden in Java 9 erweitert.

Die Quelle für diesen Artikel ist wie immer bei Github.