2.4. java.util.HashSet。重複しないArrayList。

HashSetはArrayListに似ています。同じように要素の集合を作る機能です。

ですが、次のような違いがあります。

まず、追加する要素が重なることができません。同じ要素を追加しようとしても無視されます。つまり、要素がユニークな集合が作れます。

次に、順序がありません。追加した順番に要素は並んでいません。

※どうしても順番が必要なHashSetが欲しい時には、LinkedHashSetを使うとよいです。

2.4.1. クラス図へ変換

では、早速クラス図にしてみましょう。

UML変換くんを実行。

> touml .\java\util\HashSet.java -o d:\temp\

※java1.8コード

※「-o d:temp」はクラス図の出力先です。

2.4.1.1. パッケージ図

../_images/java.util.HashSet.image001.jpg

パッケージ図はこれです。

HashSetはutil内にあり、関連するpackageはこんな感じでした。

2.4.1.2. クラス図

2.4.1.2.3. 親の親クラス。AbstractCollection

../_images/java.util.AbstractCollection3.svg

※ArrayListも使っています。

元の図は大きいので小さく切り取って、見ていきます。

2.4.2. 継承を眺めてみる

2.4.2.1. HashSet。ターゲットのクラス

../_images/java.util.HashSet.image002.jpg
../_images/java.util.HashSet.image003.jpg

2.4.2.1.1. Serializableを実現しています。

これは、シリアライズをする目印になります。

2.4.2.1.2. Setを実現しています。

Set関連のクラスは、このインタフェースを実現する設計になってます。

2.4.2.1.3. AbstractSetを継承しています。

Set関連のクラスはいくつあるので、それらのクラスで共有できる処理をAbstractSetにまとめています。

2.4.2.1.4. Cloneableを実現しています。

インスタンスをコピーできるcloneメソッドを実現するよという目印になります。

2.4.2.2. AbstractSet。親クラス。

../_images/java.util.HashSet.image004.jpg

2.4.2.2.1. AbstractCollectionを継承しています。

Collectionというのは、Setよりももっと幅の広い概念で、集めたもの集合のような意味になります。なので、この意味に共通する処理を共有しています。つまりAbstractCollectionの処理を流用しています。

2.4.2.2.2. Setを実現しています。

Set関連のクラスは、このインタフェースを実現する設計になってます。

2.4.2.3. AbstractCollection。親の親クラス

../_images/java.util.HashSet.image005.jpg

2.4.2.3.1. Collectionを実現しています。

Collection関連クラスはこのインタフェースを実現することになっています。

2.4.3. フィールドを見てみる

2.4.3.1. HashSet。ターゲットのクラス

../_images/java.util.HashSet.image006.jpg

2.4.3.1.1. serialVersionUID

~serialVersionUID:long=-5024744406713321676...{readOnly}

シリアライズ用のIDでシリアライズするときに必要なものです。

2.4.3.1.2. map

-map:HashMap<E,Object>{transient}

要素を格納する本体。 ※なんと、HashMapを使っています。 ※HashSetはHashMapをラッピング(包む)したクラスだったんですね。

2.4.3.1.3. PRESENT

-PRESENT:Object=new Object  {readOnly}

コメントには「バッキング・マップ内のオブジェクトに関連付けるダミー値。」とありました。 ※「バッキング・マップ」は裏(バック)ではMapを使う実装だよということです。

「map.put(e, PRESENT)」こんな風に要素を追加するとき、第2引数valueの方は一律このPRESENTをセットしています。 つまり、HashSetはHashMapクラスの第1引数keyのみを使い、valueはダミー値を入れて使うということです。

2.4.4. メソッドを眺めてみる。

2.4.4.1. コンストラクタ

+HashSet():void
+HashSet(c:Collection<?extends E>):void
+HashSet(initialCapacity:int,loadFactor:float):void
+HashSet(initialCapacity:int):void
~HashSet(initialCapacity:int,loadFactor:float,dummy:boolean):void

コンストラクタです。

※内部では「map = new HashMap<>();」をしてます。

2.4.4.2. iterator

+iterator():Iterator<E>

イテレーターを返します。

(iterate:繰り返す +or:使うもの=iterator :繰り返し処理に使うもの、反復子)

※内部では「return map.keySet().iterator();」してます。

2.4.4.3. size

+size():int
+isEmpty():boolean

格納している要素数を返します。

isEmptyはsizeが0ならtrueを返します。

2.4.4.4. contains

+contains(o:Object):boolean

引数oを要素の集合に含む(contain)場合はtrueを返します。

2.4.4.5. add

+add(e:E):boolean

要素を追加します。

※内部では「map.put(e, PRESENT)」してます。

※PRESENTはダミーインスタンスで、第1引数(key)のeだけ使っています。

2.4.4.6. remove

+remove(o:Object):boolean
+clear():void

内部の要素に、引数oがあったら削除します。

clearは要素を全部削除します。

2.4.4.7. clone

+clone():Object

自分のクローン(コピー)を作ります。

2.4.4.8. writeObject

-writeObject(s:java.io.ObjectOutputStream):void
-readObject(s:java.io.ObjectInputStream):void

シリアライズのときStreamへの書き込み、読み込みに使われます。

2.4.4.9. spliterator

+spliterator():Spliterator<E>

スプリテレーターを返します。

※イテレーターの一種です。イテレーターが扱う集合を簡単に分(split)けられます。

※分割したものを並列処理したりするときに便利です。

2.4.5. 関連を眺めてみる

../_images/java.util.HashSet.image007.jpg
-map:HashMap<E,Object>{transient}

mapフィールドを関連に持っています。これが、要素集合を格納する本体です。

※<E,Object>:実装ではEのみを使い、Objectはダミーで、一律に固定値がセットされます。

2.4.6. まとめ

HashSetってHashMapをラッピングしているクラスなんですね。

内部処理はほとんどHashMapのメソッドを呼んでいるだけでした。

HashSetの機能はとても便利で、ArrayListと違って要素の重複を許さないので同じ要素を入れたくない集合を作りたいときには重宝します。

ただ、並び順が毎回変わってしまうのでそこが問題なることがあります。

どうにかならないものかと探してみると、同じSet仲間でLinkedHashSetというのがありました。

これは、要素が追加順に並ぶので便利に使っています。