教妹學 Java之陣列

語言: CN / TW / HK

“哥,我看你之前的文章裡提到,ArrayList 的內部是用陣列實現的,我就對陣列非常感興趣,想深入地瞭解一下,今天終於到這個環節了,好期待呀!”三妹的語氣裡顯得很興奮。

“的確是的,看 ArrayList 的原始碼就一清二楚了。”我一邊說,一邊開啟 Intellij IDEA,並找到了 ArrayList 的原始碼。

/** 
 * The array buffer into which the elements of the ArrayList are stored. 
 * The capacity of the ArrayList is the length of this array buffer. Any 
 * empty ArrayList with elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA 
 * will be expanded to DEFAULT_CAPACITY when the first element is added. 
 */ 
transient Object[] elementData; // non-private to simplify nested class access 
 
/** 
 * The size of the ArrayList (the number of elements it contains). 
 * 
 * @serial 
 */ 
private int size; 

“瞧見沒?Object[] elementData 就是陣列。”我指著顯示屏上這串程式碼繼續說。

陣列是一個物件,它包含了一組固定數量的元素,並且這些元素的型別是相同的。陣列會按照索引的方式將元素放在指定的位置上,意味著我們可以通過索引來訪問這些元素。在 Java 中,索引是從 0 開始的。

“哥,能說一下為什麼索引從 0 開始嗎?”三妹突然這個話題很感興趣。

“哦,Java 是基於 C/C++ 語言實現的,而 C 語言的下標是從 0 開始的,所以 Java 就繼承了這個良好的傳統習慣。C語言有一個很重要概念,叫做指標,它實際上是一個偏移量,距離開始位置的偏移量,第一個元素就在開始的位置,它的偏移量就為 0,所以索引就為 0。”此刻,我很自信。

“此外,還有另外一種說法。早期的計算機資源比較匱乏,0 作為起始下標相比較於 1 作為起始下標,編譯的效率更高。”

“哦。”三妹意味深長地點了點頭。

我們可以將陣列理解為一個個整齊排列的單元格,每個單元格里面存放著一個元素。

陣列元素的型別可以是基本資料型別(比如說 int、double),也可以是引用資料型別(比如說 String),包括自定義型別。

陣列的宣告方式分兩種。

先來看第一種:

int[] anArray; 

再來看第二種:

int anOtherArray[]; 

不同之處就在於中括號的位置,是跟在型別關鍵字的後面,還是跟在變數的名稱的後面。前一種的使用頻率更高一些,像 ArrayList 的原始碼中就用了第一種方式。

同樣的,陣列的初始化方式也有多種,最常見的是:

int[] anArray = new int[10]; 

看到了沒?上面這行程式碼中使用了 new 關鍵字,這就意味著陣列的確是一個物件,只有物件的建立才會用到 new 關鍵字,基本資料型別是不用的。然後,我們需要在方括號中指定陣列的長度。

這時候,陣列中的每個元素都會被初始化為預設值,int 型別的就為 0,Object 型別的就為 null。不同資料型別的預設值不同,可以參照之前的文章。

另外,還可以使用大括號的方式,直接初始化陣列中的元素:

int anOtherArray[] = new int[] {1, 2, 3, 4, 5}; 

這時候,陣列的元素分別是 1、2、3、4、5,索引依次是 0、1、2、3、4,長度是 5。

“哥,怎麼訪問陣列呢?”三妹及時地插話到。

前面提到過,可以通過索引來訪問陣列的元素,就像下面這樣:

anArray[0] = 10; 

變數名,加上中括號,加上元素的索引,就可以訪問到陣列,通過“=”操作符可以對元素進行賦值。

如果索引的值超出了陣列的界限,就會丟擲 ArrayIndexOutOfBoundException。

既然陣列的索引是從 0 開始,那就是到陣列的 length - 1 結束,不要使用超出這個範圍內的索引訪問陣列,就不會丟擲陣列越界的異常了。

當陣列的元素非常多的時候,逐個訪問陣列就太辛苦了,所以需要通過遍歷的方式。

第一種,使用 for 迴圈:

int anOtherArray[] = new int[] {1, 2, 3, 4, 5}; 
for (int i = 0; i < anOtherArray.length; i++) { 
    System.out.println(anOtherArray[i]); 
} 

通過 length 屬性獲取到陣列的長度,然後從 0 開始遍歷,就得到了陣列的所有元素。

