ここまでは、プログラムの流れは、ブロックに分割されているもののその中では上から下へと順々に流れるだけでした。この章では、一定の範囲を繰り返したり、条件に応じて異なる処理をする方法を説明しましょう。こうした方法が利用できるので、プログラムでは大量のデータを処理したり、データの状況に応じて処理をするといったことが可能になります。また、複数のデータをまとめて扱う配列についても簡単に説明しておきます。
まず、この章で入力するプログラムとアプリケーションのひな形を作っておきましょう。手順については、これまでの章とまったく同じですので、ここでは手順は示しません。ウィザードの画面のうち、ポイントになる部分だけを示しておきます。
複数の変数を使いたい場合、値を1つ記録できる変数を、必要な数だけ用意すればよいのですが、たとえば毎月の売上のように、1、2、3…と番号を付けて管理したいデータもあります。そのようなときに便利なのが「配列」です。配列は、配列名で識別されます。配列名は、変数名と同様に付ければかまいません。そして、何番目かということは「インデックス」あるいは「添字」とも呼ばれるのですが、配列名に続いて[ ]で囲って記されます。
それでは、Repeating.javaのpublic static void mainのブロックの最後の部分に以下のプログラムを追加して実行してみてください。
int ar[]; ar = new int[10]; ar[0] = 120; ar[1] = 250; System.out.println(ar[0]+ar[1]); String words[] = {"りんご", "みかん", "バナナ"}; System.out.println("配列wordsの要素数は" + words.length); System.out.println(words[0]+"/"+words[1]+"/"+words[2]);
1行目に「int ar[];」があり、これで配列が定義されますが、実はこれだけでは配列は使えません。たとえばint型配列というのは、intのデータ記録スペースが何個かまとまったものとイメージしてもらえばよいのですが、その何個分用意するのかということを、その次の「new int[10]」で指定します。これは、int型の記録スペースを10個新しく用意したと理解すればよいでしょう。それを配列名に代入することで、用意した記録スペースをプログラムで使えるようにします。この配列領域の確保を明示的にしなければならないのは、他の言語に慣れている方にとっては違和感があるかもしれませんね。
配列のインデックスは0から始まります。10個の配列を作ると、ar[0]〜ar[9]までの10個の配列を使えることになります。配列の1つ1つの中身を「要素」と言います。arは配列ですが、ar[0]は要素で、型はこの場合intになります。要素は式の1つの項として使ってもかまいません。
なお、上記のプログラムで配列arにint10個分の領域を確保している部分は、「int ar[] = new int[10];」のようにひとまとめにして記載してもかまいません。
プログラムの後半は、配列wordsを定義しています。String型の配列なので、要素1つ1つに文字列を記録することができる配列です。ここで、=の後に、{ } でくくって、3つの文字列がカンマで区切られて並んでいます。こうすることで、配列を定義するとともに、String3つ分の記録スペースを確保し、書き並べた値を各要素に代入します。はじめからデータがわかっている場合はこの方法の方がすっきりまとまるかもしれません。
配列の要素の数は、配列変数に、.lengthというキーワードをつなげれば、int型で得られます。つまり、words.lengthが配列wordsの要素数になるというわけです。length()ではありません。
ところで、プログラミングの本などでは初心者レベルで配列の話がけっこう掲載されているのですが、実はJavaでは配列を完全に理解するというのはかなり高度な知識が必要になります。特に、2次元以上の配列というものは、オブジェクト指向を理解していないとわからないと思います。ここでは、1次元のものだけの理解でかまいません。また、こうした集合的なデータを扱う方法は配列以外に実に多彩な機能をJavaでは利用できますし、実用的なプログラミングではむしろそうした配列以外の方法を使うことの方が多いでしょう。
コンピュータで大量のデータ処理を行うというのはよく聞く話ですが、その種のプログラムは、多くの場合、同じ処理をたくさんのデータに対して行っているでしょう。そのような場合、処理を必要な回数書き並べるのではなく、言語に用意された繰り返しの機能を使って必要な回数繰り返します。
繰り返しには大きく分けて2種類あります。1つは一定の条件が満たされる間繰り返すもの、もう1つは指定した回数の繰り返しを行うものです。
まず、一定の条件が満たされる間繰り返すようにするには、次のように記述します。2種類の記述方法があります。
while(条件式) { ステートメント; ステートメント; ・・・ }
do { ステートメント; ステートメント; ・・・ }while(条件式);
まず、プログラムで1つのまとまったステップのことを「ステートメント」と呼びます。whileも、do whileも、条件式の記述があります。この条件式の結果がtrueの間は、ステートメントを繰り返すのがこれらの繰り返し処理です。
whileは、条件式のチェックを行い、trueならステートメントを実行して、再度条件式のチェックを行うという順序です。一方、do whileは、ステートメントを先に実行してから、条件式をチェックして、trueならステートメントを実行するという風に、ステートメントと条件式のチェックの順序が違います。なお、繰り返すステートメントが1つだけの場合は、{} で囲まなくてもかまいません。
なお、whileあるいはdo以降の、繰り返し処理を行う一連のブロックの各ステートメントは、Tabキーを使ってインデントをかけて記述します。つまり、繰り返し処理の部分が一段下げてあり、繰り返しであることが目で見てパッとわかるようにします。こうしたインデントはかけなくてもプログラムは実行できるのですが、プログラムを見やすくするためにも必ずかけておくように心掛けましょう。
条件式では、たとえば、「ある変数の値が10より小さい」といったような判断を行います。前に説明したwhile、do whileだけでなく、後から説明するifやforでもこの記述は使います。その場合に利用するのが、以下の表に示した論理演算子です。
優先度 | 演算子 | 意味 | 処理内容 | 例 | 例の結果 |
---|---|---|---|---|---|
1 | ! | 論理否定 | trueならfalse、falseならtrue | !(a < b) | false |
5 | < | 小なり | 左辺がより小さければtrue、そうでなければfalse | a < b | true |
c < b | false | ||||
<= | 小なりイコール | 左辺がより小さいか等しければtrue、そうでなければfalse | a <= b | true | |
c <= b | false | ||||
> | 大なり | 左辺がより大きければtrue、そうでなければfalse | a > c | false | |
b > c | false | ||||
>= | 大なりイコール | 左辺がより大きいか等しければtrue、そうでなければfalse | b >= a | true | |
b >= c | false | ||||
6 | == | イコール | 左辺と右辺が等しければtrue、そうでなければfalse | a == b | false |
!= | ノットイコール | 左辺と右辺が違えばtrue、そうでなければfalse | a != b | true | |
10 | && | 条件のAND | 両辺に論理式を記述し、両方ともtrueの場合だけ結果はtrue | (a < b)&&(c < b) | false |
11 | || | 条件のOR | 両辺に論理式を記述し、片方がtrueであれば結果はtrue | (a < b)||(c < b) | true |
「例の結果」は、a=10、b=15、c=99とする。
論理演算の結果も、やはり値なのですが、boolean型の値なので、trueかfalseのいずれかにしかなりません。論理演算子を用いて記述した式を、条件式として設定することができます。たとえば、変数aが10以上かどうかを判断するには「a >= 10」という式を書きます。この式は足し算やかけ算などと同じように、結果を持ちますが、その結果はtrueないしはfalseのどちらかです。aの値が50だったらtrueですし、aの値が5ならfalseになるという具合です。なお、条件式にはboolean型の値を指定すればよいので、必ずしも式である必要はないのですが、一般にはほとんどの場合、条件式を記述することになります。具体的には、少し先で紹介するプログラムで説明しましょう。
一方、もう1つの繰り返し処理は、決まった数の繰り返しを行うというものです。そのために、数を数えるための変数を用意しますが、数を数えて指定した数で終わらせるようにするには、プログラムをそのように組まないといけません。一般には次のように記述します。
for(初期化式 ; 条件式 ; 増加式) { ステートメント; ステートメント; ・・・ }
まず、初期化式の部分の実行が行われ、続いて条件式のチェックがなされ、それがtrueなら一連のステートメントを実行し、増加式の実行が行われます。そのあとは、条件式→ステートメント→増加式の処理が繰り返されますが、条件式がfalseになった段階で繰り返し処理を終えて、先に進みます。
一般的には以上のようなことになるのですが、たとえば10回の繰り返しを行う場合、次のように繰り返し変数 i を定義して、それでカウントしながら10回繰り返しするように、条件式などを記述します。こうした書き方が常套句だと思っていただければいいでしょう。変数は i でなくても、他のものでもかまいません。これが典型的なforの書き方です。i の値を1だけ増加させるには、++演算子を使っています。また、forの中で変数 i を新たに定義しています。
for(int i = 0 ; i < 10 ; i++) { ステートメント; ステートメント; ・・・ }
それでは、Repeating.javaにプログラムを追加した続きのところに、以下のような、典型的なwhileでの繰り返し、forによる繰り返しが含まれたプログラムを追加して実行してみます。
int c=0; while ( c < 10 ){ System.out.println("現在の変数cの値は" + c); c++; } for ( int i = 0 ; i < 10 ; i++ ) { System.out.println("現在の変数iの値は" + i); }
それぞれのプログラムは、変数cと変数iであること以外は、出力結果には違いがありません。変数cの値の表示を10行、変数iの値の出力を10行表示していることに注目してください。たとえば、変数cの値を表示するSystem.out.printlnのステートメントは、プログラムの中では1つしかありません。しかしながら、繰り返し処理によって、そのステートメントは、10回繰り返されている訳です。そのとき、whileの場合は、変数cの値が1つずつ増加しており、System.out.printlnのステートメントを実行するたびにcの値は違うものになっています。
whileとforは書き方は違いますが、根本的には同じ処理だと言えるでしょう。forの後の()の中は、セミコロンによって3つのセクションに分けられ、それぞれ省略することも文法上は可能です。最終的に思った通りの処理ができればどちらを使ってもいいのですが、従来からのプログラミングのマナーとして、こうしたカウント処理を伴う場合にはforがもっぱら使われ、条件を調べるだけで繰り返しを終わるかどうかを判断するような場合にwhileやdoが使われるという傾向があります。
whileとforを使ったプログラムとして、次のようなプログラムを、Repeating.javaの最後の方に追加して実行をしてみてください。ここでもwhileとforで同じことをしています。繰り返し処理と同時に、配列とその要素を繰り返して処理するといった状況になっていることを理解してください。標準出力への出力結果は、少し凝ったことをしてみました。記号を間違えないように入力し、実際にどのような文字列がつながった結果として表示されるのかを具体的に追ってみて下さい。
int wordsLen = words.length; int j = 0; while ( j < wordsLen ) { System.out.println("words["+j+"] = "+words[j]); j++; } for ( int k = 0 ; k < wordsLen ; k++ ) { System.out.println("words["+k+"] = "+words[k]); }
配列wordsは、これよりも前に追加したプログラム部分で要素を代入してあるので、それを利用します。最初のwhileの一連の繰り返し部分では、変数 j を使って、j の値を0から1ずつ増加させ、配列の要素数になったときに繰り返しを終了するように作ってあります。この場合、変数wordsLenには3という数値が入るので、j の値は0、1、2と変化して、それぞれでwhileの後の { } で括った部分を繰り返し処理します。whileの条件式では、「j〈 wordsLen」となっています。つまり、j がwordsLenより少ないかどうかを判断しているというわけです。このように、変数あるいは定数と、論理演算子を使って条件式を組み立てます。算術演算子と組み合わせて「j〈 wordsLen -1」のような記述でも、文法的にはOKです。この場合、wordsLenから1を引いた数値と j を比べます。演算子の適用される優先順位を表で確かめてみてください。< よりも-が先に適用されるのです。
同じことが、forではもう少しシンプルに記述できることを示しました。ここでは、int型の変数 k を定義して、その k で、0、1、2とカウントしています。forの繰り返しの中では、この変数 k の値を利用することができるのですが、ここでは、配列wordsの引数に、カウンタとして使っている変数 k の値を利用しています。
whileやdo、forの繰り返しの中で、breakというステートメントを入れれば、繰り返し処理はたちどころに中止します。ステートメントの途中で条件を判断して繰り返しを中止したいときなどに利用します。continueは、繰り返しは中止しませんが、繰り返すステートメントの処理をそこで終えて、条件式の判断に即座に飛び移ります。ただし、continueは、次に説明するifを使えば記述する必要はなくなるので、あまり使わないかもしれません。さらに、繰り返し処理が入れ子になっているときに、どの処理を終了したり飛ばして継続するかということを記述する「ラベル」という手法もあるのですが、本書では割愛します。
条件に応じて、異なる処理をさせるということもできます。そのためには、Javaでは2種類の方法が用意されています。1つはifを使ったもので、もう1つはswitchを使ったものです。
ifを使った条件判断は、次のような形式で記述します。ここでも条件式が出てきますが、指定する条件式はwhileのあとに指定するのと同様です。
if(条件式) { ステートメント; ・・・ } else { ステートメント; ・・・ }
条件式がtrueなら、ifの直後の一連のステートメントを実行します。もし条件式がfalseなら、else以降の一連のステートメントを実行します。else以降は省略することもできますが、その場合だと、条件式がtrueなら一連のステートメントを実行し、そうでなければなにもしないというプログラムを作成することができます。
やはり切り替えて実行される一連のステートメントの部分が、目で見てパッとわかりやすいように、インデントをかけます。
それではifを使ったプログラムを実行してみましょう。やはり以下のプログラムを、Repeating.javaの最後に追加して実行してみます。結果は、やはりメッセージペインで確認します。
int m = 150; if ( m > 200 ) System.out.println("変数mは200より大きな値です。"); else System.out.println("変数mは200以下の値です。"); if (( m > 100) && (m < 200)) System.out.println("変数mは100〜200の値です。");
ifやelseのあとのステートメントが1つだけであるなら、{ } でくくってブロックを明示しなくてもかまいません。ステートメントが複数なら、必ず{ } でくくります。
サンプルでは、2つのifがあります。最初のifは、条件式が「m > 200」となっています。つまり、mの値が200より大きいかを判断しますが、mの値は150が代入されているので、「m > 200」の結果はfalseになります。そのため、else以降のプログラムが実行されるという具合です。ifからelseまでのプログラムは実行されないわけです。
2つ目のifでは、2つの条件を&&演算子で結合した条件式になっています。つまり「m > 100」という条件式と「m < 200」という条件式の両方がtrueでないと、この条件式の結果はtrueになりません。意味的にはメッセージペインに表示されるとおり、100〜200の値ならtrueになるのですが、複合した条件を記述するときには、このように&&あるいは||演算子を使って条件を組み立てます。このifはelseがないので、たとえばmの値が250だったら、ifのあとのステートメントは実行しないことになります。
ifとelseでは、条件に応じて2通りの処理のどちらかをやることになります。条件によって、3通りあるいはそれ以上の処理もifで記述することは可能です。たとえば、次のように、else ifと書きますが、else ifというキーワードがあるというよりも、elseの後にif文があると考える方が適切でしょう。以下の例は4通りの分岐条件になります。このとき、「条件1」から順番に判断をして、「条件x」に合致すれば、その後の { } の中のステートメントを実行します。どの条件にも合わない場合には、else以下を実行します。
if(条件1) { ステートメント; ・・・ } else if(条件2) { ステートメント; ・・・ } else if(条件3) { ステートメント; ・・・ } else { ステートメント; ・・・ }
ある変数がある値のときはこの処理、別の値のときはさらに別の処理、さらに違う値なら別の処理…というように処理が多岐にわたる場合、もちろんifを使ってその処理を書くことはできますが、場合によっては、switchを使うとシンプルに作成できることもあります。
switch(式) { case 値1: ステートメント; break; case 値2: ステートメント; break; : case 値n: ステートメント; break; default: ステートメント; break; }
式の値が値1なら、その直後のステートメントからbreakまで、値2ならその直後…というように、式の値によってステートメントを分岐できます。caseはいくつ書いてもかまいません。caseで指定した値にあてはまらないときには、default以降を実行します。
文字列の比較には少し難しい問題がかかわります。ここで説明することは、本書の来週以降のプログラムの理解では必ずしも知っておかないといけないわけではないですが、今後プログラムを続ける場合には必ずひっかかる点でもあるので、説明しておきます。実感できなくても、そんなことがあるのだと心の片隅に置いていただくだけでもいいでしょう。文字列の判断は難しいということを知っていただくだけでかまわないでしょう。
2つの文字列が同じかどうかを調べるにはどうすればよいでしょうか。論理演算子を見ていると、同じかどうかを判定する==という演算子がありました。この演算子は=ではないので注意をしてください。たとえば、int型変数 s の値が100かどうかを判断するには、「s==100」のような条件式を記述します。これを=だけと間違えて「s=100」とすると、sに値100を代入してしまいます。
さて、この==を文字列で使うとどうなるでしょうか。まずは次のプログラムを、Repeating.javaの末尾に追加して保存し、コンパイルし、実行してみてください。
String str1 = "Javaプログラミング"; String str2 = "Javaプログラミング"; if ( str1 == str2 ) System.out.println("文字列を==で比較した結果、同じと判断しました。"); else System.out.println("文字列を==で比較した結果、違うものと判断しました。"); if ( str1.equals(str2) ) System.out.println("文字列をequalsで比較した結果、同じと判断しました。"); else System.out.println("文字列をequalsで比較した結果、違うものと判断しました。"); String str3 = new String("Javaプログラミング"); if ( str1 == str3 ) System.out.println("文字列を==で比較した結果、同じと判断しました。"); else System.out.println("文字列を==で比較した結果、違うものと判断しました。"); if ( str1.equals(str3) ) System.out.println("文字列をequalsで比較した結果、同じと判断しました。"); else System.out.println("文字列をequalsで比較した結果、違うものと判断しました。");
ifが4つありますが、1つ目と3つ目のifで、==による比較を行っています。1つ目のifを見る限り、==を使った比較で同じ文字列かどうかは判断できそうな気がしますが、実行結果の3行目をよく見てください。str1もstr3も、文字列の中身は「Javaプログラミング」と同じなのに、3つ目のifの場合は違うものだと判断しています。なぜかという理由はあとで説明します。
その前に、2つ目、4つ目のifのステートメントを見てください。ここでは、String型で利用できるequalsというメソッドを使っています。これにより、引数の文字列の中身と、equalsメソッドを適用した文字列の中身を比較して同じならtrue、違っていればfalseを戻します。ちなみに、大文字と小文字は区別します。区別しない比較を行うには、equalsIgnoreCaseというメソッドを利用します。
結論を先に言えば、文字列の中身が同じ内容かどうかを調べるには、==ではなく、equalsメソッドを利用すると言うことです。では、==というのは何でしょうか? ここで、文字列を自動車にたとえてみます。もちろん、1台ずつに違うナンバーが与えられているのですが、ここでは車種だけに注目したとしましょう。たとえばキューブは世の中にたくさんありますが、今、新宿駅前のパーキングに納められたキューブと、大阪駅前に駐車したキューブがあるとします。自動車としては同じだけれども、置き場所を含めて考えると違うものであることになります。
文字列の中身を車種、文字列を収めている変数をここでは置き場所と考えてください。そして、equalsは車種のチェックを行い、==は置き場所を比較しているのだと考えてください。つまり、==とequalsは比較する対象が違うということです。
実際、==はデータの置き場所、つまりメモリの上で展開された記録場所が同じかどうかをチェックします。同じ置き場所にあるものは中身も同じであると判断できるのですが、違う置き場所にある場合でも、文字列のように同じ文字列であれば、「等しい」と判断をしたいものもあります。こうした判断基準はものによって違います。そうした対象の種類ごとにequalsメソッドが用意されていますが、文字列の場合はequalsによって、字面が比較されると考えればよいでしょう。
では、なぜstr1==str2がtrueになり、str1==str3がfalseになるのでしょうか。str3の値を代入している部分を見てください。StringBufferのときと同じような記述になっています。つまり、新しくString型のデータを保持する領域を確保して、そこに「Javaプログラミング」という文字を入れるということを明記してあります。これが本来の文字列領域の確保の方法です。ところが、str1やstr2の値を代入しているような書き方も実は許されています。これにより、Javaの処理システム側で、データ領域の確保などを自動的に行います。
このとき、一連のプログラムで同じ文字列があれば、そのデータ領域を共通に使うというような効率化を行っているのです。そんなことをしたら問題ないのかと思うかもしれませんが、Stringでは問題になりません。というのはStringの中身は変更できないからです。代入しなおすと、実は別のStringの領域を利用するようになるので、ある段階で2つの変数が共通の領域を使うのは問題ないのです。こうしたメカニズムを利用しているため、文字列の==の結果は、trueになったりfalseになったりするわけです。繰り返しますが、文字列自体の比較ではとにかく、equalsメソッドを使わないといけません。
プログラムの中にメモを書いておく方法は以前にも説明しました。プログラム中に、//があれば、そこから改行までは自由に何を書いておいてもプログラムの処理にはいっさい関係がありません。また、/*〜*/までは複数の行に渡ってプログラムの処理に関係なくしておくことができます。こうした部分を「コメント」と呼びます。
この方法を応用すると、プログラムの一部を書き換えたいけども、今のプログラムを残しておくということもできます。たとえば、以下のような配列の初期化のプログラムがあったとします。
String words[] = {"りんご", "みかん", "バナナ"};
ここで、配列wordsの内容を違うものにして同じプログラムを実行したいとします。もちろん、前のプログラムをすっかり消し去ってもいいのですが、また元に戻したいときもあるかもしれませんので、そういう場合はファイルの中身以前のプログラムをおいておくと手軽です。つまり、このステートメントをコメントにして、次の行に新たなステートメントを書きます。そして、コメントとなる行をその都度切り替えて(具体的には//を別の行にカット&ペーストするなどして)実行をすればいいでしょう。
// String words[] = {"りんご", "みかん", "バナナ"}; String words[] = {"キャベツ", "きゅうり", "深谷ネギ", "すだち", "なす"};
6-1: ある整数を変数numberに入力する。その数が奇数か偶数かを標準出力に表示せよ。そのとき、たとえば、ある数値が13なら、「13は奇数です」といった風に表示されるようにすること。numberの値をいろいろと変更して実行し、正しく動作していることを確認すること。
ヒント:10 + "は数値です" の結果はStringになる。つまり、10を、文字列の"10"として処理してくれる。
6-2: String型の変数に文字列を入力し、文字数を数え、1文字目から順に1文字を1行ごとに表示するようなプログラムを作成すること。文字列をいろいろと変更して実行して、文字数に関わらずに処理されることを確認すること。
6-3: int型の配列を定義して適当な変数に設定しておく。初期化は { } を使ったやり方にしておこう。そして、配列の各要素の合計と平均値を求めて表示すること。