将局部变量的作用域最小化,可以增强代码的可读性和可维护性,并降低出错的可能性。
要使用局部变量的作用域最小化,最有力的方法就是在第一次使用它的地方才声明,不要过早的声明。
局部变量的作用域从它被声明的点开始扩展,一直到外围块的结束外。如果变量是在“使用它的块”之外被声明有,当程序退出该块之后,该变量仍是可见的,如果它在目标使用区之前或之后意外使用,将可能引发意外错误。
几乎每个局部变量的声明都应该包含一个初始化表达式,如果你还没有足够信息来对象一个变量进行有意义的初始化,就应该推迟这个声明,直到可初始化为止。但这条规则有个例外的情况与try-catch语句有关。如果一个变量被一个方法初始化,而这个方法可能会抛出一个检测性异常,该变量就必须在try块内部被初始化。如果变量的值必须在try块的外部使用到,它就必须在try块之前被声明,但是在try块之前,它还不能“有意义地初始化”,请参照第53条中的异常实例。
循环中提供了特殊的机会来将变量的作用域最小化。如果在循环终止之后不再需要使用循环变量的内容,for循环就优先于while循环。例如,下面是一种遍历集合的首选做法:
for(Element e: c){
doSomething(e);
}
在1.5前,首先做法如下:
for(Iterator i = c.iterator();i.hasNext();){
doSomething((Element) i.next());
}
为了弄清为什么这个for循环比while循环更好,请看下面代码:
Iterator<Element> i = c.iterator();
while(i.hasNext()){
doSomething(i.next());
}
…
Iterator<Element> i2 = c2.iterator();
while(i.hasNext()){//Bug!
doSomething(i2.next());
}
CP过来的代码未修改完,结果导致for循环编译通不过。
最后一种“将局部变量的作用域最小化”的方法是使方法小而集中。
----------------------
补充,不能在while条件中声明变量,这与for循环不一样,也不能像下面这样在while体中声明一个变量:
while(true)
int i = 1;
只可以这样:
while(true){
int i = 1;
}
46、 for-each循环优先于传统的for循环1.5前,遍历集合的首选做法如下:
for(Iterator i = c.iterator(); i.hasNext();){
doSomething((Element)i.next());
}
遍历数组的首选做法如下:
for(int i =0; i < a.length;i++){
doSomething(a[i]);
}
虽然这些做法比while循环理好,但并不完美,因为迭代器和索引变量在每个循环中出现三次,其中有两次让你出错。
1.5中完全隐藏迭代器或者索引变量,避免了混乱和出错的可能,下面这种模式同样适合于集合与数组:
for(Element e : elements){
doSomething(e);
}
集合内嵌迭代时问题:
Collection<Suit> suits = ...;
Collection<Rank> ranks = ...;
List<Card> deck =...;
for(Iterator<Suit> i = suits.iterator(); i.hasNext();)
for(Iterator<Rank> j = ranks.iterator(); j.hasNext();)
deck.add(newCard(i.next(), j.next()));//i.next在内部循环中多次调用,会出现问题
将i.next()临时存储起来可以解决这个问题:
for(Iterator<Suit> i = suits.iterator(); i.hasNext();){
Suit suit = i.next();
for(Iterator<Rank> j = ranks.iterator(); j.hasNext();)
deck.add(newCard(suit, j.next()));
}
如果使用内嵌的for-each循环,这个问题很快会完全消失,代码是如此的简洁:
for(Suit suit : suits)
for(Rank rank : ranks)
deck.add(newCard(suit, rank));
for-each循环不仅让你遍历集合数组,还让你遍历任何实现Iteralble接口的对象。这个简单的接口接口由单个方法组成,与for-each循环同时被增加到Java平台中,这个接口如下:
public interface Iterable<E>{
Iterator<E> iterator();
}
总之,for-each循环在简洁性和预防Bug方面有着传统的for循环无法比拟的优势,并且没有性能损失。应该尽可能地使用for-each循环,遗憾的是,有些情况是不适用的,比如需要显示地得到索上或迭代器然后进行其他操作,或者是内部循环的条件与外部有直接关系的(比如内层循环的起始值依赖于外层循环的条件值)。
47、 了解和使用类库假如你希望产生位于0和某个上界之间的随机整数,你可以会这么做:
PRivae static final Random rnd = new Random();
static int random(int n){
return Math.abs(rnd.nextInt())%n;
}
上面程序产生的0到n间的整数是不均的,使用类库中的Random类的nextInt(Int)可以解决,这些方法是经过专家们设计,并经过多次测试和使用过的方法,比我们自己实现可靠得多。
通过使用标准类库,可以充分利用这些编写标准类库的专家的知识,以及在你之前的其他人的使用经验。
使用标准类库中的第二个好处是,不必关心底层细节上,把时间应花在应用程序上。
使用标准类库中的第三个好处是,它们的性能往往会随着时间的推移而不断提高,无需你做任何努力。
本条目不可能总结类库中所有便利工具,但是有两种工具值得特别一提。一个是1.2发行版本中的集合框架,二是1.5版本中,在java.util.concurrent包中增加了一组并发实用工具,这个包既包含高级的并发工具来简化多线程的编程任务,还包含低级别的并发基本类型,允许专家们自己编写更高级的并发抽象,java.util.concurrent的高级部分,也应该是每个程序员基本工具箱中的一部分。
总之,不要重新发明轮子,已存在的我们就直接使用,只有不能满足我们需求时,才需自己开发,总的来说,多了解类库是有好处的,特别是为库中的工具包。
48、 如果需要精确的答案,请避免使用float和doublefloat和double类型主要是用来为科学计算和工程计算而设计的。它们执行二进制浮点运算,这是为了在广泛的数值满园上提供较为精确的快速近似计算而精心设计的,然而,它们并没有提供完全精确的结果,所以不应该被用于需要精确结果的场合。float和double类型尤其不适合用于货币的计算,因为要让一个float和double精确地表示0.1(或者10的任何其他负数次方值)是不可能的。
System.out.println(1.0-.9);//0.09999999999999998
请使用BigDecimal、int或long(int与long以货币最小单位计算)进行货币计算。
使用BigDecimal时请还请使用BigDecimal(String),而不要使用BigDecimal(float或double),因为后者在传递的过程中会丢失精度:
newBigDecimal(0.1)//0.1000000000000000055511151231257827021181583404541015625
new BigDecimal("0.1")//0.1
新闻热点
疑难解答