通用程序设计
本章主要讨论java的实现细节。讨论了局部变量的处理,控制结构,类库的用法,各种数据类型的用法,以及两种不是由语言本身提供的机制(反射机制和本地方法)的用法。最后讨论优化和命名规范。
45,将局部变量的作用域最小化
可以增强代码的可读性和可维护性,并降低出错的可能性。
- 最好的办法是在它第一次使用的地方声明。
- 几乎每个局部变量的声明都应该包含一个初始化表达式。(try-catch有例外)
- 如果在循环终止之后不再需要循环变量的内容,for循环就优先于while循环。
- 使方法小而集中。(用其他方法各执行操作)
46,for-each循环优先于传统的for循环
- 完全隐藏了迭代器或者索引变量,避免了混乱和出错的可能。
- for-each循环在简洁性和预防bug方面有着传统的for循环无法比拟的优势,并且没有性能损失。
例外:
- 过滤 —— 如果需要遍历并删除元素。
- 转换 —— 取代它的部分或全部值。
- 平行迭代 —— 如果需要并行地遍历多个集合,就需要显式地控制迭代器或者索引变量,以便所有迭代器或者索引变量都可以得到同步前移。
47,了解和使用类库
好处:
- 通过使用标准类库可以充分利用这些编写标准库类的专家知识,以及在你之前的其他人的经验。
- 不必浪费时间为那些与工作不太相关的问题提供特别的解决方案。
- 他们性能往往会随时间的推移而不断提高,无需你做任何努力。
- 可以使自己的代码融入主流。
每个程序员应该都熟悉java.lang,java.util,某种程度上还有java.io中的内容。
Collections Framework是一个统一的体系框架,用来表示和操作集合,允许它们对集合进行独立于表现细节的操作。
java.util.concurrent包中有一组并发使用工具,这个包既包含高级的并发工具来简化多线程的变成任务,还包含低级的并发基本类型,允许专家们自己编写更高级的并发工具。
48,如果需要精确地答案,请避免使用float和double
float和double类型主要是为了科学计算和工厂计算而设计的。他们执行二进制浮点运算,这是为了在广泛的数值范围上提供较为精确地快速近似计算而精心设计的。
然而它们并没有提供完全精确地结果,所以不应该用于需要精确结果的场合,尤其不适用于货币计算,因为想要让一个float和double精确表示0.1(或者10的任何其他负数次方值)是不可能的。
使用BigDecimal,int或long(小数分单位)进行货币计算。如果数值范围没有超过9为十进制,就可以使用int;如果不超过18为数字,就可以使用long;如果数值可能超过18位,就必须使用BigDecimal。
49,基本类型优先于装箱基本类型
区别:
- 基本类型只有值,而基本装箱类型可以具有相同的值和不同的同一性。
- 基本类型只有功能完备的值,而每个装箱类型除了它对应基本类型的所有功能外,还有个非功能值:null。
- 基本类型通常比装箱类型更节省时间和空间。
什么时候使用装箱:
- 作为集合中的元素,键和值。
- 在参数化类型中,必须使用装箱基本类型作为参数类型。
50,如果其他类型更合适,则尽量避免使用字符串
- 字符串不适合替代其他类型。例如是否;文件,网络或者键盘信息。
- 字符串不适合替代枚举类型。枚举类型比字符串更加适合用来表示枚举类型常量。
- 字符串不适合替代聚集类型。如果一个实体有多个组件,用一个字符串来表示这个实体通常很不给力。
- 字符串也不适合替代能力表
若使用不当,字符串回避其他类型更加笨拙,更不灵活,速度更慢,也更容易出错。
51,当心字符串的连接性能
要想产生单独一行的输出,或者构造一个字符串来表示一个较大的,大小固定的对象,使用连接操作法是非常适合的,但它不适用于在大规模的场景中。
字符串是不可变的,当两个字符串连接到一起,它们的内容都要被拷贝。为连接n个字符串而重复的使用字符串连接操作符,需要n的平方级的时间。
为了获得可以接受的性能,请使用StringBuilder替代String。
52,通过接口引用对象
如果有合适的接口类型存在,那么对于参数,返回值,变量和域来说,就都应该使用接口类型来进行声明。只有当你利用构造器创建某个对象的时候,才真正需要引用这个对象的类。
如果你养成了用接口作为类型的习惯,你的程序将会更灵活。
不适用接口情况:
- 如果没有合适的接口存在,完全可以用类而不是接口来引用对象。
- 对象属于一个框架,而框架的基本类型是类,而不是接口。java.util.TimerTask。
- 类实现了接口,但它提供的接口中不存在额外的方法——例如LinkHashMap。如果程序依赖这些额外方法。
如果接口引用对象就会使程序更加灵活就引用对象;如果不是,则使用类层次结构中提供了必要功能的最基础的类。
53,接口优先于反射机制
使用反射的缺点:
- 丧失了编译时类型检查的好处,包括异常检查。
- 执行反射访问所需要的代码非常笨拙和冗长。
- 性能损失。反射方法调用比普通方法调用慢了许多。
反射功能只是在设计时被用到。通常,普通的应用程序在运行时不应该以反射方式访问对象。
可以以反射方式创建实例,然后通过它们的接口或者超类,以正常的方式访问这些实例。
如果要编写一个包,并且它运行时必须依赖其他某个包的多个版本,这种做法可能就非常有用。这种做法就是,在支持包所需要的最小环境下对它进行编译,通常是最老版本,然后以反射访问任何更加新的类或者方法。
简而言之,反射机制是一种功能强大的机制,对于特定的复杂系统编程任务,它是非常必须的,但它也有缺点。如果你编写的程序必须要与编译时未知的类一起工作,如有可能,就应该仅仅使用反射机制来实例化对象,而访问对象时则使用编译时已知的某个接口或超类。
54,谨慎使用本地方法
所谓本地方法是指用本地程序设计语言(比如C或者C++)来编写特殊方法。
使用本地方法来提高性能的做法不值得提倡。
55,谨慎地进行优化
- 优化弊大于利,特别是不成熟的优化。
- 不要因为性能而牺牲合理的结构。要努力编写好的程序而不是快的程序。
如果好的程序不够快,它的结构将使它可以得到优化。好的程序体现了信息隐藏的原则:只要有可能,它们就会把设计决策集中在单个模块中,因此,可以改变单个模块,而不会影响到系统的其他部分。
- 努力避免那些限制性能的设计决策。遍布全局并且限制性能的结构缺陷几乎不可能被改成的,除非编写系统。
- 要考虑API设计决策的性能后果。
- 为获得好的性能二队API进行包装,这是一种非常不好的想法。
56,遵守普遍接受的命名习惯
- 包的名称应该是层次的,用句号分隔每个部分。每个部分都包括小写字母和数字(很少使用)。包名称的其余部分应该包括一个或者多个描述该包的组成部分,通常不超过8个字符,鼓励有意义的缩写。
- 类和接口的名称,包括枚举和注解类型,都应该包括一个或者多个单词,每个的单词的首字母大写。
- 方法和域的名称与 类和接口相似,只是首字母不用大写。
- 常量域,它的名称应该包含一个或者多个大写单词,中间用下划线符号隔开。
- 局部变量和成员类名称相似,只不过它允许缩写,单个字符和短字符序列的意义取决于局部变量所在的上下文环境。
- 类型参数通常由单个字母组成。通常一下五个类型:T表示任意的类型,E表示集合的元素类型,K和V表示映射的键和值的类型,X表示异常。任何类型序列可以是T,U,V或者T1,T2,T3。
- 如果方法所在的类时Bean,则有get,set开头
如果长期养成的习惯用法与此不同,不要盲目尊从这些名名规范,请运用常识。