[雑談] 試験の役に立たないJava講座 (6)

ということで引き続きインプレス社の「徹底攻略Java SE 11 Silver問題集」の章立てに沿って、少し雑談していきたいと思います。

以前よりお話ししているとおり問題集の内容に沿ったものではなく抜けているところも多々ありますので、認定資格を取得しようというのであれば問題集を購入して、そちらをしっかり勉強してください。

こちらは章立てに沿って適当に書き散らかしているものですので、試験の役には立ちません。

ということで今回は第3章の続き、判定構造、if文とswitch文について触れていきます。

文について

ここでJavaの文につぃてこれまでのところをまとめておきたいと思います。

まず、式の最後にセミコロン「;」を配置したものが文になります。

例えば以下のようなものです。

x = 3;

そして複数の文 (1個でも構わないのですが) を中括弧「{}」でくくったもの、これをブロックと言いますが、ブロックも1個の文として扱われます。

一応ブロックの例も示しておきます

{
  x = 3;
  y = 5;

}

その他にも文はいろいろありますが、とりあえずここではif文とswitch文について考えていきます。

if文


if文は特定の条件が成立したとき、言い換えると条件式の評価結果がtrueとなるときに特定の文を実行することを示す文で、if (条件式) 文の形式を取ります。

以下に例を示します。

if (x == 0) x = 3;

上の文は、x0の場合にx3を代入するという処理を表しています。

もちろん文としてブロックを使用することもできます。

if (x == 0) {
  x = 3;
  y = 4;

}

この場合にはx0のときに中括弧「{}」でくくられたブロックが一括して処理されます。

条件が成立しなかった場合に別の処理を実行したい場合、elseを使用してif (条件式) 文 else 文の形式を使用します。

if (x == 0) x = 3; else y = x;

上のように1行書いても構わないのですが、elseを見落とす恐れがあること、elseの前のセミコロン「;」を忘れる可能性があることから、以下のような書き方が私はいいと思っています。

if (x == 0)
  x = 3;
else

  y = x;

条件が成立した場合、成立しなかった場合に実行する文をインデント (字下げ) することでifelseの関係もわかりやすくなると思います。

if文、if (条件式) 文if (条件式) 文 else 文はともに1個の文として扱われます。

さて、x0のときはx1に、x1のときはx2に、x2のときはx3に、それ以外の場合はx4にするプログラムを考えてみます。

例えば下のように書いたとします。

if (x == 0)
  x == 1;
if (x == 1)

  x == 2;

if (x == 2)

  x == 3;

else

  x == 4;

この書き方だとx0の場合、2行目でx1になりますが、3行目にあるx == 1の条件が成立してしまうため、4行目でx2になり、同様に5行目のx == 2も成立してしまうため、結果としてx3になってしまいます。

x0だった場合にはx1としたあと、他のif文を実行しないようelseを使用して、以下のように書きます。

if (x == 0)
  x == 1;
else

  if (x == 1)

    x == 2;

  else

    if (x == 2)

      x == 3;

    else

      x == 4;

ただ、この書き方だと段々と右によっていくため見にくくなってしまうので、以下のような書き方よくされます。

if (x == 0)
  x == 1;
else if (x == 1)

  x == 2;

else if (x == 2)

  x == 3;

else

  x == 4;

構造を原理主義的に考えると少し気持ち悪い書き方となるのですが、同じレベルで判定がされているという感覚に近い書き方ですので、こちらの書き方を勧めます。

プログラム言語によってはこのような書き方のために専用のキーワードELSEIFあるいはELIFといったものを用意していますが、先程もちょっと触れたようにJavaではif文自体が1個の文として扱われるため、else ifは特別なキーワードではなく、単に書き方を工夫しただけという扱いになります。

ところで、elseの直後にifが置けるように、ifの直後にも当然ifを置くことができます。

if (x == 0)
  if (y == 0)
    z = 0;

ただ、この場合はelseの扱いが微妙になってきます。

2個のプログラム例を書いておきます。

if (x == 0)
  if (y == 0)
    z = 0;

else

  z = 2;


if (x == 0)
  if (y == 0)
    z = 0;

  else

    z = 2;

さて、上の2個のプログラム例の動作を考えてみましょう。

最初のものはx0以外のときにz2になるように感じられますが、次のものはx0y0以外のときにz2になるように見えます。

しかし、Javaはインデントを気にせず、連続する改行と空白文字を1個の空白文字と同等と認識するので、これらの式は同じ処理を実行します。

どうなるかは自分で調べていただくとして、ここで大事なのはこういった混乱のもとになるので、ifの直後にifを置く場合は必ず中括弧「{}」を使用してブロックとするべきということです。

ブロックを使って書き直すと以下のようになります。

if (x == 0) {
  if (y == 0)
    z = 0;

} else

  z = 2;


if (x == 0) {
  if (y == 0)
    z = 0;

  else

    z = 2;

}

このように書くことで意図が明白になり、勘違いも起きなくなります。

