多くのプログラムでは、パラメータとして設定値を外から与えることが必要となります。パラメータをプログラム中に記述する、いわゆるハードコーディングをしてしまうと、パラメータを変更するためにはソースコードを修正して再ビルドすることになってしまうからです。したがって、プログラムを実行するときにパラメータを外部から取り込んでその設定値に基づき処理を行わせる仕組みを設けます。
パラメータの外部指定について、いくつかのニーズが考えられます。
パラメータの外部指定では、こうしたニーズのどれを実現するかで使用する方法が変わってきます。
パラメータの外部指定の方法には、以下のものが考えられます。
1.のコマンドライン引数方式は、頻繁に設定を変える場合や、簡単なプログラムで設定ファイルの読み込み機能を作るまでもない場合に使用します。
2.の設定ファイル方式は、普段は変更せずにたまに設定を変える場合、設定項目が数多くある場合などに使用します。
3.のOS固有の方法は、Javaではあまり用いません。クロスプラットフォームなプログラムに反する仕組みだからです。ただし、JavaではPreferences APIという形でOS固有のパラメータ設定機構をラッピングする機能が提供されています。Preferences APIを使って実はWindows上ではレジストリを使っているということはあります。
4.のデータベース方法は、ネットワーク上で設定を共有したい場合に有用です。たとえば複数で共通の設定を利用したい場合、自分の設定を異なる計算機で共通使用するといった場合などです。
プログラム起動時にコマンドライン引数によってパラメータを外部から指定する方法です。Javaでは、プログラムにパラメータを与える方法として、プロパティファイルやリソースバンドル、さらにPreference APIといった方法がありますが、各種方法の中で、実行時にユーザが簡単にパラメータを指定・変更するにはコマンドライン引数が一番簡単です。
Javaでは標準クラスライブラリにはコマンドライン引数を便利に処理してくれるようなユーティリティは含まれていません。しかし、コマンドライン引数を取得するのは、それほど難しい手続きではないので、毎回似たような、しかしプログラム毎に固有の処理を書くことになります。
プログラム起動時に指定したコマンドライン引数は、mainメソッドの引数で全て渡されます。
public static void main(final String[] args) {
受け取った引数の個数とその内容を全てprintしてみましょう。
$ java MyApplication 1000 Sample
public class MyApplication { public static void main(final String[] args) { System.out.println("指定された引数は、" + args.length + "個です"); for (int i=0; i<args.length; ++i) { System.out.println(args[i]); } } }
コマンドライン引数はすべて文字列で渡されるため、プログラム中で整数や小数などの値として受け取るには変換が必要となります。Javaでは、文字列から数値に変換するための仕組みが用意されています。
数値型 | 使用クラス | メソッド | 例 |
---|---|---|---|
boolean | Boolean | parseBoolean | isDebug = Boolean.parseBoolean("true"); |
byte | Byte | parseByte | code = Byte.parseByte("123"); |
decode | code = Byte.decode("0x7b") | ||
char | String | charAt | ch = "Z".charAt(0) |
short | Short | parseShort | value = Short.parseShort("12345") |
decode | value = Short.decode("030071") | ||
int | Integer | parseInt | value = Integer.parseInt("1234567890") |
decode | value = Integer.decode("#499602d2") | ||
long | Long | parseLong | value = Long.parseLong("1234567890123456789") |
decode | value = Long.decode("0X112210F47DE98115") | ||
float | Float | parseFloat | value = Float.parseFloat("123.456") |
double | Double | parseDouble | value = Double.parseDouble("123456.789") |
Integer.parseInt()などの変換処理では、引数で指定した文字列が数値以外の表現であるときに、実行時例外(NumberFormatException)を投げます。また、指定した数値が範囲外であるときも同じ実行時例外を投げます。
このNumberFormatExceptionを捕捉してエラー処理を記述しないと、実行時エラーのデフォルトの振舞いであるスレッドを終了させる処理が実行されます。
コマンドライン引数の指定に誤りがあれば、誤りのままプログラム実行を継続するよりも、終了させて誤りをユーザに示すべきでしょう。この場合、デフォルトの振舞いのままでも十分と考えます。
ただし、スレッドのスタックトレース情報を直接見せたくないような場合は、エラーの原因を分析し、ユーザに分かりやすい形式でエラーを報告する処理を入れるとよいでしょう。その場合は、実行時エラーをtry-catch文で捕捉し、エラー原因の分析とエラー報告出力を行います。
try { number = Integer.parseInt(args[0]); } catch (NumberFormatExceptin e) { System.err.println("引数指定の誤り:第1引数は整数値を指定します。"); System.exit(-1); }
コマンドライン引数の与え方にも幾つかの方法が考えられます。
引数の順序を固定してしまえば、コマンドライン引数の処理は簡単になります。ここでは、プログラムの引数の指定方法として、1つ目に整数、2つ目に文字列を取る以下のような例があるとします。
$ java MyApplication 1000 Sample
上記の例のコマンドライン処理コードを見ていきます。
public class MyApplication { private static int number; private static String name; public static void main(final String[] args) { parseOptions(args); : } private static void parseOptions(final String[] args) { if (args.length<2) { System.err.println("引数指定が不足です。"); System.exit(1); } number = Integer.parseInt(args[0]); name = args[1]; } }
このコマンドライン決め打ち方式の問題点は以下のニーズに対応することが困難なことです。
順序が決め打ちなので、引数を省略すると順序が狂ってしまいます。また、順序を入れ換えることができません。また、間違って入れ換えてしまった場合にも検出が困難です。
コマンドライン引数の指定順序を任意にしたい場合や、コマンドライン引数の指定の省略時にデフォルト値を適用したい場合があります。このようなニーズに対しては、引数の種類を示す識別子を追加する方法があります。識別子の指定には幾種類かの方法があります。ここでは、以下の2つの方法についてコード例を含めて見てみます。
$ java MyApplication -count 1000 -name Sample
$ java MyApplication -count:1000 -name:Sample
コマンドライン処理コードを見ていきます。
public class MyApplication { private static int number = 100; private static String name = "Default"; public static void main(final String[] args) { for (int i=0; i<args.length; ++i) { if ("-count".equals(args[i])) { number = Integer.parseInt(args[++i]); } else if ("-name".equals(args[i])) { name = args[++i]; } else { System.err.println("引数指定の誤り:未知の引数が指定されました"); } } } }
public class MyApplication { private static int number = 100; private static String name = "Default"; public static void main(final String[] args) { for (String arg : args) { String[] param = arg.split(":"); if ("-count".equals(param[0])) { number = Integer.parseInt(param[1]); } else if ("-name".equals(param[0])) { name = param[1]; } else { System.err.println("引数指定の誤り:未知の引数が指定されました"); } } } }
コマンドライン引数を処理するライブラリがあります。これを利用すると、定型的な処理が楽になります。
C言語で利用できるコマンドライン引数の処理ライブラリGNU getoptをJavaに移植したものです。UNIX系のgetoptとほぼ同等のコマンドライン引数指定を処理できます。getoptスタイルでは、-aのように通常1文字の識別子でオプションを指定します。デフォルトでは識別子は値を伴わないので、-abcは、-a -b -cと指定したのと同じ意味となります。識別子に値を付随させるときは、値を伴う指定を行う必要があります。値を伴うときは、-a1024あるいは-a 1024のように連続して引数に指定します。
getoptスタイルでは、--nameのように--を付けることで識別子に1文字ではなく複数文字を使用することができます。
java-getoptの入手先は以下です。
入手したjava-getopt-1.0.13.jarを任意の場所に置きます。実行時にこのjarファイルをclasspathに設定します。
$ java MyApplication -c 100 -d -n Sample
import gnu.getopt.Getopt; public class MyApplication { static int count; static boolean isDebug; static String name; public static void main(final String[] args) { Getopt options = new Getopt("MyApplication", args, "c:dn:"); int c; while ( (c = options.getopt()) != -1) { switch (c) { case 'c': count = Integer.parseInt(options.getOptarg()); break; case 'd': isDebug = true; break; case 'n': name = options.getOptarg(); break; default: System.err.println("引数指定の誤り:未知の引数が指定"); } } } }
Getoptのコンストラクタで、使用する引数(識別子)の組み合わせを"c:dn:"のように指定しています。これは、-cが値を付随するオプション、-dは単独のオプション、-nが値を付随するオプションであることを設定しているものです。
指定していないオプションを実行時に指定すると、エラーが表示されます。
$ java MyApplication -c 100 -z MyApplication: -- z オプションは正しくありません。
ApacheのJakarta Commonsライブラリの中の一つ、Command Line Interface(CLI)です。UNIX系のgetoptと同等の指定やJava的な指定によるコマンドライン引数の処理を行うことができます。getopt系では、POSIX/GNUの選択も可能です。
commons CLIの入手先は以下です。
入手したcli-1.1.tar.gzを任意の場所に展開します。実行時には、展開したディレクトリ内にあるcommons-cli-1.1.jarをclasspathに含めます。
$ java MyApplication -c 100 -d -n Sample
import org.apache.commons.cli.Options; import org.apache.commons.cli.CommandLineParser; import org.apache.commons.cli.CommandLine; import org.apache.commons.cli.BasicParser; import org.apache.commons.cli.ParseException; public class MyApplication { static int count; static boolean isDebug; static String name; public static final void main(final String[] args) { Options options = new Options(); options.addOption("c", true, "count number"); options.addOption("d", false, "debug option"); optoins.addOption("n", true, "name"); CommandLineParser parser = new BasicParser(); CommandLine commandLine; try { commandLine = parser.parse(options, args); } catch (ParseException e) { System.err.println("引数解析エラー"); return; } if (commandLine.hasOption("d")) { isDebug = true; } if (commandLine.hasOption("c")) { count = Integer.parseInt(commandLine.getOptionValue("c")); } if (commandLine.hasOption("n")) { name = commandLine.getOptionValue("n"); } } }
JArgpは、コマンドライン引数を解析し、リフレクションAPIを使用してアプリケーション側で作成したクラスのフィールドに引数の値を設定する機能を持つライブラリです。このライブラリを使う最大のメリットは、コマンドライン引数の文字列を型変換して変数に代入する処理を記述しなくてもよい点にあります。
なお、このライブラリは、IBM developerWorksの記事「Javaプログラミングのダイナミックス第3回:実用的なリフレクション〜コマンド・ライン引数のフレームワークを構築する」で解説されています。
以下のURLから入手可能です。
入手したファイルjargp1_0.zipを解凍し、展開した中に含まれるlib/jargp.jarをclasspathに設定します。
以下の書式でコマンドライン引数を処理するコードについて見てみます。
$ java MyApplication -c100 -d -n James
この例では、コマンドライン引数で3つの値を指定します。
を行っています。
import org.jargp.ParameterDef; import org.jargp.IntDef; import org.jargp.BoolDef; import org.jargp.StringDef; public class MyApplication { private static int count; private static boolean isDebug; private static String name; private static ParameterDef[] parameterDefs = { new IntDef('c', "count", "number of count"), new BoolDef('d', "isDebug", "debug activation flag", true), new StringDef('n', "name", "user name") }; public final static void main(final String[] args) { MyApplication application = new MyApplication(); ArgumentProcessor.processArgs(args, parameterDefs, application); printFields(); } public static void printFields() { System.out.printf("count=%d, isDebug=%b, name=%s%n", count, isDebug, name); } }
まず、コマンドライン引数で指定される値を格納するフィールドを宣言します。JArgpでは同一クラスのフィールドに格納することを想定しているので、ここではMyApplicationクラスに3つの値を格納するフィールドを宣言します。
次に、コマンドライン引数の書式を定義するParameterDef配列を宣言しています。コマンドライン引数で指定する設定値に合わせて、IntDef(整数値)、BoolDef(真偽値)、StringDef(文字列値)の3つを配列の要素に宣言しています。
IntDefのコンストラクタは、第一引数で識別子('c')を指定し、第二引数で値を格納するフィールド名("count")を指定し、第三引数では引数の説明("number of count")を記述しています。
BoolDefのコンストラクタは、第一引数で識別子('d')を指定し、第二引数で値を格納するフィールド名("isDebug")を指定し、第三引数では引数の説明("debug activation flag")を記述し、第四引数には引数指定時に設定される値('true')を指定しています。
StringDefのコンストラクタは、第一引数で識別子('n')を指定し、第二引数で値を格納するフィールド名("name")を指定し、第三引数では引数の説明("user name")を記述しています。
JArgpは、リフレクションを用いて引数の設定値をフィールドに格納するため、アプリケーションクラスのインスタンスを生成しておく必要があります。
org.jargp.ArgumentProcessorクラスを使用して引数の解析を行います。この際、ArgumentProcessorクラスをインスタンス化する方法と、ArgumentProcessorクラスのstaticメソッドを使用する方法があります。今回は簡単なstaticメソッドの方を使用しています。
ArgumentProcessorのprocessArgsメソッドを呼び出します。このメソッドの引数には、コマンドライン引数が格納されたargs、引数の書式を定義したParameterDef配列、設定値を格納するフィールドを持つアプリケーションクラスのインスタンスを渡します。
ArgumentProcessorのlistParametersメソッドを利用すると、コマンドライン引数の書式をprintすることができます。ただし、この場合ArgumentProcessorをインスタンス化しておく必要があります。
public final static void main(final String[] args) { MyApplication application = new MyApplication(); ArgumentProcessor processor = new ArgumentProcessor(parameterDefs); processor.processArgs(args, application); processor.listParameters(79, System.out); }
ArgumentProcessorをインスタンス化するときは、引数書式を定義したParameterDef配列をコンストラクタの引数に渡します。コマンドライン引数の解析は、processArgメソッドを使用しますが、staticでメソッドではなく引数の数が違うインスタンスメソッドの方を呼びます。
第一引数は、mainメソッドの文字列配列、第二引数はコマンドライン引数の設定値を格納するフィールドを持つクラスのインスタンスを渡します。
コマンドライン引数の書式をprintするときは、ArgumentProcessorのlistParametersメソッドを呼び出します。第一引数はprintする際の一行の最大桁数を指定し、第二引数はprint先のPrintStreamを指定します。
$ java MyApplication -cNN number of count -d debug activation -n user name
args4jは、Java 2 SE 5.0から導入されたアノテーションを使って簡単にコマンドライン引数の値を対応するフィールドに格納するライブラリです。このライブラリの最大のメリットは、他のライブラリに比べてコマンドライン引数処理のコード記述量が極めて少なく済む点です。
このライブラリは、著者のKohsuke Kawaguchi氏のBlogで使い方が紹介されています。
以下のURLから入手可能です。
mavenリポジトリとして用意されています。maven以外で使用するなら、Downloadのリンクから、args4j→バージョン番号(2012-07-14時点で2.0.21が最新)をたどり、クラスファイル、ソースファイル、Javadocの各JARファイルをダウンロードします。
ファイル名 | 内容 |
---|---|
args4j-2.0.21.jar | クラスファイル |
args4j-2.0.21-javadoc.jar | Javadocファイル |
args4j-2.0.21-sources.jar | ソースファイル |
入手したクラスファイルのJARファイルをclasspathに設定します。
以下の書式でコマンドライン引数を処理するコードについて見てみます。
$ java MyApplication -c 100 -d -n James
この例では、コマンドライン引数で3つの値を指定します。
を行っています。
import java.util.ArrayList; import java.util.List; import org.kohsuke.args4j.Argument; import org.kohsuke.args4j.CmdLineException; import org.kohsuke.args4j.CmdLineParser; import org.kohsuke.args4j.ExampleMode; import org.kohsuke.args4j.Option; public class Args4jSample { @Option(name="-c", metaVar="NUM", usage="count number") static int count; @Option(name="-d", usage="debug options") static boolean isDebug; @Option(name="-n", usage="user name", metaVar="NAME") static String name; @Argument private static List<String> arguments = new ArrayList<String>(); public final static void main(final String[] args) { Args4jSample app = new Args4jSample(); CmdLineParser parser = new CmdLineParser(app); try { parser.parseArgument(args); } catch (CmdLineException e) { System.err.println(e); System.err.printf("USAGE:%n\tjava Args4jSample %s%n", parser.printExample(ExampleMode.ALL) ); parser.printUsage(System.err); } } }
注)ジェネリックスでないコレクション(List)に@Argumentアノテーションを指定すると、例外が発生します。
まず、コマンドライン引数で指定される値を格納するフィールドを宣言します。JArgpでは同一クラスのフィールドに格納することを想定しているので、ここではMyApplicationクラスに3つの値を格納するフィールドを宣言します。
そして、アノテーション@Optionを使って各フィールドがどのようなコマンドライン引数によって設定されるのかを記述します。@Optionアノテーションは以下のように定義されています。
@Retention(RUNTIME) @Target({FIELD,METHOD}) public @interface Option { String name(); String usage() default ""; String metaVar() default ""; boolean required() default false; }
フィールドcountには"-c"で指定する引数の設定値を格納します。フィールドisDebugには"-d"が指定されたときにtrueが格納されます。フィールドnameには"-n"で指定する引数の設定値を格納します。
args4jは、アノテーションを用いて引数の設定値をフィールドに格納します。その際リフレクションを使用するため、アプリケーションクラスのインスタンスを生成しておく必要があります。
org.kohsuke.args4j.CmdLineParserクラスを使用して引数の解析を行います。CmdLineParserをインスタンス化します。その際コンストラクタにアプリケーション・クラスのインスタンスを設定します。
CmdLineParserのparseArgumentメソッドを呼び出します。このメソッドの引数には、コマンドライン引数が格納されたargsを渡します。このメソッドは検査例外を発行するので、try-catchで囲み、CmdLineExceptionを捕捉します。引数の指定に間違いがあった場合等はこの例外が発行されます。
CmdLineParserのprintUsageメソッドを利用すると、コマンドライン引数の書式をprintすることができます。
$ java MyApplication USAGE: java Args4jSample -c N -d -n VAL -c NUM : count number -d : debug options -n NAME : user name
te-code command line argument parsingは、複雑なコマンドライン引数を処理するためのライブラリです。以下の機能を持ちます。
以下のURLから入手可能です。
入手したファイルte-common-3.0.0-pre3.zipを解凍し、展開された中に含まれるlib/te-common.jarをclasspathに設定します。
以下の2つの書式でコマンドライン引数を処理するコードについて見てみます。
$ java MyApplication -c 100 -d -n James
$ java MyApplication --count 100 --debug --name James
この例では、コマンドライン引数で3つの値を指定します。
を行っています。
import com.townleyenterprises.command.CommandOption; import com.townleyenterprises.command.CommandParser; import com.townleyenterprises.command.DefaultCommandListener; public class TecommonSample { private CommandParser parser; private CommandOption commandCount = new CommandOption( "count", 'c', true, "NUMBER", "number of count" ); private CommandOption commandIsDebug = new CommandOption( "debug", 'd', false, null, "activate debug mode" ); private CommandOption commandName = new CommandOption( "name", 'n', true, "NAME", "user name" ); private CommandOption[] mainOptions = { commandCount, commandIsDebug, commandName }; public final static void main(final String[] args) { TecommonSample app = new TecommonSample(args); } public TecommonSample(final String[] args) { parser = new CommandParser("TecommonSample"); parser.addCommandListener( new DefaultCommandListener("options", mainOptions) ); parser.parse(args); try { parser.executeCommands(); } catch (Exception e) { System.out.println(e); System.exit(-1); } printOptions(); } public void printOptions() { System.out.println("commandCount's arg = " + commandCount.getArg()); System.out.println("commandIsDebug's arg = " + commandIsDebug.getArg()); System.out.println("commandName's arg = " + commandName.getArg()); } }
te-code commandでは、コマンドライン引数はそれぞれCommandOptionクラスとして表現されます。オブジェクト指向設計を追及してライブラリを作成したとのことで、それぞれコマンドライン引数毎に情報がオブジェクトとして凝集する設計となっています。
ここでは3つの値を格納するフィールドとして、CommandOptionクラスを使用しています。それぞれコンストラクタにおいて、識別子文字列、識別子文字(1文字)、直後に設定値が続くか否か、USAGEの表示で使用する仮引数名、引数の説明文を指定しています。
フィールドcommandCountは、"[-c|--count NUMBER]"で指定する引数を表します。フィールドcommandIsDebugは、"[-d|--debug]"で指定する引数を表します。フィールドcommandNameは、"[-n|--name NAME]"で指定する引数を表します。
com.townleyenterprise.command.CommandParserクラスを使用して奇数の解析を行います。CommandParserをインスタンス化します。その際コンストラクタにアプリケーションの名前(ヘルプ表示で使用される)を設定します。
次に、使用するコマンドライン引数の一覧を配列としてまとめ、グループ名とともにDefaultCommandListenerクラスのコンストラクタに渡します。
このDefaultCommandListenerをCommandParserインスタンスに設定します。
そして、CommandParserのparseメソッドを呼び出します。このメソッドの引数には、コマンドライン引数が格納されたargsを渡します。
最後に、CommandParserのexecuteCommandsメソッドを呼び出します。
CommandParserのusageメソッドおよびhelpメソッドを利用すると、コマンドライン引数の書式をprintすることができます。
Usage: TecommonSample [-c|--count NUMBER] [-d|--debug] [-n|--name \ NAME] [-?|--help] [--usage]
Usage: TecommonSample [OPTION...] options: -c, --count=NUMBER number of count -d, --debug activate debug mode -n, --name=NAME user name Help options: -?, --help show this help message --usage show brief usage message
IEEE標準1003.1-2001でコマンドライン引数の書式について定めているようです。それによると・・・