效率编程 之「通用程序设计」

第 1 条:将局部变量的作用域最小化

要使局部变量的作用域最小化,最有力的方法就是在第一次使用它的地方声明。局部变量的作用域从它被声明的点开始扩展,一直到外围块的结束处。如果变量是在“使用它的块”之外被声明的,当程序退出该块之后,该变量仍然是可见的;如果变量在它的目标使用区域之前或者之后被意外地使用的话,后果将可能是灾难性的。

几乎每个局部变量的声明都应该包含一个初始化表达式。如果我们还没有足够的信息来对一个变量进行有意义的初始化,就应该推迟这个声明,直到可以初始化为止。这条规则有一个例外的情况与try-catch语句有关。如果一个变量被一个方法初始化,而这个方法可能会抛出一个受检的异常,该变量就必须在try块的内部被初始化;如果变量的值必须在try块的外部使用,它就必须在try块之前被声明,但是在try块之前,它还不能被“有意义地初始化”

循环中提供了特殊的机会来将变量的作用域最小化。无论是传统的还是高级的for循环,都允许声明循环变量,它们的作用域被限定在正好需要的范围之内,这个范围包括循环体,以及循环体之前的初始化、测试、更新部分。因此,如果在循环终止之后不再需要循环变量的内容,for循环就优先于while循环。考虑下面的代码片段,它包含两个while循环以及一个Bug

Iterator<String> it = list.iterator();
while (it.hasNext()) {
    System.out.println("列表一中的元素:" + it.next());
}

Iterator<String> it2 = list2.iterator();
while (it.hasNext()) {
    System.out.println("列表二中的元素:" + it.next());
}

如上述代码所示,第二个循环中包含了一个“剪切-粘贴”错误:它本来是要初始化一个新的循环变量it2,却使用了旧的的循环变量it,遗憾的是,这是it仍然还在有效范围之内。结果就是,代码仍然可以通过编译,运行的时候也不会抛出异常,但是它所做的事情却是错误的。反之,如果上述的“剪切-粘贴”错误出现在for循环中,结果代码就根本不可能通过编译。此外,高级的for-each循环优于传统的for循环。考虑下面的代码片段,它也包含一个Bug

Collection<Face> faces = Arrays.asList(Face.values());

for (Iterator<Face> i = faces.iterator(); i.hasNext(); ) {
    for (Iterator<Face> j = faces.iterator(); j.hasNext(); ) {
        System.out.println(i.next() + " " + j.next());
    }
}

上面的问题在于,在迭代器上对外部的集合调用了太多次next()方法了。它应该从外部循环进行调用,以便在每一次内部循环中前一次骰子的每一面只调用一次,但它却是从内部循环调用,因此它是每一面调用一次。在用完所有面之后,就会抛出NoSuchElementException异常。如果真的那么不幸,并且外部集合的大小是内部集合大小的几倍,可能因为它们是相同的结合,循环就会正常终止,但是不会完成我们想要的工作。如果使用的嵌套的for-each循环,则不会出现上面的Bug,例如:

for (Face face1 : faces) {
    for (Face face2 : faces) {
        System.out.println(face1 + " " + face2);
    }
}

最后一种“将局部变量的作用域最小化”的方式是使方法小而集中。如果把两个操作合并到同一个方法中,与其中一个操作相关的局部变量就有可能会出现在执行另一个操作的代码范围之内。为了防止这种情况发生,只要把这个方法分成两个,每个方法各执行一个操作即可。

第 2 条:基本类型优先于装箱基本类型

在基本类型和装箱基本类型之间,有三个主要的区别:

  • 第一,基本类型只有值,而装箱基本类型则具有与它们的值不同的统一性。换句话说,两个装箱基本类型可以具有相同的值和不同的统一性。
  • 第二,基本类型只有功能完备的值,而每个装箱类型除了它对应基本类型的所有功能值之外,还有个非功能值null
  • 最后一点区别是,基本类型通常比装箱基本类型更节省时间和空间。

对装箱基本类型运用==操作符几乎总是错误的。几乎在任何一种情况下,当在一项操作中混合使用基本类型和装箱基本类型时,装箱基本类型都会自动拆箱,这种情况无一例外。如果`null对象引用被自动拆箱的话,就会得到一个NPE异常。那么什么时候应该使用装箱基本类型呢?它们有几个合理的用处:

  • 第一个,作为集合中的元素、键和值。我们不能将基本类型放在集合中,因此必须使用装箱基本类型。
  • 第二个,在参数化类型中,必须使用装箱基本类型作为参数,因为 Java 不允许使用基本类型。例如,我们不能将变量声明为List<int>,而应该用List<Integer>来代替。
  • 最后,在进行反射的方法调用时,必须使用装箱基本类型。

总之,当可以选择的时候,基本类型要优先于装箱基本类型。基本类型更加简单,也更加快速。如果必须使用装箱基本类型,要特别小心!自动装箱减少了使用装箱基本类型的繁琐性,但是并没有减少它的风险。


———— ☆☆☆ —— 返回 -> 那些年,关于 Java 的那些事儿 <- 目录 —— ☆☆☆ ————

相关文章
相关标签/搜索