PR

Javaのラベルとは?初心者向けに1から使い方をわかりやすく解説

Java

まず最初に、「ラベル」という言葉を聞くと、どのようなイメージをもつでしょうか。日常生活では、荷物やファイルなどに貼り付ける付箋のような「ラベル」を思い浮かべるかもしれません。プログラミングの文脈では、ラベルは「特定のコードブロック(多くの場合はループ)に名前をつける仕組み」を指します。

Javaの場合、C言語のようにgoto文(指定した行に直接ジャンプする命令)は使えません。Javaは「構造化プログラミング」の考え方を基本としており無制限なジャンプは推奨していないため、gotoはキーワードとして予約はされていても、実際には利用できません。その代わり複数のループが重なった状態で「外側の特定のループだけを抜けたい」あるいは「外側のループを次の反復処理に進めたい」という場合に、Javaでは「ラベル」を指定してbreakcontinueを使うという方法が存在します。(参考 for文の基本

たとえば、ネスト(入れ子)になっている二つ以上のループがあったとして、通常のbreakでは「一番内側のループ」から抜けることしかできません。ところが、ラベル付きのbreakを使うと、外側の特定のループを指定して抜けることが可能になります。

この仕組みによって、複雑になりがちな多重ループの制御フローを多少柔軟に扱えるようにするのが、Javaのラベルの主な利用目的です。

スポンサーリンク

ラベルの基本的な書き方と使用例

Javaのラベルは、とてもシンプルに「任意のラベル名 + コロン (:)」で表記します。以下のような構造をイメージしてください。

ラベル名:
for (初期化; 条件; 更新) {
    // ループ処理
}

このように、ラベル名のあとにコロンを置き、それに続くブロック(多くの場合はループ)の先頭に指定します。慣例として、ラベルの名前は分かりやすい名称(たとえば outerLoop など)を使うことが多いです。

breakとラベル

ラベルを使って最もよく知られているのが、「ラベル付きbreak」です。普通のbreakは、その時点で実行中の(最も内側の)ループを抜けるだけですが、ラベル付きbreakでは「ラベルがついたループ」まで一気に抜けられます。以下に簡単な例を書きます。

outerLoop:
for (int i = 0; i < 5; i++) {
    for (int j = 0; j < 5; j++) {
        System.out.println("i=" + i + ", j=" + j);
        if (i == 2 && j == 3) {
            break outerLoop;  
            // outerLoopラベルがついたループ(iのループ)を抜ける
        }
    }
}

/*
i=0, j=0
i=0, j=1
i=0, j=2
i=0, j=3
i=0, j=4
i=1, j=0
i=1, j=1
i=1, j=2
i=1, j=3
i=1, j=4
i=2, j=0
i=2, j=1
i=2, j=2
i=2, j=3
*/

この例ではi == 2かつj == 3になった瞬間、outerLoop と名付けた外側のforループ全体を抜けることができます。もしラベルなしのbreakだけだったなら、抜けるのは内側のjループだけなので、外側のループ(iのループ)はまだ続くことになります。ラベル付きbreakを使うことで、特定条件が満たされたら外側まで一気に抜けるという制御を実現できるわけです。

continueとラベル

もう一つ、ラベルと組み合わせられるキーワードとしてcontinueがあります。こちらは、ラベルを付けない場合、内側のループの次の反復(次のループ処理)へ移るためのものですが、ラベルを付けると「ラベルのついたループ」の次の反復へ処理を移します。書き方の例は以下のとおりです。

outerLoop:
for (int i = 0; i < 3; i++) {
    for (int j = 0; j < 3; j++) {
        if (j == 1) {
            continue outerLoop;
            // j==1のとき、内側ループを中断し、outerLoopの次のiのループへ移行
        }
        System.out.println("i=" + i + ", j=" + j);
    }
}

/*
i=0, j=0
i=1, j=0
i=2, j=0
*/

ここでj == 1に遭遇すると、すぐに内側のfor (int j=0;...)をスキップして、外側ループ(outerLoop)の次の反復(次のi++実行後のi値に対応するループ)へ進むことができます。こうすることで、内側のループの一部の条件下で、外側のループを直接動かすような制御が行いやすくなります。

ラベルの内部動作をイメージする

Javaのコードはコンパイルされ、JVM(Java仮想マシン)が動かすバイトコードになります。break ラベル名;continue ラベル名; が出現すると、まずコンパイラは「ラベル名が示しているループがどのブロックなのか」を解析します。解析が済むと、そこから「そのループを抜ける命令」や「そのループの次の反復へ行く命令」という形のバイトコードが生成されます。

もちろん、実際のバイトコードではgoto命令のようなものが使われているかもしれませんが、Javaプログラマがソースコード上で自由にgotoを使ってしまうとコードが複雑になりすぎる恐れがあるため、Java言語としてはあえて「ラベル付きのbreakcontinueという制限された形」でのみ、複数階層のブロックを飛び越える制御を許可しているのです。

どのように「外側のループ」を特定するのか

ラベル付きのループがネストしていたと仮定します。

outerLoop:
for (...) {
    innerLoop:
    for (...) {
        if (何かの条件) {
            break outerLoop;
        }
    }
}

ここでbreak outerLoop;が呼ばれると、コンパイラはソースコード上でouterLoop:と記述されている直後のブロック(ここでは外側のforループ)をひとつの「ジャンプ先」として扱います。実行時には、「コンパイラが割り当てた識別番号」のようなものを手がかりにして、「そのループを抜けるためにはどこへ制御を移す必要があるか」が決定されます。ソースコード上でのラベル名は、人間が読んで分かりやすくするための別名としての機能を果たしているとも言えます。

ラベルを使うときの注意点とメリット・デメリット

注意点

  1. 可読性が低下する可能性
    ラベル付きbreakcontinueは非常に強力ですが、コードの可読性が下がる懸念があります。なぜなら、ソースコードを追っている最中に、通常なら「一番内側のループから抜けるだけだ」と考えていたところが、突然「実は外側のループを抜ける」処理になっているかもしれないからです。コードを読む人が「あれ、このbreakはどこのループを抜けるんだっけ?」と混乱するリスクが高まります。
  2. ラベルの乱用はバグの温床
    ラベルを多用すると、「あれ、このラベルってどこに付いてたんだっけ?」というように、ソースコードが煩雑になります。複数のループに対してあちこちでラベル付きbreakが出現すると、抜けるループの把握が難しくなり、予期せぬ不具合の原因になりやすいです。
  3. 「抜ける・スキップする以外の移動」はできない
    Javaにはもともとgotoがありません。ラベル付きbreakcontinueはあくまで「特定のループを抜ける」か「特定のループの次の反復へ移る」かのみです。「好きな行にジャンプして処理を続行する」といった細かい指定はできません。これはJavaの制限でもあり、ある意味でコード構造を保つための安全策とも言えます。

メリット

  1. ネストの深いループをスムーズに抜けられる
    多重ループがあるとき、通常のbreakcontinueだと対応しきれず、フラグを使って「抜けるかどうか」を管理しながらループを一個ずつ抜けていくような面倒な処理を書かなくても済むのは大きな利点です。たとえば、「とある条件を見つけたら外側のループごと終了する」という操作を一行で済ませられるのは、とても便利です。
  2. 高次の可読性が得られる場合もある
    先述のように可読性を損ねる危険性もありますが、場合によっては「フラグ変数を多用してループの終了を管理する」よりも、ラベル付きbreakを使ったほうがソースコードがすっきりし、読む人にも意図が分かりやすくなることがあります。適切な場所で、かつ適切なラベル名を付けて使うのであれば、メソッド分割などの代替手段よりも理解しやすいケースも存在します。

デメリット

  1. 構造化プログラミングの原則からはやや外れる
    本来の構造化プログラミングでは、「一つの入り口、一つの出口」を守りやすいように工夫するのが理想とされています。ラベル付きbreakは、意図的に複数のループを飛び越える形を容認しているため、どこでループが終了するかを把握しづらい。これは、読み手にとって混乱を招く可能性があります。
  2. ラベルを付けた先が大きいブロックだと影響範囲が広い
    大規模なブロックにラベルを付けてしまうと、そのブロック内の深い位置から突然制御が上の方(外側のループ終了地点)へ飛んでしまうので、コードの流れを頭の中で追うのが難しくなります。たとえば、多数のローカル変数を使っているような長いループにラベルを付けると、コードが煩雑になってしまいがちです。

ラベルの実例をもう少し詳しく解説

ここでは、もう少し現実的な(とはいえ簡略化した)例を考えてみましょう。たとえば、二次元配列の中から特定の値を見つけたら、検索を中断して、すぐに処理を終わりにしたいという場合です。

public class LabelExample {
    public static void main(String[] args) {
        int[][] matrix = {
            {1, 3, 5, 7},
            {2, 4, 6, 8},
            {10, 11, 13, 15},
            {20, 25, 30, 35}
        };

        int target = 13;
        boolean found = false;

        outerLoop: // ここでラベルを宣言
        for (int i = 0; i < matrix.length; i++) {
            for (int j = 0; j < matrix[i].length; j++) {
                if (matrix[i][j] == target) {
                    found = true;
                    System.out.println("見つかりました: i=" + i + ", j=" + j);
                    break outerLoop; 
                    // ラベル outerLoop へ break -> 外側のforループを抜ける
                }
            }
        }

        if (!found) {
            System.out.println("対象の値は見つかりませんでした。");
        }
    }
}

この例では、matrixという二次元配列に格納されている値を順番に探し、もしtarget(ここでは13)が見つかったら、すぐに検索を打ち切って外側のループまで抜けたい、という想定です。もしラベルを使わず、単純にbreakだけを使った場合は内側ループを抜けるだけなので、iを増やして次の行も検索し続けることになります。ラベル付きbreakなら、外側のループを含めて一気に抜けられるため、無駄な探索を省けるわけです。

また、同じような構造でラベル付きcontinueを使って「内側のループの探索をやめ、すぐに外側ループの次の行をチェックしたい」というパターンも応用として考えられます。ただし、多くの場合はラベル付きのbreakを使うシチュエーションが圧倒的に多いかもしれません。

タイトルとURLをコピーしました