Java集合和泛型

whlzb發表於2017-12-12

集合

常用的集合有ArrayList,TreeSet,HashMap,HashSet.

ArrayList
最常用的集合,每次插入都在後面追加元素。

TreeSet
以有序狀態保持並可防止重複。當你需要set集合或元素按照一定的順序排列時,它會很好用。當然,這需要付出一定的成本,每當插入新專案時,它必須要花時間找到適當的位置,而ArrayList只要把專案放在最後就行。

TreeSet的元素必須是Comparable的,你必須指出物件應該如何排序。我們已經在上面講過,方法有兩種,分別是:

實現Comparable類。
使用過載,取用Comparator引數的建構函式來建立TreeSet,就像sort()的使用一樣。

class ArtistCompare implements Comparator<Song>{
public int compare(Song one,Song two){
return one.getAtist().compareTo(two.getArtist());
}
}

ArtistCompare artCompare=new ArtistCompare();
TreeSet<Song> tree=new TreeSet<Song>(artCompare);
HashMap

針對經常插入或刪除中間元素所設計的高效率集合。

使用方法:

HashMap<String, Integer> scores=new HashMap<String, Integer>();
scores.put("Kathy",20);
scores.put("Jim",22);
System.out.println(scores.get("Jim"));
HashSet的遍歷方法:

