Gids voor de klasse java.util.Arrays

1. Inleiding

In deze tutorial zullen we kijken naar java.util.Arrays, een utility-klasse die sinds Java 1.2 deel uitmaakt van Java.

Gebruik makend van Arrays, we kunnen arrays maken, vergelijken, sorteren, zoeken, streamen en transformeren.

2. Creëren

Laten we eens kijken naar enkele manieren waarop we arrays kunnen maken: kopie van, copyOfRange, en vullen.

2.1. kopie van en copyOfRange

Gebruiken copyOfRange, hebben we onze originele array nodig en de beginindex (inclusief) en eindindex (exclusief) die we willen kopiëren:

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

En te gebruiken kopie van, zouden we nemen intro en een doelmatrixgrootte en we zouden een nieuwe matrix van die lengte terugkrijgen:

String [] herzien = Arrays.copyOf (intro, 3); String [] geëxpandeerd = Arrays.copyOf (intro, 5); assertArrayEquals (Arrays.copyOfRange (intro, 0, 3), herzien); assertNull (uitgebreid [4]);

Let daar op kopie van vult de array aan met nuls als onze doelgrootte groter is dan de oorspronkelijke grootte.

2.2. vullen

Een andere manier waarop we een array met een vaste lengte kunnen maken, is vullen, wat handig is als we een array willen waarin alle elementen hetzelfde zijn:

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

Uitchecken setAll om een ​​array te maken waarin de elementen verschillend zijn.

Merk op dat we de array zelf van tevoren moeten instantiëren, in tegenstelling tot zoiets String [] filled = Arrays.fill ("een keer", 3);- aangezien deze functie werd geïntroduceerd voordat generieke geneesmiddelen in de taal beschikbaar waren.

3. Vergelijken

Laten we nu overschakelen naar methoden om arrays te vergelijken.

3.1. is gelijk aan en deepEquals

We kunnen gebruiken is gelijk aan voor eenvoudige arrayvergelijking op grootte en inhoud. Als we een null toevoegen als een van de elementen, mislukt de inhoudscontrole:

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

Als we geneste of meerdimensionale arrays hebben, kunnen we deepEquals om niet alleen de elementen op het hoogste niveau te controleren, maar de controle ook recursief uit te voeren:

Object [] verhaal = nieuw Object [] {intro, nieuwe String [] {"hoofdstuk één", "hoofdstuk twee"}, einde}; Object [] copy = nieuw Object [] {intro, nieuwe String [] {"hoofdstuk één", "hoofdstuk twee"}, einde}; assertTrue (Arrays.deepEquals (verhaal, kopie)); assertFalse (Arrays.equals (verhaal, kopie));

Merk op hoe diepquals passeert maar is gelijk aan mislukt.

Dit is zo omdat deepEquals roept zichzelf uiteindelijk elke keer dat het een array tegenkomt, terwijl is gelijk aan vergelijkt eenvoudig de referenties van sub-arrays.

Dit maakt het ook gevaarlijk om een ​​array aan te roepen met een zelfreferentie!

3.2. hashCode en deepHashCode

De implementatie van hashCode geeft ons het andere deel van de is gelijk aan/hashCode contract dat wordt aanbevolen voor Java-objecten. We gebruiken hashCode om een ​​geheel getal te berekenen op basis van de inhoud van de array:

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

Nu stellen we een element van de originele array in op null en herberekenen we de hash-waarden:

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

Alternatief, deepHashCode controleert de geneste arrays op overeenkomende aantallen elementen en inhoud. Als we herberekenen met deepHashCode:

int deepHashAfter = Arrays.deepHashCode (looping);

Nu kunnen we het verschil in de twee methoden zien:

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

deepHashCode is de onderliggende berekening die wordt gebruikt wanneer we werken met datastructuren zoals Hash kaart en HashSet op arrays.

4. Sorteren en zoeken

Laten we vervolgens eens kijken naar het sorteren en zoeken van arrays.

4.1. soort

Of onze elementen primitieven zijn of ze implementeren Vergelijkbaar, we kunnen gebruiken soort om een ​​in-line sortering uit te voeren:

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

Zorg ervoor soort muteert de oorspronkelijke referentie, daarom maken we hier een kopie.

soort gebruikt een ander algoritme voor verschillende typen array-elementen. Primitieve typen gebruiken een dual-pivot quicksort en objecttypen gebruiken Timsort. Beiden hebben het gemiddelde geval van O (n log (n)) voor een willekeurig gesorteerde array.

Vanaf Java 8, parallelSort is beschikbaar voor een parallelle sorteer-samenvoeging. Het biedt een gelijktijdige sorteermethode met behulp van verschillende Arrays.sort taken.

4.2. Binaire zoekopdracht

