Java道|コマンドライン引数の基本

実行時に指令を渡せると、Javaプログラムはもっと柔軟に動ける。
コマンドライン引数を理解すると、ファイル名や設定値を外から指定できる、使いやすいプログラムを作れるようになります。

これまでのファイル入力では、読み込むファイル名をプログラムの中に直接書いていました。

たとえば、次のような形です。

new FileReader("training2.txt")

この書き方は、学習の最初にはとても分かりやすいです。

プログラムを読めば、どのファイルを読み込むのかがすぐに分かります。

しかし、実際に使いやすいプログラムを作ることを考えると、少し不便な面があります。

なぜなら、別のファイルを読み込みたいときに、毎回Javaのコードを書き換えなければならないからです。

たとえば、今日は training3.txt を読みたい。
明日は mission.txt を読みたい。
別の日には report.txt を読みたい。

このたびにソースコードを書き換えるのは大変です。

そこで役立つのが コマンドライン引数 です。

コマンドライン引数を使うと、プログラムを実行するときに、外から値を渡せます。

java Sample10 training3.txt

この場合、training3.txt という文字列が、プログラムの中へ渡されます。

鬼滅の刃風にたとえると、鬼殺隊本部の任務装置を起動するときに、あらかじめ装置の中身を書き換えるのではなく、起動時に指令札を渡すようなものです。

「今日は training3.txt の巻物を読め」
「今日は mission.txt の任務記録を読め」
「今日は report.txt の報告書を確認せよ」

このように、同じプログラムでも、実行時に渡す指令によって読み込む対象を変えられます。

コマンドライン引数を使えるようになると、プログラムは「決められたものだけを読む処理」から、「外からの指示に応じて動ける処理」へ進みます。この記事では、コマンドライン引数とは何か、main メソッドの String[] args が何を受け取るのか、args.length や args[0] の意味、while と readLine でファイルを最後まで読む流れを、鬼滅の刃風の世界観で整理していきます。

コマンドライン引数とは何か

コマンドライン引数とは、プログラムを実行するときに外から渡す値のことです。

Javaのプログラムは、main メソッドから始まります。

public static void main(String[] args)
{
    ...
}

この中にある args が、コマンドライン引数を受け取るための入れ物です。

args の型は String[] です。

つまり、文字列の配列です。

部分意味
String文字列
[]配列
args実行時に渡された文字列を受け取る変数名

実行時にスペース区切りで入力した値は、順番に args 配列へ入ります。

たとえば、次のように実行したとします。

java Sample10 training3.txt

この場合、training3.txt が args[0] に入ります。

実行時に渡した値入る場所
training3.txtargs[0]

鬼滅の刃風に言うと、args は任務開始時に本部から渡される指令札の束です。

指令札が1枚なら args[0] に入ります。
指令札が2枚なら args[0] と args[1] に入ります。
複数の指令札を渡せば、順番に配列へ並びます。

なぜコマンドライン引数が便利なのか

ファイル名をコードの中に固定すると、プログラムはそのファイルだけを読む形になります。

new FileReader("training3.txt")

この場合、training3.txt を読むことはできます。

しかし、別のファイルを読みたいときには、コードを書き換える必要があります。

一方、コマンドライン引数を使えば、ファイル名を実行時に渡せます。

java Sample10 training3.txt

また別の日には、次のように実行できます。

java Sample10 mission.txt

さらに別のファイルも指定できます。

java Sample10 report.txt

プログラムのコードは同じままです。

変えるのは、実行時に渡す引数だけです。

方法特徴
コードにファイル名を固定する別のファイルを読むたびにコード修正が必要
コマンドライン引数で渡す実行時に読みたいファイル名を指定できる

鬼滅の刃風にたとえると、毎回任務装置の内部を書き換えるのではなく、起動時に「今日はこの巻物を読め」と指令札を渡すようなものです。

装置そのものは同じでも、渡す指令札を変えれば、読み込む記録を切り替えられます。

この柔軟さが、コマンドライン引数の大きな利点です。

args は配列として扱う

コマンドライン引数は、args という配列に入ります。

配列なので、添字を使って取り出します。

書き方意味
args[0]1番目に渡した文字列
args[1]2番目に渡した文字列
args[2]3番目に渡した文字列

