深入理解 Java 陣列
:notebook: 本文已歸檔到:「 blog 」
:keyboard: 本文中的示例程式碼已歸檔到:「 javacore 」
簡介
陣列的特性
陣列對於每一門程式語言來說都是重要的資料結構之一,當然不同語言對陣列的實現及處理也不盡相同。幾乎所有程式設計語言都支援陣列。
陣列代表一系列物件或者基本資料型別,所有相同的型別都封裝到一起,採用一個統一的識別符號名稱。
陣列的定義和使用需要通過方括號 []
。
Java 中,陣列是一種引用型別。
Java 中,陣列是用來儲存固定大小的同類型元素。
陣列和容器
Java 中,既然有了強大的容器,是不是就不需要陣列了?
答案是不。
誠然,大多數情況下,應該選擇容器儲存資料。
但是,陣列也不是毫無是處:
- Java 中,陣列是一種效率最高的儲存和隨機訪問物件引用序列的方式。 陣列的效率要高於容器 (如
ArrayList
)。 - 陣列可以持有值型別,而容器則不能 (這時,就必須用到包裝類)。
Java 陣列的本質是物件
Java 陣列的本質是物件。它具有 Java 中其他物件的一些基本特點:封裝了一些資料,可以訪問屬性,也可以呼叫方法。所以,陣列是物件。
如果有兩個類 A 和 B,如果 B 繼承(extends)了 A,那麼 A[] 型別的引用就可以指向 B[] 型別的物件。
擴充套件閱讀:Java 中陣列的特性
如果想要論證 Java 陣列本質是物件 ,不妨一讀這篇文章。
Java 陣列和記憶體
Java 陣列在記憶體中的儲存是這樣的:
陣列物件(這裡可以看成一個指標)儲存在棧中。
陣列元素儲存在堆中。
如下圖所示:只有當 JVM 執行 new String[]
時,才會在堆中開闢相應的記憶體區域。陣列物件 array 可以視為一個指標,指向這塊記憶體的儲存地址。
宣告陣列
宣告陣列變數的語法如下:
int[] arr1; // 推薦風格 int arr2[]; // 效果相同 複製程式碼
建立陣列
Java 語言使用 new
操作符來建立陣列。有兩種建立陣列方式:
-
指定陣列維度
null
-
不指定陣列維度
- 用花括號中的實際元素初始化陣列,陣列大小與元素數相同。
示例 1:
public class ArrayDemo { public static void main(String[] args) { int[] array1 = new int[2]; // 指定陣列維度 int[] array2 = new int[] { 1, 2 }; // 不指定陣列維度 System.out.println("array1 size is " + array1.length); for (int item : array1) { System.out.println(item); } System.out.println("array2 size is " + array1.length); for (int item : array2) { System.out.println(item); } } } // Output: // array1 size is 2 // 0 // 0 // array2 size is 2 // 1 // 2 複製程式碼
:bulb: 說明 請注意陣列 array1 中的元素雖然沒有初始化,但是 length 和指定的陣列維度是一樣的。這表明 無論後面是否初始化陣列中的元素,陣列都已經開闢了相應的記憶體 。
陣列 array1 中的元素都被設為預設值。
示例 2:
public class ArrayDemo2 { static class User {} public static void main(String[] args) { User[] array1 = new User[2]; // 指定陣列維度 User[] array2 = new User[] {new User(), new User()}; // 不指定陣列維度 System.out.println("array1: "); for (User item : array1) { System.out.println(item); } System.out.println("array2: "); for (User item : array2) { System.out.println(item); } } } // Output: // array1: // null // null // array2: // io.github.dunwu.javacore.array.ArrayDemo2$User@4141d797 // io.github.dunwu.javacore.array.ArrayDemo2$User@68f7aae2 複製程式碼
:bulb: 說明
請將本例與示例 1 比較,可以發現:如果使用指定陣列維度方式建立陣列,且陣列元素為引用型別,則陣列中的元素元素值為 null
。
陣列維度的形式
建立陣列時,指定的陣列維度可以有多種形式:
- 陣列維度可以是整數、字元。
- 陣列維度可以是整數型、字元型變數。
- 陣列維度可以是計算結果為整數或字元的表示式。
示例:
public class ArrayDemo3 { public static void main(String[] args) { int length = 3; // 放開被注掉的程式碼,編譯器會報錯 // int[] array = new int[4.0]; // int[] array2 = new int["test"]; int[] array3 = new int['a']; int[] array4 = new int[length]; int[] array5 = new int[length + 2]; int[] array6 = new int['a' + 2]; // int[] array7 = new int[length + 2.1]; System.out.println("array3.length = [" + array3.length + "]"); System.out.println("array4.length = [" + array4.length + "]"); System.out.println("array5.length = [" + array5.length + "]"); System.out.println("array6.length = [" + array6.length + "]"); } } // Output: // array3.length = [97] // array4.length = [3] // array5.length = [5] // array6.length = [99] 複製程式碼
:bulb: 說明
當指定的陣列維度是字元時,Java 會將其轉為整數。如字元 a
的 ASCII 碼是 97。
綜上,Java 陣列的陣列維度可以是常量、變數、表示式,只要轉換為整數即可。
請留意,有些程式語言則不支援這點,如 C/C++ 語言,只允許陣列維度是常量。
陣列維度的大小
陣列維度並非沒有上限的,如果數值過大,編譯時會報錯。
int[] array = new int[6553612431]; // 陣列維度過大,編譯報錯 複製程式碼
此外,陣列過大,可能會導致棧溢位。
訪問陣列
Java 中,可以通過在 []
中指定下標,訪問陣列元素,下標位置從 0 開始。
public class ArrayDemo4 { public static void main(String[] args) { int[] array = {1, 2, 3}; for (int i = 0; i < array.length; i++) { array[i]++; System.out.println(String.format("array[%d] = %d", i, array[i])); } } } // Output: // array[0] = 2 // array[1] = 3 // array[2] = 4 複製程式碼
:bulb: 說明
上面的示例中,從 0 開始,使用下標遍歷陣列 array 的所有元素,為每個元素值加 1 。
陣列的引用
Java 中,陣列型別是一種引用型別。
因此,它可以作為引用,被 Java 函式 作為函式入參或返回值 。
陣列作為函式入參的示例:
public class ArrayRefDemo { private static void fun(int[] array) { for (int i : array) { System.out.print(i + "\t"); } } public static void main(String[] args) { int[] array = new int[] {1, 3, 5}; fun(array); } } // Output: // 135 複製程式碼
陣列作為函式返回值的示例:
public class ArrayRefDemo2 { /** * 返回一個數組 */ private static int[] fun() { return new int[] {1, 3, 5}; } public static void main(String[] args) { int[] array = fun(); System.out.println(Arrays.toString(array)); } } // Output: // [1, 3, 5] 複製程式碼
泛型和陣列
通常,陣列和泛型不能很好地結合。你不能例項化具有引數化型別的陣列。
Peel<Banana>[] peels = new Pell<Banana>[10]; // 這行程式碼非法 複製程式碼
Java 中不允許直接建立泛型陣列。但是,可以通過建立一個型別擦除的陣列,然後轉型的方式來建立泛型陣列。
public class GenericArrayDemo<T> { static class GenericArray<T> { private T[] array; public GenericArray(int num) { array = (T[]) new Object[num]; } public void put(int index, T item) { array[index] = item; } public T get(int index) { return array[index]; } public T[] array() { return array; } } public static void main(String[] args) { GenericArray<Integer> genericArray = new GenericArray<Integer>(4); genericArray.put(0, 0); genericArray.put(1, 1); Object[] array = genericArray.array(); System.out.println(Arrays.deepToString(array)); } } // Output: // [0, 1, null, null] 複製程式碼
擴充套件閱讀:https://www.cnblogs.com/jiangzhaowei/p/7399522.html
我認為,對於泛型陣列的理解,點到為止即可。實際上,真的需要儲存泛型,還是使用容器更合適。
多維陣列
多維陣列可以看成是陣列的陣列,比如二維陣列就是一個特殊的一維陣列,其每一個元素都是一個一維陣列。
Java 可以支援二維陣列、三維陣列、四維陣列、五維陣列。。。
但是,以正常人的理解能力,一般也就最多能理解三維陣列。所以,請不要做反人類的事,去定義過多維度的陣列。
多維陣列使用示例:
public class MultiArrayDemo { public static void main(String[] args) { Integer[][] a1 = { // 自動裝箱 {1, 2, 3,}, {4, 5, 6,}, }; Double[][][] a2 = { // 自動裝箱 { {1.1, 2.2}, {3.3, 4.4} }, { {5.5, 6.6}, {7.7, 8.8} }, { {9.9, 1.2}, {2.3, 3.4} }, }; String[][] a3 = { {"The", "Quick", "Sly", "Fox"}, {"Jumped", "Over"}, {"The", "Lazy", "Brown", "Dog", "and", "friend"}, }; System.out.println("a1: " + Arrays.deepToString(a1)); System.out.println("a2: " + Arrays.deepToString(a2)); System.out.println("a3: " + Arrays.deepToString(a3)); } } // Output: // a1: [[1, 2, 3], [4, 5, 6]] // a2: [[[1.1, 2.2], [3.3, 4.4]], [[5.5, 6.6], [7.7, 8.8]], [[9.9, 1.2], [2.3, 3.4]]] // a3: [[The, Quick, Sly, Fox], [Jumped, Over], [The, Lazy, Brown, Dog, and, friend]] 複製程式碼
Arrays 類
Java 中,提供了一個很有用的陣列工具類:Arrays。
它提供的主要操作有:
sort binarySearch equals fill asList hash toString
擴充套件閱讀:https://juejin.im/post/5a6ade5c518825733e60acb8
小結
(1)陣列特性
- Java 中,陣列是一種引用型別。
- Java 中,陣列是用來儲存固定大小的同類型元素。
- 陣列的定義和使用需要通過方括號
[]
。
(2)陣列和容器
-
大多數情況下,應該選擇容器儲存資料。
-
陣列的效率要高於容器
-
陣列可以持有值型別,而容器則不能
(3)Java 陣列的本質是物件。
(4)Java 陣列和記憶體
- 陣列物件(陣列指標)儲存在棧中。
- 陣列元素值儲存在堆中。
(5)宣告陣列有兩種方式:
int[] arr1; // 推薦風格 int arr2[]; // 效果相同 複製程式碼
(6)建立陣列有兩種方式:
int[] array1 = new int[2]; // 指定陣列維度 int[] array2 = new int[] { 1, 2 }; // 不指定陣列維度 複製程式碼
Java 陣列的陣列維度可以是常量、變數、表示式,只要轉換為整數即可。
陣列過大,可能會導致棧溢位。超過一定數值,甚至編譯器就會報錯。
(7)Java 中,可以通過在 [] 中指定下標,訪問陣列元素,下標位置從 0 開始。
(8)陣列可以作為函式的入參或返回值。
(9)多維陣列可視為陣列的陣列。
(10)Arrays 類是一個很有用的陣列工具類。
參考資料
- Java 程式設計思想
- JAVA 核心技術(卷 1)
- Java 中陣列的特性
- https://juejin.im/post/59cae3de6fb9a00a4551915b
- https://www.cnblogs.com/jiangzhaowei/p/7399522.html
- https://juejin.im/post/5a6ade5c518825733e60acb8