JTableクラスはJava SEのバージョンが上がるたびに機能拡張されています。例えば,J2SE 5.0では印刷のサポート,不連続セルの選択などの機能拡張が行われています。

Java SE 6でも以下の2点の機能が拡張されました。

  • カラムのセルのソーティング
  • カラムのセルのフィルタリング

そこで,今週はソーティング,来週はフィルタリングについて解説します。

J2SE 5.0でも,がんばればJTableでソーティングをすることは可能でした。例えば,Java Swing HacksのHack #22では「JTableにソートを実行させる」と題して,ソートを扱っています。

とはいうものの,デフォルトでソーティングができるのは大歓迎です。しかも,単純なソートであれば,あっという間です。

サンプルのソースコード TableSortingSample1.java

すべてのカラムをソートするには,以下のように1行加えるだけです。

TableModel model = new DefaultTableModel(data, columnNames);
JTable table = new JTable(model);
 
// テーブルをソート可にする
table.setAutoCreateRowSorter(true);

これだけで,ソーティングが可能です。簡単すぎて拍子抜けしますね。

とりあえず実行してみましょう。

図1がデフォルトの表示です。ここで,ヘッダの[Title]をクリックしてみます。するとタイトルが昇順でソートされ,ヘッダには昇順を示す三角が描画されました(図2)。もう1回クリックすると,今度は降順でソートされます(図3)。他のカラムでも同じようにソートできます。

残念なことに,デフォルトの並び順に戻すことはできないようです。

デフォルト画面
図1 デフォルト画面
昇順でソート
図2 昇順でソート
降順でソート
図3 降順でソート

気をつけなくてはならないのが,表示はソートされていても,TableModelオブジェクトは変更されていないということです。

例えば,図2では2行目にCoffee & Cigarettesが表示されていますが,TableModelオブジェクトではCoffee & Cigarettesはあいかわらず7行目として扱われます。

このために,表示の行からテーブル・モデルでの行への変換も用意されています。これもサンプルで確かめてみましょう。

サンプルのソースコード TableSortingSample2.java

サンプルでは,セルをクリックすると表示上の行と,テーブル・モデルでの行を表示します。

TableModel model = new DefaultTableModel(data, columnNames);
JTable table = new JTable(model);
table.setAutoCreateRowSorter(true);

table.addMouseListener(new MouseAdapter() {
    public void mousePressed(MouseEvent event) {
        JTable table = (JTable)event.getSource();
        int[] selection = table.getSelectedRows();
        for (int i = 0; i < selection.length; i++) {
			
            // テーブル・モデルの行数に変換
            int modelRow = table.convertRowIndexToModel(selection[i]);
				
            System.out.println("View Row: " + selection[i]
                               + " Model Row: " + modelRow);
        }
    }});

表示上の行から,テーブル・モデルでの行に変換するのがJTableクラスのconvertRowIndexToModelメソッドです。

これとは逆に,テーブル・モデルの行から,表示上の行に変換するためにconvertRowIndexToViewメソッドも用意されています。

さて,サンプルを実行して,図2の状態でCoffee & Cigarettesをクリックしてみました。

C:\temp>java TableSortingSample2
	
View Row: 1 Model Row: 6

JTableクラスは0行からはじまるので,1と6になりましたが,ちゃんと変換が行われていることがわかります。

javax.swing.table.TableRowSorterクラス

今までのサンプルはJTableクラスが勝手にソートしてくれていました。

ところで,自動でソートするためのメソッドであるsetAutoCreateRowSorterメソッドの名前が気になりませんか。単にソートするのであればsetAutoSortとかにすればいいと思うのですが,この長ったらしい名前になったのはなぜでしょう。

その理由はソートを行うのがRowSorterクラスだということです。setAutoCreateRowSorterメソッドはRowSorterオブジェクトの自動的生成を設定するメソッドだったのです。

自動で生成してくれるのはいいのですが,自分でソートをコントロールしたい場合には,TableRowSorterオブジェクトの生成を自分で記述する必要があります。

サンプルのソースコード TableSortingSample3.java

TableRowSorterオブジェクトの生成部分を示します。

TableRowSorter<TableModel> sorter
    = new TableRowSorter<TableModel>(model);

JTable table = new JTable(model);
table.setRowSorter(sorter);

TableRowSorterクラスはジェネリクスでパラメータ化されています。定義ではTableModelSorter<M extends TableModel>となっており,自作したテーブル・モデルでも使用できるようになっています。

TableRowSorterクラスのコンストラクタはM型のオブジェクトを取ります。後は,JTableクラスのsetRowSorterメソッドで指定するだけです。

この記述だけだとsetAutoCreateRowSorterメソッドで指定したのと変わらないので,少しカスタマイズしてみましょう。

まずはソートできないカラムを作ってみます。