ここで大切なのは、配列の添字は 0 から始まることです。

1番目に渡した値は args[1] ではなく、args[0] です。

たとえば、次のように実行したとします。

java Sample10 training3.txt

この場合は、次のようになります。

配列の要素中身
args[0]training3.txt

次のように複数の値を渡した場合はどうでしょうか。

java Sample10 training3.txt start check

この場合、args には次のように入ります。

配列の要素中身
args[0]training3.txt
args[1]start
args[2]check

鬼滅の刃風に言えば、指令札の束があり、先頭の札が 0番目 に入るイメージです。

Javaの配列では、最初の場所が 0番目 です。

args.length で引数の個数を調べる

コマンドライン引数を使うときは、いきなり args[0] を使うのではなく、引数が正しく渡されているか確認することが大切です。

そのために使うのが args.length です。

args.length は、渡された引数の個数を表します。

たとえば、次のように実行した場合、

java Sample10 training3.txt

引数は1個なので、args.length は 1 です。

実行例args.length
java Sample100
java Sample10 training3.txt1
java Sample10 training3.txt extra2

今回の Sample10.java では、読み込むファイル名を1つだけ受け取る想定です。

そのため、引数の個数は 1個 でなければなりません。

そこで、次のように確認します。

if(args.length != 1){
    System.out.println("読むファイル名を正しく指定してください。");
    System.exit(1);
}

この処理は、引数の個数が1個でない場合に、使い方が正しくないと判断します。

条件意味
args.length != 1引数の個数が1個ではない
System.out.println注意メッセージを表示する
System.exit(1)プログラムを終了する

この確認をしておくと、引数を入れ忘れたときに、分かりやすいメッセージを出せます。

もし確認せずに args[0] を使うと、引数がない場合に配列の範囲外アクセスが起きる可能性があります。

鬼滅の刃風にたとえると、任務開始前に「指令札が1枚あるか」を確認している状態です。

指令札がないのに任務を始めると、どの巻物を読めばよいのか分かりません。
逆に余計な指令札が多すぎても、今回の任務では扱いきれません。

そのため、最初に個数を確認することが大切です。

図:コマンドライン引数が args に入る流れ

↓クリックすると拡大表示されます。

この図が示していること

この図では、実行時に入力した training3.txt という文字列が、main メソッドの args 配列へ渡される流れを表しています。

java Sample10 training3.txt と実行すると、Sample10 は実行するクラス名として使われます。

その後ろにある training3.txt が、コマンドライン引数としてプログラムへ渡されます。

図の要素意味
java Sample10 training3.txt実行コマンド
Sample10実行するクラス名
training3.txtプログラムへ渡す引数
String[] args引数を受け取る文字列配列
args[0]1番目に渡された文字列

この図から分かることは、コマンドライン引数はプログラムの外から渡され、main メソッドの args 配列に入るということです。

ファイルの内容を1行ずつ読み込んで表示する

ここでは、実行時に渡したファイル名を使って、そのファイルの内容を1行ずつ読み込んで表示するプログラムを見ていきます。

同じフォルダに、次のようなファイルを用意しておきます。

ファイル名:training3.txt

水月は朝から型の修行をしていた
蒼真は静かに呼吸を整えていた

この training3.txt を、プログラム実行時にファイル名として渡します。

ファイル名:Sample10.java

import java.io.*;

class Sample10
{
    public static void main(String[] args)
    {
        if(args.length != 1){
            System.out.println("読むファイル名を正しく指定してください。");
            System.exit(1);
        }

        try{
            BufferedReader br =
                new BufferedReader(new FileReader(args[0]));

            String str;
            while((str = br.readLine()) != null){
                System.out.println(str);
            }

            br.close();
        }
        catch(IOException e){
            System.out.println("ファイルの入出力で問題が発生しました。");
        }
    }
}

プログラムの流れ

Sample10.java の流れを整理すると、次のようになります。

手順内容
1引数の個数が1個かどうか確認する
2args[0] をファイル名として使う
3FileReader と BufferedReader で読み込み準備をする
4readLine で1行ずつ読み込む
5読み込んだ行を画面へ表示する
6ファイルを閉じる
7問題が起きたら IOException を catch する

このプログラムでは、ファイル名をコードの中に直接書いていません。

