ポリモーフィズム
専門学校での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