// Main Performerはソート不可にする
sorter.setSortable(2, false);

setSortableメソッドは第1引数がカラム数,第2引数でソートの可/不可を決めます。

これで実行すると,[Main Performer]のカラムがソートできなくなります。

次に,ソートしたときのイベントを扱ってみましょう。ソート時に発生するイベントはjavax.swing.event.RowSorterEventクラスで,リスナーはjavax.swing.event.RowSorterListenerインタフェースです。

// ソートされたときに発生するイベント処理
sorter.addRowSorterListener(new RowSorterListener() {
    public void sorterChanged(RowSorterEvent event) {
        System.out.println("Type: " + event.getType());
    }
});

RowSorterEventクラスのgetTypeメソッドが返すのは列挙型のRowSorterEvent.Typeです。RowSorterEvent.TypeにはSORT_ORDER_CHANGEDとSORTEDが定義されています。SORT_ORDER_CHANGEDはソートの順序(昇順,降順)が変化したことを示し,SORTEDはソートが行われたことを示しています。

TableRowSorterクラスの動作

TableRowSorterクラスはソートにComparatorインタフェースを使用します。適切なComparatorオブジェクトを選択しないと,ソートのパフォーマンスが大幅に変化します。

そのためにも,Comparatorオブジェクトの選択の手順を理解しましょう。

Comparatorオブジェクトの手順は次のようになります。

  1. TableRowSorter#setComparatorメソッドでComparatorオブジェクトが指定されていれば,それを使用する
  2. TableModelクラスのgetColumnClassメソッドの戻り値がStringクラスであれば,java.text.CollatorオブジェクトをCollator.getInstanceメソッドで取得して使用する
  3. TableModel#getColumnClassメソッドの戻り値がComparableなクラスであれば,Comparable#compareToメソッドをコールするだけのComparatorクラスを使用する
  4. TableModel#getColumnClassメソッドの戻り値がそれ以外の場合,TableRowSorter#setStringConverterメソッドでTableStringConverterオブジェクトが指定されていれば,それを使用してモデルの当該オブジェクトをStringオブジェクトに変換し,Collatorクラスを使用してソートを行う
  5. TableStringConverterオブジェクトも設定されていない場合,セルの当該オブジェクトをtoStringメソッドでStringオブジェクトに変換し,Collatorクラスを使用してソートを行う

ちょっと複雑ですね。

基本的にはTableRowSorter#getComparatorでComparatorオブジェクトを指定しておくか,TableModel#getColumnClassメソッドでComparableなクラスを返すようにします。

例えば,今まで使っていたサンプルは[Year]カラムはIntegerクラスですが,DefaultTableModelクラスをそのまま使っているのでgetColumnClassメソッドはOjectクラスが返ってしまいます。このため,Integerオブジェクトに対してtoStringメソッドをコールし,文字列にしてから比較を行っています。これは明らかに無駄な処理です。

そこで,getColumnClassメソッドをオーバーライドしてみましょう。

TableModel model = new DefaultTableModel(data, columnNames) {
    public Class getColumnClass(int column) {
        if (column == 1) {
            // カラム1ならInteger
            return Integer.class;
        } else {
            // その他はString
            return String.class;
        }
    }
};

このサンプルは列数が少ないので,ソートはあっという間に終わってしまい,効果はわかりません。しかし,列数が多ければ多いほど,パフォーマンスの違いが明確になります。

使うにはちょっとくせがありますが,簡単にソートできるのはとても便利です。Java Swing Haksには「実際のところ,SwingのAPIが大規模であることを考えると,この機能をSwingがまだ提供していないのは一種の驚きです」と記述されています。

待ちに待っていた機能がやっと取り入れられたという感じですね。

さて,来週はJTableクラスのもう一つの拡張機能であるフィルタリングを取り上げます。

"Java Swing Hacks" Joshua Marinacci, Chris Adamson 著, 神戸博之, 島田秋雄 監訳, 加藤慶彦 訳, O'Reilly ISBN4-87311-278-8

著者紹介 櫻庭祐一

横河電機 ネットワーク開発センタ所属。Java in the Box 主筆

今月の櫻庭

自家製桜もち

櫻庭は洋菓子しか興味がないと思ったあなた,それは間違いです(笑)。

実は和菓子も大好きなんです。

特に冬から春にかけての和菓子。花びら餅,福豆,桜もちに柏餅。どれもおいしいですよね。

ところで,最近,江戸菓子が京菓子化しているような気がしてしかたありません。例えば,桜もち。江戸菓子の白玉粉を使った桜もちが減って,道明寺が増えているような気がします。

櫻庭は白玉粉を使った桜もちが大好きなので(もちろん道明寺も好きですけど),去年は自分で作ってしまいました。意外と簡単ですよ。

これから桜の季節なので,自家製桜もちでお花見などいかがですか。