《java程式設計思想》第十六章 陣列
16.1 陣列為什麼特殊
(1)出現泛型之前:
陣列與其他種類的容器之間的區別有三方面:效率、型別和儲存基本型別的能力。
(2)泛型之後:
泛型的出現使得容器也具備了型別檢查的能力,而自動裝箱機制使容器可以與陣列幾乎一模一樣的用於基本型別,陣列的碩果僅存的優點就是效率。
(3)理解陣列結構和優點:在Java中陣列是一種效率最高的儲存和隨機訪問物件引用序列的方式。陣列就是一個簡單的線性序列,這使得元素訪問非常快速。但是為這種速度所付出的代價是陣列物件的大小被固定,並且在其生命週期中不可改變。
16.2 陣列是第一級物件
在宣告陣列時可採用“聚集初始化”方法:
int[] integers = {0, 1, 2, 3, 4};
但是如果不在宣告時初始化則必須採用“動態聚集初始化”方法:
int[] integers;
integers = new int[] {0, 1, 2, 3, 4};
16.4 多維陣列
Java 1.5新增的Arrays.deepToString()方法可以將多維陣列轉換為可讀的String。
陣列中構造矩陣的每個向量都可以具有任意的長度,這被稱為粗糙陣列。
自動包裝機制對陣列初始化器也起作用。
16.5 陣列與泛型
Java 不支援泛型陣列,不
List<String>[] ls = new ArrayList<String>[10];
是不支援的,而
List<String>[] ls = new ArrayList[10]
卻可以。
提到了一種情況:
List<String>[] lsa = new List<String>[10]; // Not really allowed.
Object o = lsa;
Object[] oa = (Object[]) o;
List<Integer> li = new ArrayList<Integer>();
li.add(new Integer(3));
oa[1] = li; // Unsound, but passes run time store check
String s = lsa[1].get(0); // Run-time error: ClassCastException.
這種情況下,由於JVM泛型的擦除機制,在執行時JVM是不知道泛型資訊的,所以可以給oa[1]賦上一個ArrayList<Integer>而不會出現ArrayStoreException,但是在取出資料的時候卻要做一次型別轉換,所以就會出現ClassCastException,如果可以進行泛型陣列的宣告,上面說的這種情況在編譯期將不會出現任何的警告和錯誤,只有在執行時才會出錯。而對泛型陣列的宣告進行限制,對於這樣的情況,可以在編譯期提示程式碼有型別安全問題,比沒有任何提示要強很多。
基於以上的原因,Java不支援宣告泛型陣列,更確切地表達是:陣列的型別不可以是型別變數,除非是採用萬用字元的方式,看下面這個例子:
List<?>[] lsa = new List<?>[10]; // OK, array of unbounded wildcard type.
Object o = lsa;
Object[] oa = (Object[]) o;
List<Integer> li = new ArrayList<Integer>();
li.add(new Integer(3));
oa[1] = li; // Correct.
String s = (String) lsa[1].get(0); // Run time error, but cast is explicit.
因為對於萬用字元的方式,最後取出資料是要做顯式的型別轉換的,所以並不會存在上一個例子的問題。
16.6 建立測試資料
Java標準類庫Arrays有一個作用十分有限的fill()方法,只能用同一個值填充各個位置,針對物件而言,就是複製同一個引用進行填充,還可以只填充陣列的某個區域:
Arrays.fill(a9, "Hello");
Arrays.fill(a9, 3, 5, "World");
例子中有一段小數保留兩位小數的程式碼可以借鑑:
int trimmed = Math.round(r.nextFloat() * 100);
return ((float)trimmed) / 100;
16.7 Arrays實用功能
(1) System.arraycopy() 陣列複製
Java標準類庫提供static方法System.arraycopy(),用它複製陣列比用for迴圈複製快很多。
System.arraycopy(Object src, int srcPosition, Object dest, int destPosition, int length);
注意:System.arraycopy()不會執行自動包裝盒自動拆包,兩個陣列必須具有相同的確切型別。
基本型別陣列與物件陣列都可以複製。如果複製物件陣列,只是複製了物件的引用——而不是物件本身的拷貝,這被稱為淺複製。
(2) Arrays.equals() 陣列比較
Arrays類提供了靜態equals()方法,用來比較整個陣列。陣列相等的條件是元素個數必須相等,並且對應位置的元素也相等,通過對每一個元素使用equals()方法來作比較。
使用Arrays.deepEquals()可以比較多維陣列。
(3) Arrays.sort() 陣列排序
使用靜態方法Arrays.sort()用語對陣列進行排序。
Java有兩種方式來提供比較功能:
第一種:實現java.lang.Comparable介面
此介面只有一個compareTo()方法,此方法接收另一個Object為引數,如果當前物件小於引數則返回負值,如果相等則返回零,如果當前物件大於引數則返回正值。如果對沒有實現Comparable介面的類的陣列進行排序,會丟擲ClassCastException。因為sort()需要把引數的型別轉換為Comparable。
第二種:建立一個實現了Comparator介面的單獨的類
假設使用別人定義好的類並沒有實現Comparable介面,或者類實現了Comparable介面但是需要另外一種比較方式。這是策略設計模式的一個應用例項。這個類有兩個方法compare()和equals()方法。不一定要實現equals()方法,因為它間接的繼承自Object的equals()方法。
Collections類包含一個reverseOrder()靜態方法可以產生一個Comparator,它可以翻轉自然的排序順序。
String的排序演算法依據詞典編排順序排序,所以大寫字母開頭的詞都放在前面,然後是小寫字母。如果想忽略大小寫可以使用String.CASE_INSENSITIVE_ORDER比較器。
Java標準類庫的排序演算法對各種型別的正排序都進行了優化——針對基本型別設計的快速排序和針對物件設計的穩定歸併排序。所以無需單行排序的效能。
(4) Arrays.binarySearch() 執行快速查詢
如果要對未排序的陣列使用binarySearch()將產生不可預料的結果(可能指沒找到元素的返回情況)。
如果找到了目標,Arrays.binarySearch()產生的返回值大於或等於0,否則產生負返回值。
若要保持陣列的排序狀態此目標元素應該插入的位置。這個負值的計算方式的: -(插入點)-1。插入點指第一個大於查詢物件的元素在陣列中的位置,如果陣列中所有的元素都小於查詢的物件,插入點就等於陣列的長度。
如果陣列包含重複的元素,則無法保證找到的是這些副本中的哪一個。
如果需要對沒有重複元素的陣列排序可以使用TreeSet(保持排序順序),或者LinkedHashSet(保持插入順序)。這些類會自動處理所有細節。除非他們成為程式效能的瓶頸,否則不需要自己維護陣列。
如果使用Comparator排序了某個物件陣列,在使用binarySearch()時必須提供同樣的Comparator。
作者在本章的總結中建議,當使用新版本的Java時,優選容器而不是陣列。只有在已證明效能成為問題(並且切換到陣列對效能提高有所幫助)時,才應該將程式重構為使用陣列。