57. 最小化局部变量的作用域

      较早的编程语言(如 C)要求必须在代码块的头部声明局部变量,并且一些程序员继续习惯这样做。 这是一个值得改进的习惯。 作为提醒,Java 允许你在任何合法的语句的地方声明变量(as does C, since C99)。

      用于最小化局部变量作用域的最强大的技术是再首次使用的地方声明它。 如果变量在使用之前被声明,那就变得更加混乱—— 这也会对试图理解程序的读者来讲,又增加了一件分散他们注意力的事情。 到使用该变量时,读者可能不记得变量的类型或初始值。

      过早地声明局部变量可能导致其作用域不仅过早开始而且结束太晚。 局部变量的作用域从声明它的位置延伸到封闭块的末尾。 如果变量在使用它的封闭块之外声明,则在程序退出该封闭块后它仍然可见。如果在其预定用途区域之前或之后意外使用变量,则后果可能是灾难性的。

      几乎每个局部变量声明都应该包含一个初始化器。如果还没有足够的信息来合理地初始化一个变量,那么应该推迟声明,直到认为可以这样做。这个规则的一个例外是 try-catch 语句。如果一个变量被初始化为一个表达式,该表达式的计算结果可以抛出一个已检查的异常,那么该变量必须在 try 块中初始化(除非所包含的方法可以传播异常)。如果该值必须在 try 块之外使用,那么它必须在 try 块之前声明,此时它还不能被「合理地初始化」。例如,参照条目 65 中的示例。

      例如,下面是遍历集合的首选方式(详见第 58 条):

      如果需要访问迭代器,也许是为了调用它的 remove 方法,首选的习惯用法,使用传统的 for 循环代替 for-each 循环:

      要了解为什么这些 for 循环优于 while 循环,请考虑以下代码片段,其中包含两个 while 循环和一个 bug:

      第二个循环包含一个复制粘贴错误:它初始化一个新的循环变量 i2,但是使用旧的变量 i,不幸的是,它仍在范围内。 生成的代码编译时没有错误,并且在不抛出异常的情况下运行,但它做错了。 第二个循环不是在 c2 上迭代,而是立即终止,给出了 c2 为空的错误印象。 由于程序无声地出错,因此错误可能会长时间无法被检测到。

      此外,如果使用 for 循环,那么发送这种复制粘贴错误的可能性要小得多,因为没有必要在两个循环中使用不同的变量名。 循环是完全独立的,因此重用元素(或迭代器)变量名称没有坏处。 事实上,这样做通常很流行。

      for 循环比 while 循环还有一个优点:它更短,增强了可读性。

      下面是另一种循环习惯用法,它最小化了局部变量的作用域:

      关于这个做法需要注意的重要一点是,它有两个循环变量,i 和 n,它们都具有完全相同的作用域。第二个变量 n 用于存储第一个变量的限定值,从而避免了每次迭代中冗余计算的代价。作为一个规则,如果循环测试涉及一个方法调用,并且保证在每次迭代中返回相同的结果,那么应该使用这种用法。