Panduan untuk Kelas java.util.Arrays

1. Perkenalan

Dalam tutorial ini, kita akan melihat java.util.Arays , kelas utilitas yang telah menjadi bagian dari Java sejak Java 1.2.

Dengan menggunakan Array, kita dapat membuat, membandingkan, mengurutkan, mencari, mengalirkan, dan mengubah array.

2. Membuat

Mari kita lihat beberapa cara kita dapat membuat array: copyOf , copyOfRange , dan fill.

2.1. copyOf dan copyOfRange

Untuk menggunakan copyOfRange , kita membutuhkan array asli kita dan indeks awal (inklusif) dan indeks akhir (eksklusif) yang ingin kita salin:

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

Dan untuk menggunakan copyOf , kita akan mengambil intro dan ukuran array target dan kita akan mendapatkan kembali array baru dengan panjang itu:

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

Perhatikan bahwa copyOf mengisi array dengan null s jika ukuran target kita lebih besar dari ukuran aslinya.

2.2. mengisi

Cara lain, kita bisa membuat array dengan panjang tetap, is fill, yang berguna ketika kita menginginkan array yang semua elemennya sama:

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

Lihat setAll untuk membuat larik di mana elemennya berbeda.

Perhatikan bahwa kita perlu membuat instance array sendiri terlebih dahulu – sebagai lawan dari sesuatu seperti String [] diisi = Arrays.fill ("once" , 3) ; –Sejak fitur ini diperkenalkan sebelum obat generik tersedia dalam bahasa tersebut.

3. Membandingkan

Sekarang mari beralih ke metode untuk membandingkan array.

3.1. sama dan deepEquals

Kita dapat menggunakan persamaan untuk perbandingan larik sederhana berdasarkan ukuran dan konten. Jika kita menambahkan null sebagai salah satu elemen, pemeriksaan konten gagal:

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

Ketika kita memiliki larik bersarang atau multi-dimensi, kita dapat menggunakan deepEquals tidak hanya untuk memeriksa elemen tingkat atas tetapi juga melakukan pemeriksaan secara rekursif:

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

Perhatikan bagaimana deepE quals berlalu tetapi sama dengan gagal .

Ini karena deepEquals pada akhirnya memanggil dirinya sendiri setiap kali menemukan array , sedangkan persamaan hanya akan membandingkan referensi sub-array.

Selain itu, ini berbahaya untuk memanggil array dengan referensi sendiri!

3.2. hashCode dan deepHashCode

Implementasi hashCode akan memberi kita bagian lain dari kontrak sama dengan / hashCode yang direkomendasikan untuk objek Java. Kami menggunakan hashCode untuk menghitung integer berdasarkan konten array:

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

Sekarang, kami menetapkan elemen dari array asli ke nol dan menghitung ulang nilai hash:

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

Alternatifnya, deepHashCode memeriksa larik bersarang untuk mencocokkan jumlah elemen dan konten. Jika kita menghitung ulang dengan deepHashCode :

int deepHashAfter = Arrays.deepHashCode(looping);

Sekarang, kita dapat melihat perbedaan kedua metode tersebut:

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

deepHashCode adalah kalkulasi dasar yang digunakan ketika kita bekerja dengan struktur data seperti HashMap dan HashSet pada array .

4. Penyortiran dan Pencarian

Selanjutnya, mari kita lihat pengurutan dan pencarian array.

4.1. menyortir

Jika elemen kita adalah primitif atau menerapkan Comparable , kita dapat menggunakan sort untuk melakukan pengurutan in-line:

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

Berhati-hatilah agar jenis itu mengubah referensi asli , itulah sebabnya kami melakukan penyalinan di sini.

sort akan menggunakan algoritma yang berbeda untuk tipe elemen array yang berbeda. Tipe primitif menggunakan quicksort pivot ganda dan tipe Objek menggunakan Timsort. Keduanya memiliki kasus rata-rata O (n log (n)) untuk array yang diurutkan secara acak.

Mulai Java 8, parallelSort tersedia untuk penggabungan-sortir paralel. Ini menawarkan metode pengurutan bersamaan menggunakan beberapa tugas Arrays.sort .

4.2. binarySearch

Pencarian dalam array yang tidak diurutkan adalah linier, tetapi jika kita memiliki array yang diurutkan, maka kita dapat melakukannya di O (log n) , yang dapat kita lakukan dengan 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.

Kelas ini telah diperluas dalam rilis Java yang lebih baru dengan penyertaan metode produksi dan penggunaan aliran di Java 8 dan metode ketidakcocokan di Java 9.

Sumber artikel ini, seperti biasa, ada di Github.