Javaメソッドで無用な戻りを回避するにはどうすればよいですか?


115

理論的には、return2つのforループにネストされたステートメントに常に到達する状況にあります。

コンパイラーはこれに同意せずreturnforループ外のステートメントを要求します。私の現在の理解を超えているこの方法を最適化するエレガントな方法を知りたいのですが、私が試みたbreakの実装はどれもうまくいかないようです。

添付されているのは、ランダムな整数を生成し、intパラメータとしてメソッドに渡された範囲内で生成された2番目のランダムな整数が見つかるまで繰り返される反復を返す割り当てからのメソッドです。

private static int oneRun(int range) {
    int[] rInt = new int[range+1]; // Stores the past sequence of ints.
    rInt[0] = generator.nextInt(range); // Inital random number.

    for (int count = 1; count <= range; count++) { // Run until return.
        rInt[count] = generator.nextInt(range); // Add randint to current iteration.
        for (int i = 0; i < count; i++) { // Check for past occurence and return if found.
            if (rInt[i] == rInt[count]) {
                return count;
            }
        }
    }
    return 0; // Never reached
}

9
最後の項目に到達しない場合はwhile(true)、インデックス付きループではなく、ループを使用できます。これは、ループが戻らないことをコンパイラーに伝えます。
ボリス・ザ・スパイダー

101
範囲を0(または1未満の他の数)で関数を呼び出しoneRun(0)ます()と、到達不能にすばやく到達することがわかりますreturn
mcfedr

55
指定された範囲が負の場合、リターンに達します。また、入力範囲の検証が0であるため、現在他の方法でそれをキャッチしていません。
2017

4
もちろん、それが本当の答えです。全然無駄じゃない!
リスター氏2017

4
@HopefullyHelpfulいいえ、nextIntスローされるのは例外をスローするためですrange < 0リターンに到達する唯一のケースはいつrange == 0
ですか

回答:


343

コンパイラーのヒューリスティックでは、最後のを省略できませんreturn。到達しないと確信している場合はthrow、状況を明確にするためにa に置き換えます。

private static int oneRun(int range) {
    int[] rInt = new int[range+1]; // Stores the past sequence of ints.
    rInt[0] = generator.nextInt(range); // Inital random number.

    for (int count = 1; count <= range; count++) {
        ...
    }

    throw new AssertionError("unreachable code reached");
}

135
読みやすさだけでなく、コードに問題があるかどうかを確認できます。ジェネレーターにバグがある可能性は完全にあり得ます。
JollyJoker 2017

6
コードがその「到達不能なコード」に絶対に到達できないことが確実である場合は、発生する可能性があるすべてのエッジケースに対していくつかの単体テストを作成します(範囲は0、範囲は-1、範囲は最小/最大整数です)。今はプライベートな方法かもしれませんが、次の開発者はその方法を維持しないかもしれません。予期しない値を処理する方法(例外をスローする、エラー値を返す、何もしない)は、メソッドの使用方法によって異なります。私の経験では、通常、エラーを記録して戻りたいだけです。
リックライカー2017

22
多分それはthrow new AssertionError("\"unreachable\" code reached");
ボヘミアン

8
制作プログラムで自分の答えに正確に書いたものを書きます。
John Kugelman 2017

1
与えられた例では、throw決して到達しないa を追加するよりも対処するためのより良い方法があります。根本的な問題についてあまり考えずに、「すべてを統治するための1つの解決策」をすばやく適用したいと考える人があまりにも多い。しかし、プログラミングは単なるコーディング以上のものです。特にアルゴリズムに関しては。与えられた問題を(注意深く)検査すると、外側のforループの最後の反復を除外できるため、「役に立たない」戻り値を有用なものに置き換えることができることがわかります(この回答を参照)。
a_guest 2018年

36

以下のよう@BoristheSpiderが指摘よろしい二作ることができreturn文が意味的に到達不能です:

private static int oneRun(int range) {
    int[] rInt = new int[range+1]; // Stores the past sequence of ints.
    int count = 0;

    while (true) {
        rInt[count] = generator.nextInt(range); // Add randint to current iteration.
        for (int i = 0; i < count; i++) { // Check for past occurence and return if found.
            if (rInt[i] == rInt[count]) {
                return count;
            }
        }
        count++;
    }
}

コンパイルして正常に実行します。そして、もしあなたがこれを手に入れたら、ArrayIndexOutOfBoundsException明示的に何かを投げる必要なしに、実装が意味的に間違っていることを知るでしょう。


1
厳密に言うと、条件はループの制御に実際には使用されないため、そこに存在してはなりません。しかし、私はfor(int count = 0; true; count++)代わりにこれを書きます。
cmaster-2017年

