[转]Java中Set的深入研究
Set和數學中的集合是同一個概念,就是沒有重復元素的集合。
這篇文章主要論述了Set是如何實現"沒有重復元素"(no?duplicate?elements)的,以及闡述了什么是“重復”(duplicate),是相同的地址空間?是equals的返回值為true?是compareTo的返回值為0??還是有相同的hashCode?本文還給出了在什么情況下使用什么樣的Set的建議。
注:本文不涉及范型。
1、樹形結構:?
public?interface?Set??extends?Collection??{}?public?abstract?class?AbstractSet??extends?AbstractCollection???implements?Set?{}
?public?class?CopyOnWriteArraySet??extends?AbstractSet??implements?Serializable{}
?public?abstract?class?EnumSet???extends?Enum??extends?AbstractSet??implements?Cloneable,?Serializable{}
?public?class?HashSet??extends?AbstractSet??implements?Set?,?Cloneable,?Serializable{}
?public?final?class?JobStateReasons??extends?HashSet<JobStateReason>implements?PrintJobAttribute{}
?public?class?LinkedHashSet??extends?HashSet??implements?Set?,?Cloneable,?Serializable{}
?public?class?TreeSet??extends?AbstractSet??implements?SortedSet?,?Cloneable,?Serializable{}
???可以看出,可以實例化的類為:CopyOnWriteArraySet,HashSet,LinkedHashSet,TreeSet。
2、Set是如何實現元素唯一性的
???javadoc中對Set的描述第一段如下:“A?collection?that?contains?no?duplicate?elements.?More?formally,?sets?contain?no?pair?of?elements?e1?
???and?e2?such?that?e1.equals(e2),?and?at?most?one?null?element.?As?implied?by?its?name,?this?interface?models?the?mathematical?set?abstraction.”
???這段話是對是錯,請看下面分析。
???要進行下面的論述,我們先了解一下Map。Map中的元素是“鍵-值”對,其中“鍵”必須是唯一的。TreeSet和HashSet就是利用這個特性實現“no?duplicate????elements”。它把set中的元素作為Map中的“鍵”,從而保持元素的唯一性。這些鍵在Map中又是如何區分的呢?不同的Map有不同的做法,而且區別很大。
???下面我們分別就TreeSet、HashSet和CopyOnWriteArraySet進行論述:
2.1、TreeSet部分:
???以下以TreeSet為例進行分析。
???請看TreeSet的部分實體:
?{
??//?The?backing?Map
??????private?transient?SortedMap?,Object?m;?
??????//?The?keySet?view?of?the?backing?Map
??????private?transient?Set??keySet;?
??????//?Dummy?value?to?associate?with?an?Object?in?the?backing?Map
??????//這是每個鍵所指的對像
??????private?static?final?Object?PRESENT?=?new?Object();
??????//constructor
??????private?TreeSet(SortedMap?,Object??m)?{
??????????this.m?=?m;
???????????keySet?=?m.keySet();
??????}
??????public?TreeSet()?{
?????????this(new?TreeMap?,Object());
??????}
??????//以下省略.
?}
????可以看到TreeSet使用了SortedMap作為其Map保存“鍵-值”對,而這個SortedMap的真正實體是TreeMap。
????
????請看示例程序1:?
?public?class?SetTest1
?{
?????????public?static?void?main(String[]?args)
????????{
???????????Set?set?=?new?TreeSet();
???????????set.add(new?SetElement1("aa"));
???????????set.add(new?SetElement1("bb"));
?????????}
??????static?class?SetElement1
??????{
???????????String?s;
???????????public?SetElement1(String?s)
????????????{
????????????????this.s?=??s;
????????????}
?????????????public?String?toString()
?????????????{
????????????????????return?s;
?????????????}
???????????public?boolean?equals(Object?obj)?
????????????{
????????????????return?s.equals(((SetElement1)obj).s);
???????????}
?????}
?}
????該程序能夠正常編譯,但是運行時會拋出異常java.lang.ClassCastException。為什么?
????
????請看示例程序2:
?public?class?SetTest2
{
??????????public?static?void?main(String[]?args)
??????????{
???????????????Set?set?=?new?TreeSet();
???????????????set.add(new?SetElement2("aa"));
???????????????set.add(new?SetElement2("aa"));
???????????????set.add(new?SetElement2("bb"));
???????????????System.out.println(set);
??????????}
??????static?class?SetElement2?implements?Comparable
??????{
???????????????String?s;
???????????????public?SetElement2(String?s){
????????????????this.s?=??s;
??????????????????}
???????????????????public?String?toString(){
????????????????????return?s;
???????????????????}
???????????????????public?int?compareTo(Object?o){
????????????????????return?s.compareTo(((SetElement2)o).s);
???????????????????}
???????????????????public?boolean?equals(Object?obj)?{
????????????????????return?s.equals(((SetElement2)obj).s);
???????????????????}
??????????}
?}
???運行結果:
???[aa,?bb]
???這正是我們所期望的結果。那“示例程序1”和“示例程序2”有什么區別?
???是因為SetElement2實現了Comparable接口,而SetElement1沒有。SetElement2實現Comparable接口有什么用呢?因為在TreeSet的add方法中需要比較兩個?元素的“值”。請看TreeMap中的compare方法:?
????????return?(comparator==null???((Comparable<K>)k1).compareTo(k2)
?????????????????????????????????:?comparator.compare((K)k1,?(K)k2));
???}
???可見這個方法先把要比較的元素down?cast成Comparable類型。這里就可以解釋“示例程序1”中為什么會拋出異常java.lang.ClassCastException,因SetElement1沒有實現Comparable接口,當然就不能down?cast成Comparable。可見,要用TreeSet來做為你的Set,那么Set中所裝的元素都必須實現了Comparable接口。
???說到這里,你是不是想到了TreeSet中是采用Comparable接口中的compareTo方法來判斷元素是否相同(duplicate),而不是采用其他類似equals之類的東東來判斷。
???
???請看示例程序3:
???
?import?java.util.*;
?public?class?SetTest3?
{
??????public?static?void?main(String[]?args)
?????{
???????????Set?set?=?new?HashSet();
???????????set.add(new?SetElement3("aa"));
???????????set.add(new?SetElement3("aa"));
???????????set.add(new?SetElement3("bb"));
???????????System.out.println(set);
??????}
??????static?class?SetElement3?implements?Comparable
??????{
???????????String?s;
???????????public?SetElement3(String?s){
????????????this.s?=??s;
???????????}
???????????public?String?toString(){
????????????return?s;
???????????}
???????????public?int?compareTo(Object?o){
????????????//return?s.compareTo(((SetElement3)o).s);
????????????return?-1;
???????????}
???????????public?boolean?equals(Object?obj)?{
????????????return?s.equals(((SetElement3)obj).s);
???????????}
????}
?}
???運行結果:
???[bb,?aa,?aa]
???看到沒有,有兩個“aa”!!這是因為compareTo返回值始終是"-1",也就是說“把任何元素都看成不同”。
???
???綜上所述,你是否對javadoc中對Set功能的描述有了懷疑?!
???以下以HashSet為例進行分析。
???從Hashset類的主體部分:
? public?class?HashSet??extends?AbstractSet??implements?Set?,?Cloneable,?java.io.Serializable
?{
??????static?final?long?serialVersionUID?=?-5024744406713321676L;
??????private?transient?HashMap??Object?map;
??????//?Dummy?value?to?associate?with?an?Object?in?the?backing?Map
??????//這是每個鍵所指的對像
???????private?static?final?Object?PRESENT?=?new?Object();??
?????public?HashSet()?
?????{
?????????map?=?new?HashMap??Object();
??????}
?????public?boolean?add(E?o)
?????{
?????????return?map.put(o,?PRESENT)==null;
??????}
????//以下省略.
????}?
????????public?HashSet()?{?
??????????map?=?new?HashMap??Object();
????
?????}
???可以看到HashSet使用了HashMap作為其Map保存“鍵-值”對。
???
???請看示例程序4:
?import?java.util.*;
?public?class?SetTest4?{
?????????public?static?void?main(String[]?args){
??????????????Set?set?=?new?HashSet();
??????????????set.add(new?SetElement4("aa"));
??????????????set.add(new?SetElement4("aa"));
??????????????set.add(new?SetElement4("bb"));
??????????????System.out.println(set);
?????????}
?????????static?class?SetElement4{
??????????????????String?s;
??????????????????public?SetElement4(String?s){
???????????????????this.s?=??s;
??????????????????}
??????????????????public?String?toString(){
???????????????????return?s;
??????????????????}
??????????????????public?boolean?equals(Object?obj)?{
???????????????????return?s.equals(((SetElement4)obj).s);
??????????????????}
?????????}
}
???運行結果:
???[bb,?aa,?aa]
???沒有“示例程序1”中的java.lang.ClassCastException,但是運行結果似乎不對,因為有兩個“aa”。
???
???請看示例程序5:
?public?class?SetTest5?{
??????????public?static?void?main(String[]?args){
???????????Set?set?=?new?HashSet();
???????????set.add(new?SetElement5("aa"));
???????????set.add(new?SetElement5("aa"));
???????????set.add(new?SetElement5("bb"));
???????????System.out.println(set);
??????????}
??????????static?class?SetElement5{
???????????????????String?s;
???????????????????public?SetElement5(String?s){
????????????????????this.s?=??s;
???????????????????}
???????????????????public?String?toString(){
????????????????????return?s;
???????????????????}
???????????????????public?boolean?equals(Object?obj)?{
????????????????????return?s.equals(((SetElement5)obj).s);
???????????????????}
???????????????????public?int?hashCode()?{
????????????????????//return?super.hashCode();
????????????????????return?s.hashCode();
???????????????????}
??????????}
?}
????運行結果:
????[bb,?aa]
????這就對了。“示例程序4”和“示例程序5”有什么區別?是SetElement5重寫了hashCode方法。
????
????可見HashSet中是采用了比較元素hashCode的方法來判斷元素是否相同(duplicate),而不是采用其他類似equals之類的東東來判斷。
????
????說了這么多,那java類庫中到底有沒有根據equals來判斷元素是否相同(duplicate)的Set呢?請看下文。
2.2、CopyOnWriteArraySet部分:
???類CopyOnWriteArraySet是java.util.concurrent包中的一個類,所以它是線程安全的。
???CopyOnWriteArraySet是使用CopyOnWriteArrayList作為其盛放元素的容器。當往CopyOnWriteArrayList添加新元素,它都要遍歷整個List,并且用equals來????比較兩個元素是否相同。
???請看示例程序6:
?import?java.util.concurrent.*;
?public?class?SetTest6?{
??????????public?static?void?main(String[]?args){
???????????????????Set?set?=?new?CopyOnWriteArraySet();
???????????????????set.add(new?SetElement6("aa"));
???????????????????set.add(new?SetElement6("aa"));
???????????????????set.add(new?SetElement6("bb"));
???????????????????System.out.println(set);
??????????}
??????????static?class?SetElement6{
???????????????????String?s;
???????????????????public?SetElement6(String?s){
????????????????????????this.s?=??s;
???????????????????}
???????????????????public?String?toString(){
????????????????????????return?s;
???????????????????}
???????????????????public?boolean?equals(Object?obj)?{
????????????????????????return?s.equals(((SetElement6)obj).s);
???????????????????}
??????????}
?}
???運行結果:
???[aa,?bb]
???好了,一切搞定!!
3、總結:
???Javadoc中的一些描述可能是不準確的,大家要當心了!
???
???Set中實現元素互異的各種方法差異很大,大致可以分為三種:使用equals,使用hashCode,使用compareTo。但是我還沒有發現采用“判斷地址空間是否相同”來判斷元素是否相同的類,當然我們可以用現有的三種方法來實現“判斷地址空間是否相同”。
???
???綜上所述,我們可以總結出使用Set的三種不同的情形:(以下假設元素類為Element)
???A、如果想使用Element的equals方法來判斷元素是否相同,那么可以使用CopyOnWriteArraySet來構造類的實體。
???B、如果Element實現了Comparable接口,而且想使用compareTo方法來判斷元素是否相同,那么可以使用TreeSet來構造類的實體。
???C、如果想使用判斷hashCode是否相同的方法來判斷元素是否相同,那么可以使用HashSet來構造類的實體。
總結
以上是生活随笔為你收集整理的[转]Java中Set的深入研究的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 微软被指责暗藏Windows API
- 下一篇: 软件架构解读与架构师角色培养——希赛嘉宾