如何避免Java方法中无用的返回?


115

从理论上讲,我总是遇到return嵌套在两个for循环中的语句。

编译器不同意并且需要循环return外的语句for。我想知道一种优雅的方法来优化此方法,这超出了我目前的理解,并且我尝试的break实现似乎都没有用。

附带的是一个赋值方法,该赋值方法生成随机整数,并返回循环进行的迭代直到找到第二个随机整数为止,该迭代在作为int参数传递到该方法的范围内生成。

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)而不是索引循环。这告诉编译器循环将永远不会返回。
蜘蛛鲍里斯(Boris the Spider)

101
调用范围为0(或任何其他小于1的数字)的函数(oneRun(0)),您会看到您很快达到了无法访问的水平return
mcfedr

55
当提供的范围为负数时,将达到返回值。您还对输入范围进行了0验证,因此您目前没有以其他任何方式捕获它。
HopefulHelpful

4
@HopefullyHelpful当然,这是真正的答案。根本不是无用的回报!
李斯特先生,

4
@HopefullyHelpful否,因为nextInt引发异常range < 0的唯一返回返回值的情况是range == 0
njzk2

Answers:


343

编译器的试探法永远不会让您忽略最后一个return。如果您确定永远不会达到目标,我将用替换throw为明确情况。

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

6
如果您完全确定您的代码永远无法到达“无法访问的代码”,那么请为可能出现的所有极端情况创建一些单元测试(范围为0,范围为-1,范围为min / max int)。现在可能是私有方法,但是下一个开发人员可能不会保持这种方式。您如何处理意外值(引发异常,返回错误值,什么也不做)取决于您将如何使用该方法。以我的经验,您通常只想记录一个错误并返回。
里克·赖克

22
也许应该是throw new AssertionError("\"unreachable\" code reached");
Bohemian

8
我会确切地写出我在生产程序中的答案。
John Kugelman

1
对于给定的示例,有一种比简单地添加一个throw永远不会达到的更好的处理方法。人们常常只是想快速地应用“一种解决方案来统治所有人”,而又不想过多地思考潜在的问题。但是编程不仅仅是编码。特别是在算法方面。如果(仔细地)检查了给定的问题,您将意识到您可以排除外for循环的最后一次迭代,从而用有用的返回值代替“无用的”返回值(请参阅此答案)。
a_guest

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-恢复莫妮卡

3
@cmaster:您可以省略truefor(int count = 0; ; count++) …
Holger

@霍尔格 我不确定这是因为它是关于Java的,而且由于我对C / C ++的了解更多,所以我已经好多年没有接触这种语言了。因此,当我看到的使用时while(true),我认为java在这方面可能更加严格。很高兴知道您不必担心...实际上,我更喜欢省略的true版本:它清楚地表明,完全没有条件可以看:-)
cmaster-恢复莫妮卡

3
我希望不要切换为“ for”。“ while-true”是一个明显的标志,表明该块将无条件循环,除非并且直到内部的某些东西将其破坏为止。省略条件的“ for”不能清晰地传达出来。它可能更容易被忽略。
Corrodias '02

1
@ l0b0考虑到警告与代码质量相关,整个问题对于代码审查可能会更好。
苏珊(Sulthan)

18

由于您询问要打破两个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可以未初始化使用而无法编译吗?
pt

1
最有可能的。帖子的目的是展示如何突破两个循环,就像原始海报所问的那样。OP确保执行永远不会到达方法的末尾,因此可以通过将returnValue初始化为声明时的任何值来修复您提出的内容。
David Choweller

4
标签不是您最好通过某种方式避免的事情吗?
Serverfrog

2
我相信您正在考虑gotos。
David Choweller '17

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();

现在注意,我们返回的实际上是集合的大小。代码复杂度已从二次降低到线性,并且可读性更强。

现在我们可以意识到我们甚至不需要该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;
}

这样,您无需引入永远不会到达的代码。


但是,找到匹配项时,这是否需要打破两层循环吗?我没有一种减少为for语句的方法,需要生成一个新的随机数,并将其与生成另一个随机数之前的随机数进行比较。
奥利弗·本宁

您仍然剩下两个嵌套的for循环(我只是省略了第二个...),但是外循环在上一次迭代中有所减少。最后一个return语句分别处理与最后一次迭代对应的情况。尽管对于您的示例来说这是一个优雅的解决方案,但在某些情况下,这种方法变得更难以阅读;例如,如果返回值取决于内部循环,那么-而不是最后的单个return语句-您将剩下一个附加循环(代表外部循环的上一次迭代的内部循环)。
a_guest

5

使用临时变量,例如“ result”,并删除内部return。在适当的条件下将for循环更改为while循环。对我来说,只有一个返回值作为函数的最后一条语句总是比较优雅。


好吧,如果可能是外部条件,则看起来非常内部。请记住,总是有一段时间与for等效(并且更优雅,因为您不打算总是进行所有迭代)。可以肯定地简化这两个嵌套的for循环。所有这些代码闻起来“太复杂了”。
大卫,

@ Solomonoff'sSecret您的陈述是荒谬的。这意味着我们应该“总体上”针对两个或多个return语句。有趣。
戴维(David)

@ Solomonoff'sSecret这是首选编程风格的问题,无论上周la脚的本周有什么酷事,都有明显的优点,即只有一个退出点,并且在方法结束时(只需考虑一个循环)即可。许多对象return result散落在其中,然后想到现在想result在返回结果之前再进行一次检查,这只是一个示例)。也可能会有弊端,但是诸如“一般而言,没有充分的理由只有一个回报”这样的笼统说法并不能成立。
SantiBailors

@David让我重新表述:没有普遍的理由只有一个回报。在某些情况下,这可能是原因,但通常情况下,这是最糟糕的货运行为。
恢复莫妮卡

1
这与使代码更具可读性有关。如果在您的脑海中说“如果是这样,请返回我们找到的内容;否则继续进行;如果我们没有找到任何内容,请返回此其他值”。然后,您的代码应具有两个返回值。始终编​​写代码,使下一个开发人员更容易理解。编译器从Java方法只有一个返回点,即使在我们看来,它看起来像两个。
里克·赖克

3

也许这表明您应该重写代码。例如:

  1. 创建一个整数数组0 .. range-1。将所有值设置为0。
  2. 执行循环。在循环中,生成一个随机数。在列表中的该索引处查找,看该值是否为1。如果为1,则跳出循环。否则,将该索引处的值设置为1
  3. 计算列表中的1的数量,然后返回该值。

3

具有return语句且内部具有循环的方法始终需要在循环外部具有return语句。即使从未到达循环外的此语句。在这种情况下,为了避免不必要的return语句,您可以在方法的开头(即,在各个循环之前和之外)定义各自类型的变量,在您的情况下为整数。当在循环内达到期望的结果时,可以将各自的值分配给该预定义变量,并将其用于循环外的return语句。

因为您希望您的方法在rInt [i]等于rInt [count]时返回第一个结果,所以仅实现上述变量是不够的,因为当rInt [i]等于rInt [count]时该方法将返回最后一个结果。一种选择是实现两个“ 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里面的循环...
威廉·Onsem
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.