java-beginner.com ブログ

プログラミングを学習するブログ(Javaをメインに)

TreeSetに自作クラスを格納する

投稿日:

最終更新日:2016年08月11日

アイキャッチ

こんにちは。「Javaを復習する初心者」です。

以前、SetインターフェースのtoArrayメソッドを使いました。その記事では3つのクラスHashSet、LinkedHashSet、TreeSetを使い、ジェネリックスにIntegerとStringを指定しました。今回はTreeSetを使い、簡単な自作クラスを指定しようと思います。

自作クラス

自作クラスはStudentという名前にします。以下のフィールドを定義します。

  • public int year
  • public String name

要するに学年と名前です。TreeSetでは格納した要素がソート順になるのですが、自作クラスの場合、どのように並べるのかを定義する必要があります。今回は上記2つのフィールドを使い、次のように並べ方を定義します。

  1. yearが異なる場合
    1. 自然数と同じ順序で並べる。
  2. yearが同じ場合
    1. nameで辞書式順序で並べる。

さて、TreeSetの使い方は使うコンストラクタで変わります。API仕様書をみるとコンストラクタは4つありますが、今回は次の2つを使います。

  • TreeSet()
  • TreeSet(Comparator<? super E> comparator)

TreeSet()を使う場合、格納するクラスはComparableインターフェースを実装する必要があります。実装していない場合、コンパイルエラーになりませんが、実行時に次のようなエラーが出ます。

エラー

Exception in thread "main" java.lang.ClassCastException: practice.test20160811StudentComparator.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 practice.test20160811StudentComparator.SetStudent.main(SetStudent.java:36)

以下はComparableインターフェースを実装した場合のStudentクラスです。compareTo()メソッドをオーバーライドしています。また、hashCode()メソッド、equals()メソッドをオーバーライドしていますが、Setインターフェースの特徴であるこれは要素の重複を許可しないという機能が正常に動くためです。toString()メソッドは出力結果を簡単に出すためにオーバーライドしました。

Student.java

package practice.test20160811Comparable;

    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);
        }
    }

}

TreeSet()

以下はコンストラクタTreeSet()を使ってインスタンスを生成し、上記のStudentクラスを格納した例です。

SetStudent.java

package practice.test20160811Comparable;

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

public class SetStudent {

    public static void main(String[] args) {

        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();

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

        // 配列に変換
        Student[] students = treeSet.toArray(new Student[0]);

        // 出力
        System.out.printf("%-29s", "TreeSet");
        System.out.println();
        System.out.println("-----------------------------");
        for (int i = 0; i < students.length; i++) {
            System.out.printf("%-10s", students[i].toString());
            System.out.println();
        }

    }

}

結果

TreeSet                      
-----------------------------
[ year: 1, name: httgihd    ]
[ year: 1, name: oitkmmh    ]
[ year: 1, name: qokxinr    ]
[ year: 1, name: v          ]
[ year: 1, name: xlwl       ]
[ year: 1, name: zfclunxa   ]
[ year: 2, name: focjpi     ]
[ year: 2, name: y          ]
[ year: 3, name: jtie       ]
[ year: 3, name: tvw        ]

yearの自然数順序で並び、nameの辞書式順序で並んでいることが分かります。

TreeSet(Comparator<? super E> comparator)

では、TreeSet(Comparator<? super E> comparator)を使った場合はどのようなプログラムになるのかというと、やり方は以下の2つだと思います。

  • Comparatorの実装クラスを作ってコンストラクタの引数にインスタンスを指定する。
  • コンストラクタの引数に匿名クラスを指定する。

Comparatorの実装クラスは例えば、以下のようにします。

ComparatorStudent.java

package practice.test20160811StudentComparator;

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);
        }
    }

}

以下のようにコンストラクタの引数にComparatorの実装クラスのインスタンスを指定します。

ソース

        Set<Student> treeSet = new TreeSet<>(new ComparatorStudent());

もう一つは以下のように匿名クラスを引数に指定する方法です。以下はラムダ式を使った場合です。

ソース

        Set<Student> treeSet = new TreeSet<>((o1, o2) -> {
            if (o1.year != o2.year) {
                return o1.year - o2.year;
            } else {
                return o1.name.compareTo(o2.name);
            }
        });

この場合、クラスを別途作る必要がないため、楽だと思います。