原理主義的にif文の中はすべてブロックにするという書き方もありで、安全の面からは推奨されますが、私は上の例のように1行だけで済む短い代入文、またはメソッド呼び出し分の場合には改行後インデントして中括弧はなしという書き方をしています。

もちろん、組織として標準がある場合にはそれに従うのですけど。

if文と三項演算子

if文に似たものとして、三項演算子「?:」があります。

すでに演算子の項目で触れていますが、三項演算子は[条件式]? [条件式が成り立つ際に評価される式]: [条件式が成り立たない場合に評価される式]という式を構成する演算子で、何らかの条件で値が決まるといった場合に使用できます。

例えば、変数yに変数xの絶対値を代入するといった場合、以下のようなプログラムが考えられます。

if (x >= 0)
  y = x;
else

  y = -x;

これを三項演算子を使用して書くと以下のようになります。

y = (x >= 0)? x: -x;

明らかに短くはなりましたが、どちらが読みやすいかは微妙なところなので、状況に応じて使い分けるのがいいかと思います。

switch文

特定の変数の値にしたがっていくつかの処理を切り替えるといった場合が多々あります。

例えば信号が赤だったら止まる、黄色だったら安全に停止できるかを判断する、青だったら進むといったようなものを考えます。

これをif文で表現する場合には以下のようなif-else ifの連鎖を使用します。

if (signal == Signal.RED) {
  // 停止する
} else if (signal == Signal.YELLOW) {

  // 安全に停止できるか判断する

} else if (signal == Signal.GREEN) {

  // 安全を確認の上進む

} else {

  // 何らかの障害が発生しているので停止する

}

この書き方でも特段の問題はないのですが、特定の変数の値に従って処理を切り替えていることをわかりやすく表現する記述方法としてswitch文が用意されています。

上の例をswitch文を使用して記述すると以下のようになります。

switch (signal) {
case Signal.RED:
  // 停止する

  break;

case Signal.YELLOW:

  // 安全に停止できるか判断する

  break;

case Signal.GREEN:

  // 安全を確認の上進む

  break;

default:

  // 何らかの障害が発生しているので停止する

  break;

}

switch文を使用するとif-else if連鎖より記述が長くなるうえにswitch文特有の制約もあるため、使い勝手は良くないのですが、特定の変数の値に従って処理を選択しているということが一目でわかるので、プログラムの構造を理解しやすいという利点があります。

また、分岐の数が多い場合にはswitch文の方が効率的に処理できる可能性がありますが、通常の使い方では意識する必要はないと思います。

switch文の制約について少しふれておきます。

まず、caseで指定できる値はプリミティブ型、文字列型、列挙型の定数またはリテラルだけです。

定数またはリテラルに制限することで処理の効率化を図れるということになっていますが、if文に比べてどのくらい効率的なのかはちょっとわからないところです。

もう一つの制約というか考慮すべき事項としてbreak文があります。

break文を書かないと下に突き抜けてしまうということです。これをうまく使う方法として、複数の条件で同じ処理を行わせるといったことが考えられます。

先程の例でGREENと同等なBLUEを追加するといったことを考えてみます。

switch (signal) {
case Signal.RED:
  // 停止する

  break;

case Signal.YELLOW:

  // 安全に停止できるか判断する

  break;

case Signal.GREEN:

case Signal.BLUE:

  // 安全を確認の上進む

  break;

default:

  // 何らかの障害が発生しているので停止する

  break;

}

このように2個以上のcaseラベルを並べて書くと両方の条件に対して同じ処理を適用できるようになります。

もちろん、下の例のように一方で少し何らかの処理を行った後で次のcaseラベルの処理に渡すこともできますが、コードが理解しにくく、かつ修正が面倒になるのでやらないほうがいいと思います。

switch (condition) {
case Condition_1:
  // 何らかの処理

  // breakで抜けずに次の処理に続ける

case Condition_2:

  // Condition_1, Condition_2共通の処理

  break;

case ...

}

こういった場合は以下の例のように共通処理部分を別メソッドとして抽出し、そちらを呼び出す方がいいと私は考えます。

switch (condition) {
case Condition_1:
  // Condition_1特有の処理をここで実行

  execCommonProcess(); // 共通処理メソッド

  break;

case Condition_2:

  execCommonProcess(); // 共通処理メソッド

  break;

}

このようにしておくとCondition_2の側にも特有の処理が入った場合に面倒なことなく修正が可能だからです。

switch文の考慮すべき点の3番目は変数の有効範囲です。

後程たぶん触れると思いますが、switch文の本体ブロック (中かっこ「{}」の中) では同じ名前の変数を複数定義することができません。

たとえば以下のような例はエラーになります。

switch (condition) {
case Condition_1:
  int i = 0;

  // 何らかの処理

  break;

case Condition_2:

  int i = 10;

  // 何らかの処理

  break;

}

switch文の本体ブロックで使用する変数はswitch文の前で定義しておくのが適当かなと考えています。

以上、判定構造、if文とswitch文について少しまとめてみました。

0コメント

  • 1000 / 1000