読み込むファイル名は、実行時に受け取った args[0] を使っています。

つまり、コマンドライン引数は「読み込むファイル名を外から指定する入口」として働いています。

実行方法

このプログラムは、次のように実行します。

java Sample10 training3.txt

ここで、それぞれの意味は次の通りです。

部分意味
javaJavaプログラムを実行する命令
Sample10実行するクラス名
training3.txtコマンドライン引数として渡すファイル名

このとき、training3.txt という文字列が args[0] に入ります。

args[0]

つまり、プログラムの中では args[0] を使うことで、実行時に指定されたファイル名を参照できます。

実行結果

training3.txt の内容が次のようになっているとします。

水月は朝から型の修行をしていた
蒼真は静かに呼吸を整えていた

次のように実行します。

java Sample10 training3.txt

すると、実行結果は次のようになります。

水月は朝から型の修行をしていた
蒼真は静かに呼吸を整えていた

プログラムの中に training3.txt というファイル名を直接固定していなくても、実行時に渡したファイルを読み込めています。

鬼滅の刃風に言えば、任務装置を起動するときに training3.txt という指令札を渡し、その札に書かれた巻物を本部記録室から開いて読み上げている状態です。

args[0] がファイル名になるしくみ

Sample10.java の中心は、次の部分です。

new FileReader(args[0])

args[0] は、実行時に渡された最初の文字列です。

今回の実行コマンドは次の形でした。

java Sample10 training3.txt

そのため、args[0] には training3.txt が入ります。

つまり、次のコードは、

new FileReader(args[0])

実行時には、次のような意味になります。

new FileReader("training3.txt")

ただし、重要なのは、コードの中にファイル名を固定していないことです。

もし別のファイルを読みたいなら、実行コマンドを変えればよいです。

java Sample10 mission.txt

この場合は、args[0] に mission.txt が入ります。

実行コマンドargs[0] の中身
java Sample10 training3.txttraining3.txt
java Sample10 mission.txtmission.txt
java Sample10 report.txtreport.txt

このように、同じ Sample10.java でも、実行時に渡す値によって読み込むファイルを変えられます。

引数が間違っていたときの処理

Sample10.java では、最初に引数の個数を確認しています。

if(args.length != 1){
    System.out.println("読むファイル名を正しく指定してください。");
    System.exit(1);
}

これは、とても大切な処理です。

今回のプログラムでは、ファイル名を1つだけ受け取る想定です。

そのため、引数が0個でも、2個以上でも、正しい使い方ではありません。

実行例args.length判定
java Sample100不正
java Sample10 training3.txt1正しい
java Sample10 training3.txt extra2不正

引数が正しくない場合、メッセージを表示して処理を終了します。

System.out.println("読むファイル名を正しく指定してください。");
System.exit(1);

この確認がないまま args[0] を使うと、引数が渡されていない場合に args[0] が存在せず、配列の範囲外アクセスにつながる可能性があります。

鬼滅の刃風に言えば、任務開始前に「指令札がちょうど1枚あるか」を確認しています。

指令札がなければ、どの巻物を読むのか分かりません。
指令札が多すぎても、この任務では扱い方があいまいになります。

そのため、任務開始前の確認として args.length を使います。

System.exit(1) の意味

System.exit(1) は、プログラムを終了するための命令です。

今回のコードでは、引数の個数が正しくないときに使っています。

System.exit(1);

学習段階では、次のように理解すると分かりやすいです。

書き方意味
System.exit(1)条件が正しくないため、ここでプログラムを終了する

1 は、正常終了ではなく、何らかの問題があって終了したことを表す値として使われます。

つまり、引数が正しくないので、これ以上進まずに終了するという意味です。

鬼滅の刃風に言えば、指令札が正しくない状態で任務に出ると危険なので、本部の入口で任務を止めている状態です。

while 文で最後まで読み込む

Sample10.java でファイルの中身を最後まで読む中心部分が、次のコードです。

String str;
while((str = br.readLine()) != null){
    System.out.println(str);
}

この書き方は少し複雑に見えます。

しかし、分解すると分かりやすいです。

部分意味
str = br.readLine()ファイルから1行読み込んで str に入れる
!= null読み込んだ結果が null ではないか確認する
while条件が成り立つ間、繰り返す
System.out.println(str)読み込んだ1行を表示する

