まず最初に、「ラベル」という言葉を聞くと、どのようなイメージをもつでしょうか。日常生活では、荷物やファイルなどに貼り付ける付箋のような「ラベル」を思い浮かべるかもしれませんが、Javaの文脈では、ラベルは「特定のコードブロック(多くの場合はループ)に名前をつける仕組み」を指します。
Javaの場合、C言語のようにgoto文(指定した行に直接ジャンプする命令)は使えません。Javaは「構造化プログラミング」の考え方を基本としており無制限なジャンプは推奨していないため、gotoはキーワードとして予約はされていても、実際には利用できません。その代わり複数のループが重なった状態で「外側の特定のループだけを抜けたい」あるいは「外側のループを次の反復処理に進めたい」という場合に、Javaでは「ラベル」を指定してbreakやcontinueを使うという方法が存在します。(参考 for文の基本)
たとえば、ネスト(入れ子)になっている2つ以上のループがあったとして、通常の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言語としてはあえて「ラベル付きのbreakやcontinueという制限された形」でのみ、複数階層のブロックを飛び越える制御を許可しているのです。
どのように「外側のループ」を特定するのか
ラベル付きのループがネストしていたと仮定します。
outerLoop:
for (...) {
innerLoop:
for (...) {
if (何かの条件) {
break outerLoop;
}
}
}
ここでbreak outerLoop;が呼ばれると、コンパイラはソースコード上でouterLoop:と記述されている直後のブロック(ここでは外側のforループ)をひとつの「ジャンプ先」として扱います。
実行時には、「コンパイラが割り当てた識別番号」のようなものを手がかりにして、「そのループを抜けるためにはどこへ制御を移す必要があるか」が決定されます。ソースコード上でのラベル名は、人間が読んで分かりやすくするための別名としての機能を果たしているとも言えます。
ラベルを使うときの注意点とメリット・デメリット
注意点
- 可読性が低下する可能性
ラベル付きbreakやcontinueは非常に強力ですが、コードの可読性が下がる懸念があります。なぜなら、ソースコードを追っている最中に、通常なら「一番内側のループから抜けるだけだ」と考えていたところが、突然「実は外側のループを抜ける」処理になっているかもしれないからです。コードを読む人が「あれ、このbreakはどこのループを抜けるんだっけ?」と混乱するリスクが高まります。 - ラベルの乱用はバグの温床
ラベルを多用すると、「あれ、このラベルってどこに付いてたんだっけ?」というように、ソースコードが煩雑になります。複数のループに対してあちこちでラベル付きbreakが出現すると、抜けるループの把握が難しくなり、予期せぬ不具合の原因になりやすいです。 - 「抜ける・スキップする以外の移動」はできない
Javaにはもともとgotoがありません。ラベル付きbreakやcontinueはあくまで「特定のループを抜ける」か「特定のループの次の反復へ移る」かのみです。「好きな行にジャンプして処理を続行する」といった細かい指定はできません。これはJavaの制限でもあり、ある意味でコード構造を保つための安全策とも言えます。
メリット
デメリット
ラベルの実例をもう少し詳しく解説
ここでは、もう少し現実的な(とはいえ簡略化した)例を考えてみましょう。たとえば、二次元配列の中から特定の値を見つけたら、検索を中断して、すぐに処理を終わりにしたいという場合です。
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を使うシチュエーションが圧倒的に多いかもしれません。
