今日の役に立たない一言 - Today’s Trifle! -

古い記事ではさまざまなテーマを書いていますが、2007年以降はプログラミング関連の話がほとんどです。

ポリモーフィズム

専門学校でのJavaの授業の話題。前回の授業のことをここに書いてないけど、前回からポリモーフィズムについて教えてる途中。テキスト「基礎からのJava」では Shape インターフェースと、それを実装した Rect クラスと Circle クラスをサンプルにしてあるけど、GUI で目に見える方が授業を受ける方も楽しいんじゃないかと、swing コンポーネントポリモーフィズムのサンプルを作らせてみることにした。
細かいところの意味が分かるかどうかはともかく、JList と ListCellRenderer を使って比較的簡単な実装で動かしてみる。

import java.io.File;
import javax.swing.*;

class JListTest {
    public static void main(String args[]) {
        JFrame f = new JFrame();
        f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        JList list = new JList(new File(".").listFiles());
        f.getContentPane().add(list);
        f.pack();
        f.setVisible(true);
    }
}

で、これの実行結果。

JListのデフォルトだと、File#toString()で文字列に変換されたものを表示する。先頭部分の「.\」を表示しないようにするため、FileRenderer を作成する。ListCellRenderer のデフォルト実装は、javax.swing.DefaultListCellRenderer が用意されているので、それを継承する。

import javax.swing.*;

class FileRenderer extends DefaultListCellRenderer {
}

もとの JListTest も一部変更する。

import java.io.File;
import javax.swing.*;

class JListTest {
    public static void main(String args[]) {
        JFrame f = new JFrame();
        f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        JList list = new JList(new File(".").listFiles());
        list.setCellRenderer(new FileRenderer());
        f.getContentPane().add(list);
        f.pack();
        f.setVisible(true);
    }
}

ListCellRendererのデフォルト実装をそのまま使用しているので、動作は前回と全く同じになる。次は動作を変更するためにオーバーライドを作成する。アノテーションはまだ教えてないので、@Override は書かない。

import java.awt.Component;
import java.io.File;
import javax.swing.*;

class FileRenderer extends DefaultListCellRenderer {
    public Component getListCellRendererComponent(JList list,
                                                  Object value,
                                                  int index,
                                                  boolean isSelected,
                                                  boolean cellHasFocus) {
        File f = (File)value;
        setText(f.getName());
        return this;
    }
}

これをコンパイルして実行すると、期待通りに先頭の「.\」がなくなってファイル名だけになっていることが分かる。

ポリモーフィズムの実装例なんで、次は別の実装を作ってみる。ファイル名とサイズを表示する。クラス名を FileSizeRenderer にしたけど、いまいち命名が悪いなーとか思いつつ。

import java.awt.Component;
import java.io.File;
import javax.swing.*;

class FilSizeeRenderer extends DefaultListCellRenderer {
    public Component getListCellRendererComponent(JList list,
                                                  Object value,
                                                  int index,
                                                  boolean isSelected,
                                                  boolean cellHasFocus) {
        File f = (File)value;
        setText(f.getName() + " " + f.length() + "bytes");
        return this;
    }
}

JListTest で ListCellRenderer を設定する箇所も、新しいクラスに変更する。

import java.awt.Component;
import java.io.File;
import javax.swing.*;

class JListTest {
    public static void main(String args[]) {
        JListTest test = new JListTest();
        JFrame f = new JFrame();
        f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        JList list = new JList(new File(".").listFiles());
        list.setCellRenderer(new FileSizeRenderer());
        f.getContentPane().add(list);
        f.pack();
        f.setVisible(true);
    }
}

これをコンパイルして実行する。

授業では、FileRenderer を変更して拡張子別にアイコンを表示したり、表示色を変更したりした。FileRenderer には、拡張子を取得するためのメソッド getExtension() を追加した。また、ファイル名・ファイルサイズ・更新日時を表示するための FileDetailRenderer も作成した(ここでは割愛)。

    public String getExtension(File f) {
        String name = f.getName();
        int i = name.lastIndexOf(".");
        return (i < 0) ? "" : name.substring(i + 1);
    }

さらにポリモーフィズムの別の実装例を追加してみる。その準備のため、JListTest を少々変更する。list をインスタンス変数にするために、showFrame() というインスタンスメソッドを追加し、main()メソッド内ではインスタンスを生成してshowFrame()を呼ぶことにする。

import java.io.File;
import javax.swing.*;

class JListTest {
    private JList list;

    public static void main(String args[]) {
        JListTest test = new JListTest();
        test.showFrame();
    }

    public void showFrame() {
        JFrame f = new JFrame();
        f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        list = new JList(new File(".").listFiles());
        list.setCellRenderer(new FileSizeRenderer());

        JMenuBar mb = new JMenuBar();
        f.setJMenuBar(mb);
        JMenu menu = new JMenu("表示");

        f.getContentPane().add(list);
        f.pack();
        f.setVisible(true);
    }
}

この変更により、画面に「表示」メニューを含むメニューバーが追加される。

ここで、ポリモーフィズムの別の実装例として、AbstractAction を継承したクラスを作成する。最初は、actionPerformed() の中身は ActionEvent をコンソールに出力するだけにしておく。

import java.awt.event.*;
import javax.swing.*;

class SimpleAction extends AbstractAction {
    SimpleAction() {
        super("Simple");
    }
    public void actionPerformed(ActionEvent e) {
        System.out.println(e);
    }
}

JListTest のメニューに SimpleAction を追加する。

import java.io.File;
import javax.swing.*;

class JListTest {
    private JList list;

    public static void main(String args[]) {
        JListTest test = new JListTest();
        test.showFrame();
    }

    public void showFrame() {
        JFrame f = new JFrame();
        f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        list = new JList(new File(".").listFiles());
        list.setCellRenderer(new FileSizeRenderer());

        JMenuBar mb = new JMenuBar();
        f.setJMenuBar(mb);
        JMenu menu = new JMenu("表示");
        menu.add(new SimpleAction());

        f.getContentPane().add(list);
        f.pack();
        f.setVisible(true);
    }
}

これをコンパイルして実行する。表示メニューに「Simple」が追加されている。

「Simple」メニューをクリックすると、コンソールにActionEventの文字列表現が出力されるので、それもあわせて確認しておく。
SimpleAction が JList にアクセスできるように、コンストラクタの引数で渡すように変更する。これはあまり好ましい設計ではないが、コード量を少なくするために妥協しとく。actionPerformed() 内も、ListCellRenderer を設定しなおすコードを追加する。

import java.awt.event.*;
import javax.swing.*;

class SimpleAction extends AbstractAction {
    private JList list;
    SimpleAction(JList list) {
        super("Simple");
        this.list = list;
    }
    public void actionPerformed(ActionEvent e) {
        list.setCellRenderer(new FileRenderer());
    }
}

JListTest では、SimpleAction のコンストラクタに list 変数を渡すようにする。

        JMenu menu = new JMenu("表示");
        menu.add(new SimpleAction(list));

これをコンパイルして実行すると、画面は上のと同じ。でも、「Simple」メニューをクリックすると、画面がファイル名だけの表示に切り替わることが分かる。
授業では、SizeAction(FileSizeRendererを設定)とDetailAction(FileDetailRendererを設定)を作成し、メニューに追加した。これによって、「ファイル名だけ表示」と「ファイル名とサイズを表示」と「ファイル名とサイズと更新日時を表示」をメニューで切り替えられるようになる。
Windowsエクスプローラみたいなことができたでしょう!」と言うと、やっとそれに気付いたらしく、「おお〜!」と声が上がった。w