page5(update:2009/10/26)[例題5


5.制御構造


[ prev | next | index ]
 実行制御の構文はC言語に似ていますが、javaの条件判断はboolean型の値で行われます。さらに、breakやcontinueでは対応する繰り返し構文をラベル を用いて指定できます。 
 C言語に無い新しい構文として、java言語には例外処理が有ります。今回はこの例外処理をぜひ理解してください。

目次

[目次]


5.1 処理の分岐

普通プログラムは上から下へ順番に処理されるので、その処理の流れは1本になっています。しかし、条件に応じて処理を分けたい場合もあります。そこで、処理の流れを分岐させる為の構文としてif文等が用意されています。

 

5.1.1 if文 

if(条件式) 文A ;else 文B;

動作:
条件式の値がtrueなら文Aを実行、falseなら文Bを実行し次の命令に進む。条件式が戻す値はboolean型でなければいけません。

(注)文Aや文Bの部分で複数の文を入れたいなら次の様に複文(compound statement/ブロックblock)を使います。

if(条件式) {文A1;文A2;文A3;文A4;} else {文B1;文B2;}

{ }で囲まれた部分が複文です。 (elseの前には;が無いので注意)

文Bが無い場合はelseの部分を省略して書くことができます。

if(条件式) 文A 


条件式を書く為に論理演算子や関係演算子、等価演算子が用意されていますので表にしておきます。これは記憶して下さい。C言語と外見上同じに見えますが戻り値がboolean型である点が異なります。

論理演算子 一般形 意味
&& e1 && e2 論理AND:e1、e2がともにtrueなら戻り値true、異なる場合にfalse
(注)e1がfalseならe2を評価せずにfalseを戻す

|| e1 || e2 論理OR:e1、e2が何れかtrueなら戻り値true、異なる場合にfalse
(注)e1がtrueならe2を評価せずにtrueを戻す

! ! e 論理NOT: eがtrueなら戻り値false、 eがfalseなら戻り値true
関係演算子
< e1 < e2 e1<e2なら戻り値true、異なる場合にfalse
<= e1 <= e2 e1≦e2なら戻り値true、異なる場合にfalse
> e1 > e2 e1>e2なら戻り値true、異なる場合にfalse
>= e1 >= e2 e1≧e2なら戻り値true、異なる場合にfalse
等価演算子

== e1 == e2 等号:  値が等しいなら戻り値true、異なる場合にfalse
!= e1 != e2 不等号: 値が等しいなら戻り値false、異なる場合にtrue

(注)参照型の変数では等価演算子は参照する実体が同一の場合にtrueとなります。これは、同じ場所を指す場合にtrueと言うことです。指している実体の値が等しいかどうかではありません。実体の中のデータが等価かどうかはメソッドequals()を用いて行います。文字列の中身が「鹿児島大学」に等しいかどうかは

s.equals("鹿児島大学");

が戻す値がtrueかどうかで判断できます。

 

5.1.2 多重分岐

入れ子のif文で多重分岐を記述できます。3個に分岐する場合は次の様に書きます。

if(条件式1文A;else if(条件式2文B;else 文C

これはif文のelse以下がさらにif文になった形です。上記の場合は条件に応じて文A-Cの何れか1つが必ず実行されます。

ソースプログラムはフリーフォーマットと言っては改行や空白は命令の解釈においては意味を持ちません。プログラマはプログラム構造が解かり易い様に自由に改行や空白を入れることができます。上記のif文も多くの場合下記の様に改行を入れて記述します。

if(条件式1)   文A;
else if(条件式2文B;
else         文C

上の文Cをさらにif文にすればいくらでも分岐の数を増やせます。

この他、多重分岐はswitch文でも行えますが、整数型の値で分岐する点が異なります。

5.1.3 switch文

整数値や文字型のデータで多重分岐する場合によく使われる形です。下記に構文の形を示しますが式の値と等しいcase文に処理が跳びます。後で説明するbreak文にぶつかるまで上から下へ順番に実行します。C言語のswitch文と同様です。

switch(){
        case 定数式1://式の値が定数式1に等しければここから始める
                ....
                break;//ここでswitch構文の外へ
        case 定数式2:
        case 定数式3:
                //この様に重ねると式の値が定数式2or3でここに来る
                ....
                break;//ここでswitch構文の外へ
        default://該当するcase文がない場合はここから始める(不要なら省略できる)
                ....
}

 

[目次]


5.2 繰り返しの構文

 構文はC/C++言語とほとんど同じです。

5.2.1 while文

もっとも一般的な繰り返しの構文はwhile文で

while(条件式) 文 

動作:
条件式の値がtrueなら文を実行し再び条件式の判定を行う。結果的に条件式の値がfalseになるまで文を繰り返し実行する。

(注)文を実行することで条件式の値が変化しない場合は無限ループになってしまう。

上記の様に文を複文にすることでまとまったプログラムを繰り返し実行できる。

while(条件式){文1; 文2;... }

5.2.2 for文

繰り返しの回数があらかじめ決まっている場合にはfor文を使います

for(初期設定式;条件式;再設定式) 文 

動作:
初期設定式を実行後、条件式の値がtrueなら文を実行する。再設定式を実行後、再び条件式の判定を行う。文を複文にすることでまとまったプログラムを繰り返し実行できる。

多くの場合次の様な形で使われる。

.....
int sum=0;
for(int n=0; n<100 ; n++ ){/*繰り返しの初期設定条件再設定がまとまっている*/
        sum=sum+1.0/n;
}

(注)java言語ではC言語とことなり変数の宣言がプログラムの先頭以外でも可能です。for文の繰り返しの回数を数える、ここでしか使わない変数はfor文の初期設定式で宣言することもよく行われます。for(int n=0;....

5.2.3 do while文

繰り返しの文を実行後条件判定をする場合の構文です。

do 文 ; while(条件式);

あるいは

do {文1;文2;...} while(条件式);

while文やfor文は繰り返しの文を実行する前に条件判定を行うのに対してこの構文は繰り返しの文の後で条件を判定します。

5.2.4 break文

繰り返しの構文から脱出する命令で基本的にはC言語と同じですが、ラベルを使うことで入れ子の繰り返し構文から一度に抜け出すことも可能です。

.....
loop://ラベルの宣言、直後の文を指す名前
for(int i=0;i<10;i++){
    for(int j=0;j<10;j++){
        ....
        if(error)break loop;//ラベルで示された繰り返し構文から脱出し文Aに移動
        ....
    }
....
}
文A;
...
 

5.2.5 continue文

これはループからの脱出ではなくて、whileやdo while文なら次の処理を条件式へ移す命令です。for文の場合は再設定式へ処理を移します。こちらもbreak文と同様にラベル名を使うことが可能です。

 

[目次]


5.3 例外処理

 プログラムの実行中に例外的な状況になってプログラムの実行順番を変えたい場合が在ります。C言語ではif文で判定してgoto文で例外処理のプログラムへ飛ぶ事になります。しかしgotoでは自由度が在りすぎて例外処理のパターンが定まりません。そこで、java言語や今のC++言語には例外処理の構文が用意されています。

例外が起きたら通常のプログラムの流れから抜け出しますが、例外の種類ごとに行き先を決めなければなりません。

java言語の例外処理では指定された範囲で例外が起きたら決められた場所に飛ぶ構文が用意されました。tryとcatchの構文です。

下の模式図のように例外が起きなければtryブロックを実行するだけでcatchブロックは実行されません。しかし、tryのブロック内で例外が起きると直後のcatchブロックへ処理が飛び、catchブロックの処理終了でtry-catch構文を抜けます。

1) 例外の起きる場所は1か所とは限りません。そこで例外が起きたら例外インスタンスを生成し例外の情報を記録してcatchブロックに飛ぶ仕組みになっています。catchの後の( )で囲われた部分はこの例外インスタンスを参照する変数を示す部分です。関数の引数と同じような表現になっています。例外処理に必要な情報はこの例外インスタンスから得ることができます。

2) 例外の種類も一つとは限りません。例外の種類は例外インスタンスの型(クラス)で区別します。catchの後の( )内に書かれた変数型と例外インスタンスの型が一致するcatchブロックが例外に対応する処理として実行されます。 catchは例外の型に応じて複数書くことができます。

5.3.1 例外を投げる

java言語では例外処理に抜け出すために例外(Exception)投げ(throw)ます。 

例外を投げるには、まず例外インスタンスを作らなければいけません。例外インスタンスはExceptionクラスかこれを継承したクラスのインスタンスと決められています。次に、このインスタンスをthrowで投げると例外が発生する仕組みになっています。

....
if(例外が発生したか?){
    Excetption ex;
    ex=new Exception("何かメッセージがあればここに");
    throw ex;
}
....
//もう少し簡潔に書くことにすれば
if(例外が発生したか?)throw new Exception("何かのメッセージ");
....

投げた例外をメソッド(関数)の中で処理せずに、メソッドを呼び出した側に投げ上げることが可能です。

この場合はメソッドの定義で、例外を投げることを明記する必要があります。例外が発生するとメソッドの途中から脱出して呼び出し側のプログラムに例外を投げます。doSomthingメソッドがException型の例外を投げるかもしれないときは次のようメソッドの頭部でthrowsで投げる例外を明記します。

※数種類の例外を投げる可能性がある関数ではthrowsの後にカンマ区切りで複数の例外のクラスを記述します。

public void doSomthing() throws Exception{//例外を投げる関数は必ず投げる例外の種類を宣言する
    .......
    if(file==null)throw new Exception("ファイルが見つからない");//ファイルが無いとExceptionを投げる
    .......
}

もし上の関数doSomthing()を次の関数function1が呼んでいる場合、function1は渡された例外を自分で処理するか、さらにfunction1を呼んでいる関数に投げるかのいずれかの処理を行 う必要があります。自分で処理しない場合は次のプログラムの様になります。この場合はdoSomthing()が投げた例外をそのままfunction1を 呼んでいる関数に投げます。

public void function1() throws Exception{//例外を投げる関数は必ず投げる例外の種類を宣言する
   .......
    doSomthing();//doSomthing()で例外発生の可能性あり
   .......
}

もしmain関数でもこの様に例外を投げてしまえば、プログラムはそこで終了することになります。つまり次の様に書いた場合です。

static public void main(String arg[]) throws Exception{//例外を投げる関数は必ず投げる例外の種類を宣言する
    .......
    XX.function1();//function1()で例外発生の可能性あり
    .......
}

★例外を投げる可能性のある関数を呼ぶ場合、投げられた例外を受け取るか、さらに上に例外を渡すかです。どちらかの処理をしないとコンパイルエラーになってしまいます。

finally

finallyはreturnやthrowでメソッドから戻るときに必ずこれだけは実行してほしい処理がある場合に用います。記述は

finally{メソッドから戻る時に実行したい処理プログラム} 

例外を投げて戻る場合でもこのfinally部分は必ず戻る前に実行されます。メソッドを終わるときに後始末が必要な場合に重宝します。

5.3.2 例外を受け取る

例外を受け取る場合、Javaではtry catchの構文を使って捕捉します。 次のページで説明する入出力に用いるライブラリには例外を発生させる関数が多く、この様な関数を使う場合は必ず例外を処理する必要が有ります

try{
        ...
        //ここで例外:Exceptionが発生するとcatch(Exception e)へ処理が跳ぶ
        ...

}catch(Exception e){ //例外がExceptionならここで処理
        e.printStackTrace();//とりあえずこう書いておけば例外の発生場所を書き出す
}

例外インスタンスのメソッドprintStackTrace()は簡単な出力フォーマットで、例外の型と有れば例外のメッセージ、続く行で例外発生場所から 関数呼び出しの経路を順に遡って書き出します。

投げられた例外クラス名:例外のメッセージ文字列
	at 例外が発生したメソッド名(ソースファイル名:行番号)
	at 上を呼び出したメソッド名(ソースファイル名:行番号)
	---- 以下同様で大本のメソッドまで遡る ----

とりあえずこれで例外が発生するまでのプログラムのメソッド呼び出しを知ることができます。

例題5

例外を投げるメソッドtest、test2を作りmainから呼び出して発生した例外をmainメソッド内で受けとる。受けとった例外のprintStackTrace()メソッドを呼 んで、例外の種類 と、例外が投げられるまでの関数呼び出しの経路を書き出します。実行結果で例外の発生場所等を確認してください。

/*
ExceptionTest.java 
以下は説明のために行番号を付けていますので実行できません
*/

01:public class ExceptionTest{
02:        static public void main(String arg[]){
03:                int data[]={-1,0,1};
04:                for(int i=0;i<data.length;i++){
05:                        try{
06:                                test(data[i]);//例外を発生する関数を呼ぶ        
07:                                
08:                        //例外やエラーを分けて捕まえる事が可能  
09:                        }catch(Exception e){
10:                                System.err.println("例外処理");
11:                                e.printStackTrace();
12:                        }catch(Error e){
13:                                System.err.println("エラー処理");
14:                                e.printStackTrace();
15:                                //エラーを捕まえるとも可能
16:                        }
17:                }
18:                System.err.println("\n mainを終了します \n");
19:        }
20:        
21:        static void test(int data) throws Exception//投げる例外を明記
22:        {
23:                if(0<=data)test2(data);
24:                else throw new Exception("DATAが負");
25:        }
26:        
27:        static void test2(int data) throws Exception//Errorはthrowsに書く必要がない
28:        {
29:                if(data==0)throw new Exception("DATAが0");
30:                throw new Error("DATAが正");//例外の他にエラーも投げることが可能
31:        }
32:        
33:}
実行結果を見ると発生した例外、発生した場所、発生したときの関数の呼び出し状況が表示されます。
以下が実行結果
例外処理
java.lang.Exception: DATAが負
        at ExceptionTest.test(ExceptionTest.java:24) 
        at ExceptionTest.main(ExceptionTest.java:6)
これはmain関数内のプログラムの6行目からtestが呼ばれ、そのプログラムの24行目で
 Exception("DATAが負") が投げられたことを示している。

例外処理
java.lang.Exception: DATAが0
        at ExceptionTest.test2(ExceptionTest.java:29) ※main>test>test2と呼んだところで発生
        at ExceptionTest.test(ExceptionTest.java:23)
        at ExceptionTest.main(ExceptionTest.java:6)
エラー処理
java.lang.Error: DATAが正
        at ExceptionTest.test2(ExceptionTest.java:30) ※main>test>test2と呼んだところで発生
        at ExceptionTest.test(ExceptionTest.java:23)
        at ExceptionTest.main(ExceptionTest.java:6)

 mainを終了します ※エラーを捕まえて処理したのでプログラムは正常終了します

★java言語では実行時エラーが検出されるとErrorクラスのエラーインスタンスを作成して例外と同じように投げます。エラーは例外と違ってtry-catchの構文で処理する義務が有りませんが、try-catch構文で捕まえてエラーを処理すればプログラムを止めなくて済みます。

5.3.3 assertion(表明) −−jdk1.4以降の機能です−−

Error(エラー)もthrowで投げる事が可能です。これを利用してプログラム中に実行時条件チェック(assertion:表明)を書き込めます。例えば

if(条件==false)throw new Error("XXXで条件が満たされませんでした...");

をプログラムに挿入してテスト実行すれば、条件が満たされないとプログラムが止まりエラーメッセージで詳しい状況を知ることができます。Errorはcatchの義務が無く、メソッドでthrowsを書く必要もないのでプログラムにErrorを投げる文を挟むことは容易です。

しかし、この書き方では条件チェックをやめるにはソースを元に戻すしかありません。 テスト用と出荷用で2つのソースファイルを管理することにもなってプログラムの更新等で問題が起きるでしょう。

jdk1.4から新しく表明と呼ばれる機能が加えられました。これはAssertionErrorを投げる機能です。実行時の条件チェックに使いますが、エラーなのでいちいちcatchやthrows等の記述を必要としません。また実行時にこの機能をON/OFFでき、既定値はOFFです。

jdk1.4からサポートされる。実行時の条件チェック(assertion)
書式1: assert  ;
書式2: assert  : 式2 ; 

falseのときAssertionErrorを投げる
ここで式2はAssertionErrorの生成に使われる文字列等の値。

コンパイルでは

javac -source 1.4 XXX.java

-source 1.4のオプションが必要で、実行では

java -ea ClassName

 -eaのオプションが必要です。オプション無しか-daなら動作チェックは無効 となる。
コンパイル済みの運用クラスファイルが起動オプションでテストモードで実行できます。

[目次]


[ prev | next | index ]