NetBeansを使ったSwingアプリケーション開発

Java開発者の開発環境(IDE)と言えばEclipseが定番ですが、最近はNetbeansも十分に使う価値のあります。特にSwing(GUI)の開発環境(Matisse)により、ちょっとしたアプリケーションにGUIを簡単に提供できるようになりました。また、Swing・JVMのパフォーマンス向上だけでなく、PC性能の向上の恩恵も受け、Swingは遅いというのも過去の話です。
ところが、Swingに関する情報はそのほとんどがコンポーネントに関するノウハウであり、アプリケーションを作るノウハウがほとんどありません。そこで、ここ1年くらいで蓄積してきたNetbeansでのGUIアプリの構築のノウハウを何回かに分けて書いていこうと思います。

実行環境について

結論から言えば、現時点ではSwingのアプリケーションを開発する場合、Java5をターゲットにします。
理由はMac OSを動作環境としたいからです。Mac Bookを使い始めてから知ったのですが、Mac OSJava実行環境としてはJava6が正式にリリースされていません。また、32bit版は恐らくリリースされずに64bit版のみが開発版として公開されているだけです。Windowsに比べてJavaのRuntimeが自動的にアップデートされるわけでもない為、一般のMacユーザのほとんどはJava5を利用しているでしょう。
JavaGUIを構築するのですから、Windowsだけをターゲットにするのは勿体ありません。したがって、Macユーザを考慮し、Java5をターゲットにします。

Javaデスクトップアプリケーション

残念なことにJava7での採用は見送られたようですが、Swing Application Framework (JSR 296)で規定されたSwingの拡張フレームワークがあります。このフレームワークを使用するとよりより簡潔にSwingのアプリケーションを構築できるようになります。Netbeansでのプロジェクト作成時に「Javaデスクトップアプリケーション」という項目がありますが、これははSwing Application Frameworkを使用したプロジェクトになります。
ただし、Swing Application Frameworkにも色々と便利な機能がありますが、Swingの仕組みを知らずに使うと逆に混乱してしまう為、シンプルなSwingアプリケーションから学習していきましょう。

環境設定

Netbeansでプロジェクトを作成する前に環境設定を行います。
Java6で導入された新しいレイアウトマネージャにGroupLayoutがありますが、このレイアウトはMatisseで使用されていたレイアウトマネージャがベースとなっています。MatisseではVisual Studioのようにコンポーネントを貼り付けて、マウスでドラッグしながらレイアウトを作成できます。尚、GroupLayoutはIDEでコードを自動生成するのが前提となっており、手書きで書く事は困難なほど複雑です。
Java5をターゲットとしてSwingアプリケーションを作成する場合は、「Swingレイアウト拡張統合」と呼ばれるGroupLayout互換の拡張ライブラリを使用します。この拡張ライブラリはプロジェクト単位で設定することも出来ますが、環境設定からデフォルトで設定してしまいましょう。オプションの「その他」から「GUIビルダー」を選択し、「コンポーネント名の設定」で「Swing レイアウト拡張統合」を選択しましょう(自動にしている場合、開発環境のJDKに依存します)。

プロジェクトの作成

通常のJavaアプリケーションプロジェクトを作成します。メインクラスは邪魔なので生成しなくて構いません。

JFrameを作成する

プロジェクトを作成したならば、メインとなるウィンドウクラスとしてJFrameを作成してみます。
適当なパッケージを作成し、新規作成から「JFrameフォーム」を選択します。

クラス名はAppFrameとしました。

GUIコンポーネントNetbeans上ではデザインとソースの2つのビューで編集可能です。レイアウトやコンポーネントの追加は、デザインを使い、その他の処理を記述する場合は、ソースを使用します。

尚、ソース側では自動生成コードは編集できないように保護されていますし、デザイン側に影響があるようなコードを含めてしまうと、デザイン側で編
集ができなくなってしまうので注意します。

以下がNetbeansで自動生成されたコードです。

public class AppFrame extends javax.swing.JFrame {

    /** Creates new form AppFrame */
    public AppFrame() {
        initComponents();
    }

    /** This method is called from within the constructor to
     * initialize the form.
     * WARNING: Do NOT modify this code. The content of this method is
     * always regenerated by the Form Editor.
     */
    @SuppressWarnings("unchecked")
    // <editor-fold defaultstate="collapsed" desc="Generated Code">
    private void initComponents() {

        setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE);

        org.jdesktop.layout.GroupLayout layout = new org.jdesktop.layout.GroupLayout(getContentPane());
        getContentPane().setLayout(layout);
        layout.setHorizontalGroup(
            layout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING)
            .add(0, 400, Short.MAX_VALUE)
        );
        layout.setVerticalGroup(
            layout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING)
            .add(0, 300, Short.MAX_VALUE)
        );

        pack();
    }// </editor-fold>

    /**
    * @param args the command line arguments
    */
    public static void main(String args[]) {
        java.awt.EventQueue.invokeLater(new Runnable() {
            public void run() {
                new AppFrame().setVisible(true);
            }
        });
    }
    // Variables declaration - do not modify
    // End of variables declaration
}

コンポーネントの作成に関連するコードがinitComponentsメソッドに記述されており、mainメソッドが自動生成されています。

Event Dispatch Thread(EDT)

Netbeansにより自動生成されたmainメソッドでは、java.awt.EventQueue#invokeLaterという見慣れないメソッドが使われています。このメソッドは、javax.swing.SwingUtilities#invokeLaterと等価なメソッドですが、どうして単純に以下のようなコードではないのでしょうか?

public static void main(String args[]) {
  AppFrame app = new AppFrame();
  app.setVisible(true);
}

Swingでは難しいGUIプログラムをシンプルに処理する為にGUIコンポーネントに関する処理は単一のスレッドで行う設計となっています(シングルスレッドモデル)。このSwingで処理を行うスレッドはEvent Dispatch Thread(EDT)と呼ばれ、ボタンクリックなどのイベントの処理からテーブルのレンダリングまで全ての処理を行っています。

Swingのコンポーネントに関する処理は原則としてEDTでやらなければなりません。

具体的には、Swingに関係する処理はすべて、EDT上のイベントとしてキューに積まれ、適切な最適化を行った上で順次処理をする設計です。これは、一見、パフォーマンスに問題があると感じますが、パフォーマンスよりもシンプルさを重視しています。また、最近のJVM(Java6以降)は特にパフォーマンスに関するチューニングが行われています。
Swingを理解せずに組まれたGUIアプリはこの大原則を破っていたり、EDT上で長い時間を要する処理を記述してしまいます。すると処理が重くなったり予期せぬバグに遭遇する事になります。これはSwingの問題ではなく、アプリの実装者の問題です。

Swingでアプリを実装する為にはマルチスレッドプログラミングの知識は不可欠です。

尚、上記のようなmainメソッドでFrame等のインスタンスを生成しているサンプルは大量にありますが、それらは間違いですので注意してください。

invokeLater

これまで説明してきたように、Swingのコンポーネントに関する処理は全てEDT上で行わなければなりません。これはコンポーネントインスタンス生成も同様ですが、mainメソッドが実行されるのはEDTではない為、mainメソッドでAppFrameをnewするとSwingのシングルスレッド設計に反するコードとなるのです。したがって、EDT上でAppFrameをnewするようにコーディングする必要があります。
invokeLaterメソッドは、その名の通り「後でやる」仕組みを提供しています。Netbeansの自動生成コードではjava.awt.EventQueue#invokeLater(Runnable runnable)が使用されていますが、javax.swing.SwingUtilities#invokeLater(Runnable runnable)の方が有名だと思います*1
もう1度、コードをみてみましょう。

public static void main(String args[]) {
  java.awt.EventQueue.invokeLater(new Runnable() {
    public void run() {
      new AppFrame().setVisible(true);
    }
  });
}

invokeLaterの引数はRunnable型なので、無名インナークラスのインスタンスを生成しています。このインスタンスで定義されたrunメソッドが、後でEDTで実行されます。

Runnable

Runnable型はマルチスレッドプログラミングで使用される代表的なインターフェイスで、スレッド(実行する人)と処理(実行する内容)を分離する為に用いられます。Swingでは実行する人はEDTであり、処理だけをEventQueueに追加するメソッドがinvokeLaterです。実際に、何時処理がEDTで実行されるかはプログラマは意識する必要がありません。適切なタイミングで(おそらくはなるべく早く!)実行されるでしょう。

無名インナークラス

Swingのコーディングを行っているとよく無名インナークラスを使いますので、あわせて理解しておきましょう。invokeLaterのコードを通常のインナークラスを用いて記述すると次のようになります(非staticなインナークラスはインスタンスに外部クラスのインスタンスに属しますので注意してください)。

public static void main(String args[]) {
  Runnable runnable = new AppFrameRunner();
  java.awt.EventQueue.invokeLater(runnable);
}
public static class AppFrameRunner implements Runnable {
  public void run() {
    new AppFrame().setVisible(true);
  }
}

Runnableを実装したクラスを定義し、newしていますが、このクラスは1回限りしか使用しません。名前を考えるのも面倒ですし、コードも長くなってしまいます。これを簡潔に書く方法が無名インナークラスです。
無名インナークラスにすると次のようになります。この書き方は文法でしかないので、覚えるしかありません。

public static void main(String args[]) {
  Runnable runnable = new Runnable() {
    public void run() {
      new AppFrame().setVisible(true);
    }
  };
  java.awt.EventQueue.invokeLater(runnable);
}

尚、無名インナークラスの場合、単一のインターフェイス(またはクラス)を実装(継承)する事しかできません。また、コンストラクタに関しても制限があります。
これをさらに省略してローカル参照を省略すると最初のコードになります。

public static void main(String args[]) {
  java.awt.EventQueue.invokeLater(new Runnable() {
    public void run() {
      new AppFrame().setVisible(true);
    }
  });
}

SwingではListenerやHandlerなど、無名インナークラスを使用するケースが多いのでイデオムとして覚えてしまいます。EclipseNetbeansなどのIDEでは、「new Runnable」と入力し、コード補完を行えば無名インナークラスとしてのひな形が生成できますので活用しましょう。

以上、単純にJFrameを表示するというプログラムですが、EDTと無名インナークラスというWebアプリケーションだけを開発していてはあまり馴染みのない概念でした。

*1:SwingUtilities#invokeLaterはEventQueue#invokeLaterのショートカットメソッド