ラベル付きブレークを使用することはJavaの良い習慣ですか?


82

私は2001年の古いコードを見つめていましたが、次のステートメントに出くわしました。

   else {
     do {
       int c = XMLDocumentFragmentScannerImpl.this.scanContent();
       if (c == 60) {
         XMLDocumentFragmentScannerImpl.this.fEntityScanner.scanChar();
         XMLDocumentFragmentScannerImpl.this.setScannerState(1);
         break label913;
       }

私はこれまでこれを見たことがなく、ここでラベルの付いた切れ目を発見しました:

http://docs.oracle.com/javase/tutorial/java/nutsandbolts/branch.html

これは本質的に次のように機能しませんgotoか?それを使用することも良い習慣ですか?不安になります。


2
どこでlabel913定義されていますか?
ニコライ・クズネツォフ

1
この場合、かなり大きなswitchステートメントの終わりの約200行後。
avgvstvs 2013

続行すると、ラベル名により、ブロックがラベルでマークされたコードの部分を処理するコードが作成されます
user_CC 2013

5
この場合、問題はswitchステートメントのサイズです。
Cyrille Ka

8
これは生成されたコードのように見えます(実際にはパーサー)。手書きのコードでは使用されない、生成されたコードで使用されるトリックがたくさんあります(主に生成が簡単になるため)。
パルジファル2013

回答:


120

いいえ、制御フローの別の部分に「移動」することはできないため、後藤とは異なります。

リンクしたページから:

breakステートメントは、ラベル付きステートメントを終了します。制御の流れをラベルに転送しません。制御フローは、ラベル付けされた(終了した)ステートメントの直後のステートメントに転送されます。

つまり、現在実行されているループのみを中断できます。

この例を考えてみましょう。

first:
for( int i = 0; i < 10; i++) {
  second:
  for(int j = 0; j < 5; j ++ )
  {
    break xxx;
  }
}

third:
for( int a = 0; a < 10; a++) {

}

あなたは置き換えることができxxxfirstsecond(外側または内側のループを破るために)、あなたが打ったときの両方のループが、実行されているので、break文は、しかし、交換xxxにはthirdコンパイルされません。


1
@user_CCそうではありませんが、それでも制御フローから飛び出すことはできません。ラベル付き続行は、ラベル付きループの次の反復を開始するだけで、そのループがすでに実行されている間のみ機能します。
トーマス

3
したがってbreak first;、を使用した場合、コンパイラはすぐにその行に戻り、first:それらのループを再度実行しますか?
ungeheuer 2015年

15
@JohnnyCoderいいえ、というラベルの付いたループbreak first;中断(終了)します。firstつまり、third。で続行します。一方continue first;、の現在の反復を終了し、それに沿ってfirst中断secondし、iinの次の値を続行しfirstます。
トーマス

1
@TheTechExpertGuyええ、そうではありません。それはgotoif-elseステートメントとほぼ同じ意味でのみ似ています。Gotoは、「どこにでも移動」のように、ループの前の位置など、はるかに「柔軟性」があります(実際、古い言語がループを実装するために使用した方法です。「条件が満たされない場合は、ループ本体の先頭に移動します」)。 。私は最近それらに対処しなければならなかったし、私を信じています:gotoラベルの付いた休憩はそうではありませんが、お尻の本当の痛みです。
トーマス

1
@TheTechExpertGuyどういたしまして:)-あなたは初心者なので、もう一度アドバイスをしたいと思いますgoto。C++でサポートされていても、まったく使用しないでください。ほとんどの場合、後で多くの頭痛の種を減らすことができる問題を解決するためのより良い方法があります。誘惑が来るかもしれません...抵抗してください!
トーマス

16

gotoラベル付きステートメント(通常はループ構造)の最後にのみ制御を送信するため、それほどひどいことではありません。作るものgotoラベルを含むunlikeableはそれがどこかに任意の分岐であるということですが、あなたは自家製ルーピング行動を持つことができるように、メソッドのソースに高いアップを発見しました。Javaのラベル解除機能では、制御が進むだけなので、そのような狂気は許されません。

ネストされたループから抜け出す必要がある状況では、約12年前に一度だけ使用しましたが、より構造化された代替手段により、ループ内のより複雑なテストが可能になります。あまり使用することはお勧めしませんが、自動的に悪いコードの臭いとしてフラグを立てることはしません。


6
@Boann:gotoがすべてのコンテキストで完全に悪であると示唆するつもりはありません。これは、Javaの作成者が問題に値しないと判断した、ポインタ演算、演算子のオーバーロード、明示的なメモリ管理などの1つにすぎません。そして、90%以上のgotoであると思われるCOBOLプログラムを読んだことがあるので、彼らがそれをJavaから除外したのは悲しいことではありません。
ネイサンヒューズ

1
ああ、あなたは初めて@NathanHughesが正しかった、gotoはかなりひどい悪だ。もちろん、すべての状況にあるわけではありませんが、そうです、私はそれを見逃すことはありません。
知覚

2
gotoはラベル@Boannよりも「便利」かもしれませんが、それはメンテナンスコストが完全に上回っています。ラベルはgotosのような魔法の杖ではありません。それは良いことです。
DavidS 2016年

9

交換はいつでも可能です break新しい方法はです。

2つのリストの一般的な要素をコードチェックすることを検討してください。

List list1, list2;

boolean foundCommonElement = false;
for (Object el : list1) {
    if (list2.contains(el)) {
        foundCommonElement = true;
        break;
    }
}

次のように書き直すことができます。

boolean haveCommonElement(List list1, List list2) {
    for (Object el : list1) {
        if (list2.contains(el)) {
            return true;
        }
    }
    return false;
}

もちろん、2つのリスト間で共通の要素をチェックlist1.retainAll(new HashSet<>(list2))するには、メソッドを使用しO(n)O(n)追加のメモリを使用するか、で両方のリストを並べ替えてからでO(n * log n)共通の要素を見つけることをお勧めしO(n)ます。


私の意見では、これはほとんどの場合正しいアプローチです。gotoそれはあなたに非常に多くの場所を送る可能性があるので(潜在的に)有害です-何が達成されているのかはまったく明らかではありません。ヘルパー関数を作成することは、あなたが何をしようとして、あなたはそれをやろうとしている場所へと隣の男に明確な境界を与え、そしてそれは、機能の一部という名前にあなたに機会を与えてくれます。この方法ではるかに明確になります。
ethanbustad

@ethanbustadしかし、これはラベル付けされたものでありbreak、ではありませんgoto。確かに、ここに示されているリファクタリングはどちらの場合も良い考えです。しかし、labeledbreakはそれほどモデレートされておらず、コードをそのままスパゲッティングする傾向gotoがあります。
underscore_d 2017年

この例はひどいです、もしブレークが機能しないなら、ここのラベルはただ役に立たないです。
Diracnote

8

この回答の残りの部分を読む前に、「有害と見なされるステートメントに移動」をお読みください。あなたがそれを完全に読みたくないのであれば、これが私が重要なポイントと考えるものです:

go toステートメントを無制限に使用すると、プロセスの進行状況を説明するための意味のある座標のセットを見つけることが非常に困難になるという即時の結果が生じます。

言い換えると、問題gotoは、プログラマーがその時点でのプログラムの状態を理解していなくても、プログラムがコードのブロックの途中に到着する可能性があることです。標準のブロック指向構造は、状態遷移を明確に描写するように設計されています。ラベル付きbreakは、プログラムを特定の既知の状態(含まれているラベル付きブロックの外側)に移動することを目的としています。

実際の命令型プログラムでは、状態はブロック境界によって明確に描写されていないため、ラベル付けbreakが適切かどうかは疑問です。ブロックがブロックの外側から見える状態に変化し、ブロックを出るポイントが複数ある場合、labeledbreakはプリミティブと同等gotoです。唯一の違いは、状態が不確定なブロックの途中に着地する可能性ではなく、状態が不確定な新しいブロックを開始することです。

したがって、一般的に、私はラベルbreakを危険と見なします。私の意見では、それは、囲んでいるスコープへのアクセスが制限された状態で、ブロックを関数に変換する必要があることを示しています。

ただし、このサンプルコードは明らかにパーサジェネレータの製品でした(OPはXercesソースコードであるとコメントしました)。また、パーサージェネレーター(または一般的なコードジェネレーター)は、状態について完全な知識を持っており、人間がそれを理解する必要がないため、生成するコードを自由に使用できることがよくあります。


同意しません。breakいくつかの場面で非常に便利です。たとえば、いくつかの順序付けられたソースから来る可能性のある値を探している場合です。あなたは一つずつ試みています、そして最初のものが見つかるとき、あなたはbreak。よりエレガントなコードですif / else if / else if / else if。(少なくとも行数は少なくなります。)すべてをメソッドのコンテキストに移動することは、常に実用的であるとは限りません。もう1つのケースは、いくつかのネストされたレベルから条件付きでブレークしている場合です。つまり、が気に入らない場合は、メソッドの終わりを除いて他のどこもbreak気に入らないのreturnです。
Ondraジシュカ

文脈のために; 「有害考慮GO TO文は、」書かれていたときのようなものdowhile存在していなかったと誰もが使っていたif(.. ) goto代わりに、どこにでも。これは、言語設計者がdoやなどのサポートを追加することを奨励することを目的としていましたwhile。悲しいことに、時流が転がり始めると、それは無知な人々breakが「一度だけ実行される」ループに入れて、誰が何を知っているかについて例外を投げることによって煽られる誇大宣伝列車に成長しましたgoto
ブレンダン

1

これはgoto、フロー制御を逆方向にジャンプするステートメントとは異なります。ラベルは、中断が発生した場所を(プログラマーに)示すだけです。また、フロー制御はbreak。の後の次のステートメントに移されます。

個人的には、数千行に相当するコードを書き始めると意味がなくなるので、あまり役に立たないと思います。しかし、繰り返しになりますが、それはユースケースに依存します。


for(int i = 0; i < 1; i++) { do_something(); if(I_want_to_go_backwards) { i = 0; continue; } }
ブレンダン

1

意図をより明確に表現する方法は、少なくとも私の意見では、ループを含むコードフラグメントを別のメソッドに入れて、それから単純にreturnすることです。

たとえば、次のように変更します。

someLabel:
for (int j = 0; j < 5; j++)
{
    // do something
    if ...
        break someLabel;
}

これに:

private void Foo() {
    for (int j = 0; j < 5; j++)
    {
        // do something
        if ...
            return;
    }
}

これは、将来(または将来あなたが)コードを操作する可能性のある他の言語に堪能な開発者にとっても慣用的です

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.