3
@cmaster:省略できますtruefor(int count = 0; ; count++) …
Holger

@ホルガーああ。これはJavaに関するものなので、確信が持てませんでした。また、C / C ++にずっと興味があるので、何年もその言語に触れていません。そのため、の使用を見て、while(true)おそらくこの点でjavaの方が少し厳しいと思いました。心配する必要はありませんでした...実は、省略されたtrueバージョンの方がずっと好きです。それは、見るべき条件がないことを視覚的に明らかにします:-)
cmaster-モニカを復活させる

3
「for」に切り替えたくない。「while-true」は、内部の何かがそれを壊さない限り、ブロックが無条件にループすることが期待されるという明らかなフラグです。条件が省略された "for"は、それほど明確に通信しません。より簡単に見落とされる可能性があります。
コロディアス2017

1
@ l0b0警告はコード品質に関連しているので、質問全体はおそらくコードレビューに適しています。
スルタン

18

2つのforループから抜け出すことについて質問したので、ラベルを使用してそれを行うことができます(以下の例を参照)。

private static int oneRun(int range) {
    int returnValue=-1;

    int[] rInt = new int[range+1]; // Stores the past sequence of ints.
    rInt[0] = generator.nextInt(range); // Inital random number.

    OUTER: for (int count = 1; count <= range; count++) { // Run until return.
        rInt[count] = generator.nextInt(range); // Add randint to current iteration.   
        for (int i = 0; i < count; i++) { // Check for past occurence and return if found.
            if (rInt[i] == rInt[count]) {
                returnValue = count;
                break OUTER;
            }
        }
    }
    return returnValue;
}

returnValue初期化せずに使用できるため、これはコンパイルに失敗しませんか?
パート

1
最も可能性が高い。投稿の目的は、元のポスターがこれについて尋ねたように、両方のループから抜け出す方法を示すことでした。OPは、実行がメソッドの最後に到達しないことを確信していたため、returnValueを宣言するときに任意の値に初期化することによって、何をもたらしたかを修正できます。
David Choweller

4
ラベルは、どうしても避けたい種類のことではありませんか?
Serverfrog 2017

2
あなたは後藤を考えていると思います。
David Choweller 2017

4
高(-っぽい)レベルのプログラミング言語のラベル。絶対に野蛮な...
xDaizu

13

一方、アサートは優れた高速ソリューションです。一般に、この種の問題は、コードが複雑すぎることを意味します。私があなたのコードを見ているとき、配列に以前の数値を保持させたくないのは明らかです。あなたはSet

Set<Integer> previous = new HashSet<Integer>();

int randomInt = generator.nextInt(range);
previous.add(randomInt);

for (int count = 1; count <= range; count++) {
    randomInt = generator.nextInt(range);
    if (previous.contains(randomInt)) {
       break;
    }

    previous.add(randomInt);
}

return previous.size();

ここで、返されるのは実際にはセットのサイズです。コードの複雑さが2次から線形に減少し、すぐに読みやすくなりました。

これで、そのcountインデックスも必要ないことに気付くことができます。

Set<Integer> previous = new HashSet<Integer>();

int randomInt = generator.nextInt(range);

while (!previous.contains(randomInt)) {          
    previous.add(randomInt);      
    randomInt = generator.nextInt(range);
}

return previous.size();

8

戻り値は外部ループの変数に基づいているので、外部ループの条件を単にに変更してcount < range、関数の最後でこの最後の値(先ほど省略したもの)を返すことができます。

private static int oneRun(int range) {
    ...

    for (int count = 1; count < range; count++) {
        ...
    }
    return range;
}

この方法では、決して到達しないコードを導入する必要はありません。


しかし、これは、一致が見つかったときにループの2つの層からの脱出を必要としませんか?forステートメントを1つに減らす方法がわかりません。新しい乱数を生成し、前の乱数と比較してから、別の乱数を生成する必要があります。
オリバーベニング

まだ2つの入れ子になったforループが残っています(2つ目のforループを省略しただけです...)が、最後の反復によって外側のループが削減されました。この最後の反復に対応するケースは、最後のreturnステートメントによって個別に処理されます。これは例のエレガントなソリューションですが、このアプローチが読みにくくなるシナリオがあります。たとえば、戻り値が内側のループに依存している場合、最後の単一のreturnステートメントの代わりに、追加のループ(外側のループの最後の反復の内側のループを表す)が残されます。
a_guest 2017

5

"result"などの一時変数を使用して、内部の戻り値を削除します。forループをwhileループに適切な条件で変更します。私にとっては、関数の最後のステートメントとしてリターンを1つだけ持つほうが常にエレガントです。


