HashSet/LinkedHashSet/TreeSetのtoArrayで出力順序を確認する

SetインターフェースにtoArrayメソッドが定義されています。Setインターフェースの実装クラスであるHashSet/LinkedHashSet/TreeSetでtoArrayメソッドの出力結果を確認しました。この記事の趣旨はtoArrayメソッドで以下の出力順序、特にTreeSetの出力を確認することです。

|  クラス       |  順序          |
|:--------------|:---------------|
| HashSet       |  決まってない  |
| LinkedHashSet |  格納順        |
| TreeSet       |  ソート順      |

ここでソート順はComparable#compareToメソッドの実装で決まります。

#Integer型を格納した場合

API仕様書を見るとcompareToの実装は「2つのIntegerオブジェクトを数値的に比較します。」となっています。結果を見ると一目瞭然ですが、数値の昇順です。

以下、実験ソースと結果です。

```Java:SetInteger.java
package practice.test20160809;

import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.Random;
import java.util.Set;
import java.util.TreeSet;

public class SetInteger {

    public static void main(String[] args) {

        Set<Integer> hashSet = new HashSet<>();
        Set<Integer> linkedHashSet = new LinkedHashSet<>();
        Set<Integer> treeSet = new TreeSet<>();

        // 乱数格納
        Random random = new Random();
        int randomNum = 0;
        for (int i = 0; i < 10; i++) {
            randomNum = random.nextInt(1000);
            hashSet.add(randomNum);
            linkedHashSet.add(randomNum);
            treeSet.add(randomNum);
        }

        // 配列に変換
        Integer[] a = hashSet.toArray(new Integer[0]);
        Integer[] b = linkedHashSet.toArray(new Integer[0]);
        Integer[] c = treeSet.toArray(new Integer[0]);

        // 出力
        System.out.printf("%-3s %-3s %-3s", "H", "L", "T");
        System.out.println();
        System.out.println("-----------");
        for (int i = 0; i < a.length; i++) {
            System.out.printf("%3d %3d %3d", a[i], b[i], c[i]);
            System.out.println();
        }

    }

}
```

```:結果
H   L   T  
-----------
145 611 116
449 987 145
706 910 436
611 436 449
436 116 611
116 706 706
916 916 772
772 145 910
987 449 916
910 772 987
```

#String型の場合

API仕様書を確認すると、compareToの実装は「2つの文字列を辞書的に比較します。」となってます。

以下、実験ソースと結果です。

```Java:SetString.java
package practice.test20160809;

import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.Random;
import java.util.Set;
import java.util.TreeSet;

public class SetString {

    public static void main(String[] args) {

        Set<String> hashSet = new HashSet<>();
        Set<String> linkedHashSet = new LinkedHashSet<>();
        Set<String> treeSet = new TreeSet<>();

        // ランダム文字列格納
        Random random = new Random();
        for (int i = 0; i < 10; i++) {

            // 単語生成
            int randomLength = random.nextInt(10) + 1;
            StringBuilder sb = new StringBuilder();
            for (int j = 0; j < randomLength; j ++) {
                char c = (char)('a' + random.nextInt(26));
                sb.append(c);
            }
            String s = sb.toString();
            hashSet.add(s);
            linkedHashSet.add(s);
            treeSet.add(s);
        }

        // 配列に変換
        String[] a = hashSet.toArray(new String[0]);
        String[] b = linkedHashSet.toArray(new String[0]);
        String[] c = treeSet.toArray(new String[0]);

        // 出力
        System.out.printf("%-10s %-10s %-10s", "Hash", "LinkedHash", "Tree");
        System.out.println();
        System.out.println("--------------------------------");
        for (int i = 0; i < a.length; i++) {
            System.out.printf("%-10s %-10s %-10s", a[i], b[i], c[i]);
            System.out.println();
        }

    }

}
```

```:結果
Hash       LinkedHash Tree      
--------------------------------
grsenjlhnf ic         ehwsfshpi 
ehwsfshpi  grsenjlhnf grsenjlhnf
xokldpxsc  uidkyjo    hojvhutuh 
ifis       xokldpxsc  ic        
hojvhutuh  hojvhutuh  ifis      
ic         zziqsg     jfk       
uidkyjo    jfk        rlfu      
zziqsg     ehwsfshpi  uidkyjo   
rlfu       ifis       xokldpxsc 
jfk        rlfu       zziqsg    
```

TreeSetでは辞書式に出力されているのが分かります。

#自作クラスの場合

TreeSetのインスタンスをコンストラクタTreeSet()で生成した場合、このインスタンスに要素を格納するためにはComparableインターフェースを実装し、compareToメソッドを実装する必要があります。もし、実装していないクラスをaddの引数に指定すると以下のエラーが出ます。TreeSet(Comparator<? super E> comparator) を使った場合、前述の実装は不要です。

```:エラー
Exception in thread "main" java.lang.ClassCastException: practice.test20160809.Student cannot be cast to java.lang.Comparable
	at java.util.TreeMap.compare(TreeMap.java:1290)
	at java.util.TreeMap.put(TreeMap.java:538)
	at java.util.TreeSet.add(TreeSet.java:255)
	at ...
```

