Swingでウインドウを表示する

TestFrame

小さなJavaアプリケーションを作るときのやり方を一つ。
人によってかなり差があるかもしれませんが。
一応、実行時の画像も載せます。
Endボタンで終了するだけで機能は全然ないです。

とりあえず先にコードを。

ソースコード

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class TestFrame extends JFrame {
private JPanel panel;
private JLabel label;
private JButton button;
public TestFrame() {
super("TestFrame");
initComponents();
initLayout();
setSize(150,70);
setResizable(false);
setLocationRelativeTo(null);
setDefaultCloseOperation(EXIT_ON_CLOSE);
setVisible(true);
}
public void initComponents() {
panel = new JPanel();
label = new JLabel("Hello World");
button = new JButton("End");
button.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent arg0) {
actionButton();
}
});
}
public void initLayout() {
panel.setLayout(new BorderLayout());
panel.add(label, BorderLayout.NORTH);
panel.add(button, BorderLayout.CENTER);
getContentPane().add(panel);
}
protected void actionButton() {
System.exit(0);
}
public static void main(String[] args) {
new TestFrame();
}
}

私がいつもやるのはコンストラクタで、フレームやパネルのあるべき姿を作ることです。この例ではJFrameを継承してその持ち物に部品をフィールドとして持っています。このコードではフィールドにしなくてもよさそうですが、別のウインドウや処理から参照するアクセサメソッドが必要になると、このようにしている方が便利な場合が多いです。

コンストラク

    public TestFrame() {
super("TestFrame");
initComponents();
initLayout();
setSize(150,70);
setResizable(false);
setLocationRelativeTo(null);
setDefaultCloseOperation(EXIT_ON_CLOSE);
setVisible(true);
}

この部分がコンストラクタですが、親コンストラクタを呼び出した後に、コンポーネントの生成、レイアウトの調整を行って、最後にフレーム自体のレイアウトや初期設定を行っています。

コンポーネント生成

    public void initComponents() {
panel = new JPanel();
label = new JLabel("Hello World");
button = new JButton("End");
button.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent arg0) {
actionButton();
}
});
}

コンポーネント生成のメソッドですが、未だにpublicかprotectedかで悩みます。ヘルパメソッドをprivateにすることはあまりありませんが、フレームの初期化を他から実行するかどうかは一般的にはどちらが良いのでしょうか。
内容は生成と部品の初期化で必要な作業のレイアウト以外を行います。この場合はボタン押下のアクションを定義しています。

レイアウト調整

    public void initLayout() {
panel.setLayout(new BorderLayout());
panel.add(label, BorderLayout.NORTH);
panel.add(button, BorderLayout.CENTER);
getContentPane().add(panel);
}

レイアウト作業です。この中身は時と場合によって大きく変化します。

アクションメソッド

    protected void actionButton() {
System.exit(0);
}

ここではイベントが一つしかないので直接処理を記述していますが、複数のアクションイベントがある場合はコントローラメソッドを作って分岐することもあります。

このようなイメージでフレームに外部で定義したJPanelの継承クラスなどを配置して作ってます。只、規模が大きくなるとレイアウトの調整などがまとまっていないとツライかなとも思いますが。

Swingは一つ一つがキレイなオブジェクトなので(勿論Javaのコアライブラリは遍くそう設計されていますが)同じ思想に乗っかるだけで簡単にオブジェクティブなコードを書くことができます。
今回はActionListenerを無名クラスにしているので少し手抜き感がありますが、これも必要であればクラスとして作っていくと、一つのクラスには一つの概念のみを定義すべきとのオブジェクト指向の考え方に則って作ることができて、スッキリしたコードで実現することができます。

Javaアプリケーションの案件はなぜ少ない

最近のもっぱらの仕事はWebシステムの開発がほとんどですが、なぜJavaアプリケーションの開発が少ないのでしょうか。私の視点からすれば、Java言語を使うよりも別の言語の方が向いているWebシステムはたくさんあると思います。

人の入れ替わるプロジェクトなら誰でも同じようなコードになるJavaが適しているとも言えるかもしれませんが、他のスクリプト言語などに比べどうしても手間です。

例えば、Strutsフレームワーク上で動くシステムを作るとして、まず下準備が大変です。足りないタグリブを補ったり、設定ファイルもいろいろ書かなければなりません。画面遷移ごとにXMLマッピングを追加したりしますし、単純にデータベースの値を一覧表示するだけでも、時間がかかります。

その点、PerlPHPRubyなどはWebのような開発には向いています。フレームワークも多くありますし、テキストと直結している面があるのでJSPの役割自体が不要と言えます。正規表現もほぼ直接書けますし。いずれにしても、Ruby on Railsフレームワークと同等の速度で開発することは非常に難しいと言えるでしょう。

逆にJava言語での開発メリットを挙げるなら、安全なコードが記述しやすく他のプログラマーに強制もしやすいので、頑固なWebアプリケーションが必要な場合は有利かもしれません。

私はいかにもJava言語が向いているのはGUIアプリケーション開発だと思います。「JavaGUIは遅い」というのはある程度は本当ですが、それだけを理由に選択肢から外すには勿体無いと思います。

完全なオブジェクト指向開発が行えるので開発工数は少なくできますし、メンテナンスもやり易いです。国際化にも柔軟ですし、プラットフォーム間をまたげる利点もあります。

SourceForge.netJavaのプロジェクトがC++のそれを抜いたという記事がありましたが、まだまだ日本のフリーソフトや開発案件を見る限り少ないと思いますので、より一般的になるべきかと思います。

日本語Java

Javaにマルチバイトが使えるのは知っていましたが、実験的に変数名くらいしか書いたことはありませんでしたので遊びで書いてみました。JavaではなくUMLを書いているような錯覚に捕らわれます。

public class 人間 {
private int[] 座標 = new int[2];
private int 体力;
public 人間(int 体力) {
this.体力 = 体力;
}
public void 歩く(int 歩数) {
for (int 回数 = 0; (回数 < 歩数) && (体力 > 0); 回数++) {
移動(1,0);
体力--;
}
}
public void 現在地表示() {
System.out.println("現在地:" + 座標[0] + "," + 座標[1]);
}
protected void 移動(int X軸, int Y軸) {
座標[0] += X軸;
座標[1] += Y軸;
}
public static void main(String[] 引数) {
人間 桃太郎 = new 人間(100);
桃太郎.現在地表示();
桃太郎.歩く(50);
桃太郎.現在地表示();
}
}

ORACLEでテーブル名やカラム名を全て日本語で命名している現場は経験したことがありますが、Javaはどうでしょうか。どこにもコメントがないのにコメントがあるように見えてしまう不思議なコードです。

オブジェクト指向なストアドプロシージャ

今までにPL*SQLしか使ったことがないので、ひょっとしたら世の中には既に存在するのかもしれませんが、ストアドプロシージャもオブジェクト指向で実装できれば、コードを書く量がグンと減りそうです。

例えば、従業員テーブルのオブジェクトを作成して一人分のデータを登録する場合に以下のようにできれば楽ではないでしょうか。

$employee = Table.getInstance("employee");
$employee.employee_id = 10;
$employee.first_name = 'cloned';
INSERT INTO $employee;

上の構文を書くためには、$employeeはテーブル名やカラムフィールドなどが正しく定義されている必要があるので、Tableインタフェースを実装した変数を取得するようなイメージです。

ストアドプロシージャであればデータベースに定義されているテーブルの状態を調べることは簡単だと思いますので、上の例で続けると、$employee変数の元となるクラスを自分で定義する必要はないでしょう。Table.getInstance()ファクトリで指定されたテーブル名のテーブル定義のオブジェクトを自動的に作ることができると思います。

SELECTで複数のテーブルから取得する場合のオブジェクトは自分で定義するしかなさそうですが、PL*SQLの%TYPEによるフィールド定義ができれば元テーブルに変更があっても修繕の必要がありません。

class emp_dep implements Table {
$employee_name    employee.employee_name%TYPE;
$department_name  department.name%TYPE;
}

このような感じでしょうか。最近流行のJavaScriptのようなプロトタイプベースにして既存テーブルオブジェクトに入れ込んでいくイメージでも良いかもしれません。

通常の言語では上記のような内容をORマッピングで実現していると思いますが、データバッチなどストアドプロシージャで行った方がストレートで良い場合もあると思います。

誰か作らないでしょうか。

Javaの学習本

Java言語を取り扱った書籍だけでも何冊あるかわからないほど本があります。今まで役に立った書籍を紹介します。

Javaチュートリアル第3版 (The Java series)

Javaチュートリアル第3版 (The Java series)

入門書としては少し難しいかもしれませんが、Java言語を覚えるのに丁度よいレベルだと思います。

プログラミング言語Java (The Java Series)

プログラミング言語Java (The Java Series)

  • 作者: ケンアーノルド,デビッドホームズ,ジェームズゴスリン,Ken Arnold,David Holmes,James Gosling,柴田芳樹
  • 出版社/メーカー: ピアソンエデュケーション
  • 発売日: 2001/06
  • メディア: 単行本
  • クリック: 26回
  • この商品を含むブログ (17件) を見る

教科書的な要素でお勧めです。JDK1.3について書かれているので少々古いのですが、言語仕様ついて曖昧な部分をきっちり学ぶことができます。

Java魂―プログラミングを極める匠の技

Java魂―プログラミングを極める匠の技

タイトルがすごいですが、それほどマニアックな内容ではありません。気付きにくい機能や落とし穴、実践向けのテクニックが紹介されています。Javaの理解を深めるには丁度よいと思います。

Effective Java プログラミング言語ガイド

Effective Java プログラミング言語ガイド

これはまだ読んでいないのですが、是非読みたいので挙げておきます。

String VS StringBuffer

前回のStringの呼び出しコストに続き、今回は文字列結合に最適な方法について決着をつけようと思います。

検証に使ったコードは以下のとおりです。

public class ConcatString {
private static int testCount;
private static StringBuffer buffer;
public static void test(int count) {
testCount = count;
buffer = new StringBuffer(testCount * 10);
        TEST_END:
for (int i = 0; ; i++) {
buffer.delete(0,buffer.length());
long start = System.currentTimeMillis();
switch (i) {
case 0:
System.out.println("concatLiteral()");
concatLiteral();
break;
case 1:
System.out.println("concatMethod()");
concatMethod();
break;
case 2:
System.out.println("appendMethod()");
appendMethod();
break;
case 3:
System.out.println("appendDirectConcatLiteral()");
appendDirectConcatLiteral();
break;
case 4:
System.out.println("appendPreConcat1Litera()");
appendPreConcat1Litera();
break;
case 5:
System.out.println("appendAppendMethod()");
appendAppendMethod();
break;
case 6:
System.out.println("concatString()");
concatString();
break;
            default:
break TEST_END;
}
long end = System.currentTimeMillis();
double result = (double)(end - start) / 1000;
System.out.println(result + "秒");
}
}
protected static void concatLiteral() {
for (int i = 0; i < testCount; i++) {
String s = "ABC" + "ABC";
}
}
protected static void concatMethod() {
for (int i = 0; i < testCount; i++) {
String s = "ABC".concat("ABC");
}
}
protected static void appendMethod() {
for (int i = 0; i < testCount; i++) {
buffer.append("ABC");
}
}
protected static void appendDirectConcatLiteral() {
for (int i = 0; i < testCount; i++) {
buffer.append("ABC" + "ABC");
}
}
protected static void appendPreConcat1Litera() {
for (int i = 0; i < testCount; i++) {
String s = "ABC" + "ABC";
buffer.append(s);
}
}
protected static void appendAppendMethod() {
for (int i = 0; i < testCount; i++) {
buffer.append("ABC");
buffer.append("ABC");
}
}
protected static void concatString() {
for (int i = 0; i < testCount; i++) {
String s = "ABC";
s += "ABC";
}
}
public static void main(String[] args) {
ConcatString.test(5000000);
}
}

この検証コードはVMにある程度メモリを渡さないとOutOfMemoryErrorになると思いますので実行する際には注意して下さい。

一応、比較しやすいように実行時の結果も載せておきます。

concatLiteral()
0.015秒
concatMethod()
1.235秒
appendMethod()
0.515秒
appendDirectConcatLiteral()
0.516秒
appendPreConcat1Litera()
0.516秒
appendAppendMethod()
1.015秒
concatString()
2.719秒

concatLiteral()
String変数に値を設定する際にリテラルを+演算子によって結合したものです。これはコンパイラ

String s = "ABCABC";

に書き換えるので、文字列結合自体のオーバーヘッドがなく高速です。

concatMethod()
+演算子ではなくconcatメソッドで結合しました。これはコンパイラによって自動で一つの文字列になることはありませんのでかなり低速です。Stringが変更不可なオブジェクトであることを考えると再生性するコストがかかっていることが予想できます。

appendMethod()
StringBufferによる文字列結合です。バッファ値を再設定するこのとないよう初期値で設定しているので純粋なappendメソッドの検証となりますが、concatメソッドの半分程度のコストしかかかりませんでした。

appendDirectConcatLiteral()
appendメソッド内にString結合があるとどうなるかというものです。この場合はString結合にStringBufferが使用され一つ余分なStringBufferが生成されてしまうと言われますが、ベンチでの結果は全く同じでしたので、速度面では気にする必要はなさそうです。但し、余分なメモリを消費するかについては検証できていませんので、このようなコーディングは避けた方がよいのかもしれません。

appendPreConcat1Litera()
appendメソッド内ではなく前もって結合した文字列を用意してあげるとどうかです。これも速度面での差はなく、単純にappendした際と同じ結果でした。

appendAppendMethod()
StringBufferにさらに追加した場合です。これは単純に倍のコストが掛かったのでバッファの再設定がなければappendメソッドを呼び出すオーバーヘッドのみとなるようです。

concatString()
最後にStringを+=演算子を用いて結合する場合ですが、これは非常に低速でした。concatメソッドのさらに倍程度のコストが掛かっています。+=演算子でなければ実現しないような場合以外は避けた方がよさそうです。

結果としては、文字列が可変でない場合は潔くStringを+演算子で繋いで一つの文字列にしてしまうのが一番良さそうです。文字列が可変の場合はStringBufferを利用する方が高速ですね。検証コードではバッファ値を初期設定で指定していますが、指定しなかった場合でもStringのconcatメソッドで結合するよりは高速でしたので、結果として生成される文字列のサイズがわからなくてもStringBufferを利用する価値はあります。StringBuffer自体の生成コストやtoString()時のコストは無視できるレベルだと思います。

ということで一般的な論が大幅正解というところでしょうか。ただ、見落としがちなのは単純結合の場合はStringBufferを利用するより確実に低コストな点です。比較してみます。

String query = ""
+ "SELECT "
+     "name "
+ "FROM "
+     "human";
StringBuffer query = new StringBuffer();
query.append("SELECT "  );
query.append(    "name ");
query.append("FROM "    );
query.append(    "human");

コスト面では前者が優位です。但しコーディング規約で後者で統一するのも一つだと思いますので、中身の挙動を知って選択することが大切だと思います。

文字を連ねてGoogle検索すると?

調査はしていませんので理由はわかりませんが、文字を連ねてGoogle検索を行うと矢鱈とPDFファイルに引っかかります。連ねてというのは、「jjaavvaa」や「eecclliippssee」という感じで検索するという意味です。

その他の一般的な英単語(例えば「hheelllloo」や「kkiinngg」)でも同じ現象です。PDFに詳しい人からすると至極当然の理屈があったりするのかもしれませんが、もし知っている方がいらっしゃったらコメントかトラックバックで教えてもらえると、今日はすっきり眠れそうです。

ついでにキャンペーンに参加。アップルのiMac G5欲しい!