由至周末,再更新一章的笔记,做到后来发现,78条建议只读标题可以都懂一些东西,再都每一条里面的例子又是另外一些知识。
8. 通用程序设计
45. 将局部变量的作用于最小化(本条与第13条——使类和成员的可访问性最小化本质上是类似的)
- 本条建议:
- 最有力的方法是在第一次使用局部变量的地方声明它:过早生命局部变量会扩展它的作用域,可能导致意外的发生;对于代码的阅读者来说,过早的声明一个没有用到的变量会让他们疑惑,分散注意力。
- 几乎每个局部变量的声明都应当包含一个初始化表达式:如果声明的时候不能进行有意义的初始化,那么就应该推迟这个声明。对此的一个例外是try-catch的语句,如果一个变量初始化可能抛出异常并且在try块之外还需要被使用,那么它可以在try块之前声明。
- 循环中提供了特殊的机会来使变量的作用域最小化:如果在循环终止之后不再需要循环变量的内容,for循环优先于while循环,因为for循环可以使局部变量的生命周期结束在
{}
内 - 使方法小而集中:减少方法变量的交叉可以预防代码混乱出错。
46. for-each循环优先于传统的for循环
不能使用for-each的情况:
- 过滤:遍历集合并删除元素的使用需要显式的迭代器,以调用remove方法
- 转换:便利集合并且需要部分取代它的元素时,需要迭代器或者数组索引
- 平行迭代:同时遍历多个集合,要求这多个集合同步前移的时候
如果需要编写一个类型,来表示一组元素,那么即使不扩展Collection接口,也要实现Iterable接口,这样客户代码可以使用for-each遍历这个类的对象。
47. 了解和使用类库
使用类库的好处:
- 无需关心实现细节:类库的方法大多经过严格的测试,值得信赖,就算有缺陷,也会在下一个发行版本中被修正。通过使用类库,可以充分利用编写标准类库的专家的知识,以及前人的使用经验。
- 不必浪费时间在与工作不太相关的问题上提供特别的解决方案。
- 可以使自己的代码融入主流,自己的代码更易读、易维护、更好重用。
建议学习Java的人熟悉的类库:java.lang、java.util、java.io
48. 如果需要精确地答案,请避免使用float和double
float和double是为了在广泛的数值范围内提供较为精确的快速近似计算而设计的,但它们不能提供完全精确的结果,,它们无法精确地表示任何10的负次数幂。
本条建议:使用BigDecimal、int、long类型进行货币计算。其中BigDecimal作为引用类型,提供了8种舍入模式,int和long则有较高的性能。
如果数字没有超过9位十进制数字范围,可以使用int、没有超过18位十进制数字,可以使用long、超过18位十进制数字的话只能使用BigDecimal
49. 基本类型优先于装箱基本类型
基本类型与基本装箱类型的区别:
- 基本类型只有值,装箱基本类型除了值还有与它们值不同的同一性(==的性质,引用地址不同)
- 基本类型只有功能完备的类,而装箱基本类型除了功能值之外还有null
- 基本类型比装箱基本类型更加节省时间和空间
装箱基本类型的合理用途:
- 作为集合中的元素、键值,因为集合中不能存放基本类型
- 泛型中必须使用装箱基本类型作为类型参数
- 在反射的方法调用时,必须使用装箱基本类型
装箱基本类型的问题(针对区别的三点):
- 当时用==比较两个装箱基本类型时,执行的是同一性比较,比较地址,这种比较多数情况下不是我们希望的
- 当涉及到装箱和拆箱基本类型时,遇到装箱基本类型是null的情况,拆箱会抛出NullPointerException异常
- 使用装箱基本类型会导致高开销和不必要的对象创建
50. 如果其他类型更合适,则尽量避免使用字符串
- 为什么不都是用String:
- String不适合替代其他类型的值,该是什么类型,就转换成什么类型
- String不适合代替枚举类型
- String不适合代替聚集类型。聚集类型,是指一个实体,有多个组件(属性)的情况,有人用String+分隔符表示一个聚集类型,但这种方法不好。如果用于分隔域的字符也出现在某一个域中会导致混乱, 而且访问单独的域需要解析字符串,这个过程比较繁琐耗时。
- String不适合代替能力表:有时会使用String作为某个条件判断某个功能是否可用(授权),但是String是存放在常量池中,可以被共享的,多个线程如果共享一个String,那么使用这个String判断授权就会产生问题。
51. 当心字符串连接接的性能
String是不可变的,每一次连接字符串都会生成无用的中间字符串,影响性能。为连接n个字符串而重复使用字符串连接操作,需要n的平方级的时间。
本条建议:使用StringBuilder代替String
52. 通过接口引用对象(类似与第0条提到的使用接口而不是类作为参数的类型)
使用接口的好处:隐藏了内部实现,方便后续版本更改内部实现而无需更换API
Java中的典型使用:ThreadLocal类:这个类内部有一个包级私有的Map域,1.3版本中使用HashMap,1.4版本中更改为专用的IdentityHashMap,然而这对于客户代码没有带来影响。
不适合使用接口引用对象的情况:
- 如果没有合适的接口存在,完全可以使用类而不是接口来引用对象:比如值类和具体的类
- 对象属于一个框架,而这个框架的基本类型就是类
- 类实现了接口,但是它提供了接口不存在的额外方法:比如LinkedHashMap、Vector。如果程序依赖于这些额外的方法,就应当使用类,而不是接口引用。
53. 接口优先于反射机制
反射的缺点:
- 失去了编译时类型检查额好处
- 执行反射访问所需要的代码非常笨拙而冗长
- 性能损失
反射和接口相配合:对于有些程序,它们必须要用到在编译时无法获取的类,这是可以通过编译时存在适当的接口或者引用这个类。即通过反射创建实例,然后通过它们的接口或者超类,以正常的方式访问这些实例。
54. 谨慎地使用本地方法
本地方法:本地程序设计语言来编写的特殊方法,本地方法在本地语言中可以执行任何的计算任务,并返回到Java程序设计语言。
本地方法的用途:
- “访问特定于平台的机制”的能力,比如注册表、文件锁
- 访问遗留代码库的能力。
- 通过本地语言,编写程序中注重性能的部分,提高系统的性能
不推荐本地方法的原因:
- 随着Java平台的不断发展,它提供了越来越多以前只有宿主平台才拥有的特性。
- JVM正在变得越来越快,使用本地方法提高性能的做法也就不值得推荐了
- 使用本地语言是不安全的,Java无法防止本地方法中对内存的破坏
- 本地方法与平台相关,使用本地方法的应用程序的可移植性更差
- 本地方法的调用更难调试
55. 谨慎地选择优化
- 本条建议:
- 好的结构优先于性能:好的结构影响整个程序的体系,一旦形成再难更改,而性能调优则可以在后期慢慢进行
- 好的设计模式(API的设计)优先于性能:两者相比,好的设计模式更重要,或者说好的设计会带来好的性能,二者不矛盾
- 在性能剖析工具的帮助下,选择优化的重点
56. 遵守普遍接受的命名惯例
字面命名惯例:
1.包的命名:有层次的,以句点分开;每部分包含小写字母和数字(很少使用数字);任何将本组织之外使用的包,以名称以本组织的Internet域名开头,并且顶级域名放在前面;包名的剩余部分应该包含一个或多个描述该包的组成部分- 类和接口的命名:一个或多个单词,每个单词首字母大写,避免使用缩写;
- 方法和域、局部变量的命名:类似于类和接口的命名,一个明显的区别是方法和域的第一个单词首字母小写;一个例外是常量域应当所有字母大写,多个单词以下划线分开
- 类型参数的命名:T代表任意类型;E代表集合的元素类型;K和V代表键值对的类型;X代表异常;任何类型的序列可以使T、U、V或者T1、T2、T3
语法命名惯例:
- 类的命名:通常是一个名词或者名词短语
- 接口的命名:与类相似,可以以-able或者-ible结尾的形容词命名
- 执行某个动作的方法命名:以动词或者动词短语明明
- 返回boolean的方法命名:往往以“is”开头,很少用“has”,后跟名词或者名词短语
- 返回非boolea的方法命名:通常用名词、名词短语,或者“get”开头的动词短语
- 特别的方法命名:
- 转换对象类型的方法、返回不同类型的独立对象的方法:形式如toType
- 返回视图的方法:形式如asType
- 返回要给与被调用对象同值的基本类型的方法:形式如typeValue
- 静态工厂的常用名称:valueOf、of、 getInstance、newIntance、getType、NewType