我已经用Java编程好几年了,但我最近才回到学校获得一个正式学位。我很惊讶地发现,在我的上一个作业中,我因为使用下面这样的循环而失分。

do{
     //get some input.
     //if the input meets my conditions, break;
     //Otherwise ask again.
} while(true)

现在对于我的测试,我只是扫描一些控制台输入,但我被告知这种循环是不鼓励的,因为使用break类似于goto,我们只是不这样做。

我完全理解goto和它的Java兄弟break:label的陷阱,而且我很明智地不使用它们。我也意识到,一个更完整的程序会提供一些其他的逃避方式,比如结束程序,但我的教授并没有提到这一点,所以……

do-while(正确)怎么了?


当前回答

在break语句中使用while(正确)没有大问题,但是有些人可能会认为它会略微降低代码的可读性。试着给变量起个有意义的名字,在合适的位置求值表达式。

对于你的例子,这样做似乎更清楚:

do {
   input = get_input();
   valid = check_input_validity(input);    
} while(! valid)

如果do while循环变长,尤其如此——您确切地知道在哪里检查是否发生了额外的迭代。在抽象级别上,所有变量/函数都有适当的名称。while(true)语句的作用是告诉你,处理不在你认为的地方。

也许你想在第二次循环时得到不同的输出。类似的

input = get_input();
while(input_is_not_valid(input)) {
    disp_msg_invalid_input();
    input = get_input();
}

这样我就更容易理解了

do {
    input = get_input();
    if (input_is_valid(input)) {
        break;
    }
    disp_msg_invalid_input();
} while(true);

同样,通过一个简单的例子,两者都很容易理解;但是如果循环变得非常大或嵌套很深(这意味着您可能已经重构过了),那么第一种样式可能会更清晰一些。

其他回答

我猜用break对老师来说就像折断树枝来得到果实,用一些其他的技巧(向树枝鞠躬),这样你得到了果实,而树枝仍然活着。

Douglas Crockford说过,他多么希望JavaScript包含一个循环结构:

loop
{
  ...code...
}

我也不认为Java会因为拥有循环结构而变得更糟糕。

while(true)循环本身并没有什么错,但是老师们倾向于不鼓励它们。从教学的角度来看,很容易让学生创造出无穷无尽的循环,而不理解为什么循环没有被转义。

但是他们很少提到的是,所有的循环机制都可以用while(true)循环复制。

while( a() )
{
  fn();
}

loop
{
  if ( !a() ) break;
  fn();
}

and

do
{
  fn();
} while( a() );

等于:

loop
{
  fn();
  if ( !a() ) break;
}

and

for ( a(); b(); c() )
{
  fn();
}

等于:

a();
loop
{
  if ( !b() ) break;
  fn();
  c();
}

只要你能以一种工作的方式设置你的循环,你选择使用的结构是不重要的。如果它恰好适合for循环,则使用for循环。

最后一点:保持循环的简单性。如果在每次迭代中都需要执行许多功能,那么就将其放入一个函数中。在你让它工作之后,你总是可以优化它。

对我来说,问题在于可读性。

带有true条件的while语句不会告诉您任何关于循环的信息。这使得理解它的工作更加困难。

从这两个片段中更容易理解什么?

do {
  // Imagine a nice chunk of code here
} while(true);

do {
  // Imagine a nice chunk of code here
} while(price < priceAllowedForDiscount);

我想没什么,真的。老师们对goto过敏,因为他们听说那里很糟糕。否则你可以这样写:

bool guard = true;
do
{
   getInput();
   if (something)
     guard = false;
} while (guard)

这几乎是一样的。

也许这样更简洁(因为所有循环信息都包含在块的顶部):

for (bool endLoop = false; !endLoop;)
{

}

也许是我运气不好。或许我只是缺乏经验。但是每次我回忆起处理while(true)内部有break时,可以改进将Extract Method应用到while-block的代码,它保留了while(true),但(巧合吗?)将所有的break转换为返回。

根据我的经验,没有休息(即返回或抛出)是相当舒服和容易理解的。


  void handleInput() {
      while (true) {
          final Input input = getSomeInput();
          if (input == null) {
              throw new BadInputException("can't handle null input");
          }
          if (input.isPoisonPill()) {
              return;
          }
          doSomething(input);
      }
  }