Zoeken in een ongesorteerde array is lineair, maar als we een gesorteerde array hebben, kunnen we dat doen in O (logboek n), dat is wat we kunnen doen Binaire zoekopdracht:

int exact = Arrays.binarySearch (gesorteerd, "tijd"); int caseInsensitive = Arrays.binarySearch (gesorteerd, "TiMe", String :: CompareToIgnoreCase); assertEquals ("tijd", gesorteerd [exact]); assertEquals (2, exact); assertEquals (exact, caseInsensitive);

Als we geen Comparator als derde parameter, dan Binaire zoekopdracht rekent erop dat ons elementtype van het type is Vergelijkbaar.

En nogmaals, merk dat op als onze array niet eerst is gesorteerd, dan Binaire zoekopdracht zal niet werken zoals we verwachten!

5. Streamen

Zoals we eerder zagen, Arrays is bijgewerkt in Java 8 met methoden die de Stream API gebruiken, zoals parallelSort (hierboven vermeld), stroom en setAll.

5.1. stroom

stroom geeft ons volledige toegang tot de Stream API voor onze array:

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

We kunnen inclusieve en exclusieve indices voor de stream bieden, maar we mogen een ArrayIndexOutOfBoundsException als de indices niet in de juiste volgorde, negatief of buiten bereik zijn.

6. Transformeren

Tenslotte, toString,asList, en setAll geef ons een paar verschillende manieren om arrays te transformeren.

6.1. toString en deepToString

Een geweldige manier om een ​​leesbare versie van onze originele array te krijgen, is met toString:

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

Opnieuw we moeten de diepe versie gebruiken om de inhoud van geneste arrays af te drukken:

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

6.2. asList

De handigste van alle Arrays methoden die we kunnen gebruiken is de asList. We hebben een gemakkelijke manier om van een array een lijst te maken:

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

Echter, de teruggekeerden Lijst zal een vaste lengte hebben, dus we kunnen geen elementen toevoegen of verwijderen.

Merk ook op dat, merkwaardig genoeg, java.util.Arrays heeft zijn eigen ArrayList subklasse, die asList geeft terug. Dit kan erg misleidend zijn bij het debuggen!

6.3. setAll

Met setAllkunnen we alle elementen van een array instellen met een functionele interface. De generatorimplementatie neemt de positionele index als parameter:

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

En het afhandelen van uitzonderingen is natuurlijk een van de meer lastige onderdelen van het gebruik van lambda's. Dus onthoud dat hier, als de lambda een uitzondering genereert, dan definieert Java niet de eindtoestand van de array.

7. Parallel voorvoegsel

Een andere nieuwe methode in Arrays geïntroduceerd sinds Java 8 is parallelPrefix. Met parallelPrefixkunnen we op een cumulatieve manier op elk element van de invoerarray werken.

7.1. parallelPrefix

Als de operator de optelling uitvoert zoals in het volgende voorbeeld, [1, 2, 3, 4] zal resulteren in [1, 3, 6, 10]:

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

We kunnen ook een subbereik specificeren voor de bewerking:

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

Merk op dat de methode parallel wordt uitgevoerd, dus de cumulatieve operatie dient vrij van bijwerkingen en associatief te zijn.

Voor een niet-associatieve functie:

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

gebruik makend van parallelPrefix zou inconsistente resultaten opleveren:

@Test openbare leegte whenPrefixNonAssociative_thenError () {boolean consistent = true; Random r = new Random (); voor (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); voor (int i = 1; i <arrB.length; i ++) {arrB [i] = niet-associatieveFunc (arrB [i - 1], arrB [i]); } consistent = Arrays.equals (arrA, arrB); if (! consistent) breken; } assertFalse (consistent); }

7.2. Prestatie

Berekening van parallelle prefixen is meestal efficiënter dan sequentiële lussen, vooral voor grote arrays. Bij het uitvoeren van micro-benchmark op een Intel Xeon-machine (6 cores) met JMH, kunnen we een geweldige prestatieverbetering zien:

Benchmark Mode Cnt Score Error Units largeArrayLoopSum thrpt 5 9.428 ± 0.075 bewerkingen / s largeArrayParallelPrefixSum thrpt 5 15.235 ± 0.075 bewerkingen / s Benchmark Mode Cnt Score Error Units largeArrayLoopSum gemiddelde 5 105.825 ± 0.846 bewerkingen / s largeArrayParallel28

Hier is de benchmarkcode:

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

7. Conclusie

In dit artikel hebben we geleerd hoe sommige methoden voor het maken, zoeken, sorteren en transformeren van arrays met behulp van de java.util.Arrays klasse.

Deze klasse is uitgebreid in recentere Java-releases met de opname van methoden voor het produceren en consumeren van streams in Java 8 en niet-overeenkomende methoden in Java 9.

De bron voor dit artikel is, zoals altijd, op Github.