Effective Java第8章 通用程序设计
45.将局部变量的作用域最小化
在第一次使用它的地方声明;
几乎每个局部变量的声明都应该包含一个初始化表达式;
使方法小而集中。
46.for-each循环优先于传统的for循环
for-each循环在简洁性和预防bug方面有着传统的for循环无法比拟的优势,并且没有性能损失。
三种无法使用for-each循环的情况:
过滤——如果需要遍历集合,并删除选定的元素,就需要使用显式的迭代器,以便可以调用它的
remove方法。转换——如果需要遍历列表或者数组,并取代它部分或者全部的元素值,就需要列表迭代器或者数组索引,以便设定元素的值。
平行迭代——如果需要并行地遍历多个集合,就需要显式地控制迭代器或者索引变量,以便所有迭代器或者索引变量都可以得到同步前移。
47.了解和使用类库
48.如果需要精确的答案,请避免使用float和double
float和double类型的主要设计目的是为了科学计算和工程计算。它们执行二进制浮点运算,这是为了在广域数值范围上提供较为精确的快速近似计算而精心设计的。然而,它们没有提供完全精确的结果,所以不应该被用于要求精确结果的场合。因为要让一个float或者double精确地表示0.1(或者10的任何负数次方值)是不可能的,float和double类型对于货币计算尤为不合适,因为要让一个float或double精确地表示0.1或10的任何其他负数次方值是不可能的。比如System.out.println(2.0-1.1)将会打印0.899999999999999,而不是你所希望的0.9,这种舍入错误产生的原因是浮点数实际上是用二进制系统实现的,而分数1⁄10在二进制系统中没有精确的表示,其道理就如同在十进制系统中无法精确表示1⁄3一样;再比如0.5在二进制系统中有精确表示,而0.55在二进制系统中没有精确表示。
对于任何要精确答案的计算任务,请不要用double和float。如果你想计算十进制小数点,并且不介意非基本类型带来的不便,那就用BigDecimal;使用BigDecimal还有个额外的好处就是他允许你完全控制舍入,每当一个操作涉及舍入的时候它允许你从8种舍入模式中选择一种。如果你正确通过法定要求的舍入行为进行业务计算使用BigDecimal时非常方便的;如果性能十分关键,并且你又不介意自己记录十进制的小数点,而且涉及数值不大,就可以使用int或者long;如果数值范围没有超过9位的十进制数字,可以用int;如果数值范围没有超过18位的十进制数字,用long如果范围超过了18位数,那我们必须使用BigDecimal。
49.基本类型优先于装箱基本类型
基本类型和装箱基本类型的区别:
基本类型只有值,而装箱基本类型则具有与它们的值不同的同一性。
基本类型只有功能完备的值,而每个装箱基本类型除了它对应基本类型的所有功能值之外,还有个非功能值:
null。基本类型通常比装箱基本类型更节省时间和空间。
当在一项操作中混合使用基本类型和装箱基本类型时,装箱基本类型就会自动拆箱,这种情况无一例外。如果null对象引用被自动拆箱,就会得到一个NullPointerException异常。
装箱基本类型的合理用处:
作为集合中的元素、键和值。你不能将基本类型放在集合中,因此必须使用装箱基本类型。
在参数化类型中,必须使用装箱基本类型作为类型参数,因为
Java不允许使用基本类型。在进行反射的方法调用时,必须使用装箱基本类型。
基本类型与装箱基本类型==比较出现的几种情况:
Integer.valueOf()和Integer.intValue()进行了自动装箱和自动拆箱。通过
==对两个new出来的Integer类型的实例进行比较时,即使表示相同的数值,由于Integer类型的数据还要进行同一性的比较,因此不相等。通过
==比较两个自动装箱的Integer实例时,只要两者所表示的数值相同,结果就是相等的,因为它们的同一性是相同的。通过
equals进行比较的时候,只要同类型(包括自动装箱和拆箱)代表的数值相同,就是相等的。
50.如果其他类型更适合,则尽量避免使用字符串
字符串不适合代替其他的值类型。
字符串不适合代替枚举类型。
字符串不适合代替聚集类型。
字符串不适合代替能力表。
51.当心字符串连接的性能
由于字符串不可变,为连接n个字符串而重复地使用字符串连接操作符,需要n的平方级的时间。考虑使用StringBuilder替代String。
52.通过接口引用对象
如果有合适的接口类型存在,那么对于参数、返回值、变量和域来说,就都应该使用接口类型进行声明。
比如声明Vector类,它是List接口的实现,在声明变量的时候应该养成这样的习惯:List<String> strs = new Vector<String>();
而不是这样的声明:Vector<String> strs = new Vector<String>();
如果养成用接口作为类型的习惯,你的程序会更加灵活,当你决定更换实现时,所有要做的事只是改变构造器中类的实现,或者使用不同的静态工厂:List<String> strs = new ArrayList<String>();
不存在适当接口类型的部分情形:
值类,比如
String和BigInteger。记住值类很少会用多个实现编写,它们通常是final,并且很少有对应的接口。使用值类做参数、变量、域或者返回值类型是再合适不过的。对象属于框架,而框架的基本类型的类,不是接口。如果对象属于这种基于类的框架,那么就应该使用基类(通常是抽象类)来引用这个对象,而不是实现类。
Java.util.TimerTask抽象类就是这种情景。类实现接口,但是它提供接口中不存在的额外方法,如果程序依赖这些额外方法,这种类应该只被用来引用它的实例。
53.接口优先于反射机制
反射的代价:
丧失了编译时类型检查的好处,包括异常检查。
执行反射访问所需要的代码非常笨拙和冗长。
性能损失。
54.谨慎地使用本地方法
本地方法主要有三种用途:1.提供了“访问特定于平台的机制”的能力;2.提供了访问遗留代码库的能力,从而可以访问遗留数据;3.本地方法可以通过本地语言,编写应用程序中注重性能的部分,以提高系统的性能。
本地方法的缺点:1.不安全;2.与平台相关,使用本地方法的应用程序也不再是可自由移植的;3.更难调试。