簡介
集合和數(shù)組的區(qū)別:
數(shù)組存儲基礎(chǔ)數(shù)據(jù)類型,且每一個數(shù)組都只能存儲一種數(shù)據(jù)類型的數(shù)據(jù),空間不可變。
集合存儲對象,一個集合中可以存儲多種類型的對象。空間可變。
嚴(yán)格地說,集合是存儲對象的引用,每個對象都稱為集合的元素。根據(jù)存儲時數(shù)據(jù)結(jié)構(gòu)的不同,分為幾類集合。但對象不管存儲到什么類型的集合中,既然集合能存儲任何類型的對象,這些對象在存儲時都必須向上轉(zhuǎn)型為object類型,也就是說,集合中的元素都是object類型的對象。
既然是集合,無論分為幾類,它都有集合的共性,也就是說雖然存儲時數(shù)據(jù)結(jié)構(gòu)不一樣,但該有的集合方法還是得有。在java中,collection接口是集合框架的根接口,所有集合的類型都實現(xiàn)了此接口或從其子接口中繼承。
collection接口
根據(jù)數(shù)據(jù)結(jié)構(gòu)的不同,一些collection允許有重復(fù)的元素,而另一些則不允許。一些collection是有序的,而另一些則是無序的。
java sdk不提供直接繼承自collection的類,java sdk提供的類都是繼承自collection的"子接口"如list和set。也就是說,無法直接new一個collection對象,而是只能new一個實現(xiàn)collection類的子接口的對象,如new arraylist();。
所有的collection類都必須至少提供兩個構(gòu)造方法:無參數(shù)構(gòu)造方法構(gòu)造一個空集合;帶collection參數(shù)的構(gòu)造方法構(gòu)造一個包含該collection內(nèi)容的集合。例如,arraylist就有3個構(gòu)造方法,其中之二就滿足這兩個構(gòu)造方法的要求。
collection是java.util包中的類,因此要實現(xiàn)集合的概念,需要先導(dǎo)入該包。
arraylist繼承自list接口,list接口又繼承自collection接口。arraylist類存儲的集合中,元素有序、可重復(fù)。
import java.util.*;
collection coll = new arraylist();
因為collection接口不允許直接實現(xiàn),因此需要通過實現(xiàn)它的子類來實現(xiàn)集合的概念,此處創(chuàng)建的是arraylist對象,使用了父類引用,好處是擴(kuò)展性較好。
collection有一些集合的通用性操作方法,分為兩類:一類是普通方法;一類是帶有all的方法,這類方法操作的是集合。
add():向集合的尾部插入元素,返回值類型為boolean,插入成功返回true。注意集合只能存儲對象(實際上是對象的引用)。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
|
import java.util.*; // public class testcoll { public static void main(string[] args) { collection coll = new arraylist(); coll.add( "abcd" ); //插入字符串對象 coll.add( 123 ); //插入int對象 coll.add( 123 ); coll.add( new student( "gaoxiaof" , 23 )); //插入student對象 coll.add( new student( "gaoxiaof" , 23 )); //插入另一個student對象 system.out.println(coll); //直接輸出集合中的元素,得到結(jié)果[abcd,123,123,gaoxiaof 23,gaoxiaof 23] } } // class student { private string name; private int age; student(string name, int n) { this .name = name; this .age = n; } public string getname() { return this .name; } public int getage() { return this .age; } public string tostring() { return this .name + " " + this .age; } } |
上面插入的"abcd"和"123"都是經(jīng)過自動裝箱轉(zhuǎn)換為對象后存儲在集合中的。其中兩個add(123)是重復(fù)的對象元素,因為判斷集合中的元素是否重復(fù)的唯一方法是equals方法是否返回0。integer已經(jīng)重寫過equals()。而后面的兩個student對象是不同對象,因為student類中沒有重寫equals()方法,所以它們是不重復(fù)的元素。
remove():刪除集合中首次出現(xiàn)的元素。確定是否能刪除某個元素,唯一的方法是通過equals()方法確定對象是否相等,相等時刪除才返回true。
1
2
3
4
5
6
7
|
collection coll = new arraylist(); coll.add( "abcd" ); coll.add( new integer( 128 )); coll.add( new student( "gaoxiaofang" , 23 )); system.out.println(coll.remove( new integer( 128 ))); //true coll.remove( new student( "gaoxiaofang" , 23 )); //false,因為沒有重寫equals() system.out.println(coll); //return: [abcd,gaoxiaofang 23] |
clear():清空該集合中的所有元素。
contains(object obj):是否包含某對象元素。判斷的依據(jù)仍然是equals()方法。
1
2
3
|
collection coll = new arraylist(); coll.add( new integer( 128 )); system.out.println(coll.contains( new integer( 128 ))); //true |
isempty():集合是否不包含任何元素。
size():返回該集合中元素的個數(shù)。
equals(object obj):比較兩個集合是否完全相等。依據(jù)是集合中的所有元素都能通過各自的equals得到相等的比較。
addall(collection c):將整個集合c中的元素都添加到該集合中。
containsall(collection c):該集合是否包含了c集合中的所有元素,即集合c是否是該集合的子集。
removeall(collection c):刪除該集合中那些也包含在c集合中的元素。即刪除該集合和c集合的交集元素。
retainall(collection c):和removeall()相反,僅保留該集合中和集合c交集部分的元素。
iterator(collection c):返回此集合中的迭代器,注意返回值類型為iterator。迭代器用于遍歷集合。見下文。
iterator通用迭代器
因為不同類型的集合存儲數(shù)據(jù)時數(shù)據(jù)結(jié)構(gòu)不同,想要寫一個通用的遍歷集合的方法是不現(xiàn)實的。但無論是哪種類型的集合,只有集合自身對集合中的元素是最了解的,因此在實現(xiàn)collection接口時,不同集合類都實現(xiàn)了自己獨(dú)有的遍歷方法,這稱為集合的迭代器iterator。其實collection繼承了java.lang.iterable接口,該接口只提供了一個方法:iterator(),只要是實現(xiàn)了這個接口的類就表示具有迭代的能力,也就具有foreach增強(qiáng)遍歷的能力。
迭代器自身是一個接口,通過collection對象的iterator()方法就可以獲取到對應(yīng)集合類型的迭代器。例如:
collection coll = new arraylist();
iterator it = coll.iterator(); //獲取對應(yīng)類型的集合的迭代器
iterator接口提供了3個方法:
hasnext():判斷是否有下一個元素。
next():獲取下一個元素。注意它返回的是object(暫不考慮泛型)類型。
remove():移除迭代器最后返回的一個元素。此方法為collection迭代過程中修改元素的唯一安全的方法。
雖然有不同種類型的集合,但迭代器的迭代方法是通用的。例如,要遍歷coll集合中的元素。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
|
import java.util.*; public class testcoll { public static void main(string[] args) { collection coll = new arraylist(); coll.add( "abcd" ); coll.add( new integer( 129 )); coll.add( new student( "gaoxiaofang" , 23 )); iterator it = coll.iterator(); while (it.hasnext()) { //iterator遍歷的方法 system.out.println(it.next()); //return:abcd,129,gaoxiaofang 23 } } } class student { private string name; private int age; student(string name, int n) { this .name = name; this .age = n; } public string getname() { return this .name; } public int getage() { return this .age; } public string tostring() { return this .name + " " + this .age; } } |
但是通常來說,上面的遍歷方式雖然正確,但下面的遍歷方式更佳。因為it對象只用于集合遍歷,遍歷結(jié)束后就應(yīng)該消失,所以將其放入到for循環(huán)的內(nèi)部,由于for循環(huán)的第三個表達(dá)式缺失,所以不斷地循環(huán)第二個表達(dá)式即可。
for (iterator it = coll.iterator();it.hasnext();) {
system.out.println(it.next());
}
通過iterator遍歷到的元素是集合中的一個對象,對象也是有屬性的。如何引用這些屬性?只需將遍歷出的元素作為對象來使用即可,但由于next()返回的元素都是object對象,直接操作這個元素對象無法獲取對應(yīng)元素中的特有屬性。因此必須先強(qiáng)制對象類型轉(zhuǎn)換。
例如,獲取coll中為student對象元素的name屬性,并刪除非student對象的元素。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
collection coll = new arraylist(); coll.add( "abcd" ); coll.add( new integer( 129 )); coll.add( new student( "gaoxiaofang" , 23 )); for (iterator it = coll.iterator();it.hasnext();) { object obj = it.next(); if (obj instanceof student) { student s = (student)obj; system.out.println(s.getname()); //return: gaoxiaofang } else { it.remove(); } } system.out.println(coll); //return: [gaoxiaofang 23] |
因為集合中有些非student對象元素,因此需要判斷it.next()是否滿足instanceof的要求,但不能直接寫為下面的代碼:
1
2
3
4
5
6
|
for (iterator it = coll.iterator();it.hasnext();) { if (it.next() instanceof student) { student s = (student)it.next(); system.out.println(s.getname()); } } |
因為每執(zhí)行一次it.next(),元素的游標(biāo)指針就向下滑動1,在這個寫法中if判斷表達(dá)式中使用了一次it.next(),在if的代碼塊中又調(diào)用了一次it.next()。所以應(yīng)該將it.next()保存到對象變量中。而it.next()返回的類型是object類型,因此定義object obj = it.next()。
只有remove()方法是iterator迭代器迭代過程中修改集合元素且安全的方法。以迭代時add()為例,當(dāng)開始迭代時,迭代器線程獲取到集合中元素的個數(shù),當(dāng)?shù)^程中執(zhí)行add()時,它將采用另一個線程來執(zhí)行(因為add()方法不是iterator接口提供的方法),結(jié)果是元素個數(shù)就增加了,且導(dǎo)致新增的元素?zé)o法確定是否應(yīng)該作為迭代的一個元素。這是不安全的行為,因此會拋出concurrentmodificationexception異常。而remove()方法是迭代器自身的方法,它會使用迭代器線程來執(zhí)行,因此它是安全的。
對于list類的集合來說,可以使用iterator的子接口listiterator來實現(xiàn)安全的迭代,該接口提供了不少增刪改查list類集合的方法。
list接口
list接口實現(xiàn)了collection接口。
list接口的數(shù)據(jù)結(jié)構(gòu)特性是:
1.有序列表,且?guī)饕齣ndex。所謂有序指先后插入的順序,即index決定順序。而向set集合中插入數(shù)據(jù)會被打亂
2.大小可變。
3.數(shù)據(jù)可重復(fù)。
4.因為有序和大小可變,使得它除了有collection的特性,還有根據(jù)index精確增刪改查某個元素的能力。
5.實現(xiàn)list接口的兩個常用類為:
(1).arraylist:數(shù)組結(jié)構(gòu)的有序列表;
1).長度可變,可變的原因是在減少或添加元素時部分下標(biāo)整體減一或加一,如果已分配數(shù)組空間不夠,則新創(chuàng)建一個更大的數(shù)組,并拷貝原數(shù)組的內(nèi)存(直接內(nèi)存拷貝速度極快);
2).查詢速度快,增刪速度慢。查詢快是因為內(nèi)存空間連續(xù),增刪速度慢是因為下標(biāo)移動。
3).除了arraylist是不同步列表,它幾乎替代了vector類。
(2).linkedlist:鏈表結(jié)構(gòu)的有序列表;
1).不同步;
2).增刪速度快,查詢速度慢。增刪速度快的原因是只需修改下鏈表中前后兩個元素的索引指向即可;
3).能夠?qū)崿F(xiàn)堆棧(后進(jìn)先出lifo,last in first out)、隊列(queue,通常是fifo,first in first out)和雙端隊列(double ends queue)。
arraylist類的方法和list方法基本一致,所以下面介紹了list通用方法和listiterator就沒必要介紹arraylist。但linkedlist比較特殊,所以獨(dú)立介紹。
list接口通用方法
除了因為繼承了collection而具有的通用方法外,對于list接口也有它自己的通用方法。一般list的這些通用方法針對的是序列的概念。有了序列和下標(biāo)索引值,可以精確地操控某個位置的元素,包括增刪改查。
(1).增:add(index,element)
(2).刪:remove(index)、remove(obj)刪除列表中第一個obj元素
(3).改:set(index,element)
(4).查:get(index)
(5).indexof(obj):返回列表中第一次出現(xiàn)obj元素的索引值,如不存在則返回-1
(6).lastindexof(obj)
(7).sublist(start,end):返回列表中從start到end(不包括end邊界)中間的元素組成列表。注意返回的是list類型。
(8).listiterator():返回從頭開始遍歷的list類集合的迭代器listiterator。
(9).listiterator(index):返回從index位置開始遍歷的list結(jié)合迭代器listiterator。
因為有了get()方法,除了iterator迭代方式,還可以使用get()方法遍歷集合:
1
2
3
4
|
list l = new arraylist(); for ( int i= 0 ;i<l.size();i++) { system.out.println(l.get(i)); } |
但注意,這種方法不安全,因為l.size()是即時改變的,如果增刪了元素,size()也會隨之改變。
示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
|
import java.util.*; public class testlist { public static void main(string[] args) { list ls = new arraylist(); ls.add( new student( "malong1" , 21 )); ls.add( new student( "malong2" , 22 )); ls.add( 1 , new student( "malong3" , 23 )); //[malong1 21,malong3 23,malong2 22] system.out.println(ls.indexof( new student( "malong3" , 23 ))); // return:1 ls.set( 2 , new student( "gaoxiao1" , 22 )); //[malong1 21,malong3 23,gaoxiao1 22] for (iterator it = l.iterator();it.hasnext();) { //第一種迭代 student stu = (student)it.next(); if (stu.getage() == 22 ) { it.remove(); // the safe way to operate element //ls.add(new student("malong4",24)); //throw concurrentmodificationexception } } //[malong1 21,malong3 23] system.out.println(l+ "\n---------------" ); for ( int i= 0 ;i<ls.size();i++) { //第二種迭代 system.out.println(ls.get(i)); } } } class student { private string name; private int age; student(string name, int n) { this .name = name; this .age = n; } public string getname() { return this .name; } public int getage() { return this .age; } //override tostring() public string tostring() { return this .name + " " + this .age; } //override equals() public boolean equals(object obj) { if ( this == obj) { return true ; } if (!(obj instanceof student)) { throw new classcastexception( "class error" ); } student stu = (student)obj; return this .name.equals(stu.name) && this .age == stu.age; } } |
上面的代碼中,如果將ls.add(new student("malong4",24));的注釋取消,將拋出異常,因為iterator迭代器中唯一安全操作元素的方法是iterator接口提供的remove(),而add()方法是list接口提供的,而非iterator接口的方法。但對于list集合類來說,可以使用listiterator迭代器,它提供的操作元素的方法更多,因為是迭代器提供的方法,因此它們操作元素時都是安全的。
list集合的迭代器listiterator
通過listiterator()方法可以獲取listiterator迭代器。該迭代器接口提供了如下幾種方法:
hasnext():是否有下一個元素
hasprevious():是否有前一個元素,用于逆向遍歷
next():獲取下一個元素
previour():獲取前一個元素,用于逆向遍歷
add(element):插入元素。注:這是迭代器提供的add(),而非list提供的add()
remove():移除next()或previous()獲取到的元素。注:這是迭代器提供的remove(),而非list提供的remove()
set(element):設(shè)置next()或previour()獲取到的元素。注:這是迭代器提供的set(),而非list提供的set()
例如:前文示例在iterator迭代過程中使用list的add()添加元素拋出了異常,此處改用listiterator迭代并使用listiterator提供的add()方法添加元素。
1
2
3
4
5
6
7
8
9
10
11
12
13
|
list l = new arraylist(); l.add( new student( "malong1" , 21 )); l.add( new student( "malong2" , 22 )); l.add( 1 , new student( "malong3" , 23 )); //[malong1 21,malong3 23,malong2 22] l.set( 2 , new student( "gaoxiao1" , 22 )); //[malong1 21,malong3 23,gaoxiao1 22] for (listiterator li = l.listiterator();li.hasnext();) { student stu = (student)li.next(); if (stu.getage() == 22 ) { //l.add(new student("malong4",24)); //throw concurrentmodificationexception li.add( new student( "malong4" , 24 )); } } |
linkedlist集合
linkedlist類的數(shù)據(jù)結(jié)構(gòu)是鏈表類的集合。它可以實現(xiàn)堆棧、隊列和雙端隊列的數(shù)據(jù)結(jié)構(gòu)。其實實現(xiàn)這些數(shù)據(jù)結(jié)構(gòu)都是通過linkedlist提供的方法按照不同邏輯實現(xiàn)的。
提供的其中幾個方法如下:因為是實現(xiàn)了list接口,所以除了下面的方法,還有l(wèi)ist接口的方法可用。
addfirst(element):向鏈表的首部插入元素
addlast(element):向鏈表的尾部插入元素
getfirst():獲取鏈表的第一個元素
getlast():獲取鏈表最后一個元素
removefirst():移除并返回第一個元素,注意返回的是元素
removelast():移除并返回最后一個元素,注意返回的是元素
linkedlist模擬隊列數(shù)據(jù)結(jié)構(gòu)
隊列是先進(jìn)先出fifo的數(shù)據(jù)結(jié)構(gòu)。封裝的隊列類myqueue代碼如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
|
import java.util.*; class myqueue { private linkedlist mylist; myqueue() { mylist = new linkedlist(); } // add element to queue public void add(object obj) { mylist.addfirst(obj); //fisrt in } //get element from queue public object get() { return mylist.removelast(); //first out } //queue is null? public boolean isnull() { return mylist.isempty(); } //the size of queue public int size() { return mylist.size(); } //remove element in queue by index public boolean remove( int index) { if ( this .size()- 1 < index) { throw new indexoutofboundsexception( "index too large!" ); } mylist.remove(index); return true ; } //remove the first appearance element in queue by object public boolean remove(object obj) { return mylist.remove(obj); } public string tostring() { return mylist.tostring(); } } |
操作該隊列數(shù)據(jù)結(jié)構(gòu)程序代碼如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
import java.util.*; public class fifo { public static void main(string[] args) { myqueue mq = new myqueue(); mq.add( "malong1" ); mq.add( "malong2" ); mq.add( "malong3" ); mq.add( "malong4" ); //[malong4,malong3,malong2,malong1] system.out.println(mq.size()); //return:4 mq.remove( 2 ); //[malong4,malong3,malong1] mq.remove( "malong1" ); //[malong4,malong3] system.out.println(mq); while (!mq.isnull()) { system.out.println(mq.get()); } } } |
set接口
set接口也實現(xiàn)了collection接口。它既然能單獨(dú)成類,它和list集合的數(shù)據(jù)結(jié)構(gòu)一定是大有不同的。
set接口的數(shù)據(jù)結(jié)構(gòu)特性是:
1.set集合中的元素?zé)o序。這里的無序是相對于list而言的,list的有序表示有下標(biāo)index的順序,而set無需是指沒有index也就沒有順序。
2.set集合中的元素不可重復(fù)。
3.因為無序,因此set集合中取出元素的方法只有一種:迭代。
4.實現(xiàn)set接口的兩個常見類為:
(1).hashset:hash表數(shù)據(jù)結(jié)構(gòu);
1).不同步;
2).查詢速度快;
3).判斷元素是否重復(fù)的唯一方法是:先調(diào)用hashcode()判斷對象是否相同,相同者再調(diào)用equals()方法判斷內(nèi)容是否相同。所以,要將元素存儲到此數(shù)據(jù)結(jié)構(gòu)的集合中,必須重寫hashcode()和equals()。
(2).treeset:二叉樹數(shù)據(jù)結(jié)構(gòu);
1).二叉樹是用來排序的,因此該集合中的元素是有序的。這個有序和list的有序概念不同,此處的有序指的是存儲時對元素進(jìn)行排序,例如按照字母順序,數(shù)字大小順序等,而非index索引順序。
2).既然要排序,而equals()方法只能判斷是否相等。因此數(shù)據(jù)存儲到treeset集合中時需要能夠判斷大小。
3).有兩種方法用于構(gòu)造有序的treeset集合:
a.待存儲對象的類實現(xiàn)comparable接口并重寫它的compareto()方法;
b.在構(gòu)造treeset集合時指定一個比較器comparator。這個比較器需要實現(xiàn)comparator接口并重寫compare()方法。
(3).linkedhashset:鏈表形式的hashset,僅在hashset上添加了鏈表索引。因此此類集合有序(linked)、查詢速度快(hashset)。不過很少使用該集合類型。
hashset集合
hashset的用法沒什么可解釋的,方法都繼承自set再繼承自collection。需要說明的是它的無序性、不可重復(fù)性、計算hash值時的方法以及判斷重復(fù)性時的方法。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
import java.util.*; public class testhashset { public static void main(string[] args) { set s = new hashset(); s.add( "abcd4" ); s.add( "abcd1" ); s.add( "abcd2" ); s.add( "abcd3" ); s.add( "abcd1" ); //重復(fù) for (iterator it = s.iterator();it.hasnext();) { object obj = it.next(); system.out.println(obj); } } } |
得到的結(jié)果是無序且元素是不可重復(fù)的:
1
2
3
4
|
abcd2 abcd3 abcd4 abcd1 |
這里判斷字符串對象是否重復(fù)的方法是先調(diào)用string的hashcode()進(jìn)行判斷,如果相同,再調(diào)用string的equals()方法。其中string的hashcode()方法在計算hash值時,是根據(jù)每個字符計算的,相同字符位置處的相同字符運(yùn)算結(jié)果相同。
所以上面幾個字符串對象中,前綴"abcd"子串部分的hash運(yùn)算結(jié)果相同,最后一個字符決定了這些字符串對象是否相同。插入時有兩個"abcd1",所以總共調(diào)用了一次string的equals()方法。
如果是存儲自定義的對象,如student對象,該對象定義方式如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
class student { string name; int age; student(string name, int n) { this .name = name; this .age = n; } //override tostring() public string tostring() { return this .name + " " + this .age; } //override equals() public boolean equals(object obj) { if ( this == obj) { return true ; } if (!(obj instanceof student)) { return false ; } student stu = (student)obj; return this .name.equals(stu.name) && this .age == age; } } |
即使重寫了equals(),插入屬性相同的student對象到hashset中時,也會認(rèn)為不重復(fù)的。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
import java.util.*; public class testhashset { public static void main(string[] args) { set s = new hashset(); s.add( new student( "malong1" , 21 )); s.add( new student( "malong1" , 21 )); s.add( new student( "malong1" , 21 )); for (iterator it = s.iterator();it.hasnext();) { object obj = it.next(); system.out.println(obj); } } } |
結(jié)果:
1
2
3
|
malong1 21 malong1 21 malong1 21 |
這是因為hastset集合的底層首先調(diào)用student的hashcode()方法,而student沒有重寫該方法,而是繼承自object,所以每個對象的hashcode()都不相同而直接插入到集合中。
因此,需要重寫student的hashcode()方法。以下是一種重寫方法:
1
2
3
|
public int hashcode() { return this .name.hashcode() + age* 31 ; //31可以是任意數(shù),但不能是1或0。 } |
如果不加上"age*31",那么name部分的hash值有可能是相同的,但這很可能不是同一student對象,所以應(yīng)該加上age屬性作為計算hash值的一部分元素。但不能直接加age,因為這樣會導(dǎo)致"new student("lisi3",23)"和"new student("lisi2",24)"的hashcode相同(3+23=2+24),因此需要為age做一些修改,例如乘一個非0和1的整數(shù)。
在student中重寫hashcode()后,再插入下面這些student對象,就能相對精確地判斷是否為同一個student元素。
1
2
3
4
5
6
7
|
s.add( new student( "lisi1" , 21 )); s.add( new student( "lisi1" , 21 )); //此處將調(diào)用equals(),且最終判斷為重復(fù)對象 s.add( new student( "lisi2" , 24 )); s.add( new student( "lisi3" , 23 )); //此處將調(diào)用equals() s.add( new student( "gaoxiao1" , 23 )); s.add( new student( "gaoxiao2" , 21 )); s.add( new student( "gaoxiao3" , 22 )); |
結(jié)果:
1
2
3
4
5
6
|
lisi1 21 gaoxiao1 23 gaoxiao3 22 lisi2 24 lisi3 23 gaoxiao2 21 |
linkedhashset集合
鏈表順序的hashset集合,相比hashset,只需多記錄一個鏈表索引即可,這就使得它保證了存儲順序和插入順序相同。實現(xiàn)方式除了new對象時和hashset不一樣,其他任何地方都是一樣的。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
import java.util.*; public class testhashset { public static void main(string[] args) { set s = new linkedhashset(); s.add( new student( "lisi1" , 21 )); s.add( new student( "lisi1" , 21 )); s.add( new student( "lisi2" , 24 )); s.add( new student( "lisi3" , 23 )); s.add( new student( "gaoxiao1" , 23 )); s.add( new student( "gaoxiao3" , 21 )); s.add( new student( "gaoxiao2" , 22 )); for (iterator it = s.iterator();it.hasnext();) { object obj = it.next(); system.out.println(obj); } } } |
結(jié)果:
1
2
3
4
5
6
|
lisi1 21 lisi2 24 lisi3 23 gaoxiao1 23 gaoxiao3 21 gaoxiao2 22 |
treeset集合
treeset集合以二叉樹數(shù)據(jù)結(jié)構(gòu)存儲元素。二叉樹保證了元素之間是排過序且相互唯一的,因此實現(xiàn)treeset集合最核心的地方在于對象之間的比較。
比較對象有兩種方式:一是在對象類中實現(xiàn)comparable接口重寫compareto()方法;二是定義一個專門用于對象比較的比較器,實現(xiàn)這個比較器的方法是實現(xiàn)comparator接口并重寫compare()方法。其中comparable接口提供的比較方法稱為自然順序,例如字母按照字典順序,數(shù)值按照數(shù)值大小順序。
無論是哪種方式,每個待插入的元素都需要先轉(zhuǎn)型為comparable,確定了將要存儲在二叉樹上的節(jié)點(diǎn)位置后,然后再轉(zhuǎn)型為object存儲到集合中。
插入string類對象。
由于string已經(jīng)重寫了compareto(),因此下面插入string對象到treeset集合中沒有任何問題。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
import java.util.*; // public class testtreeset { public static void main(string[] args) { set t = new treeset(); t.add( "abcd2" ); t.add( "abcd11" ); t.add( "abcd3" ); t.add( "abcd1" ); //t.add(23); //t.add(21); //t.add(21); for (iterator it = t.iterator();it.hasnext();) { object obj = it.next(); system.out.println(obj); } } } |
但不能將上面"t.add(23)"等取消注釋,雖然integer類也重寫了compareto(),但在插入這些integer類元素時,集合中已經(jīng)存在string類的元素,string類的compareto()和integer的compareto()的比較方法不一樣,使得這兩類元素之間無法比較大小,也就無法決定數(shù)值類的元素插入到二叉樹的哪個節(jié)點(diǎn)。
插入實現(xiàn)了comparable接口且重寫了compareto()的自定義對象。
例如student對象,如果沒有重寫compareto()方法,將拋出異常,提示無法轉(zhuǎn)型為comparable。
t.add(new student("malongshuai1",23));
結(jié)果:
1
2
3
4
5
|
exception in thread "main" java.lang.classcastexception: student cannot be cast to java.lang.comparable at java.util.treemap.compare(unknown source) at java.util.treemap.put(unknown source) at java.util.treeset.add(unknown source) at testtreeset.main(testtreeset.java: 8 ) |
所以,修改student重寫compareto(),在重寫應(yīng)該考慮哪個作為主排序?qū)傩裕膫€作為次要排序?qū)傩浴@缫詎ame為主排序?qū)傩裕琣ge為次排序?qū)傩浴ompareto()返回正數(shù)則表示大于,返回負(fù)數(shù)則表示小于,返回0則表示等于。如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
class student implements comparable { string name; int age; student(string name, int n) { this .name = name; this .age = n; } public string tostring() { return this .name + " " + this .age; } public int compareto(object obj) { if (!(obj instanceof student)) { throw new classcastexception( "class cast wrong!" ); } student stu = (student)obj; // compare name first, then age int temp = this .name.compareto(stu.name); return temp == 0 ? this .age - stu.age :temp; } } |
于是插入student時,將根據(jù)name中的字母順序,相同時再根據(jù)age大小順序,最后如果都相同,則認(rèn)為元素重復(fù),不應(yīng)該插入到集合中。
1
2
3
4
5
|
t.add( new student( "malongshuai1" , 23 )); t.add( new student( "malongshuai3" , 21 )); t.add( new student( "malongshuai2" , 23 )); t.add( new student( "malongshuai1" , 23 )); //重復(fù) t.add( new student( "malongshuai1" , 22 )); |
結(jié)果:
1
2
3
4
|
malongshuai1 22 malongshuai1 23 malongshuai2 23 malongshuai3 21 |
使用比較器comparator實現(xiàn)排序。此時treeset的構(gòu)造方法為"treeset(comparator comp)"。
當(dāng)使用了比較器后,插入數(shù)據(jù)時將默認(rèn)使用比較器比較元素。
比較器是一個實現(xiàn)了java.util.comparator接口并重寫了compare()方法的類,可以根據(jù)不同比較需求,創(chuàng)建不同的比較器。 例如創(chuàng)建一個根據(jù)age作為主排序?qū)傩裕琻ame作為次排序?qū)傩缘谋容^器sortbyage,由于這個比較器是用來比較student對象大小的,因此必須先轉(zhuǎn)型為student。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
import java.util.*; // public class sortbyage implements comparator { public int compare(object o1,object o2) { //cast to student first if (!(o1 instanceof student) || !(o2 instanceof student)) { throw new classcastexception( "wrong" ); } student s1 = (student)o1; student s2 = (student)o2; //compare age first, then name int temp = s1.age - s2.age; return temp == 0 ? s1.name.compareto(s2.name) : temp; } } |
指定treeset的比較器為sortbyage,并插入一些student對象:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
public class testtreeset { public static void main(string[] args) { set t = new treeset( new sortbyage()); t.add( new student( "malongshuai1" , 23 )); t.add( new student( "malongshuai3" , 21 )); t.add( new student( "malongshuai2" , 23 )); t.add( new student( "malongshuai1" , 23 )); //重復(fù) t.add( new student( "malongshuai1" , 22 )); for (iterator it = t.iterator();it.hasnext();) { object obj = it.next(); system.out.println(obj); } } } |
當(dāng)為treeset集合指定了比較器時,結(jié)果將先按照age順序再按照name排序的,盡管student類中仍然重寫了compareto()方法:
1
2
3
4
|
malongshuai3 21 malongshuai1 22 malongshuai1 23 malongshuai2 23 |
總結(jié)
以上就是本文關(guān)于集合框架(collections framework)詳解及代碼示例的全部內(nèi)容,希望對大家有所幫助。如有不足之處,歡迎留言指出。感謝朋友們對本站的支持!
原文鏈接:http://www.cnblogs.com/f-ck-need-u/p/7791128.html