実験的にStudentクラスを作成しました。フィールドにint型、String型の変数を持つクラスです。Comparableインターフェースを実装しました。

並べ方の優先順位は以下のように実装しました。

* yearで数値の昇順
* nameで辞書式順序

また、出力結果を出しやすくするため、toStringメソッドをオーバーライドしてます。

```Java:Student.java
package practice.test20160809;

    public  class Student implements Comparable<Student> {

    public int year;
    public String name;

    public Student(int year, String name) {
        super();
        this.year = year;
        this.name = name;
    }

    @Override
    public String toString() {
        return  String.format("[ year: %d, name: %-10s ]", year, name);
    }

    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + ((name == null) ? 0 : name.hashCode());
        result = prime * result + year;
        return result;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        Student other = (Student) obj;
        if (name == null) {
            if (other.name != null)
                return false;
        } else if (!name.equals(other.name))
            return false;
        if (year != other.year)
            return false;
        return true;
    }

    @Override
    public int compareTo(Student o) {
        if (this.year != o.year) {
            return this.year - o.year;
        } else {
            return this.name.compareTo(o.name);
        }
    }

}
```

以下、上記クラスを使った例と実行結果です。

```Java:SetStudent.java
package practice.test20160809;

import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.Random;
import java.util.Set;
import java.util.TreeSet;

public class SetStudent {

    public static void main(String[] args) {

        Set<Student> hashSet = new HashSet<>();
        Set<Student> linkedHashSet = new LinkedHashSet<>();
        Set<Student> treeSet = new TreeSet<>();

        // ランダム文字列格納
        Random random = new Random();
        for (int i = 0; i < 10; i++) {
            // 学年生成
            int year = random.nextInt(3) + 1;

            // 名前生成
            int randomLength = random.nextInt(10) + 1;
            StringBuilder sb = new StringBuilder();
            for (int j = 0; j < randomLength; j ++) {
                char c = (char)('a' + random.nextInt(26));
                sb.append(c);
            }
            String name = sb.toString();

            // 格納
            hashSet.add(new Student(year, name));
            linkedHashSet.add(new Student(year, name));
            treeSet.add(new Student(year, name));
        }

        // 配列に変換
        Student[] a = hashSet.toArray(new Student[0]);
        Student[] b = linkedHashSet.toArray(new Student[0]);
        Student[] c = treeSet.toArray(new Student[0]);

        // 出力
        System.out.printf("%-29s %-29s %-29s", "Hash", "LinkedHash", "Tree");
        System.out.println();
        System.out.println("-----------------------------------------------------------------------------------------");
        for (int i = 0; i < a.length; i++) {
            System.out.printf("%-10s %-10s %-10s", a[i].toString(), b[i].toString(), c[i].toString());
            System.out.println();
        }

    }

}
```

```:結果
Hash                          LinkedHash                    Tree                         
-----------------------------------------------------------------------------------------
[ year: 3, name: tpn        ] [ year: 3, name: slq        ] [ year: 1, name: bgqndpfr   ]
[ year: 2, name: axobm      ] [ year: 1, name: bgqndpfr   ] [ year: 1, name: vgcdsse    ]
[ year: 3, name: swez       ] [ year: 2, name: axobm      ] [ year: 2, name: axobm      ]
[ year: 2, name: grdhguyhw  ] [ year: 3, name: uija       ] [ year: 2, name: grdhguyhw  ]
[ year: 2, name: kgfcfsckl  ] [ year: 1, name: vgcdsse    ] [ year: 2, name: kgfcfsckl  ]
[ year: 3, name: slq        ] [ year: 3, name: ojdgacgzf  ] [ year: 3, name: ojdgacgzf  ]
[ year: 3, name: uija       ] [ year: 3, name: swez       ] [ year: 3, name: slq        ]
[ year: 1, name: vgcdsse    ] [ year: 3, name: tpn        ] [ year: 3, name: swez       ]
[ year: 1, name: bgqndpfr   ] [ year: 2, name: grdhguyhw  ] [ year: 3, name: tpn        ]
[ year: 3, name: ojdgacgzf  ] [ year: 2, name: kgfcfsckl  ] [ year: 3, name: uija       ]
```

TreeSetで並べ替えが機能しているが分かります。

##TreeSet(Comparator<? super E> comparator)の例

例えば以下のようにします。

```Java:ComparatorStudent.java
package practice.test20160809StudentComparator;

import java.util.Comparator;

public class ComparatorStudent implements Comparator<Student>{

    @Override
    public int compare(Student o1, Student o2) {
        if (o1.year != o2.year) {
            return o1.year - o2.year;
        } else {
            return o1.name.compareTo(o2.name);
        }
    }

}
```

```Java:TreeSetの生成
        Set<Student> treeSet = new TreeSet<>(new ComparatorStudent());
```


#修正履歴
saka1029さんの指摘に対応。

* 「自作クラスの場合」の冒頭の記述を修正。
*  TreeSet(Comparator<? super E> comparator)の例を追加。