Javaのbreakとは?for/while/switch文を抜けるbreakの使い方
Javaのbreakは、ループ文やswitch文をすぐに終了させて、抜けるためのものです。プログラムを作る上では欠かせないものです。
breakはやっていることそのものは大変シンプルで、しかもループ文があるプログラミング言語であれば名前は違えども必ずあるものですので、使い方もそんなに難しいものではありません。知ってるよ、という方も多いでしょう。
ただ、breakには広く知られている基本的なものとは少し違う使い方が、実はあるのです。これを上手く使うとプログラムが作りやすく、読みやすくなるので、この記事をお読みになって、ぜひ使えるようになっていただきたいです!!
この記事では、breakについて基本からちょっとした応用まで、幅広く初心者向けにお伝えします。
※この記事のサンプルは、Java 11の環境で動作確認しています
1.breakの基本
ここではbreakの基本を学びましょう。breakはfor/while/do-while文でのループやswitch文で使うのが普通ですので、それぞれ実例を交えて説明します。
なお、breakを実行する時は「breakする」というように呼ぶのが普通なので、以下でも同じように呼ぶことにします。
1-1.ループ(for/while/do-while文)でのbreak
for文、while文、do-while文などのループ中でbreakすると、現在実行しているループ文そのものを抜けて、ループ文の次の処理に移ります。breakはループ文中のどこからでもできます。
なお、ループ文でbreakするということは、もうそのループ文ではすべきことがないと判断した、ということです。ですので、ループ文でのbreakは、breakすべきか判断するif文と組み合わせるのが普通です。
System.out.println("for文を実行します"); for (int i = 0 ; i < 5; i++) { System.out.println("今は" + i + "です"); if (i == 2) { System.out.println("for文を抜けます"); break; } } System.out.println("for文を抜けました");
実行結果
for文を実行します 今は0です 今は1です 今は2です for文を抜けます for文を抜けました
System.out.println("while文を実行します"); int i = 0; while (i < 5) { System.out.println("今は" + i + "です"); if (i == 2) { System.out.println("while文を抜けます"); break; } i++; } System.out.println("while文を抜けました");
実行結果
while文を実行します 今は0です 今は1です 今は2です while文を抜けます while文を抜けました
System.out.println("do-while文を実行します"); int i = 0; do { System.out.println("今は" + i + "です"); if (i == 2) { System.out.println("do-while文を抜けます"); break; } i++; } while (i < 5); System.out.println("do-while文を抜けました");
実行結果
do-while文を実行します 今は0です 今は1です 今は2です do-while文を抜けます do-while文を抜けました
1-1-1.breakとcontinueの違いはループが終わったあとの動き
breakとcontinueは「今実行しているループを終わらせる」という役割は同じですが、終わらせたあとにどうするかが違います。ループ文そのものを抜けるか(break)、次のループを行うか(continue)です。continueという言葉は「続ける」ですし、イメージ的にもぴったりですよね。
1-2.ネストしたループ文からのbreak
for/while/do-while文はネスト(nest、入れ子、何かの中に何かを含むこと)させられます。ループ分がネストした状態でbreakすると、breakしたところから「最も近い」ループから抜けることになります。
例えば以下では、breakしているところから「上へ」たどっていって、もっとも近いループ文である「jのfor文」をbreakします。ですから「iのfor文」はそのまま何事もなかったかのように続くのです。
System.out.println("ネストしたfor文を実行します"); for (int i = 0; i < 2; i++) { System.out.println("今は i = " + i + "です"); for (int j = 0; j < 2; j++) { System.out.println(" 今は j = " + j + "です"); if (j == 1) { System.out.println(" jのfor文を抜けます"); break; } } } System.out.println("ネストしたfor文を抜けました");
実行結果
ネストしたfor文を実行します 今は i = 0です 今は j = 0です 今は j = 1です jのfor文を抜けます 今は i = 1です 今は j = 0です 今は j = 1です jのfor文を抜けます ネストしたfor文を抜けました
ですので、ループがネストした状態でbreakをすると、意図したとおりのbreakが難しくなります。もうこのループ全体を終わってもいいと、ネストが深いところにあるループ文で分かった場合は面倒です。ループ全体の継続要否を管理する変数を使ったりすることになります。
そういうプログラムは複雑になりがちです。しかし!! そういう用途に使えるよう、いくらループが深くなってもループ全体を一気にbreakするやり方がちゃんとあるのでご安心ください。それについては後述します。
1-3.switch文でのbreak
switch文でのbreakは、ループ文と似たような意味を持ち、switch文から抜ける動きをします。
Javaのswitch文で要注意なのが、breakをしなければ次にあるcaseに処理が移ってしまうことです。この動きを「フォールスルー(fall through)」とも呼ぶのですが、breakはそれを止めるためにもあるものです。
int i = 1; System.out.println("switch文を実行します"); switch (i) { case 1: System.out.println("case 1です"); break; case 2: System.out.println("case 2です"); case 3: System.out.println("case 3です"); break; } System.out.println("switch文を抜けました");
実行結果
switch文を実行します case 1です switch文を抜けました
int i = 2; // iの値は2 System.out.println("switch文を実行します"); switch (i) { case 1: System.out.println("case 1です"); break; case 2: System.out.println("case 2です"); // ここに来て… case 3: System.out.println("case 3です"); // case 2にはbreakがないのでここに来る!! break; } System.out.println("switch文を抜けました");
実行結果
switch文を実行します case 2です case 3です switch文を抜けました
1-4.ネストしたswitch文からのbreak
ループ文と同じように、switch文もネストできます。その場合もfor文と同じで、breakをしたところから「最も近い」switch文をbreakすることになります。
int i = 2; // iの値は2、 System.out.println("switch文を実行します"); switch (i) { case 1: System.out.println("iのcase 1です"); break; case 2: System.out.println("iのcase 2です"); // だからここに来て、 int j = 1; // jの値は1なので… switch (j) { case 1: System.out.println(" jのcase 1です"); // さらにここに来て… break; // ここからbreakするとjのswitchは抜けるけれども、 case 2: System.out.println(" jのcase 2です"); case 3: System.out.println(" jのcase 3です"); } case 3: System.out.println("iのcase 3です"); // iのcase 2はbreakしていないので、ここに来る!! break; } System.out.println("switch文を抜けました");
実行結果
switch文を実行します iのcase 2です jのcase 1です iのcase 3です switch文を抜けました
1-5.ネストしたループ文・switch文からのbreak
もちろん、ループ文とswitch文を組み合わせることはできます。その場合でもルールは同じで、breakをしたところから最も近いモノをbreakすることになります。
System.out.println("for文を実行します"); for (int i = 0; i < 2; i++) { System.out.println("今は i = " + i + "です"); System.out.println("switch文を実行します"); switch (i) { case 0: System.out.println(" case 0です"); break; case 1: System.out.println(" case 1です"); break; case 2: System.out.println(" case 2です"); break; } System.out.println("switch文を抜けました"); } System.out.println("for文を抜けました");
実行結果
for文を実行します 今は i = 0です switch文を実行します case 0です switch文を抜けました 今は i = 1です switch文を実行します case 1です switch文を抜けました for文を抜けました
2.【発展】breakは実はブロックを抜けるためのもの
2-1.breakとラベルとブロックの関係
breakは、実はブロックを抜けるためのものなのです。for/while/do-wihle文、switch文の中でしか使えないものではなかったりします。
ブロックとは、プログラムの中で “{}” で囲まれた部分を表す言葉です。for/while/do-while文の{}、switch文の{}、if文の{}、tryの{}など、それらはすべてブロックです。そして、ブロックはそれらの構文とは関係なく、自由に作ることができるものです。
// ただの変哲もないブロックを作る例 { System.out.println("ただのブロックでーす"); }
今まで見てきたbreakは、抜けるブロックをプログラマが指定しない時のデフォルトの動きです。ループ文やswitch文では、breakをした場所から最も近いループ文・switch文のブロックが選ばれてbreakされるということです。
breakでどのブロックから抜けるかは「ラベル」という文字列で指定できます。ラベルを使って、ブロックに名前をぺたりと貼り付けるイメージです。そうして付けたラベルでbreakすると、そのラベル付けされたブロックを抜けます。
まあ、物は試しです。ブロックにラベルを付けるところから、ラベル付きブロックをbreakするところまでをざっと一通りやってみましょう!!
2-2.ラベルの付け方
2-2-1.ラベルは文字列 + “:”でつける
ラベルは、ラベル文字列の後ろにコロン(:)を付けると宣言できます。ラベル文字列には普通の変数などと同じ文字が使えます。ラベル文字列は、ラベルだとすぐ分かるように、大文字が使われることが普通です。
ラベル文字列:
2-2-2.ブロックへのラベル付けはブロックの先頭で行う
ブロックにラベル付けするには、以下のようにブロックの始まりを表す “{” の前にラベルを置きます。for/while/do-while/switch/tryのブロックにラベル付けをする時も同じで、それぞれのキーワードの前にラベルを置きます。
LABEL: { // ブロックの中身 } LABEL: for(int i = 0; i < 10; i++) { // ブロックの中身 } LABEL: while(true) { // ブロックの中身 } LABEL: do { // ブロックの中身 } while(true); int i = 0; LABEL: switch(i) { case 0: // ブロックの中身 case 1: } LABEL: try { // ブロックの中身 } finally { }
もちろん、ラベル付けしたブロックがたくさんネストしていても大丈夫です。それぞれのブロックに、ラベル付けがされます。
LABEL1: { // ここはLABEL1の中 LABEL2: { // ここはLABEL2の中 LABEL3: { // ここはLABEL3の中 } } }
ただし、同じラベルのブロックの中では、既に使われているラベルを使えません。ブロックの区別が付けられないからで、この考え方は変数のスコープと似ています。
ですが、外にある違うブロック同士なら同じラベルでも問題ありません。なぜかと言えば、ラベルの有効範囲は紐づけたブロックの中だけだからです。
LABEL: { LABEL: { // コンパイルエラー!! ラベル付けされたブロックの中では、同じラベルは使えない } } LABEL: { // 外にある違うブロックへなら同じラベルが使える }
2-2-3.【参考】ラベルは文へ付けられるもの
ラベルは実は文(Statement)として扱えるものに付けられるのです。ただ、今のJavaの構文上で実用的に使えるのは、breakと組み合わせるためのブロックへのラベルです。この記事はbreakが題材なので、breakしたいブロックへのラベルの付け方を紹介しています。
int i = 0; LABEL1: i = 100; // 代入文にラベルを付ける LABEL2: System.out.println(i); // 普通の文にラベルを付ける
2-3.ラベルを使ったブロックからのbreak
2-3-1.ラベル付きブロックをbreakする
いよいよラベル付きブロックをbreakしてみます。以下のように、breakの後ろにbreakしたいラベル文字列を指定しましょう。
break ラベル文字列;
System.out.println("ラベル付きブロックに入ります"); boolean doBreak = true; LABEL: { System.out.println("ラベル付きブロックに入りました"); // これが実行された後、 if (doBreak) { System.out.println("LABELをbreakします"); // ここに来て、 break LABEL; // ラベル付きブロックをbreak!! } System.out.println("ラベル付きブロックのbreakした後です"); // すると、ここには来ず、 } System.out.println("ラベル付きブロックから抜けました"); // ブロックを抜けたここに来る
実行結果
ラベル付きブロックに入ります ラベル付きブロックに入りました LABELをbreakします ラベル付きブロックから抜けました
そして、ネストしたブロックでも、親のブロックに付けたラベルを指定すれば、一気に抜けることができるのです!!
System.out.println("ネストしたラベル付きブロックに入ります"); boolean doBreak = true; LABEL1: { System.out.println("LABEL1に入りました"); // これが実行された後、 LABEL2: { System.out.println("LABEL2に入りました"); // さらにこれが実行された後、 if (doBreak) { System.out.println("LABEL1をbreakします"); // ここに来て、 break LABEL1; // 親ブロックであるLABEL1をbreak!! } System.out.println("LABEL2でbreakした後です"); // すると、ここには来ず、 } System.out.println("LABEL1に戻ってきました"); // さらに、ここにも来ず、 } System.out.println("ネストしたラベル付きブロックから抜けました"); // ブロックを抜けたここに来る
実行結果
ネストしたラベル付きブロックに入ります LABEL1に入りました LABEL2に入りました LABEL1をbreakします ネストしたラベル付きブロックから抜けました
2-3-2.ラベル付きループ文/switch文/try文をbreakする
では、ループ文/switch文/try文でラベル付きbreakをしてみましょう。それぞれで、ネストが最も深いところから、ラベルが付いたブロックを直接breakできていることが分かると思います。
System.out.println("ラベル付きfor文に入ります"); LABEL1: for (int i = 0; i < 2; i++) { System.out.println("今は i = " + i + "です"); LABEL2: for (int j = 0; j < 2; j++) { System.out.println(" 今は j = " + j + "です"); if (j == 1) { System.out.println("LABEL1をbreakします"); break LABEL1; } } } System.out.println("ラベル付きfor文から抜けました");
実行結果
ラベル付きfor文に入ります 今は i = 0です 今は j = 0です 今は j = 1です LABEL1をbreakします ラベル付きfor文から抜けました
System.out.println("ラベル付きswtich文に入ります"); int i = 1; LABEL1: switch (i) { case 1: System.out.println("iのcase 1です"); int j = 1; LABEL2: switch (j) { case 1: System.out.println(" jのcase 1です"); System.out.println("LABEL1をbreakします"); break LABEL1; case 2: System.out.println(" jのcase 2です"); } case 2: System.out.println("iのcase 2です"); } System.out.println("ラベル付きswtich文を抜けました");
実行結果
ラベル付きswtich文に入ります iのcase 1です jのcase 1です LABEL1をbreakします ラベル付きswtich文を抜けました
なお、try-finallyの場合はbreakした後にfinallyが実行されます。他の文と少し違いますので覚えておきましょう。
System.out.println("ラベル付きtry文に入ります"); boolean doBreak = true; LABEL1: try { System.out.println("LABEL1のtry文に入りました"); LABEL2: try { System.out.println("LABEL2のtry文に入りました"); if (doBreak) { System.out.println("LABEL1をbreakします"); break LABEL1; } System.out.println("LABEL2でのbreak後です"); } finally { System.out.println("LABEL2のfinallyです"); } System.out.println("LABEL1の後部分です"); } finally { System.out.println("LABEL1のfinallyです"); } System.out.println("ラベル付きtry文が終わりました");
実行結果
ラベル付きtry文に入ります LABEL1のtry文に入りました LABEL2のtry文に入りました LABEL1をbreakします LABEL2のfinallyです LABEL1のfinallyです ラベル付きtry文が終わりました
2-4.ラベルを使ったbreakはどういう時に使うのか
ラベルを使ったbreakは、以下を行いたい時に使います。特に2はループに限らず使えるのがいいところです。
- 深いネストから一気に脱出する時
- ブロック全体の実行を終えてもいい時
1.は、ネストを構成するループ文やswitch文全体で、ループ継続状態を管理する必要がなくなります。また、ネストした全てのブロックを一気に脱出しなくてもよく、途中での必要なブロックだけにもできるので、柔軟に活用できます。
2.は「この条件を満たせばブロックの中はこれ以上実行しなくてもいい、でもまだ後には処理があるのでメソッドからのreturnはできない」というケースで使うと効果的でしょう。if文などが減らせるので、プログラムが簡素化できます。
なお、ラベル付きbreakはいわゆるGOTO文ではありません。ずっとお伝えしているとおり、breakしたブロックを抜けて、ブロックの次にある処理に速やかに移るためのものです。
GOTO文で出来るような、処理をさかのぼってのループなどは、ラベル付きbreakではできません。メソッドをまたがったbreakもできません。ですから、Javaではプログラムはは必ず上から下に順番どおりに流れるのです。
3.【発展】breakのいろいろ
3-1.Streamではbreakできない
Java 8から追加されたStream APIはループ文に似た処理ができます。ですが、あくまで「ループに似たもの」であり、構文上のループ文ではないのでbreakはできません。
List<String> list = Arrays.asList("A", "B", "C"); list.stream().forEach(s -> { break; // ←コンパイルエラー!! for/whileのようなループ文の中ではないため、breakは行えない });
Streamは、正確に言えば何かの集まりにある要素一つ一つに対して処理を並行にやるための仕組みであって、ループ的に何かをするわけではないからです。ですので、要素一つに対する処理をreturnで中断することはできますが、ループ文と同じようにStream全体を中断することはできないのです。
ですので、ループ中で中断する必要があるなら従来どおりfor/while文を使うことになります。また、似た結果を得るなら、Stream上で不要なデータを除外するために、中間処理としてfilterやtakeWhile(Java 9以降)をするなどの方法があります。
3-2.ループではなるべく早いタイミングでbreakすべき
ループ中でbreakできるなら、なるべく早いタイミングで行うのがいいと考えます。returnには早期returnという考え方がありますが、breakやcontinueにも同じようなことが言えます。
ループ中ではbreakできるなら早くやってしまえば、そこから後ろの処理ではbreakする条件以外であるということが明確になりますので、ループ文などの全体的な見通しが良くなります。
今のプログラムでbreakをしてる箇所は、本当にそこで行わなければならないのか考えてみましょう。もっとシンプルにできるかもしれません。ただ、最初にbreakをしたいがために自然なロジックを捻じ曲げるべきではありませんので、バランス感覚が大事です。
for (int i = 0; i < 10; i++) { if (breakできる条件) { break; } // ここから後ろはbreakできる条件以外だと確定する } for (int i = 0; i < 10; i++) { if (何かの条件1) { if (何かの条件2) { } } else if (何かの条件3) { // 本当にここでなければbreakができないのか? if (breakできる条件) { break; } } }
4.まとめ
この記事ではbreakの使い方についてお伝えしてきました。breakはループ文やswitch文を途中で中断して、後続の処理を実行するためにあるものです。
そして、実はbreakは、ラベル付きブロックの中断もできます。ラベルは、ループ文、switch文、try文などの他にも、ごく普通のブロックにも付けられます。ラベル付きブロックのbreakは上手く使えばプログラムを分かりやすくする効果がありますので、活用できそうか考えてみましょう。
コメント