为什么这个循环是不良实践?

20

以下循环不是一个好的做法。这是因为一个 String 作为 for 循环的主要条件而不是一个 int 变量,导致了无限的 for 循环吗?此外,是否没有实例输入 'end' 来停止循环?

Scanner in = new Scanner(System.in);
int i = 0;
for (String s = in.next(); !s.equals("end"); i++) 
{
    System.out.println("The value of i is: " + i + " and you entered " + s);
}

我该如何改写它,使其符合通用的风格要求?

(这是一道过去考试题中的问题。)


29
这并不是一个坏的习惯,而更像是一个无限循环... - njzk2
@ericbn http://www.oracle.com/technetwork/java/javase/documentation/codeconvtoc-136057.html - turbo
5
我认为所提出的问题没有答案,因为在给定状态下,无法将代码作为解决方案进行分析。这不是正确的代码。无论这个解决方案是否符合“良好实践”的要求,取决于在获得实际可行的解决方案时它的表现如何。 - nmclean
“还有就是没有实例可以输入 'end' 来停止循环?” Bingo。 - Radiodef
2
我为那些不得不应对如此糟糕设计的问题和作业的学生感到遗憾。 - dansalmo
显示剩余3条评论
17个回答

39

你的字符串s从未改变过,这可能导致无限循环。你可能想要这样:

for (String s = in.next(); !s.equals("end"); s = in.next(), i++) {
    ...
}

有些人(包括我在内)可能会认为 i++ 不应该在此循环的增量部分中,因为它与条件没有直接关系:

for (String s = in.next(); !s.equals("end"); s = in.next()) {
    ...
    i++;
}

是因为for循环的主要条件是一个字符串而不是一个整数变量,导致for循环无限循环吗?

原始循环确实是无限的(至少,在输入了初始值并且假设“end”不是第一次输入后)。然而,它并不是你所说的原因。for循环通常使用整数控制变量编写,但并非总是如此。例如,通过以下常见惯用语迭代访问链接列表:

for (Node node = list.head; node != null; node = node.next) {
    ...
}

你的循环问题在于字符串s从未改变,因此它永远不会等于"end",除非那是第一个输入。


即使如此,您仍需要确保它循环的任何内容最终都将具有“end”作为值,否则它将无限循环。 - user2366842
2
for (String s; !(s = in.next()).equals("end");) 消除了重复。 - Marko Topolnik
7
马尔科:同时也不再重视可读性… - Petter

34

我建议将循环条件和调用Scannner.next()分开:

while (in.hasNext()) {
    String s = in.next();
    if (s.equals("end")) {
      break;
    }
    System.out.println("The value of i is: " + i + " and you entered " + s);
    i++;
}

我认为这比试图将所有内容挤入一个for表达式要容易理解得多。


6
我会支持这个选择。对于迭代次数未知的循环,使用 while 循环是更好的实践方法。 - Paul Samsotha
是的,这比试图将所有逻辑压缩到一个布尔表达式中的解决方案要清晰得多。 - Petter
@peeskillet 我不能说我同意那个观点。是的,for循环通常具有已知的迭代长度,但如果正确使用,则使用不具有已知迭代长度的循环并不一定是不良实践。事实上,OP在这里的确实是for循环的有效用例:清晰的初始化、相关条件和相关增量步骤。事实上,还有其他常见情况,其中for循环没有已知的迭代长度;我在我的答案中简要涉及了这一点。 - arshajii

6

这段代码存在多个问题:

  1. s在初始赋值后没有改变,因此导致了无限循环。
  2. 调用.next()可能会抛出NoSuchElementExceptionIllegalStateException异常。而我认为,比起捕获这些异常,事先检查.hasNext()更加恰当,因为耗尽输入是可以预见的情况,而不是异常情况。然而,采用另一种“请求宽恕”的风格也是可以接受的。
  3. for循环头部内容不连贯——它初始化了s并测试了s,但更新了i
  4. 在我看来,System.out.format()比通过字符串连接使用System.out.println()稍微好一些。

我将其重写为:

Scanner in = new Scanner(System.in);
int i = 0;
String s;
while (in.hasNext() && !"end".equals(s = in.next())) {
    System.out.format("The value of i is: %d and you entered %s\n", i++, s);
}

在用户界面上,告诉用户end是终止循环的魔术词语可能会增加一些亲和力(假设循环已被修改为按预期工作)。


那个while条件不太易读。最好将其保留为while(in.hasNext()),并将其余逻辑放在循环体中。 - Petter
@Petter 我觉得这个代码足够易读。相比于 @andersschuller 的解决方案,我更喜欢这里的 while (in.hasNext()) 条件,因为它准确地描述了循环应该何时终止。 - 200_success

4
常见的for循环做法是在每个项中重复计数器变量:
for(int i=...; i<... ; i++)

在上面的例子中,代码混合了变量。这会让读者感到困惑,并可能导致循环仅在您将end作为第一个值输入时终止。

2

这不是一个好主意,因为字符串s可能永远不会等于"end"。你可能想要检查扫描器是否有另一个字符串。此外,你只初始化字符串为in.next(),但你需要在循环的每次迭代后将s设置为下一个字符串。