さて、外側のwhile条件である可能性がある場合は、非常に内側に見えます。forと同等の時間が常にあることに注意してください(常にすべての反復を実行することを計画していないため、よりエレガントです)。これらの2つの入れ子になったforループは確かに単純化できます。これらのコードはすべて「複雑すぎる」においがします。
デビッド

@ Solomonoff'sSecretあなたの発言はばかげています。これは、「一般的に」2つ以上のreturnステートメントを目指す必要があることを意味します。面白い。
デビッド

@ Solomonoff'sSecretこれは好ましいプログラミングスタイルの問題であり、先週のラメだった今週のクールな機能に関係なく、出口点が1つだけであることの明確な長所があり、メソッドの最後に(とのループについて考えるだけです)多くはその中にreturn result散りばめられており、それからresultそれを返す前に見つかったものについてもう1つのチェックをしたいと考えています。これは単なる例です)。短所もあるかもしれませんが、「一般的に、1つのリターンだけを持っている十分な理由はありません」などの幅広いステートメントは水を保持しません。
SantiBailors 2017

@David言い換えさせてください。1つのリターンのみを返す一般的な理由はありません。特定のケースではそれが理由かもしれませんが、通常それは最悪の場合、貨物カルトです。
モニカを

1
それは、コードをより読みやすくする問題です。あなたの頭の中で「これなら、私たちが見つけたものを返します。そうでなければ続行します。何かが見つからなかった場合、この別の値を返します」と言います。次に、コードには2つの戻り値が必要です。次の開発者がすぐに理解できるように、常にコーディングします。私たちの目には2つのように見えても、コンパイラーはJavaメソッドからの戻り点を1つだけ持っています。
リックライカー

3

多分これはあなたがあなたのコードを書き直すべきであることを示しています。例えば:

  1. 整数0 ..範囲1の配列を作成します。すべての値を0に設定します。
  2. ループを実行します。ループで、乱数を生成します。リストをそのインデックスで見て、値が1かどうかを確認します。1の場合は、ループを抜けます。それ以外の場合は、そのインデックスの値を1に設定します
  3. リスト内の1の数を数え、その値を返します。

3

returnステートメントがあり、ループ内にループがあるメソッドには、常にループの外側にreturnステートメントが必要です。ループ外のこのステートメントに到達することはありません。このような場合、不必要なreturnステートメントを回避するために、メソッドの最初、つまりそれぞれのループの前と外側で、それぞれの型の変数(この場合は整数)を定義できます。ループ内の目的の結果に到達したら、それぞれの値をこの事前定義された変数に帰することができ、それをループ外のreturnステートメントに使用できます。

rInt [i]がrInt [count]と等しい場合にメソッドが最初の結果を返すようにするため、rInt [i]がrInt [count]と等しい場合にメソッドが最後の結果を返すため、上記の変数のみを実装するだけでは不十分です。1つのオプションは、目的の結果が得られたときに呼び出される2つの「breakステートメント」を実装することです。したがって、メソッドは次のようになります。

private static int oneRun(int range) {

        int finalResult = 0; // the above-mentioned variable
        int[] rInt = new int[range + 1];
        rInt[0] = generator.nextInt(range);

        for (int count = 1; count <= range; count++) {
            rInt[count] = generator.nextInt(range);
            for (int i = 0; i < count; i++) {
                if (rInt[i] == rInt[count]) {
                    finalResult = count;
                    break; // this breaks the inside loop
                }
            }
            if (finalResult == count) {
                break; // this breaks the outside loop
            }
        }
        return finalResult;
    }

2

到達できないステートメントが発生した場合、例外をスローする必要があることに同意します。同じメソッドがこれをより読みやすい方法で実行できる方法を示したかっただけです(Java 8ストリームが必要です)。

private static int oneRun(int range) {
    int[] rInt = new int[range + 1];
    return IntStream
        .rangeClosed(0, range)
        .peek(i -> rInt[i] = generator.nextInt(range))
        .filter(i -> IntStream.range(0, i).anyMatch(j -> rInt[i] == rInt[j]))
        .findFirst()
        .orElseThrow(() -> new RuntimeException("Shouldn't be reached!"));
}

-1
private static int oneRun(int range) {
    int result = -1; // use this to store your result
    int[] rInt = new int[range+1]; // Stores the past sequence of ints.
    rInt[0] = generator.nextInt(range); // Inital random number.

    for (int count = 1; count <= range && result == -1; count++) { // Run until result found.
        rInt[count] = generator.nextInt(range); // Add randint to current iteration.   
        for (int i = 0; i < count && result == -1; i++) { // Check for past occurence and leave after result found.
            if (rInt[i] == rInt[count]) {
                result = count;
            }
        }
    }
    return result; // return your result
}

ループ内で省略できる多くresult == -1チェックを行うため、コードも非効率的returnです
Willem Van Onsem
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.