57. 将局部变量的作用域最小化
将局部变量的作用域最小化,可以增强代码的可读性和可维护性,并降低出错的可能性。
要使局部变量的作用域最小化,最有力的方法就是在第一次要使用它的地方进行声明。另一方法是使方法小而集中。
几乎每一个局部变量的声明都应该包含一个初始化表达式。
for循环优先于while循环。for循环更能避免“剪切-粘贴”错误;更简短,可读性更好。
58. for-each循环优先于传统的for循环
与传统的for循环相比,for-each循环在简洁性、灵活性以及出错预防性方面都占有绝对优势,并且没有性能惩罚的问题。多重循环时,for循环容易出错。
无法使用for-each的三种情况:
a. 如果需要遍历集合,并删除选定的元素,就需要使用显式的迭代器,以便调用remove方法。
b. 如果需要遍历列表或者数组,并取代它的部分或者全部元素值,就需要用迭代器或数组索引。
c. 如果需要并行地遍历多个集合,就需要显式地控制迭代器或者索引变量,以便所有迭代器或索引变量可以同步前进。
59. 了解和使用类库
使用标准类库的好处:
a. 可以充分利用这些编写标准类库的专家的知识,以及在你之前的其他人的使用经验。
b. 不必浪费时间在底层细节,而把时间花在应用程序上。
c. 它们的性能会随着时间推移而不断提高。
d. 它们会随着时间的推移而增加新的功能。
e. 可以使自己的代码融入主流。
每个程序员都应该熟悉java.lang、java.util、java.io及其子包中的内容。
优先在类库查找所需功能,如果类库找不到,下一个选择应该是在高级的第三方类库去寻找,如Guava类库。如果都找不到,最好才是选择自己实现这些功能。
60. 如果需要精确的答案,请避免使用float和double
float和double类型尤其不适合用于货币计算。请优先使用BigDecimal,如果性能是关键,可以使用int或long。如果数值不超过9位十进制数,使用int;如果不超过18位,使用long;如果可能超过18位,还是用BigDecimal。
61. 基本类型优先于装箱基本类型
基本类型(int,double,boolean)比装箱基本类型(Integer,Double,Boolean)更加简单、快速,应优先使用。
61.1 基本类型和装箱类型的区别:
a. 基本类型只有值,而装箱类型具有与它们的值不同的同一性。
b. 基本类型只有函数值,而装箱类型除了函数值,还有个null。
c. 基本类型通常比装箱类型更节省时间和空间
61.2 什么时候用装箱类型
a. 作为集合中的元素、键和值
b. 在参数化类型和方法中,必须用装箱类型作类型参数
c. 在进行反射的方法调用时
61.3 使用装箱注意事项
a. 对装箱类型运用==操作符通常是错误的,因为比较的是对象而不是值
b. 当在一项操作中混用基本类型和装箱类型是,装箱类型会自动拆箱,可能抛出空指针异常
c. 当基本类型转装箱时,会导致资源消耗和不必要的对象创建
62. 如果有其他类型更适合,则尽量避免使用字符串
字符串不适合代替基本类型。
字符串不适合代替枚举类型。
字符串不适合代替聚合类型。
字符串不适合代替能力表(capabilities),如授权访问中作key。
63. 了解字符串连接的性能
63.1 不要使用字符串连接操作符来合并多个字符串(需要n的平方级的时间)
63.2 使用StringBuilder的append方法来连接字符串
63.3 使用字符数组,或者每次只处理一个字符串,而不是将它们组合起来
64. 通过接口引用对象
如果有合适的接口类型存在,那么对于参数、返回值、变量和域来说,就都应该使用接口类型进行声明。养成用接口作为类型的习惯,程序将会更加灵活。如果没有合适的接口,就用类层次结构中提供了必要功能的最小的具体类来引用对象。
不存在适当接口类型的情形:
a. 值类(如String,BigInteger)经常是final的,且很少有对应的接口。
b. 对象属于一个框架,而框架的基本类型是类,不是接口。一般用基类(抽象类)引用对象。
c. 类实现了接口,但它提供了接口不存在的额外方法。 这种类只能引用它的实例。
65. 接口优先于反射机制
核心反射机制(core reflection facility),java.lang.reflect包,提供了“通过程序来访问任意类”的能力。反射机制允许一个类使用另一个类,即使当前者被编译的时候,后者还根本不存在。如果编写的程序必须要与编译时未知的类一起工作,如有可能应该仅仅用反射机制来实例化对象,而访问对象时则使用编译时已知的某个接口或者超类。
反射机制的代价:
a. 损失了编译时类型检查的优势,包括异常检查。可能运行时失败。
b. 执行反射访问所需的代码非常笨拙和冗长,阅读困难。
c. 性能损失。 反射方法调用比普通方法调用慢许多。
66. 谨慎地使用本地方法
所谓本地方法是指用本地编程语言(如C或C++)来编写的方法。JNI允许Java程序调用本地方法。使用本地方法来访问特定于平台的机制是合法的,但是几乎没有必要。
使用本地方法来提高性能的做法不值得提倡。
使用本地方法的一些缺陷:
a. 程序可能受内存毁坏错误的影响,因为本地语言不是安全的。
b. 程序不可自由移植,因为本地语言是平台相关的。
c. 程序更难调试
d. 还可能降低性能,因为内存回收不是自动的,并且进入和退出本地代码需要相关开销。
e. 需要“胶合代码”的本地方法编写起来单调乏味,难于阅读。
67. 谨慎地进行优化
优化的弊大于利,特别是不成熟的优化。
不要为了性能而牺牲合理的结构。努力编写好的程序而不是快的程序。
必须在设计过程中考虑性能问题,而不是系统完成之后。要努力避免那些限制性能的设计决策。最主要的组件是API、交互层协议以及永久数据格式。要考虑API设计决策的性能后果。
在每次试图做优化之前和之后,要对性能进行测量。如果将要在多个JVM实现和多种硬件平台上运行程序,则需要在每个Java实现和平台上测量优化效果。
系统构建完后应该测量性能。如果不够快,则可以在性能分析器的帮助下,找到问题的根源,然后设法优化系统中相关的部分。第一个步骤是检查所选择的算法:再多的低层优化也无法弥补算法的选择不当。必要时重复这个过程。
68. 遵守普遍接受的命名惯例
命名惯例分两大类:字面的和语法的。
标识符类型 | 字面惯例 | 示例 |
---|---|---|
包或者模块 | 名称应该是层次状的,用句号分割每个部分。每个部分是小写字母,偶有数字。 | org.junit.jupiter.api |
类或者接口(包括枚举和注解) | 一个或多个单词,每个单词的首字母大写 | Stream,HttpClient |
方法或者域 | 第一个字母小写 | remove,groupingBy |
常量域 | 字母都小写,每个单词间用下划线 | MIN_VALUE |
局部变量 | 允许缩写,单个字符和短字符序列,取决于上下文环境 | i,houseNum |
类型参数 | 单个大写字母 | T: 任意类型 E: 集合的元素类型 K和V: 映射的键和值 X: 异常 R: 函数的返回类型 T、U、V、T1、T2、T3: 任何类型的序列 |
语法命名惯例比字面惯例更加灵活,也更有争议。
标识符类型 | 语法惯例 | 示例 |
---|---|---|
可实例化的类(包括枚举) | 名词或名词短语 | Thread |
不可实例化的工具类 | 复数名词 | Collectors |
接口 | 名词或形容词 | Collection,Runnable,Accessible |
注解 | 名词、动词、介词、形容词 | Inject,Singleton |
执行动作的方法 | 动词或动词短语 | append |
返回boolean的方法 | is开头 | isDigit |
设置对象属性的方法 | set开头 | setAttribute |
获取对象属性的方法 | get开头 | getAttribute |
转换对象类型的实例方法 | toType | toString,toArray |
返回视图的方法 | asType | asList |
返回与被调用对象同值的方法 | typeValue | intValue |
静态工厂 | from,of,valueOf, instance,getInstance,newInstance, getType,newType |
不要盲目遵从惯例,请使用大家公认的做法。