java.util.Arrays 類就是爲數組而生的專用工具類,基本上常見的對數組的操作,Arrays 類都考慮到了,這讓我由衷地覺得,是時候給該類的作者 Josh Bloch、Neal Gafter、John Rose 點個讚了。

(我是怎麼知道作者名的?看源碼就可以,小夥伴們,裝逼吧)

Arrays 都可以幹嘛呢?常見的有:

  • 創建數組

  • 比較數組

  • 數組排序

  • 數組檢索

  • 數組轉流

  • 打印數組

  • 數組轉 List

  • setAll(沒想好中文名)

  • parallelPrefix(沒想好中文名)

那接下來,小夥伴們是不是已經迫不及待想要和二哥一起來打怪進階了。走你。

01、創建數組

使用 Arrays 類創建數組可以通過以下三個方法:

  • copyOf,複製指定的數組,截取或用 null 填充

  • copyOfRange,複製指定範圍內的數組到一個新的數組

  • fill,對數組進行填充

1)copyOf,直接來看例子:

String[] intro = new String[] { "沉", "默", "王", "二" };
String[] revised = Arrays.copyOf(intro, 3);
String[] expanded = Arrays.copyOf(intro, 5);
System.out.println(Arrays.toString(revised));
System.out.println(Arrays.toString(expanded));

revised 和 expanded 是複製後的新數組,長度分別是 3 和 5,指定的數組長度是 4。來看一下輸出結果:

[沉, 默, 王]
[沉, 默, 王, 二, null]

看到沒?revised 截取了最後一位,因爲長度是 3 嘛;expanded 用 null 填充了一位,因爲長度是 5。

2)copyOfRange,直接來看例子:

String[] intro = new String[] { "沉", "默", "王", "二" };
String[] abridgement = Arrays.copyOfRange(intro, 0, 3);
System.out.println(Arrays.toString(abridgement));

copyOfRange() 方法需要三個參數,第一個是指定的數組,第二個是起始位置(包含),第三個是截止位置(不包含)。來看一下輸出結果:

[沉, 默, 王]

0 的位置是“沉”,3 的位置是“二”,也就是說截取了從 0 位(包含)到 3 位(不包含)的數組元素。那假如說下標超出了數組的長度,會發生什麼呢?

String[] abridgementExpanded = Arrays.copyOfRange(intro, 0, 6);
System.out.println(Arrays.toString(abridgementExpanded));

結束位置此時爲 6,超出了指定數組的長度 4,來看一下輸出結果:

[沉, 默, 王, 二, null, null]

仍然使用了 null 進行填充。爲什麼要這麼做呢?小夥伴們思考一下,我想是作者考慮到了數組越界的問題,不然每次調用 Arrays 類就要先判斷很多次長度,很麻煩。

3)fill,直接來看例子:

String[] stutter = new String[4];
Arrays.fill(stutter, "沉默王二");
System.out.println(Arrays.toString(stutter));

使用 new 關鍵字創建了一個長度爲 4 的數組,然後使用 fill() 方法將 4 個位置填充爲“沉默王二”,來看一下輸出結果:

[沉默王二, 沉默王二, 沉默王二, 沉默王二]

如果想要一個元素完全相同的數組時, fill() 方法就派上用場了。

02、比較數組

Arrays 類的 equals() 方法用來判斷兩個數組是否相等,來看下面這個例子:

String[] intro = new String[] { "沉", "默", "王", "二" };
boolean result = Arrays.equals(new String[] { "沉", "默", "王", "二" }, intro);
System.out.println(result);
boolean result1 = Arrays.equals(new String[] { "沉", "默", "王", "三" }, intro);
System.out.println(result1);

輸出結果如下所示:

true
false

指定的數組爲沉默王二四個字,比較的數組一個是沉默王二,一個是沉默王三,所以 result 爲 true,result1 爲 false。

簡單看一下 equals() 方法的源碼:

public static boolean equals(Object[] a, Object[] a2) {
if (a==a2)
return true;
if (a==null || a2==null)
return false;

int length = a.length;
if (a2.length != length)
return false;

for (int i=0; i<length; i++) {
if (!Objects.equals(a[i], a2[i]))
return false;
}

return true;
}