第二種,使用 for-each 迴圈:

for (int element : anOtherArray) { 
    System.out.println(element); 
} 

如果不需要關心索引的話(意味著不需要修改陣列的某個元素),使用 for-each 遍歷更簡潔一些。當然,也可以使用 while 和 do-while 迴圈。

在 Java 中,可變引數用於將任意數量的引數傳遞給方法,來看 varargsMethod() 方法:

void varargsMethod(String... varargs) {} 

該方法可以接收任意數量的字串引數,可以是 0 個或者 N 個,本質上,可變引數就是通過陣列實現的。為了證明這一點,我們可以看一下反編譯一後的位元組碼:

public class VarargsDemo 
{ 
 
    public VarargsDemo() 
    { 
    } 
 
    transient void varargsMethod(String as[]) 
    { 
    } 
} 

所以,我們其實可以直接將陣列作為引數傳遞給該方法:

VarargsDemo demo = new VarargsDemo(); 
String[] anArray = new String[] {"沉默王二", "一枚有趣的程式設計師"}; 
demo.varargsMethod(anArray); 

也可以直接傳遞多個字串,通過逗號隔開的方式:

demo.varargsMethod("沉默王二", "一枚有趣的程式設計師"); 

在 Java 中,陣列與 List 關係非常密切。List 封裝了很多常用的方法,方便我們對集合進行一些操作,而如果直接運算元組的話,有很多不便,因為陣列本身沒有提供這些封裝好的操作,所以有時候我們需要把陣列轉成 List。

“怎麼轉呢?”三妹問到。

最原始的方式,就是通過遍歷陣列的方式,一個個將陣列新增到 List 中。

int[] anArray = new int[] {1, 2, 3, 4, 5}; 
 
List<Integer> aList = new ArrayList<>(); 
for (int element : anArray) { 
    aList.add(element); 
} 

更優雅的方式是通過 Arrays 類的 asList() 方法:

List<Integer> aList = Arrays.asList(anArray); 

但需要注意的是,該方法返回的 ArrayList 並不是 java.util.ArrayList,它其實是 Arrays 類的一個內部類:

private static class ArrayList<E> extends AbstractList<E> 
        implements RandomAccess, java.io.Serializable{} 

如果需要新增元素或者刪除元素的話,需要把它轉成 java.util.ArrayList。

new ArrayList<>(Arrays.asList(anArray)); 

Java 8 新增了 Stream 流的概念,這就意味著我們也可以將陣列轉成 Stream 進行操作。

String[] anArray = new String[] {"沉默王二", "一枚有趣的程式設計師", "好好珍重他"}; 
Stream<String> aStream = Arrays.stream(anArray); 

如果想對陣列進行排序的話,可以使用 Arrays 類提供的 sort() 方法。

基本資料型別按照升序排列

實現了 Comparable 介面的物件按照 compareTo() 的排序

來看第一個例子:

int[] anArray = new int[] {5, 2, 1, 4, 8}; 
Arrays.sort(anArray); 

排序後的結果如下所示:

[1, 2, 4, 5, 8] 

來看第二個例子:

String[] yetAnotherArray = new String[] {"A", "E", "Z", "B", "C"}; 
Arrays.sort(yetAnotherArray, 1, 3, 
                Comparator.comparing(String::toString).reversed()); 

只對 1-3 位置上的元素進行反序,所以結果如下所示:

[A, Z, E, B, C] 

有時候,我們需要從陣列中查詢某個具體的元素,最直接的方式就是通過遍歷的方式:

int[] anArray = new int[] {5, 2, 1, 4, 8}; 
for (int i = 0; i < anArray.length; i++) { 
    if (anArray[i] == 4) { 
        System.out.println("找到了 " + i); 
        break; 
    } 
} 

上例中從陣列中查詢元素 4,找到後通過 break 關鍵字退出迴圈。

如果陣列提前進行了排序,就可以使用二分查詢法,這樣效率就會更高一些。Arrays.binarySearch() 方法可供我們使用,它需要傳遞一個數組,和要查詢的元素。

int[] anArray = new int[] {1, 2, 3, 4, 5}; 
int index = Arrays.binarySearch(anArray, 4); 

“除了一維陣列,還有二維陣列,三妹你可以去研究下,比如說用二維陣列列印一下楊輝三角。”說完,我就去陽臺上休息了,留三妹在那裡學習,不能打擾她。

分享到: