はじめてのプログラムといえば、"Hello, World!"をコンソールに表示するプログラムを書くことが定番となっています。そこで、Javaでもこれを作ってみますが、ただ普通にコンソールに表示させるのではおもしろくありません。そこで、Javaの標準GUIライブラリであるSwingを使ってGUIに表示させるとともに、Java 2, Standard Edition, v 1.4(以下、J2SE,v1.4)で新たに加ったAssertionやLogging機構を使ったHelloWorldプログラムを紹介します。
さらに、表示文字列をロケールに応じて切り替える国際化プログラムとして作成します。今回は、地域化リソース(表示文字列)をJavaクラスとして持たせる方法を使用しています。そのため、プログラム構成が3つのファイルとなっています。
ファイル名 | 概要 |
---|---|
HelloWorld.java | プログラム本体 |
HelloWorldResource.java | デフォルトの地域化リソースを保持するクラス |
HelloWorldResource_ja.java | 日本語の地域化リソースを保持するクラス |
/* * Copyright (C) 2001 by Toru TAKAHASHI, All Rights Reserved. */ package jp.gr.java_conf.torutk.coding.hello; import javax.swing.JOptionPane; import java.util.logging.Logger; import java.util.ResourceBundle; /** * Java 2時代のHello worldプログラム。 * * 対象Javaプラットフォーム:Java 2 Standard Edition, ver.4.0<br> * * 本クラスをコンパイルする際には、下記オプションを指定する必要がある。<br> * -source 1.4<br> * 本クラスをアサーション付で実行する際には、下記のオプションを指定する。<br> * -ea<br> * 本クラスのロギングをデフォルト以外に設定して実行する際には、下記の * オプションを指定する。<br> * -Djava.util.logging.config.file=mylogging.prop<br> * * @author <a href="mailto:torutk@alles.or.jp">Toru TAKAHASHI</a> * @version $Id: HelloWorld.java 16 2005-01-23 07:58:03Z toru $ */ public class HelloWorld { /** * 挨拶メッセージを引数にとるコンストラクタ。 * @param aGreeting ダイアログに表示する挨拶メッセージ文字列 * @throws NullPointerException greetingがnullの場合 */ public HelloWorld(String greeting) { this(greeting, DEFAULT_TITLE); } /** * 挨拶メッセージとダイアログタイトルを引数にとるコンストラクタ。 * @param aGreeting ダイアログに表示する挨拶メッセージ文字列 * @param aTitle ダイアログのタイトルバーに表示する文字列 * @throws NullPointerException aGreetingまたはaTitleがnullの場合 */ public HelloWorld(String greeting, String title) { if (greeting == null || title == null) { throw new NullPointerException("messages cannot be null"); } setGreeting(greeting); setTitle(title); logger.config("HelloWorld(" + greeting + "," + title + ") is created."); } /** * GUIのダイアログに挨拶文を表示する。 * Swingのメッセージダイアログを表示させ、そこにgreetingプロパティに * 指定された文字列を出す。 */ public void printDialog() { logger.entering(CLASS_NAME, "printDialog"); JOptionPane.showMessageDialog(null, getGreeting(), getTitle(), JOptionPane.PLAIN_MESSAGE); logger.exiting(CLASS_NAME, "printDialog"); } /** * 挨拶文を設定するメソッド。 * 事前条件:aGreetingがnullでないこと */ private final void setGreeting(String aGreeting) { logger.entering(CLASS_NAME, "setGreeting"); assert aGreeting != null; greeting = aGreeting; logger.exiting(CLASS_NAME, "setGreeting"); } /** * 挨拶文を取り出すメソッド。 * @return 挨拶を表わすnullではない文字列 */ private final String getGreeting() { logger.entering(CLASS_NAME, "getGreeting"); assert greeting != null; logger.exiting(CLASS_NAME, "getGreeting"); return greeting; } /** * タイトル文字列を設定するメソッド。 * 事前条件:aTitleがnullでないこと */ private final void setTitle(String aTitle) { logger.entering(CLASS_NAME, "setTitle"); assert aTitle != null; title = aTitle; logger.exiting(CLASS_NAME, "setTitle"); } /** * タイトル文字列を取り出すメソッド。 * @return nullではないタイトル文字列 */ private final String getTitle() { logger.entering(CLASS_NAME, "getTitle"); assert title != null; logger.exiting(CLASS_NAME, "getTitle"); return title; } private String greeting; // ダイアログに表示する挨拶メッセージ private String title; // ダイアログのタイトルバーに表示するメッセージ // 省略時のダイアログのタイトル文字列 private static final String DEFAULT_TITLE = "HelloWorld"; // ロギング用のロガー private static Logger logger = Logger.getLogger("jp.gr.java_conf.torutk.coding.hello.HelloWorld"); // ロギングで使用するクラス名 private static final String CLASS_NAME = "jp.gr.java_conf.torutk.coding.hello.HelloWorld"; /** * VM 起動時のエントリポイントとなるメソッド。 * 国際化対応しており、リソースバンドルから表示メッセージを取得して * 設定する。リソースバンドルは、本クラスと同じパッケージ * jp.gr.java_conf.torutk.coding.hello * - HelloWorldResource (デフォルト) * - HelloWorldResource_ja (日本語ロケール) */ public static void main(String[] args) { ResourceBundle resource = ResourceBundle.getBundle( "jp.gr.java_conf.torutk.coding.hello.HelloWorldResource" ); String title = resource.getString(HelloWorldResource.KEY_TITLE); String firstMessage = resource.getString(HelloWorldResource.KEY_MESSAGE_1); String secondMessage = resource.getString(HelloWorldResource.KEY_MESSAGE_2); HelloWorld hello = new HelloWorld(firstMessage, title); hello.printDialog(); hello.setGreeting(secondMessage); hello.printDialog(); hello.setGreeting(null); hello.printDialog(); System.exit(0); } } // HelloWorld
/* * Copyright (C) 2001 by Toru TAKAHASHI, All Rights Reserved. */ package jp.gr.java_conf.torutk.coding.hello; import java.util.ListResourceBundle; /** * HelloWorldResource.java * * * Created: Tue Feb 12 23:03:03 2002 * * @author <a href="mailto:torutk@alles.or.jp">Toru TAKAHASHI</a> * @version $Id: HelloWorldResource.java 16 2005-01-23 07:58:03Z toru $ */ public class HelloWorldResource extends ListResourceBundle { static final String KEY_TITLE = "title"; static final String KEY_MESSAGE_1 = "message_1"; static final String KEY_MESSAGE_2 = "message_2"; static final Object[][] contents = { {KEY_TITLE, "HelloWorld Application" }, {KEY_MESSAGE_1, "Hello, World!" }, {KEY_MESSAGE_2, "Welcome to Java 2 Standard Edition, v.1.4" }, }; protected Object[][] getContents() { return contents; } }// HelloWorldResource
package jp.gr.java_conf.torutk.coding.hello; /** * HelloWorldResource_ja.java * * * Created: Tue Feb 12 23:50:50 2002 * * @author <a href="mailto:torutk@alles.or.jp">Toru TAKAHASHI</a> * @version $Id: HelloWorldResource_ja.java 16 2005-01-23 07:58:03Z toru $ */ public class HelloWorldResource_ja extends HelloWorldResource { static final String KEY_TITLE = "title"; static final String KEY_MESSAGE_1 = "message_1"; static final String KEY_MESSAGE_2 = "message_2"; static final Object[][] contents = { {KEY_TITLE, "こんにちは世界算譜" }, {KEY_MESSAGE_1, "こんにちは、世界!" }, {KEY_MESSAGE_2, "ようこそ Java 2 Standard Edition, v.1.4" }, }; protected Object[][] getContents() { return contents; } }// HelloWorldResource_ja
HelloWorld.javaは以下の構成になっています。
著作権表記やプロジェクト名等を記述します。
必ずパッケージ文を使用して、パッケージを特定するようにします。
単独の型インポート宣言を使って利用しているクラスをimport文で記述します。単独の型インポート宣言を使用する理由はこちらを参照。
クラスの概要・責務について記述します。Javadocドキュメントを抽出するときに使用されます。JavadocでHTMLドキュメントを生成するときは、改行させたり箇条書するタグ(<br>や<ul>等)を使用するとよいでしょう。
クラスの宣言を記述します。
クラス修飾子
public、abstract、final、strictfp
設計に応じて、クラス修飾子を指定します。パッケージ外部から利用されるクラスであれば、publicを付けます。別なクラスのスーパークラスとして存在し、自身のインスタンスを持たないなら、abstractを付けます。このクラスのサブクラスの存在を許さないのであれば、finalを付けます。クラス内では浮動小数点演算をFP-Strictモードで実行させる時は、strictfpを付けます。
メソッドは、可視性の高いものから順に並べて記述します。ただし、mainメソッドだけは最後に書いています。
次に、メソッドでは引数の妥当性をチェックするコードを記述しています。publicなメソッドでは、if文で有効範囲をチェックして無効な場合は実行時例外(IllegalArgumentException)をスローし、publicでないメソッドは、assertを使っています。これは「契約によるプログラミング」という設計手法を持ちいる時のJavaでの(事前条件)実装方法の一例です。
アクセッサメソッドは、可能な限りprivateにし、かつfinal宣言を付けています。final化することによってサブクラスで書き換えられないようになります。
フィールドは可能な限りprivateにします。また、可能な限りfinalにします。そして、同じクラス内からでもアクセッサメソッドを除いては直接アクセスしないようにしています。同じフィールドを使用するメソッドが多いとそれだけクラス内のメソッド同士の結合度が高くなってしまうからです。これは「リファクタリング」において自己カプセル化として紹介されています。
アクセッサを介すると遅いという意見があるかもしれません。しかし昨今のVMはHotSpot標準搭載なので、実行時に最適化されますからほとんどの場合そのような心配はしなくてすみます。むしろソースコードレベルでチューニングをしたつもりになって分かりにくいコードを書いてしまうと、実行時最適化を阻害することになりかねません。
ソースファイルをコンパイルする時に、ソースファイルの文字コードはプラットフォームのデフォルトエンコーディングであると仮定されます。例えば、Windows上ではMS932コードと仮定されます。
プラットフォームのデフォルト文字コード以外で記述されたソースファイルをコンパイルする場合、-encodingオプションでソースファイルの文字コードを指定します。
work> javac -encoding EUCJIS \ jp/gr/java_conf/torutk/coding/hello/HelloWorld.java
指定できる文字コードについては、Java Encoding & Aliases TableのURLを参照するとよいでしょう。
J2SE,v1.4時代のプログラミングでは、GUI、アサーション、ロギングが標準となってきます。そこで、Hello Worldプログラムもこれらを取り入れることとします。また、表示文字列は、国際化対応となるようリソースバンドルを用いています。
SwingのJOptionPaneクラスを利用し、簡単にダイアログを表示しています。他にGUIは使用しないので、第一引数(親コンポーネントの指定)はnullにしています。JOptionPaneは、たった一行で各種ダイアログを表示させることができるので、ちょっとしたプログラムを作る際に重宝します。
publicでないメソッドは、アサーションを使って引数の妥当性をチェックします。アサーションを使用すると、実行時にアサーション機構のオン/オフを制御することができます。
なお、publicメソッドについては、通常の妥当性チェックコードを記述し、事前条件が満たされない場合は実行時例外(IllegalArgumentException、IndexOutOfBoundsException、NullPointerExceptionなど)をスローします。
このように引数の妥当性をチェックして実行時例外を発生させるのは、プログラムのバグを早期発見早期治療するためには必須のテクニックです。
assert文をコンパイルするには、Java 2 SDK,Standard Edition, v1.4以降の開発キットが必要です。以下のように、-source 1.4オプションを指定する必要があります。
work> javac -source 1.4 \ jp/gr/java_conf/torutk/coding/hello/HelloWorld.java work>
アサーションは実行時に有効/無効を指定することができます。アサーション制御は、クラスやパッケージを個別に指定することが可能です。
まずは、全てのassertを有効にして実行します。
work> java -ea jp.gr.java_conf.torutk.coding.hello.HelloWorld : work>
これは、jp.gr.java_conf.torutk.パッケージ以下に属するクラスのassertを有効にして実行します。
work> java -ea:jp.gr.java_conf.torutk.coding... \ jp.gr.java_conf.torutk.coding.hello.HelloWorld : work>
クラスを指定して実行しています。
work> java -ea:jp.gr.java_conf.torutk.coding.hello.HelloWorld \ jp.gr.java_conf.torutk.coding.hello.HelloWorld : work>
複数のクラスやパッケージを指定する時は、-eaオプションを複数使用します。
work> java -ea:jp.gr.java_conf.torutk.coding.hello... \ -ea:jp.gr.java_conf.torutk.coding.util... \ jp.gr.java_conf.torutk.coding.hello.HelloWorld : work>
ロギングは、プログラムの実行の過程を外部に出力させる時に使用します。ロギングを組み込んでおくことによって、整然とデバッグが行なえるようになり、開発効率がアップします。ロギングもプロとしてソフトウェアを作成する際には必須のテクニックと言えるでしょう。
ロギングを行うには、各クラス単位でLoggerインスタンスを1つ取得しておきます。このインスタンスに対してログ出力メソッドを呼び出して必要なメッセージを外部に出力します。Loggerインスタンスは階層構造を取っており、階層に対してロギングの制御を行います。そこで、通常はクラス名のFQCNと同じ階層を使用します。
// ロギング用のロガー private static Logger logger = Logger.getLogger("jp.gr.java_conf.torutk.coding.hello.HelloWorld");
デフォルトの設定ではINFOレベル以上のログしか出力されないので、以下のように独自のログ設定ファイルを記述し、実行時にこれを指定します。
handlers= java.util.logging.ConsoleHandler .level= ALL java.util.logging.ConsoleHandler.level = ALL java.util.logging.ConsoleHandler.formatter = \ java.util.logging.SimpleFormatter
work> java \ -Djava.util.loggin.config.file=conf/mylogging.properties \ jp.gr.java_conf.torutk.coding.hello.HelloWorld 2002/01/03 19:36:08 \ jp.gr.java_conf.torutk.coding.hello.HelloWorld setGreeting 詳細レベル (中): ENTRY 2002/01/03 19:36:08 \ jp.gr.java_conf.torutk.coding.hello.HelloWorld setGreeting 詳細レベル (中): RETURN : work>
国際化は、表示文字列などのロケール依存部分(地域化リソース)をプログラム本体から分離し、リソースとして別にまとめて扱います。Javaには、地域化リソースを扱うAPIとしてリソースバンドル(java.util.ResourceBundle)が用意されています。
リソースバンドルには、次の2つの実装方法があります。
クラスとして定義
配列を用いてキーと値の組を定義する方法
プロパティファイルとして定義
プロパティファイルにキーと値の組を定義する
今回のHelloWorldプログラムでは、1.の方法で実装しています。まず、リソースバンドルAPIを使ってリソースを検索しているプログラム本体を見てみましょう。
ResourceBundle resource = ResourceBundle.getBundle( "jp.gr.java_conf.torutk.coding.hello.HelloWorldResource" );
リソースとして、デフォルトの地域化リソースを保持するクラス名をFQCNで指定しています。getBundleメソッドにはロケールを明示的に指定していないので、プログラムを実行している環境のロケールが適用されます。
次に、取得したリソースバンドルから、表示文字列を取り出すコードを見てみます。
String title = resource.getString(HelloWorldResource.KEY_TITLE); String firstMessage = resource.getString(HelloWorldResource.KEY_MESSAGE_1); String secondMessage = resource.getString(HelloWorldResource.KEY_MESSAGE_2);
Javaプログラムを実行する時に、ロケールを切り替えたい場合は、システムプロパティuser.languageを使って任意のロケールを指定することができます。
work> java -Duser.language=en \ jp.gr.java_conf.torutk.coding.hello.HelloWorld
2.の方法でリソースバンドルを使用します。といっても、1.の方法とリソースバンドルのAPIは共通です。違いは、リソースバンドルクラスの代りにプロパティファイルを作成することです。
プロパティファイルの名前は、
リソース種類 | ファイル名 |
---|---|
規定(英語) | HellowWorldResource.properties |
日本語 | HelloWorldResource_ja.properties |
リソースバンドルをプロパティファイルで記述する際、ファイルのエンコーディングはISO8859-1だけが有効です。それ以外のエンコーディングの場合(例えばShift JIS)は、Unicodeエスケープする必要があります。
Java2, SDKには、各エンコーディングで記述されたファイルをUnicodeエスケープへ変換するツールnative2asciiが提供されています。
例えば、Shift JISで記述する時にはHelloI18NHelloResource_ja.properties.sjisというファイルに記述し、
work> native2ascii HelloI18NHelloResource_ja.properties.sjis > \ HelloI18NHelloResource_ja.properties
と実行すればよいでしょう。