readLine は、1行読み込めた場合、その行の文字列を返します。

しかし、ファイルの最後まで読み終わると、これ以上読める行がないため null を返します。

つまり、この while 文は次のように動きます。

状況readLine の結果while の動き
まだ行がある文字列を返す表示して繰り返す
最後まで読んだnull を返す繰り返しを終了する

鬼滅の刃風にたとえると、記録係が巻物を1行ずつ読み上げていき、最後まで読んだら「もう次の行はありません」という合図を受け取るようなものです。

その合図が null です。

図:while と readLine で最後まで読む流れ

↓クリックすると拡大表示されます。

この図が示していること

この図では、readLine でファイルを1行ずつ読み込み、while 文で最後まで繰り返す流れを表しています。

readLine は、行がある間は文字列を返します。

その文字列が str に入ります。

str が null でなければ、その行を表示します。

そして、また次の行を読み込みます。

最後まで読み終わると、readLine は null を返します。

その時点で while の条件が成り立たなくなり、繰り返しが終了します。

処理意味
br.readLine()1行読む
str = br.readLine()読んだ行を str に入れる
!= null読めた行があるか確認する
System.out.println(str)読み込んだ行を表示する
nullこれ以上読める行がない合図

この図から分かることは、while と readLine を組み合わせることで、行数が決まっていないファイルでも最後まで読み込めるということです。

null とは何か

null は、何も参照していない状態や、値がない状態を表す特別な値です。

今回の readLine では、ファイルの最後まで読み終わったときに null が返されます。

つまり、null は「もう読める行がありません」という合図です。

readLine の結果意味
文字列1行読み込めた
nullファイルの終わりに到達した

たとえば、training3.txt に2行だけ書かれている場合、readLine の動きは次のようになります。

呼び出し回数戻り値
1回目水月は朝から型の修行をしていた
2回目蒼真は静かに呼吸を整えていた
3回目null

3回目で null が返るため、while 文は終了します。

鬼滅の刃風に言えば、巻物を読み進めて、最後の行まで読み終わったあと、記録係が「次の記録はありません」と知らせてくれる状態です。

その合図を見て、読み込み処理を終えます。

コマンドライン引数は複数渡すこともできる

今回の Sample10.java では、ファイル名を1つだけ受け取っています。

しかし、コマンドライン引数は複数渡すこともできます。

たとえば、次のように実行できます。

java Sample10 training3.txt start check

この場合、args 配列には次のように入ります。

要素内容
args[0]training3.txt
args[1]start
args[2]check

今回の Sample10.java は、ファイル名1つだけを受け取る設計なので、args.length が 1 でない場合は終了します。

しかし、別のプログラムでは、複数の引数を使って、ファイル名、検索文字列、モード、設定値などを渡すこともできます。

渡す情報
ファイル名training3.txt
検索文字列水月
表示モードall
設定値10

鬼滅の刃風に言えば、任務開始時に複数の指令札を渡せるということです。

1枚目は読む巻物名。
2枚目は探す隊士名。
3枚目は表示方法。
このように、プログラムの動きを外から細かく指定できるようになります。

FileReader と BufferedReader の役割

Sample10.java では、次のようにファイルを読み込む準備をしています。

BufferedReader br =
    new BufferedReader(new FileReader(args[0]));

この中では、FileReader と BufferedReader を組み合わせています。

クラス役割
FileReader指定したファイルから文字を読み込む
BufferedReader効率よく読み込み、readLine を使えるようにする

今回のポイントは、FileReader に渡している値が args[0] であることです。

new FileReader(args[0])

これにより、実行時に渡されたファイル名を読み込み対象にできます。

実行時の引数FileReader が読むファイル
training3.txttraining3.txt
mission.txtmission.txt
report.txtreport.txt

鬼滅の刃風に言えば、FileReader は本部記録室の巻物を開く係です。

args[0] が「どの巻物を開くか」を示す指令札です。

IOException に備える理由

Sample10.java では、ファイル入力を try-catch で囲んでいます。

try{
    BufferedReader br =
        new BufferedReader(new FileReader(args[0]));

    String str;
    while((str = br.readLine()) != null){
        System.out.println(str);
    }

    br.close();
}
catch(IOException e){
    System.out.println("ファイルの入出力で問題が発生しました。");
}

