作成日: 2010/11/05

メモ: Java でクラスのソートのためのユーティリティクラス

概要

たまーに Java でクラスのソートをしたくなるので、ユーティリティクラスを作ってみました。

private および protected フィールドの値を取得するために、リフレクション時に Field#setAccessible() を利用していますが、 これを使用したくない場合は sortutil.SortUtil の SortComparator#getFieldValue() の該当の箇所を削除してください。
なお、Field#setAccessible() を使用しない場合には、private フィールド、および、別パッケージの protected フィールドはソートの対象にできなくなります。

ソートのためのユーティリティクラス(sortutil.SortUtil)
package sortutil;

import java.lang.reflect.*;
import java.util.*;

/**
 * クラスのソートのためのユーティリティ
 */
public class SortUtil<T> {

    /**
     * ソートオーダー
     */
    public enum SortOrder {
        ASCENDING,    // 昇順
        DESCENDING,    // 降順
    }

    // ソートのための比較クラス
    private SortComparator sortComparator = new SortComparator();

    /**
     * ソートの情報を追加する
     * @param itemName 項目名
     * @param order ソートオーダー
     */
    public void add(String itemName, SortOrder order) {
        sortComparator.add(new SortInfo(itemName, order));
    }

    /**
     * ソートを行う
     * @param list ソート対象の List オブジェクト
     */
    public void sort(List<T> list) {

        Collections.sort(list, sortComparator);
    }

    // ソートの情報
    private class SortInfo {
        // 項目名
        private String itemName;
        public String getItemName() {
            return itemName;
        }

        // ソートオーダー
        private SortOrder order;
        public SortOrder getOrder() {
            return order;
        }

        // コンストラクタ
        public SortInfo(String itemName, SortOrder order) {
            this.itemName = itemName;
            this.order = order;
        }
    }

    // ソートのための比較クラス
    private class SortComparator implements Comparator<T> {

        // ソートの情報を保持する
        private List<SortInfo> sortInfoList = new ArrayList<SortInfo>();

        // ソートの情報を追加する
        public void add(SortInfo sortInfo) {
            sortInfoList.add(sortInfo);
        }

        // 比較を行う(Comparator#compare() の実装)
        @Override
        public int compare(T item1, T item2) {

            try {

                int result = 0;

                // ソートの優先順位の高い項目から順に比較する。
                for (int i = 0; i < sortInfoList.size(); ++i) {

                    SortInfo sortInfo = sortInfoList.get(i);

                    Object value1 = getFieldValue(item1, sortInfo.getItemName());
                    Object value2 = getFieldValue(item2, sortInfo.getItemName());

                    result = compareInternal(value1, value2);
                    if (result == 0) {
                        // 項目値が同じであれば、次に優先順位の高い項目の比較を行う
                        continue;
                    }

                    if (sortInfo.getOrder() == SortOrder.DESCENDING) {
                        result *= -1;
                    }
                    break;
                }

                return result;

            } catch (Exception ex) {
                throw new RuntimeException(ex.toString());
            }
        }

        // 実際の比較処理を行う
        private int compareInternal(Object value1, Object value2) {
            if (value1 == null && value2 == null) {
                return 0;
            } else if (value1 == null && value2 != null) {
                return -1;
            } else if (value1 != null && value2 == null) {
                return 1;
            } else {

                // TODO: とりあえず以下の型のみ。必要に応じ適宜追加してください。
                if (value1 instanceof Integer) {
                    return ((Integer) value1).compareTo((Integer) value2);
                } else if (value1 instanceof Long) {
                    return ((Long) value1).compareTo((Long) value2);
                } else if (value1 instanceof String) {
                    return ((String) value1).compareTo((String) value2);
                } else {
                    throw new RuntimeException("対応していない型です: " + value1.getClass().getCanonicalName());
                }
            }
        }

        // フィールドの値を得る
        private Object getFieldValue(Object obj, String fieldName) {
            try {
                Field field = obj.getClass().getDeclaredField(fieldName);

                // NOTE: Field#setAccessible() を使用したくない場合は、この行を削除してください。
                //       なお、削除した場合には、private フィールド、および、別パッケージの protected フィールドは
                //       ソートの対象にできません。
                field.setAccessible(true);

                return field.get(obj);
            } catch (Exception ex) {
                throw new RuntimeException("フィールド値を取得できません: " + ex.toString());
            }
        }
    }
}
使い方

以下のような感じで使います。

import java.util.*;

import sortutil.SortUtil;

public class Main {

    // ソート対象のクラス
    public static class TestItem {
        public String NaMe1;
        private int value1;
        public String name2;

        public int getValue1() {
            return value1;
        }

        public TestItem(String name1, int value1, String name2) {
            this.NaMe1 = name1;
            this.value1 = value1;
            this.name2 = name2;
        }
    }

    // ソートのユーティリティの使用例
    public static void main(String[] args) {

        try {

            // ソート対象のデータを List に詰め込む
            List<TestItem> list = new ArrayList<TestItem>();
            list.add(new TestItem("c", 2, "BCD"));
            list.add(new TestItem("a", 1, "EFG"));
            list.add(new TestItem("b", 1, "CDE"));
            list.add(new TestItem("c", 1, "ABC"));
            list.add(new TestItem("a", 2, "FGH"));
            list.add(new TestItem("b", 2, "DEF"));

            // ソートのユーティリティの準備をする
            SortUtil<TestItem> su = new SortUtil<TestItem>();
            // ・第 1 引数(項目名): ソート対象のクラスのフィールド名を指定(大文字・小文字を区別する)する
            // ・第 2 引数(ソートオーダー): 昇順・降順を指定する
            su.add("NaMe1", SortUtil.SortOrder.DESCENDING);
            su.add("value1", SortUtil.SortOrder.ASCENDING);

            // ソート対象のデータの List を渡しソートする
            su.sort(list);

            printTestItemList(list);

        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }

    // ソート結果を出力する
    public static void printTestItemList(List<TestItem> list) {
        System.out.println(">>----------");
        for (TestItem ti : list) {
            System.out.println(ti.NaMe1 + "," + ti.getValue1() + "," + ti.name2);
        }
        System.out.println("<<----------");
    }
}

実行結果

>>----------
c,1,ABC
c,2,BCD
b,1,CDE
b,2,DEF
a,1,EFG
a,2,FGH
<<----------
プロジェクト一式

java ソースのエンコーディングが UTF-8 になっていますので、お気を付け下さい。
なお、プログラムの作成には Eclipse 3.6 Helios Pleiades All in One (MergeDoc Project で公開されているバージョンです) を利用しました。 大変有益なリソースを公開されており、いつも利用させていただいております。ありがたく深謝いたします。

SortUtil.zip (20.1 KB)