因爲數組是一個對象,所以先使用“==”操作符進行判斷,如果不相等,再判斷是否爲 null,兩個都爲 null,返回 false;緊接着判斷 length,不等的話,返回 false;否則的話,依次調用 Objects.equals() 比較相同位置上的元素是否相等。

感覺非常嚴謹,這也就是學習源碼的意義,鑑賞的同時,學習。

除了 equals() 方法,還有另外一個訣竅可以判斷兩個數組是否相等,儘管可能會出現誤差(概率非常小)。那就是 Arrays.hashCode() 方法,先來看一下該方法的源碼:

public static int hashCode(Object a[]) {
if (a == null)
return 0;

int result = 1;

for (Object element : a)
result = 31 * result + (element == null ? 0 : element.hashCode());

return result;
}

哈希算法本身是非常嚴謹的,所以如果兩個數組的哈希值相等,那幾乎可以判斷兩個數組是相等的。

String[] intro = new String[] { "沉", "默", "王", "二" };

System.out.println(Arrays.hashCode(intro));
System.out.println(Arrays.hashCode(new String[] { "沉", "默", "王", "二" }));

來看一下輸出結果:

兩個數組的哈希值相等,畢竟元素是一樣的。但這樣確實不夠嚴謹,優先使用 Objects.equals() 方法。

03、數組排序

Arrays 類的 sort() 方法用來判斷兩個數組是否相等,來看下面這個例子:

String[] intro1 = new String[] { "chen", "mo", "wang", "er" };
String[] sorted = Arrays.copyOf(intro1, 4);
Arrays.sort(sorted);
System.out.println(Arrays.toString(sorted));

由於排序會改變原有的數組,所以我們使用了 copyOf() 方法重新複製了一份。來看一下輸出結果:

[chen, er, mo, wang]

可以看得出,按照的是首字母的升序進行排列的。基本數據類型是按照雙軸快速排序的,引用數據類型是按照 TimSort 排序的,使用了 Peter McIlroy 的“樂觀排序和信息理論複雜性”中的技術。

04、數組檢索

數組排序後就可以使用 Arrays 類的 binarySearch() 方法進行二分查找了。否則的話,只能線性檢索,效率就會低很多。

String[] intro1 = new String[] { "chen", "mo", "wang", "er" };
String[] sorted = Arrays.copyOf(intro1, 4);
Arrays.sort(sorted);
int exact = Arrays.binarySearch(sorted, "wang");
System.out.println(exact);
int caseInsensitive = Arrays.binarySearch(sorted, "Wang", String::compareToIgnoreCase);
System.out.println(caseInsensitive);

binarySearch() 方法既可以精確檢索,也可以模糊檢索,比如說忽略大小寫。來看一下輸出結果:

排序後的結果是 [chen, er, mo, wang] ,所以檢索出來的下標是 3。

05、數組轉流

Stream 流非常強大,需要入門的小夥伴可以查看我之前寫的一篇文章:

一文帶你入門Java Stream流,太強了

Arrays 類的 stream() 方法可以將數組轉換成流:

String[] intro = new String[] { "沉", "默", "王", "二" };
System.out.println(Arrays.stream(intro).count());

還可以爲 stream() 方法指定起始下標和結束下標:

System.out.println(Arrays.stream(intro, 1, 2).count());

如果下標的範圍有誤的時候,比如說從 2 到 1 結束,則程序會拋出 ArrayIndexOutOfBoundsException 異常:

Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: origin(2) > fence(1)
at java.base/java.util.Spliterators.checkFromToBounds(Spliterators.java:387)

06、打印數組

關於數組的打印方式,我之前單獨寫過一篇文章:

打印Java數組最優雅的方式是什麼?

裏面談了很多種數組打印的方式,因爲數組是一個對象,直接 System.out.println 的話,結果是這樣的:

[Ljava.lang.String;@3d075dc0

那最優雅的方式,其實文章裏面已經出現過很多次了,就是 Arrays.toString()

public static String toString(Object[] a) {
if (a == null)
return "null";

int iMax = a.length - 1;
if (iMax == -1)
return "[]";

StringBuilder b = new StringBuilder();
b.append('[');
for (int i = 0; ; i++) {
b.append(String.valueOf(a[i]));
if (i == iMax)
return b.append(']').toString();
b.append(", ");
}
}

小夥伴可以好好欣賞一下這段源碼,感覺考慮得非常周到。

07、數組轉 List

儘管數組非常強大,但它自身可以操作的工具方法很少,比如說判斷數組中是否包含某個值。轉成 List 的話,就簡便多了。

String[] intro = new String[] { "沉", "默", "王", "二" };
List<String> rets = Arrays.asList(intro);
System.out.println(rets.contains("二"));

不過需要注意的是, Arrays.asList() 返回的是 java.util.Arrays.ArrayList ,並不是   java.util.ArrayList ,它的長度是固定的,無法進行元素的刪除或者添加。

rets.add("三");
rets.remove("二");

執行這兩個方法的時候,會拋出異常:

Exception in thread "main" java.lang.UnsupportedOperationException
at java.base/java.util.AbstractList.add(AbstractList.java:153)
at java.base/java.util.AbstractList.add(AbstractList.java:111)

要想操作元素的話,需要多一步轉化:

List<String> rets1 = new ArrayList<>(Arrays.asList(intro));
rets1.add("三");
rets1.remove("二");

08、setAll

Java 8 新增了 setAll() 方法,它提供了一個函數式編程的入口,可以對數組的元素進行填充:

int[] array = new int[10];
Arrays.setAll(array, i -> i * 10);
System.out.println(Arrays.toString(array));

這段代碼什麼意思呢?i 就相當於是數組的下標,值從 0 開始,到 9 結束,那麼 i * 10 就意味着 0 * 10 開始,到 9 * 10 結束,來看一下輸出結果:

[0, 10, 20, 30, 40, 50, 60, 70, 80, 90]

比之前的 fill() 方法強大多了,對吧?不再填充的是相同的元素。

09、parallelPrefix

parallelPrefix() 方法和 setAll() 方法一樣,也是 Java 8 之後提供的,提供了一個函數式編程的入口,通過遍歷數組中的元素,將當前下標位置上的元素與它之前下標的元素進行操作,然後將操作後的結果覆蓋當前下標位置上的元素。

int[] arr = new int[] { 1, 2, 3, 4};
Arrays.parallelPrefix(arr, (left, right) -> left + right);
System.out.println(Arrays.toString(arr));

上面代碼中有一個 Lambda 表達式( (left, right) -> left + right ),需要入門的小夥伴可以查看我之前寫的一篇文章:

Lambda 表達式入門,看這篇就夠了

那爲了讓小夥伴看得更明白一些,我們把上面這段代碼稍微改造一下:

int[] arr = new int[]{1, 2, 3, 4};
Arrays.parallelPrefix(arr, (left, right) -> {
System.out.println(left + "," + right);
return left + right;
});
System.out.println(Arrays.toString(arr));

先來看一下輸出結果:

1,2
3,3
6,4
[1, 3, 6, 10]

也就是說, Lambda 表達式執行了三次:

  • 第一次是 1 和 2 相加,結果是 3,替換下標爲 1 的位置

  • 第二次是 3 和 3 相加,結果是 6,也就是第一次的結果和下標爲 2 的元素相加的結果

  • 第三次是 6 和 4 相加,結果是 10,也就是第二次的結果和下標爲 3 的元素相加的結果

有點強大,對吧?

10、總結

好了,我親愛的小夥伴們,以上就是本文的全部內容了,能看到這裏的都是最優秀的程序員,二哥必須要爲你點個贊。

------------------

公衆號:沉默王二(ID:cmower)
CSDN:沉默王二
這是一個有顏值卻靠才華喫飯的程序員,你知道,他的文章風趣幽默,讀起來就好像花錢一樣爽快。

長按下圖二維碼關注,你將感受到一個有趣的靈魂, 且每篇文章都有乾貨。

-------------- --- -

原創不易,莫要白票,如果覺得有點用的話,請毫不留情地素質三連吧,分享、點贊、在看,我不挑 ,因爲這將是我寫作更多優質文章的最強動力。

相關文章