ファイルは外部の資源です。

そのため、次のような問題が起きる可能性があります。

起こりうる問題内容
ファイルが存在しない指定したファイル名が見つからない
ファイル名が間違っているargs[0] の指定が正しくない
ファイルを読み込めない権限や状態に問題がある
読み込み中に問題が起きる外部資源とのやりとりで失敗する

そのため、IOException に備えています。

鬼滅の刃風に言えば、指定された巻物を記録室で探しても見つからないかもしれません。
棚が封印されているかもしれません。
巻物が破損していて読めないかもしれません。

そのような場合に備えて、支援隊士が警告を受け取るのが catch(IOException e) です。

close でファイルを閉じる

ファイルを読み終わったら、close で閉じます。

br.close();

close は、使い終わったファイルを閉じるための処理です。

ファイルを開いたままにしておくと、不要な資源を使い続けることになります。

メソッド役割
close読み込みが終わったファイルを閉じる

鬼滅の刃風にたとえると、巻物を読み終えたあと、きちんと閉じて記録棚へ戻す作業です。

任務記録を読み終えたのに、巻物を開きっぱなしで机の上に置いておくのはよくありません。

Javaでも、読み込みが終わったら close で後片付けをします。

図:コマンドライン引数で読み込むファイルを変える

↓クリックすると拡大表示されます。

この図が示していること

この図では、同じ Sample10.java を使いながら、実行時に渡す引数を変えることで、読み込むファイルを切り替えられることを表しています。

java Sample10 training3.txt と実行すれば、args[0] には training3.txt が入ります。

java Sample10 mission.txt と実行すれば、args[0] には mission.txt が入ります。

プログラムの中では、どちらの場合も次のコードを使っています。

new FileReader(args[0])
実行コマンドargs[0]読み込むファイル
java Sample10 training3.txttraining3.txttraining3.txt
java Sample10 mission.txtmission.txtmission.txt

この図から分かることは、コマンドライン引数を使うと、ソースコードを変更せずに、実行時の指定だけで処理対象を変えられるということです。

コマンドライン引数を使うと何がうれしいのか

コマンドライン引数を使う一番の利点は、プログラムを作り直さなくても使い方を変えられることです。

ファイル名をコードに固定している場合、別のファイルを読みたくなったらコードの修正が必要になります。

しかし、コマンドライン引数を使えば、実行時にファイル名を指定できます。

固定値だけのプログラムコマンドライン引数を使うプログラム
ファイル名が決め打ち実行時に好きなファイル名を指定できる
柔軟性が低い使い回ししやすい
変更のたびにソース修正が必要実行コマンドを変えるだけで対応できる

これは、学習用のプログラムから、実際に使える道具へ近づくための大切な考え方です。

鬼滅の刃風に言えば、任務装置が毎回同じ巻物しか読めない状態から、起動時の指令札によって読む巻物を変えられる状態になるようなものです。

装置そのものを作り直さなくても、渡す指令を変えるだけで動きを変えられます。

コマンドライン引数で大切なポイント

コマンドライン引数を学ぶときは、次の点を押さえておくと理解しやすくなります。

ポイント内容
コマンドライン引数実行時にプログラムへ渡す文字列
String[] args渡された文字列を受け取る配列
args[0]最初に渡した文字列
args[1]2番目に渡した文字列
args.length渡された文字列の個数
引数チェック使い方の誤りを早めに見つけるために重要
new FileReader(args[0])実行時に渡したファイル名を読み込む
while と readLineファイルの最後まで1行ずつ読む基本形
nullこれ以上読める行がないことを表す合図
System.exit(1)条件が正しくない場合に処理を終了する

コマンドライン引数を理解すると、Javaプログラムは「決められた通りにしか動かないもの」から、「外からの指示に応じて動きを変えられるもの」へ変わります。

ファイル名をコードに固定せず、実行時に渡す。

この考え方は、ファイル入力だけでなく、検索文字列、設定値、処理モードなどにも応用できます。

鬼滅の刃風に言えば、任務開始時に渡される指令札によって、同じ装置が別の巻物を読み、別の任務に対応できるようになるということです。