Effective Java 第三版——57. 最小化局部变量的做用域

Tips
书中的源代码地址:https://github.com/jbloch/effective-java-3e-source-code
注意,书中的有些代码里方法是基于Java 9 API中的,因此JDK 最好下载 JDK 9以上的版本。java

Effective Java, Third Edition

9. 通用编程

这一章专门讨论Java语言的具体细节。讨论了局部变量、控制结构、类库、数据类型以及两种Java语言以外工具:反射和本地方法。最后,讨论了优化和命名惯例。git

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

这条目在性质上相似于条目 15,即“最小化类和成员的可访问性”。经过最小化局部变量的做用域,能够提升代码的可读性和可维护性,并下降出错的可能性。程序员

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

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

过早地声明局部变量可能致使其做用域不只过早开始并且结束太晚。 局部变量的做用域从声明它的位置延伸到封闭块的末尾。 若是变量在使用它的封闭块以外声明,则在程序退出该封闭块后它仍然可见。若是在其预约用途区域以前或以后意外使用变量,则后果多是灾难性的。编程语言

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

循环提供了一个特殊的机会来最小化变量的做用域。传统形式的for循环和for-each形式都容许声明循环变量,将其做用域限制在须要它们的确切区域。 (该区域由循环体和for关键字与正文之间的括号中的代码组成)。所以,若是循环终止后不须要循环变量的内容,那么优先选择for循环而不是while循环测试

例如,下面是遍历集合的首选方式(条目 58):优化

// Preferred idiom for iterating over a collection or array
for (Element e : c) {
    ... // Do Something with e
}

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

// Idiom for iterating when you need the iterator
for (Iterator<Element> i = c.iterator(); i.hasNext(); ) {
    Element e = i.next();
    ... // Do something with e and i
}

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

Iterator<Element> i = c.iterator();
while (i.hasNext()) {
    doSomething(i.next());
}
...
Iterator<Element> i2 = c2.iterator();
while (i.hasNext()) {             // BUG!
    doSomethingElse(i2.next());
}

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

若是将相似的复制粘贴错误与for循环(for-each循环或传统循环)结合使用,则生成的代码甚至没法编译。第一个循环中的元素(或迭代器)变量不在第二个循环中的做用域中。下面是它与传统for循环的示例:

for (Iterator<Element> i = c.iterator(); i.hasNext(); ) {
    Element e = i.next();
    ... // Do something with e and i
}
...

// Compile-time error - cannot find symbol i
for (Iterator<Element> i2 = c2.iterator(); i.hasNext(); ) {
    Element e2 = i2.next();
    ... // Do something with e2 and i2
}

此外,若是使用for循环,那么发送这种复制粘贴错误的可能性要小得多,由于没有必要在两个循环中使用不一样的变量名。 循环是彻底独立的,所以重用元素(或迭代器)变量名称没有坏处。 事实上,这样作一般很流行。

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

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

for (int i = 0, n = expensiveComputation(); i < n; i++) {
    ... // Do something with i;
}

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

最小化局部变量做用域的最终技术是保持方法小而集中。 若是在同一方法中组合两个行为(activities),则与一个行为相关的局部变量可能会位于执行另外一个行为的代码范围内。 为了防止这种状况发生,只需将方法分为两个:每一个行为对应一个方法。

相关文章
相关标签/搜索