方法
0. 方法太多会使类难以学习
不要过于追求提供便利的方法,每个方法都应该尽其所能,只有当一项操作被经常用到的时候,才考虑为它提供快捷方式。
1. 谨慎设计方法签名
- 谨慎地选择方法的名称,一般使用约定俗成的方式。
- 避免过长的参数列表,可以通过1)分解方法、2)创建辅助类(一般为静态成员类)用于保存参数、3)Builder模式,这三种方法来缩短参数列表。
- 对于参数类型,优先使用接口而不是类。
- 如果可能涉及到拓展,对于布尔参数,要优先使用两个元素的枚举类型。
2. 慎用可变参数
可变参数的机制是通过先创建一个数组,数组的大小为在调用位置所传递的参数的数量,然后将参数值传到数组,最后将数组传递给方法。
可变参数可以代替final数组,但只有在方法参数数目确实不定时才应该这么做。
使用时需要注意是否有必要的参数,如果有则不能将其视为可变参数的一部分,应该将其单独作为1个参数;如果可变参数的数目常用在某个范围以下,比如某个方法一般使用0~3个参数,那么最好为其写4个重载0~3个参数的方法,外加一个3个参数加1个可变参数的方法。
3. 慎用重载(overload)
调用重载方法是在编译时基于参数的编译时类型做出决定的,这与重写的运行时决定截然不同。
对于任何一组实际的参数,哪个重载方法是适用,一旦产生这样的疑问,就应该慎重。比如:
- 某个方法重载类层次关系的参数,比如Collection和List ,因为后者的编译时类型为前者,所以会调用到前者的重载方法。
- 可变参数的保守策略是根本不要去重载它。
- 注意自动装箱拆箱。
不同的参数(非可变参数)数目必然会有不同的重载结果,所以当重载相同参数数目的方法时需要特别谨慎。
4. 检查参数的有效性
绝大多数方法和构造器对于传递给它们的参数值都会有某些限制,你应该在文档中清楚地指明所有这些限制,通过参数检查来强制施加这些限制,并抛出合适的异常。
5. 必要时进行保护性拷贝
对于不可变的类,保护性拷贝很有必要,以防他人恶意或不当攻击。保护性拷贝的思想就是不接受可变对象,而是在类的内部新建一个这个对象的拷贝;不返回类内部的成员,而是拷贝一份返回,类似返回”视图”的意思。
编写方法或者构造器时,如果它允许客户提供的对象进入到内部数据结构,则有必要考虑一下,客户提供的对象是否有可能是可变的,如果是则需要考虑是否应该进行拷贝。
6. 返回零长度的数组或者集合,而不是null
返回null,如果客户端程序员忘记检查null,可能会导致潜伏多年的错误。正确的做法是返回零长度的数组或者集合。
7. 为所有导出的API元素编写文档注释
通用程序设计
1. 将局部变量的作用域最小化
将局部变量的作用域最小化,可以增强代码的可读性和可维护性,并降低出错的可能性。
C语言要求局部变量必须在一个代码块的开头处进行声明,而Java允许你在任何可以出现语句的地方声明变量,过早的声明不仅会降低可读性,并且会使其作用域扩大。有两种常见方式可以最小化局部变量的作用域:
- 在第一使用它的地方声明而不过早声明。几乎每个局部变量的声明都应该有初始化表达式,除非当前无法进行时则需要被无意义地初始化,比如常见的try-catch-finally结构,在try块前面就要进行无意义的初始化
InputStream in = null
。 - 优先使用for循环而非while循环,使其控制循环条件的变量位于for()中。
2. for-each循环优于传统的for循环
一般而言for-each是最优的循环遍历选择,有三种常见的情况无法使用for-each循环:
- 过滤:需要遍历集合删除选定元素;
- 转换:需要遍历集合替代某些元素;
- 平行迭代:如果需要并行地遍历多个集合。
可以看出,当需要索引变量时,for-each是不适用的。
3. 了解和使用类库
- 不要重复造轮子。
- 一般人造的轮子肯定没有类库的好。
后面有必要找个时间好好系统认识学习一些非常常用的类库了,最近自己也在这方面吃了一点亏,明明调用类库几行代码搞定的事,自己又写了一大堆,还不见得好。学习一次,到处高效运行!
4. 如果需要精确的答案,避免使用float和double
这两者主要是为了科学计算和工程计算而设计的,不应该被用于需要精确结果的场合。可以使用BigDecimal、int、long替代,比如当我们说0.01秒的时候,可以用10ms来代替。
5. 基本类型优于装箱基本类型
基本类型是值类型,装箱类型是引用类型,具有对象特征,当我们使用==
比较时,前者比较的是值,后者比较的是对象地址,语义完全不同。
当在一项操作中混合使用了基本类型和装箱类型时,装箱类型会自动拆箱,而如果它的初始值为null时,毫无疑问会抛出空指针异常。
频繁的装箱操作会导致高开销和不必要的对象创建,基本类型通常比装箱类型更节省时间和空间。
6. 当心字符串的连接性能
这个老生常谈的问题了。要产生单独一行的输入,或者构造一个字符串来表示一个较小的、性能无关紧要的对象,使用连接操作符是非常适合的。否则就应该使用StringBuilder。
7. 通过接口引用对象
如果有合适的接口类型存在,那么对于参数、返回值、变量和字段来说,都应该使用接口类型进行声明,遵循依赖倒置原则。
如果没有合适的接口存在,完全可以用类而不是接口来引用对象。
类实现了接口,但是提供接口不存在的额外方法,如果程序依赖这些额外的方法,也可以使用类而不是接口引用对象。
8. 接口优先于反射机制
反射机制允许一个类使用另外一个类,即使当前者被编译的时候,后者还根本不存在。然而这种能力是有代价的。
- 丧失了编译时类型检查的功能。
- 代码笨拙和冗长。
- 性能损失。
对于有些程序,它们必须用到在编译时无法获取的类,但是编译时存在适当的接口或者父类,那么应该通过它们应用这个对象,这样一旦对象被实例化,它与其他Set实例就难以区分,可以减少使用其他反射方法。
9. 谨慎地使用本地方法
JNI允许Java应用程序调用本地方法。使用本地方法来提高性能的做法是不值得提倡的,并且使用本地方法有一些严重的缺点,比如1)本地语言是不安全的,所以使用本地方法的应用程序也不再能免受内存错误的影响。2)难以调试;3)可读性差;4)如果只是做少量工作,反而会降低性能。
极少数的情况下会需要使用本地方法来提高性能,如果需要本地方法访问底层资源或者遗留代码库时,也要尽可能少用本地代码。
10. 谨慎地进行优化
文中并未明确表明所说的优化是指什么,我觉得作者所说的优化是指花费大量时间去优化算法,试图提高程序的运行效率。然而这样做往往是得不偿失的,因为很有可能你的优化点根本不是瓶颈点。
不要因为性能和牺牲合理的结构,要努力编写好的程序而不是快的程序,程序写好了,速度自然会上来。比如,如果误用装箱类型而非使用合理的基本类型来进行大量计算,对算法一致的程序也会产生巨大性能差异。