要想數組用的 6,怎能不懂 java.util.Arrays
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 流非常強大,需要入門的小夥伴可以查看我之前寫的一篇文章:
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、打印數組
關於數組的打印方式,我之前單獨寫過一篇文章:
裏面談了很多種數組打印的方式,因爲數組是一個對象,直接 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
),需要入門的小夥伴可以查看我之前寫的一篇文章:
那爲了讓小夥伴看得更明白一些,我們把上面這段代碼稍微改造一下:
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:沉默王二
這是一個有顏值卻靠才華喫飯的程序員,你知道,他的文章風趣幽默,讀起來就好像花錢一樣爽快。
長按下圖二維碼關注,你將感受到一個有趣的靈魂, 且每篇文章都有乾貨。
-------------- --- -
原創不易,莫要白票,如果覺得有點用的話,請毫不留情地素質三連吧,分享、點贊、在看,我不挑 ,因爲這將是我寫作更多優質文章的最強動力。