Iterator iter = scores.entrySet().iterator();    
while (iter.hasNext()) { 
            Map.Entry entry = (Map.Entry) iter.next(); 
            Object key = entry.getKey(); 
            Object val = entry.getValue(); 
            String relkey=(String)key;
            Integer relval=(Integer) val;
            doprocessing();

HashSet

防止重複的集合,可快速的尋找相符的元素。

這裡有個問題,物件要怎樣才算相等?是引用到完全相同的物件,還是可以使不同的物件,但是我們所關心的值相等。於是,引出一個關鍵問題:引用相等性和物件相等性。

引用相等性 堆上同一物件的不同引用,如果對這兩個引用呼叫hashCode(),結果相同。如果沒有被覆蓋的話,hashCode()預設的行為會返回每個物件特定的序號,這個序號一般跟物件的記憶體位置有關,因此是唯一的。

如果想要知道兩個引用是否相等,可以使用==來比較變數上的位元組組合,如果引用到相同的物件,位元組組合也會一樣。

物件相等性 堆上的兩個不同物件在意義上是相等的,如果你想把兩個不同的物件視為相等,就必須override hashCode()和equals()函式。

hashCode()
HashSet使用hashcode來達到存取速度較快的儲存方法,如果你嘗試用物件來尋找ArrayList中的相同物件,(不用索引來找),ArrayList會從頭開始找起,但HashSet不是,它是根據hashcode來找的,不需要從頭找起。

重點在於hashcode相同並不一定保證物件時相等的,學過資料結構的同學肯定知道,hashCode()使用的雜湊演算法很可能將多個物件傳回相同的雜湊值,越糟糕的雜湊演算法越容易碰撞,而且也跟資料值域的分佈特性有關,因此如果兩個物件的hashcode相同,並不能說它倆就相等,此時還需要使用equals()函式來進一步確認是否相等。你可以這樣認為,hashcode用來縮小尋找成本,但是最後還需要equals()來確定是否真的找到了相同的物件。

hashCode()的預設行為時對在heap上的物件產生獨特的值,如果你沒有override過,則該class的兩個物件永遠不可能被認為是相同的。

class Song implements Comparable<Song>{
String title;
String artist;

public boolean equals(Object aSong ){
Song s = (Song) aSong;
return getTitle.equals(s.getTitle()); //String覆蓋過equals(),我們可以呼叫。
}

public int hashCode(){
return title.hsahCode(); //String覆蓋過hashCode(),我們直接呼叫就可以了。
}

public int compareTo(Song s){
return title.compareTo(s.getTitle());
}

public getTitle(){
return title;
}

public getAtirst(){
return artist;
}
}

euqals()
equals()的預設行為是執行==的比較,也就是說會去測試兩個引用是否堆上heap上同一個物件,如果eqauls沒有被覆蓋過,兩個物件永遠都不會被視為相同的,因為不同的物件有不同的位元組組合。

toString()
toString()是定義在Object類裡的,所以每個java類都會繼承到,且因為物件被System.out.println(anObject)列出來時會呼叫toString(),所以當你想用被System.out.println輸出你自定義的物件時,你需要重定義toString().

泛型

只要你再java程式或檔案中看到<>這一組符號,就代表泛型正在起作用。泛型的主要目的是讓你寫出有型別安全性的集合,也就是,讓編譯器能夠幫忙防止你把Dog放到一群Cat中。

使用泛型的兩種方法:

使用定義在類宣告的型別引數:

    public class ArrayList<E> extends AbstractList<E>...{
    public boolean add (E o)
    ...
    }

使用未定義在類宣告的引數 public void takeThing (ArrayList list)
Collections.sort()
Collections.sort()是集合的常用方法,它只接受Comparable物件的list。因此,如果你自定義了一個類,然後生成了很多類物件,並存入集合中,如果你期望用Collections.sort()來對它們進行排序。你需要對你的自定義類做額外的工作。

你有兩種方法:

第一種,實現Comparable 類,並覆蓋它的int compareTo(T o)方法 java.lang.Comparable介面:

public interface Comparable<T>{
    int compareTo(T o);
    }
class Song implements Comparable<Song>{
String title;
String artist;

public int compareTo(Song s){
return title.compareTo(s.getTitle());
}

public getTitle(){
return title;
}

public getAtirst(){
return artist;
}
}

第二種,自制Comparator

使用compareTo()方法時,list中的元素只能有一種將自己與同型別的另一元素相比較的方法。但Comparator是獨立於所比較元素型別之外的--它是獨立的類。因此,你可以有各種不同的比較方法。例如,除了按照title排序,你也可以按照artist排序。

因此,取用Comparator版的sort()方法會用Comparator而不是內建的 compareTo()方法。有如下規則:

呼叫單一引數的sort(List o)方法代表由list元素上的compareTo()方法來決定順序。因此元素必須實現Comparable這個介面。 呼叫sort(List o,Comparator c)方法代表不會呼叫list元素的compareTo()方法,而會使用Comparator的compare()方法,這也意味著list元素不用實現

Comparable.
class ArtistCompare implements Comparator<Song>{
public int compare(Song one,Song two){
return one.getAtist().compareTo(two.getArtist());
}
}

ArtistCompare artCompare=new ArtistCompare();
Collections.sort(songList,artCompare);
集合的多型
陣列的多型
如果方法的引數是Animal的陣列,它也能夠取用Animal次型別的陣列。

Animal [] animals={new Dog(),new Cat(),new Dog()};
Dog [] dogs={new Dog(),new Dog(),new Dog()};
takeAnimals(animals); 
//takeAnimals()能夠存取Animal[]或Dog[]引數,因為Dog也是一個Animal,多型在此處起作用
takeAnimals(dogs);

public void takeAnimals(Animal [] animals){
for(Animal a : animals){
a.eat();}
}

abstract class Animal{ 
void eat(){}
}
class Dog extends Animal{ 
void bark(){}
}
class Cat extends Animal{ 
void meow(){}
}

ArrayList的多型

ArrayList<Animal> animals= new ArrayList<Animal>();
animals.add(new Dog());
animals.add(new Dog());
animals.add(new Dog());
takeAnimals(animals);

public void takeAnimals(ArrayList<Animal> animals){
for(Animal a : animals){
a.eat();}
}

因為多型的關係,編譯器會讓Dog陣列通過取用Animal陣列引數的方法,這沒有問題,問題是ArrayList< Animal >引數能接受ArrayList< Dog >嗎,回答是,不行。如果我們在上面的程式里加上這一段,將出現編譯錯誤。

ArrayList dogs = new ArrayList();
dogs.add(new Dog());
dogs.add(new Dog());
takeAnimals(dogs);
可能很多同學會疑惑,畢竟多型的意義就在於Animal能做的事情,Dog也能做,但你想過嗎,如果我們的takeAnimals()是這樣的:

public void takeAnimals(ArrayList<Animal> animals){
animals.add(new Cat());
}

這就會有問題了,理論上把Cat加到ArrayList< Animal >是合法的,這也是使用Animal的本意--讓各種Animal都可以加入到此ArrayList中。但是如果你傳入的是一個Dog的ArrayList給該方法,那麼將會是Cat強行加入了一個Dog ArrayList中,編譯器當然不會讓這種事情發生。

所以,如果把方法宣告成ArrayList< Animal >,它只能接受ArrayList< Animal >的引數,ArrayList< Dog>和ArrayList< Cat>都不行。

很多同學肯定又要問了,那為什麼陣列可以過關,而ArrayList卻不行,畢竟陣列也會遇到這樣的問題啊。

其實這跟jvm有關,陣列型別是在執行期間檢查的,但集合的型別檢查發生在編譯期間。

下面這段程式在編譯時不會出錯,但執行時出錯。

Dog [] dogs={new Dog(),new Dog(),new Dog()};
takeAnimals(dogs);

public void takeAnimals(Animal [] animals){
animals[0]=new Cat();
}

那有沒有一種方法,讓我們能夠使用多型化的集合引數,就像陣列那樣,這樣我們就可以傳入Cat,Dog的集合了,只要我們自己保證不會做出格的行為,比如往Cat集合中加入Dog等等。

萬能的java當然有辦法,使用這種宣告方式:

public void takeThing(ArrayList list)
當你這樣宣告函式時,編譯器會阻止任何可能破壞引用引數所指集合的行為。也就是,你可以呼叫list中任何元素的方法,但是不能加入元素。

也就是說你可以操作集合元素,但是不能增加集合元素。如此才能保障執行期間的安全性。編譯器會阻止執行期的恐怖活動。

所以下面的程式是可行的:

for (Animal a :animals){a.eat();}
但這個就過不了編譯:

animals.add(new Cat());

相關文章