はじめに
現在、私が進行しているJavaの勉強会でまとめた内容です。
内容に不正確な点があれば、ご指摘いただけるとありがたいです。
韓国人として、日本語とコンピュータの勉強を同時に行うために、ここに文章を書いています
翻訳ツールの助けを借りて書いた文章なので、誤りがあるかもしれません
制御文
ソースファイル内の文は、通常、上から下に向かって記述された順に実行されます。しかし、制御フロー文を使用すると、実行の流れを分岐させたり、条件に応じて特定のコードブロックを実行したりすることができます。
-
条件文 (Decision-Making Statements)
if
if-else
switch
-
繰り返し文 (Looping Statements)
for
while
do-while
-
分岐文 (Branching Statements)
break
continue
return
制御文
コードの流れを制御する文法
意思決定文 (Decision-Making Statements)
if
- 条件式が真である場合、コードを実行します
if (条件式) {
// 条件式が真のときに実行されるコード
}
if-else
- 条件式が真であれば if、偽であれば else を実行します
if (条件式) {
// 条件式が真のときに実行されるコード
} else {
// 条件式が偽のときに実行されるコード
}
else-if
- 条件式を順次評価し、真のときに該当するコードを実行します
if (条件式1) {
// 条件式1が真のときに実行されるコード
} else if (条件式2) {
// 条件式2が真のときに実行されるコード
} else {
// 上記のすべての条件が偽の場合に実行されるコード
}
switch / case
- 変数の値に応じて複数のケース (case) の中から1つを選択します
- case の値が一致する場合にそのコードを実行します
switch (変数) {
case 値1:
// 変数の値が値1のときに実行されるコード
break;
case 値2:
// 変数の値が値2のときに実行されるコード
break;
default:
// 上記のすべての case に該当しないときに実行されるコード
}
switchとifの速度比較
次のようにクラスファイルを作成し、コンパイルされたバイトコードを確認しました。
次のように記述されています。
ここで重要な部分は、
25: tableswitch { // 1 to 12
1: 88
2: 94
3: 100
4: 106
5: 112
6: 118
7: 124
8: 130
9: 136
10: 142
11: 148
12: 154
default: 160
}
この部分が重要だと考えています。
通常、switch文はバイトコードで tableswitch
と lookupswitch
に分けられます
(4バイト以内で連続するコードは tableswitch
、非順序データは lookupswitch
です)。
これらのコードは、特定のインデックスでジャンプテーブルにアクセスし、「ジャンプ」するように定義されています。
このため、O(1) の時間複雑度を持ちます。
一方、このコードを if-else で再定義すると
次のように if-else 文で再定義し、バイトコードを確認したところ、
以下のようにすべてのインデックスを線形に検査していることが確認できました。
条件が n 個追加されるごとに処理時間も n 個分増加します。
したがって、if-else 文の時間複雑度は O(n) となります。
- 詳細な構成については、こちらで
tableswitch
、lookupswitch
を検索すると確認できます
- バイトコードを確認すると、switch 文はインデックスジャンプを行い、if-else 文は線形探索を行います
- これを時間複雑度で比較すると、switch 文は O(1)、if-else 文は O(n) です
以上の理由から、switch
文は if-else
文よりも性能が優れています。
switch 文の fall-through とその防止
-
break
を使用しない場合、一致するcase
文の比較後、その後のすべての文が順次実行されます -
これを「fall-through」と呼びます
前述の switch case コードで、10月の場合を除き、すべての break
文を削除してみました。
作成されたコードのバイトコードを確認すると、
10月を除き、すべての goto
文が抜けていることが確認できます。
また、コードで検査してみると、すべてのインデックスを順に通過し、10月の箇所で goto
によって "October" が出力されることが確認できます。
これにより、fall-through
がなぜ発生するのかがよくわかります。
バイトコードを確認するとわかるように、最後の節では break
文は必要ありません(次のインデックスに直接続くため)が、保守性やコードの一貫性を考慮して break
文を付けることが望ましいです。
fall-through
を防ぐために矢印演算子 ->
を使用できます。
次のように ->
演算子を使用すると、break
文を省略できます。
String monthName = switch (month)
...
そして、次のように switch 文の返り値を直接割り当てることもできます。
また、->
を使用すると複数のケースをまとめて宣言することが可能です。
バイトコードを見ると、すべてがインデックス 88 を指し、複数のケースをまとめて宣言できる機能がサポートされていることが確認できます。
String result = switch (day) {
case 1, 2, 3 -> "Weekday";
case 4, 5 -> "Weekend";
default -> {
yield "Invalid day";
}
};
System.out.println(result);
そして、次のように yield
構文を使用して値を返すこともできます。
switch 文に ->
を使うべき理由のまとめ
- 自動的に
break
が挿入され、fall-through を防止できる - 1つの case で複数の値を処理できる
-
yield
を使って複雑な値を返すことができる
繰り返し文
while
- 条件式が真である間、中括弧内のコードを繰り返します
while (条件式) {
// 条件式が真のときに繰り返し実行されるコード
}
do while
- while 文を実行する前に必ず実行するコードを定義できます
do {
// 条件式が真のときに実行されるコード
} while (条件式);
for
- 条件式が真である間、中括弧内のコードを繰り返します
for (初期値; 条件; 方向&間隔) {
// 条件が真のときに繰り返し実行されるコード
}
for-each
- すべてのオブジェクト要素に1つずつアクセスする際に使用します
for (<型> <インスタンス>: <繰り返し可能なオブジェクト>) {
// 繰り返し可能なオブジェクトの各要素に対して実行されるコード
}
分岐文 (Branching Statements)
break
- 該当ブロックを即座に終了し、次の実行フローに移動します
for (int i = 0; i < 10; i++) {
if (i == 5) {
break; // i が 5 のときにループを終了
}
System.out.println(i);
}
return
- 実行を終了し、呼び出し元に値を返します
public int add(int a, int b) {
return a + b; // 2つの数の合計を返す
}
continue
- 現在の繰り返しをスキップし、次の繰り返しを実行します
for (int i = 1; i <= 10; i++) {
if (i % 2 == 0) {
continue; // 偶数の場合、以降のコードをスキップして次のループに移動
}
System.out.println(i); // 奇数のみ出力
}