Java基礎:對象的集合(上)
1.數組:數組與其它容器的區別體現在三個方面:效率,類型識別以及可以持有primitives。數組是 Java 提供的,能隨機存儲和訪問 reference 序列的諸多方法中的,最高效的一種。數組是一個簡單的線性序列,所以它可以快速的訪問其中的元素。但是速度是有代價的;當你創建了一個數組之後,它的容量就固定了,而且在其生命週期裏不能改變。java泛型容器類還包括 List,Set 和 Map。它們處理對象的時候就好像這些對象都沒有自己的具體類型一樣。也就是說,容器將它所含的元素都看成是(Java 中所有類的根類)Object 的。這樣你只需創建一種容器,就能把所有類型的對象全都放進去。與其他泛型容器相比,這裏體現出數組的第二個優勢:創建數組的時候,你也同時指明瞭它所持有的對象的類型(這又引出了第三點 —— 數組可以持有 primitives,而容器卻不行)。也就是說,它會在編譯的時候作類型檢查,從而防止你插入錯誤類型的對象,或者是在提取對象的時候把對象的類型給搞錯了。
2.數組是第一流的對象:不管你用的是那種類型的數組,數組的標識符實際上都是一個“創建在堆(heap)裏的實實在在的對象的”reference。實際上是那個對象持有其他對象的 reference。reference(Object) 數組與primitives 數組不同的是對象數組持有 reference,而 primitive 數組則直接持有值。
import com.bruceeckel.simpletest.*;
class Weeble {} // A small mythical creature
public class ArraySize {
public static void main(String[] args) {
// Arrays of objects:
Weeble[] a; // Local uninitialized variable
Weeble[] b = new Weeble[5]; // Null references
Weeble[] c = new Weeble[4];//數組的定義方式1
for(int i = 0; i < c.length; i++)
if(c[i] == null) // Can test for null reference
c[i] = new Weeble( );
// Aggregate initialization:
Weeble[] d = {new Weeble( ), new Weeble( ), new Weeble( )};//數組的定義方式2
// Dynamic aggregate initialization:
a = new Weeble[] {new Weeble( ), new Weeble( )};//數組的定義方式3
System.out.println("a.length=" + a.length);
System.out.println("b.length = " + b.length);
// The references inside the array are
// automatically initialized to null:
for(int i = 0; i < b.length; i++)
System.out.println("b[" + i + "]=" + b[i]);//就算b裏沒有值也可以輸出,只不過是null而已,
//最重要的是Weeble[] b = new Weeble[5];分配了空間
System.out.println("c.length = " + c.length);
System.out.println("d.length = " + d.length);
a = d;//改變了a的指向
System.out.println("a.length = " + a.length);
// Arrays of primitives:
int[] e; // Null reference
int[] f = new int[5];//數組的定義方式1
int[] g = new int[4];
for(int i = 0; i < g.length; i++)
g[i] = i*i;
int[] h = { 11, 47, 93 };//數組的定義方式2
// Compile error: variable e not initialized:
//!System.out.println("e.length=" + e.length);
System.out.println("f.length = " + f.length);
// The primitives inside the array are
// automatically initialized to zero:
for(int i = 0; i < f.length; i++)
System.out.println("f[" + i + "]=" + f[i]);
System.out.println("g.length = " + g.length);
System.out.println("h.length = " + h.length);
e = h;//改變了e的指向
System.out.println("e.length = " + e.length);
e = new int[] { 1, 2 };//數組的定義方式3
System.out.println("e.length = " + e.length);
}
} ///:~
3.返回一個數組:假設你寫了一個方法,它返回的不是一個而是一組東西。在 C 和 C++之類的語言裏,這件事就有些難辦了。因爲你不能返回一個數組,你只能返回一個指向數組的指針。由於要處理“控制數組生命週期”之類的麻煩事,這麼做很容易會出錯,最後導致內存泄漏。Java 採取了類似的解決方案,但是不同之處在於,它返回的“就是一個數組”。與 C++不同,你永遠也不必爲 Java 的數組操心——只要你還需要它,它就還在;一旦你用完了,垃圾回收器會幫你把它打掃乾淨。(也就是說你可以把不同類型的東西同時都塞到一個數組裏進行返回,只要你知道那個是哪個就行。這種情況也可以適合Java的其他容器裏)
import com.bruceeckel.simpletest.*;
import java.util.*;
public class IceCream {
private static Random rand = new Random( );
public static final String[] flavors = {
"Chocolate", "Strawberry", "Vanilla Fudge Swirl",
"Mint Chip", "Mocha Almond Fudge", "Rum Raisin",
"Praline Cream", "Mud Pie"
};
public static String[] flavorSet(int n) {
String[] results = new String[n];
boolean[] picked = new boolean[flavors.length];
for(int i = 0; i < n; i++) {
int t;
do
t = rand.nextInt(flavors.length);
while(picked[t]);
results[i] = flavors[t];
picked[t] = true;
}
return results;
}
public static void main(String[] args) {
for(int i = 0; i < 20; i++) {
System.out.println(
"flavorSet(" + i + ") = ");
String[] fl = flavorSet(flavors.length);
for(int j = 0; j < fl.length; j++)
System.out.println("/t" + fl[j]);
}
}
} ///:~
4.Arrays 類:java.util 裏面有一個 Arrays 類,它包括了一組可用於數組的 static方法,這些方法都是一些實用工具。其中有四個基本方法:用來比較兩個數組是否相等的 equals( );用來填充數組的 fill( );用來對數組進行排序的 sort( );以及用於在一個已排序的數組中查找元素的binarySearch( )。所有這些方法都對 primitive 和 Object 進行了重載。此外還有一個 asList( )方法,它接受一個數組,然後把它轉成一個List 容器。
(1)fill( )方法,但是它太簡單了;它只是簡單的把一個的值複製到數組各個位置,如果是對象,則將相同的reference 拷貝到每個位置,如:
int[] a5 = new int[10];
Arrays.fill(a5, 19);//a5數組的每個位置都是19
String[] a9 = new String[10];
Arrays.fill(a9, "Hello");//a9數組的每個位置都是"Hello"
Arrays.fill(a9, 3, 5, "World");//用"World"來填充a[3],a[4],包前不包後。
(2)java 標準類庫提供了一個 System.arraycopy( )的 static 方法。相比 for 循環,它能以更快的速度拷貝數組。如:
int[] i = new int[7];
int[] j = new int[10];
Arrays.fill(i, 47);
Arrays.fill(j, 99);
System.arraycopy(i, 1, j, 0, i.length-1);//把i考到j裏,1爲i的下標,0爲j的開始下標,i.length-1爲j的結束下標
(3)Arrays 提供了經重載的 equals( )方法。當然,也是針對各種 primitive 以及 Object 的。兩個數組要想完全相等,它們必須有相同數量的元素,而且數組的每個元素必須與另一個數組的相對應的位置上的元素相等。元素的相等性,用 eqauls( )判斷。(對於 primitive,它會使用其 wrapper 類的 equals( );比如 int 使用Integer.equals( )。)例如:
import com.bruceeckel.simpletest.*;
import java.util.*;
public class ComparingArrays {
public static void main(String[] args) {
int[] a1 = new int[10];
int[] a2 = new int[10];
Arrays.fill(a1, 47);
Arrays.fill(a2, 47);
System.out.println(Arrays.equals(a1, a2));
a2[3] = 11;
System.out.println(Arrays.equals(a1, a2));
String[] s1 = new String[5];
Arrays.fill(s1, "Hi");
String[] s2 = {"Hi", "Hi", "Hi", "Hi", "Hi"};
System.out.println(Arrays.equals(s1, s2));//數組是否相等是基於其內容,(通過 Object.equals( )),}
} ///:~
(4)Arrays.sort(a)可以對數組a(a可以爲任何的數組)進行排序.內置的排序方法能對任何數組排序,不論是 primitive的還是對象數組,只要它實現了 Comparable 接口或有一個與之相關的 Comparator(針對對象數組,primitive 數組不允許使用 Comparator)接口就行了。[1]:Comparable 接口,要重寫一個方法 compareTo( ),這個方法能接受另一個對象作爲參數,如果現有對象比參數小,它會返回一個負數,如果相同則返回零,如果現有的對象比參數大,它就返回一個正數。[2]Comparator 接口,有兩個方法 compare( )和 equals( )。但是除非是有特殊的性能要求,否則你用不着去實現 equals( )。因爲只要是類,它就都隱含地繼承自Object,而 Object 裏面已經有了一個 equals( )了。所以你儘可以使用缺省的 Object 的 equals( ),這樣就已經滿足接口的要求了。 Arrays.asList( )方法把數組改造成 List:如
List a = Arrays.asList("one two three four five six seven eight".split(" "));
例子[1]:
import com.bruceeckel.util.*;
import java.util.*;
public class CompType implements Comparable {
int i;
int j;
public CompType(int n1, int n2) {
i = n1;
j = n2;
}
public String toString( ) {
return "[i = " + i + ", j = " + j + "]";
}
public int compareTo(Object rv) {
int rvi = ((CompType)rv).i;
return (i < rvi ? -1 : (i == rvi ? 0 : 1));
}
private static Random r = new Random( );
public static Generator generator( ) {
return new Generator( ) {
public Object next( ) {
return new CompType(r.nextInt(100),r.nextInt(100));
}
};
}
public static void main(String[] args) {
CompType[] a = new CompType[10];
Arrays2.fill(a, generator( ));
System.out.println(
"before sorting, a = " + Arrays.asList(a));
Arrays.sort(a);
System.out.println("after sorting, a = " + Arrays.asList(a));
}
} ///:~
如果CompType 沒有實現 Comparable 接口,那麼程序運行調用到sort( )的時候,就會引發一個 ClassCastException 錯誤。這是因爲sort( )會把傳給它的參數轉換成 Comparable。
例子[2]:
import com.bruceeckel.util.*;
import java.util.*;
class CompTypeComparator implements Comparator {
public int compare(Object o1, Object o2) {
int j1 = ((CompType)o1).j;
int j2 = ((CompType)o2).j;
return (j1 < j2 ? -1 : (j1 == j2 ? 0 : 1));
}
}
public class ComparatorTest {
public static void main(String[] args) {
CompType[] a = new CompType[10];
Arrays2.fill(a, CompType.generator( ));
System.out.println("before sorting, a = " + Arrays.asList(a));
Arrays.sort(a, new CompTypeComparator( ));//利用Comparator比較方法來排列a數組,
//因爲自動調用compare方法
System.out.println("after sorting, a = " + Arrays.asList(a));
}
} ///:~
(5)查詢有序數組,一旦數組排完序,你就能用 Arrays.binarySearch( )進行快速查詢了。但是切忌對一個尚未排序的數組使用 binarySearch( );因爲這麼做的結果是沒意義的。如果 Arrays.binarySearch( )找到了,它就返回一個大於或等於 0
的值。否則它就返回一個負值,而這個負值要表達的意思是,如果你手動維護這個數組的話,這個值應該插在哪個位置。這個值就是:(插入點)-1, “插入點”就是,在所有“比要找的那個值”更大值中,最小的那個值的下標,或者,如果數組中所有的值都比要找的值小,它就是a.size( )。如果數組裏面有重複元素,那它不能保證會返回哪一個。如果排序的時候用到了 Comparator ,那麼binarySearch( )的時候,也必須使用同一個 Comparator (用這個方法的重載版)。
import com.bruceeckel.simpletest.*;
import com.bruceeckel.util.*;
import java.util.*;
public class AlphabeticSearch {
public static void main(String[] args) {
String[] sa = new String[30];
Arrays2.fill(sa, new Arrays2.RandStringGenerator(5));
CompTypeComparator comp = new CompTypeComparator( );
Arrays.sort(sa, comp);//利用了Comparator比較排序
int index = Arrays.binarySearch(sa, sa[10],comp);//也一定要用Comparator進行比較
System.out.println("Index = " + index);
}
} ///:~
(6)容器簡介:Java 的容器類分成兩種基本類型。它們的區別就在,每個位置能放多少對象。Collection 只允許每個位置上放一個對象(這個名字有點誤導,因爲容器類庫也常被統稱爲 collections)。它包括“以一定順序持有一組對象”的 List,以及“只能允許添加不重複的對象”的 Set。ArrayList 是一種 List,而 HashSet 則是一種 Set。你可以用 add( )方法往Collection 裏面加對象。Map 保存的是“鍵(key)—值”形式的 pair,很像是一個微型數據庫。上面這段程序用了一種叫HashMap 的 Map。如果你建了一個“州和首府”的 Map,然後想查一下 Ohio 的首府在哪裏,你就可以用它來找了。用法和用下標查數組是一樣的。(Map 又被稱爲關聯性數組associative array。)你可以用 put( )方法往 Map 裏面加元素。它接受鍵—值形式 pair 作參數。
List 會老老實實地持有你所輸入的所有對象,既不做排序也不做編輯。Set 則每個對象只接受一次,而且還要用它自己的規則對元素進行重新排序(一般情況下,你關心的只是Set 包沒包括某個對象,而不是它到底排在哪裏——如果是那樣,你最好還是用 List)。Map 也不接收重複的 pair,至於是不是重複,要由key 來決定。此外,它也有它自己的內部排序規則,不會受輸入順序影響。如果插入順序是很重要的,那你就只能使用 LinkedHashSet 或LinkedHashMap 了。
Collection 和 Map 默認情況下的打印輸出(使用容器類的 toString( )方法)的效果很不錯
,所以我們就不再提供額外的打印支持了。打印出來的 Collection 會用方括號括起來,元素與元素之間用逗號分開。Map 會用花括號括起來,鍵和值之間用等號聯起來(鍵在左邊,值在右邊)。
(7).容器的缺點:不知道對象的類型:Java 的容器有個缺點,就是往容器裏面放對象的時候,會把對象的類型信息給弄丟了。這是因爲開發容器類的程序員不會知道你要用它來保存什麼類型的對象,而讓容器僅只保存特定類型的對象又會影響它的通用性。所以容器被做成只持有 Object,也就是所有對象的根類的 reference,這樣它就能持有任何類型的對象了。(當然不包括 primitive,因爲它們不是對象,也沒有繼承別的對象。)這是一個很了不起的方案,只是:
[1]由於在將對象放入容器的時候,它的類型信息被扔掉了,所以容器對“能往裏面加什麼類型的對象”沒有限制。比方說,即使你想讓它只持有 cat,別人也能很輕易地把 dog 放進去。
[2]由於對象的類型信息沒了,容器只知道它持有的 Object 的 reference,所以對象在使用之前(在容器裏取出的時候)還必須進行類型轉換。
(8)迭代器:“迭代器(iterator)”的概念(又是一個設計模式)就是用來達成這種抽象的。迭代器是一個對象,它的任務是,能在讓“客戶程序員在不知道,或者不關心他所處理的是什麼樣的底層序列結構”的情況下,就能在一個對象序列中前後移動,並選取其中的對象。java的iterator可做的事情:
[1]用 iterator( )方法叫容器傳給你一個 Iterator 對象。第一次調用Iterator 的 next( )方法的時候,它就會傳給你序列中的第一個元素。
[2]用 next( )方法獲取序列中的下一個對象。
[3]用 hasNext( )方法查詢序列中是否還有其他對象。
[4]用 remove( )方法刪除迭代器所返回的最後一個元素。
package c11;
public class Cat {
private int catNumber;
public Cat(int i) { catNumber = i; }
public void id( ) {
System.out.println("Cat #" + catNumber);
}
public String toString(){
System.out.println("Cat''id =" + catNumber);
}
} ///:~
package c11;
import com.bruceeckel.simpletest.*;
import java.util.*;
public class CatsAndDogs2 {
public static void main(String[] args) {
List cats = new ArrayList( );//ArrayList是一個能夠自動擴展的數組,但是他是一個List
for(int i = 0; i < 7; i++)
cats.add(new Cat(i));
Iterator e = cats.iterator( );//Iterator的創建方式
while(e.hasNext( ))//先用hasNext( )進行判斷
((Cat)e.next( )).id( );
System.out.println(e.next());//直接調用toString()方法.
}
} ///:~
(9)Collection 的功能:Collection 的所有功能,也就是你能用 Set 和 List做什麼事(不包括從 Object 自動繼承過來的方法)。
[1]boolean add(Object):確保容器能持有你傳給它的那個參數。如果沒能把它加進去,就返回 false。(這是個“可選”的方法,本章稍後會再作解釋。)
[2]boolean addAll(Collection):加入參數 Collection 所含的所有元素。只要加了元素,就返回 true。(“可選”)
[3]void clear( ):清除容器所保存的所有元素。(“可選”)
[4]boolean contains(Object):如果容器持有參數 Object,就返回true。
[5]boolean containsAll(Collection):如果容器持有參數 Collection 所含的全部元素,就返回 true。
[6]boolean isEmpty( ):如果容器裏面沒有保存任何元素,就返回 true。
[7]Iterator iterator( ):返回一個可以在容器的各元素之間移動的 Iterator。
[8]boolean remove(Object):如果容器裏面有這個參數 Object,那麼就把其中的某一個給刪了。只要刪除過東西,就返回 true。(“可選”)
[9]boolean removeAll(Collection):刪除容器裏面所有參數 Collection 所包含的元素。只要刪過東西,就返回true。(“可選”)。
[10]boolean retainAll(Collection):只保存參數 Collection 所包括的元素(集合論中“交集”的概念)。如果發生過變化,則返回 true。(“可選”)
[11]int size( ) :返回容器所含元素的數量。
[12]Object[] toArray( ):返回一個包含容器中所有元素的數組。
[13]Object[] toArray(Object[] a):返回一個包含容器中所有元素的數組,且這個數組不是普通的 Object 數組,它的類型應該同參數數組 a 的類型相同(要做類型轉換)。
(10)List 的功能:ist 的基本用法是用 add( )加對象,用 get( )取對象,用iterator( )獲取這個序列的 Iterator。
[1]List (接口):List 的最重要的特徵就是有序;它會確保以一定的順序保存元素。List 在 Collection 的基礎上添加了大量方法,使之能在序列中間插入和刪除元素。(只對 LinkedList 推薦使用。)List 可以製造ListIterator 對象,你除了能用它在 List 的中間插入和刪除元素之外,還能用它沿兩個方向遍歷List。
[2]ArrayList*:一個用數組實現的 List。能進行快速的隨機訪問,但是往列表中間插入和刪除元素的時候比較慢。ListIterator 只能用在反向遍歷 ArrayList 的場合,不要用它來插入和刪除元素,因爲相比LinkedList,在 ArrayList 裏面用ListIterator 的系統開銷比較高。
[3]LinkedList:對順序訪問進行了優化。在 List 中間插入和刪除元素的代價也不高。隨機訪問的速度相對較慢。(用ArrayList 吧。)此外它還有 addFirst( ),addLast( ),getFirst( ),getLast( ),removeFirst( )和 removeLast( )等方法(這些方法,接口和基類均未定義),你能把它當成棧(stack),隊列(queue)或雙向隊列(deque)來用。
(11)Set 的功能:Set 的接口就是 Collection 的,所以不像那兩個 List,它沒有額外的功能。Set 會拒絕持有多個具有相同值的對象的實例(對象的“值”又是由什麼決定的呢?這個問題比較複雜,我們以後會講的)。
[1]Set (接口):加入 Set 的每個元素必須是唯一的;否則,Set 是不會把它加進去的。要想加進 Set,Object 必須定義 equals( ),這樣才能標明對象的唯一性。Set 的接口和 Collection 的一模一樣。Set 的接口不保證它會用哪種順序來存儲元素。
[2]HashSet*:爲優化查詢速度而設計的 Set。要放進HashSet 裏面的 Object 還得定義hashCode( )。
[3]TreeSet:是一個有序的 Set,其底層是一棵樹。這樣你就能從 Set 裏面提取一個有序序列了。
[4]LinkedHashSet(JDK 1.4): 一個在內部使用鏈表的 Set,既有 HashSet 的查詢速度,又能保存元素被加進去的順序去(插入順序)。用 Iterator 遍歷 Set 的時候,它是按插入順序進行訪問的。
HashSet 保存對象的順序是和 TreeSet和 LinkedHashSet 不一樣的。這是因爲它們是用不同的方式來存儲和查找元素的。(TreeSet 用了一種叫紅黑樹的數據結構『red-black treedata structure』來爲元素排序,而 HashSet 則用了“專爲快速查找而設計”的散列函數。LinkedHashSet 在內部用散列來提高查詢速度,但是它看上去像是用鏈表來保存元素的插入順序。)你寫自己的類的時候,一定要記住,Set 要有一個判斷以什麼順序來存儲元素的標準,也就是說你必須實現 Comparable 接口,並且定義 compareTo( )方法。下面就是舉例:
//: c11:Set2.java
// Putting your own type in a Set.
import com.bruceeckel.simpletest.*;
import java.util.*;
public class Set2 {
public static Set fill(Set a, int size) {
for(int i = 0; i < size; i++)
a.add(new MyType(i));
return a;
}
public static void test(Set a) {
fill(a, 10);
fill(a, 10); // Try to add duplicates
fill(a, 10);
a.addAll(fill(new TreeSet( ), 10));
System.out.println(a);
}
public static void main(String[] args) {
test(new HashSet( ));
test(new TreeSet( ));
test(new LinkedHashSet( ));
}
} ///:~
無論是用哪種 Set,你都應該定義 equals( ),但是只有在“要把對象放進 HashSet”的情況下,你才需要定義 hashCode( )(最好還是定義一個,因爲通常情況下 HashSet 是 Set 的首選)。但是作爲一種編程風格,你應該在覆寫 equals( )的同時把 hashCode( )也覆寫了。
SortedSet:SortedSet(只有 TreeSet 這一個實現可用)中的元素一定是有序的。這使得 SortedSet 接口多了一些方法(注意,SortedSet 意思是“根據對象的比較順序”,而不是“插入順序” 進行排序。):
[1]Comparator comparator( ):返回 Set 所使用的 Comparator對象,或者用 null 表示它使用 Object 自有的排序方法。
[2]Object first( ): 返回最小的元素。
[3]Object last( ): 返回最大的元素。
[4]SortedSet subSet(fromElement, toElement): 返回 Set 的子集,其中的元素從 fromElement 開始到toElement 爲止(包括fromElement,不包括 toElement)。
[5]SortedSet headSet(toElement):返回 Set 的子集,其中的元素都應小於 toElement。
[6]SortedSet headSet(toElement):返回 Set 的子集,其中的元素都應大於 fromElement。
2.數組是第一流的對象:不管你用的是那種類型的數組,數組的標識符實際上都是一個“創建在堆(heap)裏的實實在在的對象的”reference。實際上是那個對象持有其他對象的 reference。reference(Object) 數組與primitives 數組不同的是對象數組持有 reference,而 primitive 數組則直接持有值。
import com.bruceeckel.simpletest.*;
class Weeble {} // A small mythical creature
public class ArraySize {
public static void main(String[] args) {
// Arrays of objects:
Weeble[] a; // Local uninitialized variable
Weeble[] b = new Weeble[5]; // Null references
Weeble[] c = new Weeble[4];//數組的定義方式1
for(int i = 0; i < c.length; i++)
if(c[i] == null) // Can test for null reference
c[i] = new Weeble( );
// Aggregate initialization:
Weeble[] d = {new Weeble( ), new Weeble( ), new Weeble( )};//數組的定義方式2
// Dynamic aggregate initialization:
a = new Weeble[] {new Weeble( ), new Weeble( )};//數組的定義方式3
System.out.println("a.length=" + a.length);
System.out.println("b.length = " + b.length);
// The references inside the array are
// automatically initialized to null:
for(int i = 0; i < b.length; i++)
System.out.println("b[" + i + "]=" + b[i]);//就算b裏沒有值也可以輸出,只不過是null而已,
//最重要的是Weeble[] b = new Weeble[5];分配了空間
System.out.println("c.length = " + c.length);
System.out.println("d.length = " + d.length);
a = d;//改變了a的指向
System.out.println("a.length = " + a.length);
// Arrays of primitives:
int[] e; // Null reference
int[] f = new int[5];//數組的定義方式1
int[] g = new int[4];
for(int i = 0; i < g.length; i++)
g[i] = i*i;
int[] h = { 11, 47, 93 };//數組的定義方式2
// Compile error: variable e not initialized:
//!System.out.println("e.length=" + e.length);
System.out.println("f.length = " + f.length);
// The primitives inside the array are
// automatically initialized to zero:
for(int i = 0; i < f.length; i++)
System.out.println("f[" + i + "]=" + f[i]);
System.out.println("g.length = " + g.length);
System.out.println("h.length = " + h.length);
e = h;//改變了e的指向
System.out.println("e.length = " + e.length);
e = new int[] { 1, 2 };//數組的定義方式3
System.out.println("e.length = " + e.length);
}
} ///:~
3.返回一個數組:假設你寫了一個方法,它返回的不是一個而是一組東西。在 C 和 C++之類的語言裏,這件事就有些難辦了。因爲你不能返回一個數組,你只能返回一個指向數組的指針。由於要處理“控制數組生命週期”之類的麻煩事,這麼做很容易會出錯,最後導致內存泄漏。Java 採取了類似的解決方案,但是不同之處在於,它返回的“就是一個數組”。與 C++不同,你永遠也不必爲 Java 的數組操心——只要你還需要它,它就還在;一旦你用完了,垃圾回收器會幫你把它打掃乾淨。(也就是說你可以把不同類型的東西同時都塞到一個數組裏進行返回,只要你知道那個是哪個就行。這種情況也可以適合Java的其他容器裏)
import com.bruceeckel.simpletest.*;
import java.util.*;
public class IceCream {
private static Random rand = new Random( );
public static final String[] flavors = {
"Chocolate", "Strawberry", "Vanilla Fudge Swirl",
"Mint Chip", "Mocha Almond Fudge", "Rum Raisin",
"Praline Cream", "Mud Pie"
};
public static String[] flavorSet(int n) {
String[] results = new String[n];
boolean[] picked = new boolean[flavors.length];
for(int i = 0; i < n; i++) {
int t;
do
t = rand.nextInt(flavors.length);
while(picked[t]);
results[i] = flavors[t];
picked[t] = true;
}
return results;
}
public static void main(String[] args) {
for(int i = 0; i < 20; i++) {
System.out.println(
"flavorSet(" + i + ") = ");
String[] fl = flavorSet(flavors.length);
for(int j = 0; j < fl.length; j++)
System.out.println("/t" + fl[j]);
}
}
} ///:~
4.Arrays 類:java.util 裏面有一個 Arrays 類,它包括了一組可用於數組的 static方法,這些方法都是一些實用工具。其中有四個基本方法:用來比較兩個數組是否相等的 equals( );用來填充數組的 fill( );用來對數組進行排序的 sort( );以及用於在一個已排序的數組中查找元素的binarySearch( )。所有這些方法都對 primitive 和 Object 進行了重載。此外還有一個 asList( )方法,它接受一個數組,然後把它轉成一個List 容器。
(1)fill( )方法,但是它太簡單了;它只是簡單的把一個的值複製到數組各個位置,如果是對象,則將相同的reference 拷貝到每個位置,如:
int[] a5 = new int[10];
Arrays.fill(a5, 19);//a5數組的每個位置都是19
String[] a9 = new String[10];
Arrays.fill(a9, "Hello");//a9數組的每個位置都是"Hello"
Arrays.fill(a9, 3, 5, "World");//用"World"來填充a[3],a[4],包前不包後。
(2)java 標準類庫提供了一個 System.arraycopy( )的 static 方法。相比 for 循環,它能以更快的速度拷貝數組。如:
int[] i = new int[7];
int[] j = new int[10];
Arrays.fill(i, 47);
Arrays.fill(j, 99);
System.arraycopy(i, 1, j, 0, i.length-1);//把i考到j裏,1爲i的下標,0爲j的開始下標,i.length-1爲j的結束下標
(3)Arrays 提供了經重載的 equals( )方法。當然,也是針對各種 primitive 以及 Object 的。兩個數組要想完全相等,它們必須有相同數量的元素,而且數組的每個元素必須與另一個數組的相對應的位置上的元素相等。元素的相等性,用 eqauls( )判斷。(對於 primitive,它會使用其 wrapper 類的 equals( );比如 int 使用Integer.equals( )。)例如:
import com.bruceeckel.simpletest.*;
import java.util.*;
public class ComparingArrays {
public static void main(String[] args) {
int[] a1 = new int[10];
int[] a2 = new int[10];
Arrays.fill(a1, 47);
Arrays.fill(a2, 47);
System.out.println(Arrays.equals(a1, a2));
a2[3] = 11;
System.out.println(Arrays.equals(a1, a2));
String[] s1 = new String[5];
Arrays.fill(s1, "Hi");
String[] s2 = {"Hi", "Hi", "Hi", "Hi", "Hi"};
System.out.println(Arrays.equals(s1, s2));//數組是否相等是基於其內容,(通過 Object.equals( )),}
} ///:~
(4)Arrays.sort(a)可以對數組a(a可以爲任何的數組)進行排序.內置的排序方法能對任何數組排序,不論是 primitive的還是對象數組,只要它實現了 Comparable 接口或有一個與之相關的 Comparator(針對對象數組,primitive 數組不允許使用 Comparator)接口就行了。[1]:Comparable 接口,要重寫一個方法 compareTo( ),這個方法能接受另一個對象作爲參數,如果現有對象比參數小,它會返回一個負數,如果相同則返回零,如果現有的對象比參數大,它就返回一個正數。[2]Comparator 接口,有兩個方法 compare( )和 equals( )。但是除非是有特殊的性能要求,否則你用不着去實現 equals( )。因爲只要是類,它就都隱含地繼承自Object,而 Object 裏面已經有了一個 equals( )了。所以你儘可以使用缺省的 Object 的 equals( ),這樣就已經滿足接口的要求了。 Arrays.asList( )方法把數組改造成 List:如
List a = Arrays.asList("one two three four five six seven eight".split(" "));
例子[1]:
import com.bruceeckel.util.*;
import java.util.*;
public class CompType implements Comparable {
int i;
int j;
public CompType(int n1, int n2) {
i = n1;
j = n2;
}
public String toString( ) {
return "[i = " + i + ", j = " + j + "]";
}
public int compareTo(Object rv) {
int rvi = ((CompType)rv).i;
return (i < rvi ? -1 : (i == rvi ? 0 : 1));
}
private static Random r = new Random( );
public static Generator generator( ) {
return new Generator( ) {
public Object next( ) {
return new CompType(r.nextInt(100),r.nextInt(100));
}
};
}
public static void main(String[] args) {
CompType[] a = new CompType[10];
Arrays2.fill(a, generator( ));
System.out.println(
"before sorting, a = " + Arrays.asList(a));
Arrays.sort(a);
System.out.println("after sorting, a = " + Arrays.asList(a));
}
} ///:~
如果CompType 沒有實現 Comparable 接口,那麼程序運行調用到sort( )的時候,就會引發一個 ClassCastException 錯誤。這是因爲sort( )會把傳給它的參數轉換成 Comparable。
例子[2]:
import com.bruceeckel.util.*;
import java.util.*;
class CompTypeComparator implements Comparator {
public int compare(Object o1, Object o2) {
int j1 = ((CompType)o1).j;
int j2 = ((CompType)o2).j;
return (j1 < j2 ? -1 : (j1 == j2 ? 0 : 1));
}
}
public class ComparatorTest {
public static void main(String[] args) {
CompType[] a = new CompType[10];
Arrays2.fill(a, CompType.generator( ));
System.out.println("before sorting, a = " + Arrays.asList(a));
Arrays.sort(a, new CompTypeComparator( ));//利用Comparator比較方法來排列a數組,
//因爲自動調用compare方法
System.out.println("after sorting, a = " + Arrays.asList(a));
}
} ///:~
(5)查詢有序數組,一旦數組排完序,你就能用 Arrays.binarySearch( )進行快速查詢了。但是切忌對一個尚未排序的數組使用 binarySearch( );因爲這麼做的結果是沒意義的。如果 Arrays.binarySearch( )找到了,它就返回一個大於或等於 0
的值。否則它就返回一個負值,而這個負值要表達的意思是,如果你手動維護這個數組的話,這個值應該插在哪個位置。這個值就是:(插入點)-1, “插入點”就是,在所有“比要找的那個值”更大值中,最小的那個值的下標,或者,如果數組中所有的值都比要找的值小,它就是a.size( )。如果數組裏面有重複元素,那它不能保證會返回哪一個。如果排序的時候用到了 Comparator ,那麼binarySearch( )的時候,也必須使用同一個 Comparator (用這個方法的重載版)。
import com.bruceeckel.simpletest.*;
import com.bruceeckel.util.*;
import java.util.*;
public class AlphabeticSearch {
public static void main(String[] args) {
String[] sa = new String[30];
Arrays2.fill(sa, new Arrays2.RandStringGenerator(5));
CompTypeComparator comp = new CompTypeComparator( );
Arrays.sort(sa, comp);//利用了Comparator比較排序
int index = Arrays.binarySearch(sa, sa[10],comp);//也一定要用Comparator進行比較
System.out.println("Index = " + index);
}
} ///:~
(6)容器簡介:Java 的容器類分成兩種基本類型。它們的區別就在,每個位置能放多少對象。Collection 只允許每個位置上放一個對象(這個名字有點誤導,因爲容器類庫也常被統稱爲 collections)。它包括“以一定順序持有一組對象”的 List,以及“只能允許添加不重複的對象”的 Set。ArrayList 是一種 List,而 HashSet 則是一種 Set。你可以用 add( )方法往Collection 裏面加對象。Map 保存的是“鍵(key)—值”形式的 pair,很像是一個微型數據庫。上面這段程序用了一種叫HashMap 的 Map。如果你建了一個“州和首府”的 Map,然後想查一下 Ohio 的首府在哪裏,你就可以用它來找了。用法和用下標查數組是一樣的。(Map 又被稱爲關聯性數組associative array。)你可以用 put( )方法往 Map 裏面加元素。它接受鍵—值形式 pair 作參數。
List 會老老實實地持有你所輸入的所有對象,既不做排序也不做編輯。Set 則每個對象只接受一次,而且還要用它自己的規則對元素進行重新排序(一般情況下,你關心的只是Set 包沒包括某個對象,而不是它到底排在哪裏——如果是那樣,你最好還是用 List)。Map 也不接收重複的 pair,至於是不是重複,要由key 來決定。此外,它也有它自己的內部排序規則,不會受輸入順序影響。如果插入順序是很重要的,那你就只能使用 LinkedHashSet 或LinkedHashMap 了。
Collection 和 Map 默認情況下的打印輸出(使用容器類的 toString( )方法)的效果很不錯
,所以我們就不再提供額外的打印支持了。打印出來的 Collection 會用方括號括起來,元素與元素之間用逗號分開。Map 會用花括號括起來,鍵和值之間用等號聯起來(鍵在左邊,值在右邊)。
(7).容器的缺點:不知道對象的類型:Java 的容器有個缺點,就是往容器裏面放對象的時候,會把對象的類型信息給弄丟了。這是因爲開發容器類的程序員不會知道你要用它來保存什麼類型的對象,而讓容器僅只保存特定類型的對象又會影響它的通用性。所以容器被做成只持有 Object,也就是所有對象的根類的 reference,這樣它就能持有任何類型的對象了。(當然不包括 primitive,因爲它們不是對象,也沒有繼承別的對象。)這是一個很了不起的方案,只是:
[1]由於在將對象放入容器的時候,它的類型信息被扔掉了,所以容器對“能往裏面加什麼類型的對象”沒有限制。比方說,即使你想讓它只持有 cat,別人也能很輕易地把 dog 放進去。
[2]由於對象的類型信息沒了,容器只知道它持有的 Object 的 reference,所以對象在使用之前(在容器裏取出的時候)還必須進行類型轉換。
(8)迭代器:“迭代器(iterator)”的概念(又是一個設計模式)就是用來達成這種抽象的。迭代器是一個對象,它的任務是,能在讓“客戶程序員在不知道,或者不關心他所處理的是什麼樣的底層序列結構”的情況下,就能在一個對象序列中前後移動,並選取其中的對象。java的iterator可做的事情:
[1]用 iterator( )方法叫容器傳給你一個 Iterator 對象。第一次調用Iterator 的 next( )方法的時候,它就會傳給你序列中的第一個元素。
[2]用 next( )方法獲取序列中的下一個對象。
[3]用 hasNext( )方法查詢序列中是否還有其他對象。
[4]用 remove( )方法刪除迭代器所返回的最後一個元素。
package c11;
public class Cat {
private int catNumber;
public Cat(int i) { catNumber = i; }
public void id( ) {
System.out.println("Cat #" + catNumber);
}
public String toString(){
System.out.println("Cat''id =" + catNumber);
}
} ///:~
package c11;
import com.bruceeckel.simpletest.*;
import java.util.*;
public class CatsAndDogs2 {
public static void main(String[] args) {
List cats = new ArrayList( );//ArrayList是一個能夠自動擴展的數組,但是他是一個List
for(int i = 0; i < 7; i++)
cats.add(new Cat(i));
Iterator e = cats.iterator( );//Iterator的創建方式
while(e.hasNext( ))//先用hasNext( )進行判斷
((Cat)e.next( )).id( );
System.out.println(e.next());//直接調用toString()方法.
}
} ///:~
(9)Collection 的功能:Collection 的所有功能,也就是你能用 Set 和 List做什麼事(不包括從 Object 自動繼承過來的方法)。
[1]boolean add(Object):確保容器能持有你傳給它的那個參數。如果沒能把它加進去,就返回 false。(這是個“可選”的方法,本章稍後會再作解釋。)
[2]boolean addAll(Collection):加入參數 Collection 所含的所有元素。只要加了元素,就返回 true。(“可選”)
[3]void clear( ):清除容器所保存的所有元素。(“可選”)
[4]boolean contains(Object):如果容器持有參數 Object,就返回true。
[5]boolean containsAll(Collection):如果容器持有參數 Collection 所含的全部元素,就返回 true。
[6]boolean isEmpty( ):如果容器裏面沒有保存任何元素,就返回 true。
[7]Iterator iterator( ):返回一個可以在容器的各元素之間移動的 Iterator。
[8]boolean remove(Object):如果容器裏面有這個參數 Object,那麼就把其中的某一個給刪了。只要刪除過東西,就返回 true。(“可選”)
[9]boolean removeAll(Collection):刪除容器裏面所有參數 Collection 所包含的元素。只要刪過東西,就返回true。(“可選”)。
[10]boolean retainAll(Collection):只保存參數 Collection 所包括的元素(集合論中“交集”的概念)。如果發生過變化,則返回 true。(“可選”)
[11]int size( ) :返回容器所含元素的數量。
[12]Object[] toArray( ):返回一個包含容器中所有元素的數組。
[13]Object[] toArray(Object[] a):返回一個包含容器中所有元素的數組,且這個數組不是普通的 Object 數組,它的類型應該同參數數組 a 的類型相同(要做類型轉換)。
(10)List 的功能:ist 的基本用法是用 add( )加對象,用 get( )取對象,用iterator( )獲取這個序列的 Iterator。
[1]List (接口):List 的最重要的特徵就是有序;它會確保以一定的順序保存元素。List 在 Collection 的基礎上添加了大量方法,使之能在序列中間插入和刪除元素。(只對 LinkedList 推薦使用。)List 可以製造ListIterator 對象,你除了能用它在 List 的中間插入和刪除元素之外,還能用它沿兩個方向遍歷List。
[2]ArrayList*:一個用數組實現的 List。能進行快速的隨機訪問,但是往列表中間插入和刪除元素的時候比較慢。ListIterator 只能用在反向遍歷 ArrayList 的場合,不要用它來插入和刪除元素,因爲相比LinkedList,在 ArrayList 裏面用ListIterator 的系統開銷比較高。
[3]LinkedList:對順序訪問進行了優化。在 List 中間插入和刪除元素的代價也不高。隨機訪問的速度相對較慢。(用ArrayList 吧。)此外它還有 addFirst( ),addLast( ),getFirst( ),getLast( ),removeFirst( )和 removeLast( )等方法(這些方法,接口和基類均未定義),你能把它當成棧(stack),隊列(queue)或雙向隊列(deque)來用。
(11)Set 的功能:Set 的接口就是 Collection 的,所以不像那兩個 List,它沒有額外的功能。Set 會拒絕持有多個具有相同值的對象的實例(對象的“值”又是由什麼決定的呢?這個問題比較複雜,我們以後會講的)。
[1]Set (接口):加入 Set 的每個元素必須是唯一的;否則,Set 是不會把它加進去的。要想加進 Set,Object 必須定義 equals( ),這樣才能標明對象的唯一性。Set 的接口和 Collection 的一模一樣。Set 的接口不保證它會用哪種順序來存儲元素。
[2]HashSet*:爲優化查詢速度而設計的 Set。要放進HashSet 裏面的 Object 還得定義hashCode( )。
[3]TreeSet:是一個有序的 Set,其底層是一棵樹。這樣你就能從 Set 裏面提取一個有序序列了。
[4]LinkedHashSet(JDK 1.4): 一個在內部使用鏈表的 Set,既有 HashSet 的查詢速度,又能保存元素被加進去的順序去(插入順序)。用 Iterator 遍歷 Set 的時候,它是按插入順序進行訪問的。
HashSet 保存對象的順序是和 TreeSet和 LinkedHashSet 不一樣的。這是因爲它們是用不同的方式來存儲和查找元素的。(TreeSet 用了一種叫紅黑樹的數據結構『red-black treedata structure』來爲元素排序,而 HashSet 則用了“專爲快速查找而設計”的散列函數。LinkedHashSet 在內部用散列來提高查詢速度,但是它看上去像是用鏈表來保存元素的插入順序。)你寫自己的類的時候,一定要記住,Set 要有一個判斷以什麼順序來存儲元素的標準,也就是說你必須實現 Comparable 接口,並且定義 compareTo( )方法。下面就是舉例:
//: c11:Set2.java
// Putting your own type in a Set.
import com.bruceeckel.simpletest.*;
import java.util.*;
public class Set2 {
public static Set fill(Set a, int size) {
for(int i = 0; i < size; i++)
a.add(new MyType(i));
return a;
}
public static void test(Set a) {
fill(a, 10);
fill(a, 10); // Try to add duplicates
fill(a, 10);
a.addAll(fill(new TreeSet( ), 10));
System.out.println(a);
}
public static void main(String[] args) {
test(new HashSet( ));
test(new TreeSet( ));
test(new LinkedHashSet( ));
}
} ///:~
無論是用哪種 Set,你都應該定義 equals( ),但是只有在“要把對象放進 HashSet”的情況下,你才需要定義 hashCode( )(最好還是定義一個,因爲通常情況下 HashSet 是 Set 的首選)。但是作爲一種編程風格,你應該在覆寫 equals( )的同時把 hashCode( )也覆寫了。
SortedSet:SortedSet(只有 TreeSet 這一個實現可用)中的元素一定是有序的。這使得 SortedSet 接口多了一些方法(注意,SortedSet 意思是“根據對象的比較順序”,而不是“插入順序” 進行排序。):
[1]Comparator comparator( ):返回 Set 所使用的 Comparator對象,或者用 null 表示它使用 Object 自有的排序方法。
[2]Object first( ): 返回最小的元素。
[3]Object last( ): 返回最大的元素。
[4]SortedSet subSet(fromElement, toElement): 返回 Set 的子集,其中的元素從 fromElement 開始到toElement 爲止(包括fromElement,不包括 toElement)。
[5]SortedSet headSet(toElement):返回 Set 的子集,其中的元素都應小於 toElement。
[6]SortedSet headSet(toElement):返回 Set 的子集,其中的元素都應大於 fromElement。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.