第八章、通用程序设计(一)
第四十五条、将局部变量的作用域最小化
-
将局部变量的作用最小化,可以增强代码的可读性和可维护性,并降低出错的可能性。异于C语言要求局部变量必须要在一个代码块的开头处进行声明,Java允许在任何可以出现语句的地方声明变量。
-
最有力的方法是在第一次使用它的地方声明。过早地声明局部变量不仅会使它的作用域过早地扩展,而且结束地过晚了。局部变量的作用域从它被声明的点开始拓展,一直到外围块的结束处。
-
几乎每个局部变量的声明都应该包含一个初始表达式。如果你还没有足够的信息来对一个变量进行有意义的初始化,就应该推迟这个声明,直到可以初始化为止。(有个例外是try-catch语句有关,变量被一个方法初始化,而这个方法可能会抛出一个checked exception,则该变量必须在try的内部被初始化。)
-
循环中提供了特殊的机会来将变量的作用域最小化。for循环都允许声明循环变量(loop variable),它的作用域被限定在正好需要的范围内:循环体、循环体之前的初始化、测试、更新部分。所以,如果在循环终止后不再需要循环变量的内容,for循环就优先于while循环。for循环相较于while循环的另一个优势在于:更简短,从而增加了可读性。
第四十六条、for-each循环优先于传统的for循环
-
在Java1.5之前,对集合进行遍历的首选做法:
for(Iterator i = c.iterator();i.hasNext();){ doSomething((Element)i.next()); }
对数组进行遍历的首选做法:
for(int i = 0 ; i < a.length ; i++){ doSomething(a[i]); }
但是它们并不完美:迭代器和索引变量都会造成一些混乱,也代表着出错的可能。
Java1.5 引入for-each循环,通过完全隐藏迭代器或者索引变量,避免了混乱和出错的可能:for(Element e:elements){ doSomething(e); }
冒号代表在。。。里面,可以读作对于元素elements中的每个元素e。这个在性能上可能还稍有优势。
-
在对多个集合进行嵌套式迭代的时候,for-each循环的优势更加明显。
for-each循环不仅让你遍历集合和数组,还让你遍历任何实现Iterable接口的对象。这个简单的接口由单个方法组成:public interface Iterable<E>{ Iterator<E> iterator(); }
-
有三种情况下无法使用for-each循环:
- 过滤:如果需要遍历集合,并删除选定的元素,就需要使用显式的迭代器,以便可以调用它的remove方法;
- 转换:如果需要遍历列表或者数组,并取代他部分或者全部的元素值,就需要列表迭代器或者数组索引,以便设定元素的值;
- 平行迭代:如果需要并行地遍历多个集合,就需要显式地控制迭代器或者索引变量,以便所有的迭代器或者索引变量都可以同步前移。
第四十七条、了解和使用类库
-
通过使用标准类库,可以充分利用这些编写标准类库的专家的知识,以及在你之前的其他人的使用经验。而且不必浪费时间为那些与工作不太相关的问题提供特别的解决方案。而且它们的性能往往随着时间的推移而不断提高。可以使自己的代码融入主流。
-
每个Java程序员应该熟悉java.lang、java.util还有java.io中的内容,关于其他类库的知识可以随时学习。
-
两个工具:
- Collection Framework(集合框架)被加入到java.util包中,是一个统一的体系结构,用来表示和操作集合,允许它们对集合进行独立于表示细节的操作,从而减轻了编程的负担且提高了效率和性能。
- java.util.concurrent 包中有一组并发实用工具,既包含高级的并发工具来简化多线程的编程任务,还包含低级别的并发基本类型,允许专家们自己编写更高级的并发抽象。
第四十八条、如果需要精确的答案,请避免使用float和double
-
float和double类型主要是为了科学计算和工程计算而设计的。它们执行二进制浮点运算,这是为了在广泛的数值范围上提供较为精确的快速近似计算而精心设计的。然而,它们并没有提供完全精确的结果。尤其不适用于货币计算,让float和double精确地表示0.1是不可能的。
-
使用BigDecimal、int或者long进行货币计算:如果想让系统来记录十进制小数点,且不介意一丝不方便,请使用BigDecimal;如果性能特别关键,而且又不介意自己记录十进制的小数点,涉及的数值不是很大,可以使用int和long。如果数值超过18位数字,就必须使用BigDecimal。
第四十九条、基本类型优先于装箱基本类型
-
java有一个类型系统由两个部分组成:
基本类型(primitive)如:int、double和boolean;引用类型(reference type)如:String和List。每个基本类型都有一个对应的引用类型,称作装箱基本类型(boxed primitive)。int/double/boolean对应于Integer、Double和Boolean。自动装箱和自动拆箱模糊了但是没有完全抹去基本类型和装箱基本类型之间的区别。
-
两者之间的主要区别:
- 基本类型只有值,而装箱基本类型则具有与它们的值不同的同一性。
- 基本类型只有功能完备的值,装箱基本类型还有个非功能值null。
- 基本类型通常比装箱基本类型更节省时间和空间。
-
对装箱基本类型使用==操作符几乎总是错误的;当一项操作中混合使用基本类型和装箱基本类型时,装箱基本类型就会自动拆箱,如果null对象引用被自动拆箱,就会得到一个
NullPointerException
。 -
什么时候应该使用装箱基本类型呢?
- 作为集合中的元素、键和值,不能将基本类型放在集合中,因此必须使用装箱基本类型;
- 在参数化类型中,必须使用装箱基本类型作为参数类型;
- 在反射的方法调用时,必须使用装箱基本类型。
-
总结:当可以选择的时候,基本类型优先于装箱基本类型。基本类型更加简单也更加快速,如果必须使用装箱基本类型,则要特别小心,自动装箱减少了使用装箱基本类型的繁琐性,但是并没有减少它的风险。
第五十条、如果使用其他类型更合适,则尽量避免使用字符串
-
字符串的优点:被用来表示文本,十分通用且Java支持的比较好。
-
不应该使用字符串的情形:
- 字符串不适合代替其他的值,当一段数据从文件、网络或者键盘设备,进入到程序中,通常是以字符串的形式存在,如果它是数值,就应该被转换为适当的数值类型等等;
- 字符串不适合替代枚举类型:见第三十条
- 字符串不适合代替聚集类型:如果一个实体有多个组件,用字符串表示这个实体通常是很不恰当的,更好的做法当然是编写一个类来描述这个数据集,通常是一个私有的静态成员类。
- 字符串不适合代替能力表(capabilities):有时候,字符串被用于对某种功能进行授权访问,例如考虑设计一个提供线程局部变量机制,这个机制提供的变量在每个线程中都有自己的值。几年前面对这样的设计任务时,有些人利用客户提供的字符串键,对每个线程局部变量的内容进行访问授权。
-
总之:如果可以使用更加合适的数据类型,或者可以编写更加适当的数据类型,就应该避免用字符串来表示对象。基本类型、枚举类型和聚集类型经常被错误地用字符串来代替。
第五十一条、当心字符串连接的性能
-
+
(字符串连接操作符)是把多个字符串合并为一个字符串的便利途径。要想产生单独一行的输出,或者构造一个字符串来表示一个较小的、大小固定的对象,使用+是非常合适的。但是它不适合应用在大规模的场景中,为连接n个字符串而重复的使用字符串连接操作符,需要n的平方级的时间。因为字符串不可变而导致的,当两个字符串被连接在一起的时候,它们的内容都要被拷贝。 -
为了获得可以接受的性能,请使用StringBuilder替代String,两种方法性能差别巨大,原先的做法开销是随着数量呈平方增加,改进后是线性增加。
public String statement(){ StringBuilder b = new StringBuilder(numItems() *LINE_WIDTH); for(int i = 0; i< numItems();i++){ b.append(lineForItem(i)); } return b.toString(); }
原先的:
public String statement(){ String result = ""; for (int i = 0;i < numItems(); i++){ result += lineForItem(i); } return result; }
-
总结:不要使用字符串连接操作符来合并多个字符串,除非性能无关紧要。相反,应该使用StringBuilder的append方法。