while(in.hasNext()) {
  String s = in.next();
  if (s.equals("end")) {
    break;
  }
  // ..
}

2

这个循环是一个坏主意,因为你只从用户输入一次设置s,而不是在每次迭代中都进行设置。 因此,如果s被填充了一个与"end"不同的值,它将导致您无限运行。

您可能想要类似于以下内容:

for (String s; (s = in.nextLine()).equals("end"); i++) 
{
    System.out.println("The value of i is: " + i + " and you entered " + s);
}

2

这种方法太糟糕了。

给定的代码:

Scanner in = new Scanner(System.in);
int i = 0;
for (String s = in.next(); !s.equals("end"); i++) 
{
    System.out.println("The value of i is: " + i + " and you entered " + s);
}
< p > for循环的第一部分仅执行一次。 < /p >
String s = in.next() //Execute only once in life

这个for循环的第二部分永远不会为真,因为输入控制台永远不会允许输入第二个输入。

!s.equals("end")//2nd part

这个程序永远不会允许从控制台输入第二个输入,因为in.next()只会执行一次。而且这个循环的退出标记是"end",在第一次输入之后不可能再输入。

这种类型的循环应该使用while循环实现。

Scanner in = new Scanner(System.in);
while(in.hasNext()){
String yourdata=in.next();
if(yourdata.equals("end")){
//Stop the loop 
}
//Do you code here
}

1
字符串s从未被修改。循环永远不会结束。那么这个呢:
    Scanner in = new Scanner(System.in);
    String s = "";
    for (int i = 0 ; !s.equals("end"); i++) {
        s = in.next();
        System.out.println("The value of i is: " + i + " and you entered "
                + s);
    }

1
其他人已经提到了循环不会结束,因为您没有改变s的值,所以循环永远不会结束。这可能是您的教授想要的,也可能不是。糟糕的代码是不好的实践,但这是不好的实践还有其他原因。
我认为这里不好的做法是使用for循环,教授可能本意如此。正如我的教授告诉我:“当您知道代码何时结束时,请使用for循环,而当您不知道代码何时结束时,请使用while循环。”因此,如果您有一个可迭代的i,例如这段代码:
for(i = 0; i<100; i++)
{
    ...
}

在这段代码中,你需要迭代 i 从 0 到 100。当你的教授讨论的情况时,你需要使用 while 循环。
int counter;
while(*(str+counter))
    counter++;

你不知道循环何时结束,因为你不知道str有多长,但你知道它最终会到达空指针,循环将终止。这通常是最佳实践。

所以对于你的教授发布的代码,你可能希望它看起来像这样:

Scanner in = new Scanner(System.in);
int i = 0;
while(!s.equals("end"))
{
    i++;
    String s = in.next();
    System.out.println("The value of i is: " + i + " and you entered " + s);
}

“正如我的教授告诉我的那样” - 在像Java这样的编程语言中,你的教授是相当客观地错误的。 - Konrad Rudolph
1
@KonradRudolph 这个有附带的解释吗? - Jacqlyn
1
我认为从上下文中很明显:这与已经确立的最佳实践相矛盾。在Java中,在某些情况下使用for循环,而无法静态地证明边界是一种已经确立的惯用语法。而且,既然反对这样做的唯一论点是约定俗成,那么这个论点就站不住脚了。 - Konrad Rudolph
@KonradRudolph 我猜他们告诉我这个是针对C的,我的Python书上也有类似的内容。在学习C或Python之前,我先学了Java,所以对我来说,这个逻辑在Java中也是成立的。这个帖子中的许多其他人也在说同样的事情,即while循环更适合这种情况。你所说的无法静态证明边界的情况是什么? - Jacqlyn
但是对于C语言来说,同样的情况也绝对适用 - 实际上,这就是Java从中获得的习惯。在这种特定情况下,使用while循环可能更合适。这与您所说的不同(因此我没有看到其他人同意您的观点)。for(Iterator i = something.iterator(); i.hasNext();) 是这样一种惯用法,for(Scanner in = new Scanner(System.in); in.hasNext();) 也是如此。Python是完全不同的情况,因为Python的for循环的功能不同。 - Konrad Rudolph

1
我认为这是不好的做法,因为没有必要使用 for 循环。在这种情况下,我认为它是无用的。可以只写成这样:
Scanner in = new Scanner(System.in);
String s = in.next();
if (!s.equals("end"))
{
    System.out.println("You have enetered" + s);
}

看,没有必要使用循环。你之前的循环使事情变得比必要的复杂。我一直认为,除非需要复杂性,否则应该尽可能保持简单。只有在想让代码执行多个操作时才需要使用for循环。在上面的例子中,只有一个操作:打印println语句,因此不需要循环。这是不必要的...

此外,循环永远不会结束。所以还有这个问题,但那只是错误的代码。这不是它不好的原因。它不好的原因是不必要地使用了for循环。它也有错误,因为代码是错的。所以这段代码有两个不同的问题。


是的,需要使用循环。它应该反复接受输入并打印它,直到输入为"end"为止。 - nmclean

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接