《Effective Java》第8章 通用程序设计

本章主要涉及Java语言的具体细节,讨论了局部变量的处理,控制结构,类库的用法,各种数据类型的用法,以及两种不是语言本身提供的机制(reflection和native method,虚拟机JVM提供支持的)的用法。

1.将局部变量的作用域最小化 【Item 45】
1) Java允许你在任何可以出现语句的地方声明变量
2) 要使局部变量的作用域最小化,最有力的方法就是在第一次使用它的地方声明
3) 几乎每个局部变量的声明都应该包含一个初始化表达式,还不能初始化时就尽量推迟声明到可以初始化的位置
4) 无论是传统的还是for-each形式的for循环,都允许声明循环变量,它们的作用域被限定在正好需要的范围之内,因此在循环终止之后不再需要循环变量的内容,for循环就优先于while循环,避免无意识错误的发生
2.for-each循环优先于传统的for循环 【Item 46】
1) java 1.5引入的for-each循环,通过完全隐藏迭代器或者索引变量,避免了混乱和出错的可能,这种模式适用于实现过Iterable接口的类,如数组和集合
2) 那么为了可以使用for-each循环,你在编写的类型表示一组元素时,即使你选择不让它实现Collection,也要让它实现Iterable接口
3) 想要在遍历的时候修改所遍历的数组或者集合无法使用for-each循环,这种情形有如下三种:
a. 过滤,遍历过程中需要删除相关元素,需要用显示的迭代器进行,以便可以调用remove方法
b. 转换,遍历过程中想要取代其部分或者全部元素,需要用迭代器或数组索引,以便设定元素的值
c. 平行迭代,需要并行遍历多个集合,需要显示的控制迭代器或者索引变量,以便所有迭代器或者索引变量都可以同步的前移
如上三种情形,可以用传统for循环进行处理。
3.了解和使用类库 【Item 47】
1) 通过使用标准类库的好处:
a. 可以充分利用这些编写标准类库的专家的知识,以及在你之前的其他人的使用经验
b. 不必浪费时间为那些与工作不太相关的问题提供特别的解决方案
c. 类库的性能往往会随着时间的推移而不断提高,无需你做任何努力
d. 可以使自己的代码融入主流,这样的代码更易读,更易维护,更易被大多数开发人员重用
2) 每个程序员都应该熟悉java.lang,java.util,某种程度上还有java.io中的内容
3) java.util的重点Collection Framework以及java.util.concurrent关于多线程相关部分
4) 不建议重新发明轮子,君子要善假于物也
4.如果需要精确的答案,请避免使用float和double 【Item 48】
1) float和double尤其不适合于货币运算,因为在JVM实现中只是针对float和double有一些value set,在处理这类数据时经常会进行约等于的操作
2) 而精确的计算应该用BigDecimal,int或long进行货币计算,如果数值范围没有超过9位十进制数字,就可以使用int;如果不超过18位数字,就可以使用long;如果数值可能超过18位数字,就必须使用BigDecimal
5.基本类型优先于装箱节本类型 【Item 49】
1) 基本类型和装箱类型之间三个区别:
a. 基本类型只有值,而装箱基本类型则具有与它们的值不同的同一性,也就是说两个装箱基本类型可以具有相同的值和不同的同一性
b.基本类型只有功能完备的值,而每个装箱基本类型除了它对应的基本类型的所有功能值之外,还有个分功能值:null
c. 基本类型通常比装箱节本类型更加节省时间和空间
2) 对于装箱基本类型使用==操作几乎总是错误的
3) 当在一项操作中混合使用基本类型和装箱基本类型时,装箱基本类型就会自动拆箱,如果null对象引用被自动拆箱,就会出现一个NullPointerException
4) 变量被反复的装箱和拆箱,导致明显的性能下降
5) 装箱基本类型的应用场景:
a. 作为集合中的元素,键和值,不能将基本类型放入集合中,因此必须使用装箱基本类型
b. 在参数化类型中,必须使用装箱基本类型作为类型参数,因为Java不允许使用基本类型
c. 进行反射的方法调用时,必须使用装箱基本类型
6) 自动装箱减少了使用装箱基本类型的繁琐性,但是并没有减少它的风险,当程序使用==操作符比较两个装箱基本类型时,它做了同一性比较,即对象引用的一致性,并不是单纯的值比较
6.如果其他类型更适合,则尽量避免使用字符串 【Item 50】
1) 一些不应该使用字符串的情形:
a. 字符串不适合代替其它的值类型,例如不应该用字符串来代替int,float或者boolean等
b. 字符串不适合代替枚举类型
c. 字符串不适合代替聚集类型
d. 字符串也不适合代替能力表
2) 如果可以使用更加合适的数据类型,或者可以编写更加适当的数据类型,就应该避免使用字符串来表示对象
7.当心字符串连接的性能 【Item 51】
1) 当连接n个字符串而重复地使用字符串连接操作符,需要n的平方级的时间,这是由于字符串不可变而导致的不幸结果,当两个字符串被连接在一起时,它们的内容都要被拷贝
2) 为了获得可以接受的性能,请使用StringBuilder代替String,Java1.5发行版本中增加了非同步的StringBuilder类,代替现在已经过时的StringBuffer类
3) 不要使用字符串连接操作符来合并多个字符串,除非性能无关紧要,应该使用StringBuilder的append方来合并或者使用字符数组,或者每次只处理一个字符串,而不是将它们组合起来
8.通过接口引用对象 【Item 52】
1) 如果有合适的接口类型存在,那么对于参数,返回值,变量和域来说,就都应该使用接口类型进行声明
2) 如果你养成了用接口作为类型的习惯,你的程序将会更加灵活
3) 不能用接口声明的情况:
a. 如果没有合适的接口存在,完全可以用类而不是接口来引用对象,典型的值类通常没有合适的接口可用,用值类本身作为参数,变量,域或者返回类型就可以了
b. 对象属于一个框架,而框架的节本类型是类,不是接口,如果对象属于这种基于类的框架,就应该使用相关的基类来引用这个对象,而不是它的实现类
c. 类实现了接口,但是它提供了接口中不存在的额外方法,如果程序依赖于这些额外方法,这种类就应该只被用来引用它的实例,这种类很少被用作参数类型
4) 总之建议引用对象的类型建议范围较广一些,这样增加程序的灵活性,比如使用接口,或者对象的基类
9.接口优先于反射机制 【Item 53】
1) 核心反射机制java.lang.reflect提供了“通过程序来访问关于已装载的类的信息”的能力
2) 反射机制的代价:
a. 丧失了编译时类型检查的好处
b. 执行反射访问所需要的代码非常笨拙和冗长
c. 性能损失
3) 反射功能只是在设计时被用到,通常,普通应用程序在运行时不应该以反射方式访问对象
4) 有一些复杂的应用程序需要使用反射机制,包括类浏览器,对象监视器,代码分析工具,解释型的内嵌式系统,RPC等
5) 如果只是以非常有限的形式使用反射机制,虽然也要付出少许代价,但是可以获取更多的好处,比如必须用到编译时无法获取到的类,但是在编译时存在适当的接口或者超类,通过它们可以引用这个类
6) 如果适当的构造器不带参数,甚至根本不需要使用java.lang.reflect包,Class.newInstance方法就已经提供了所需功能
7) 类对于在运行时可能不存在的其他类、方法、或者域的依赖性,用反射法进行管理,这种用法是合理的
8) 应该仅仅使用放射机制来实例化对象,而访问对象时则使用编译时已知的接口或者超类
10.谨慎地使用本地方法 【Item 54】
1) JNI(Java Native Interface)允许Java应用程序可以调用本地方法(native method),所谓本地方法是指的用本地程序设计语言(比如C/C++)来编写的特殊方法
2) 本地方法的三个用途:
a. 提供了“访问特定于平台的机制”的能力,比如访问注册表,和文件锁等
b. 提供了访问遗留代码库的能力,从而访问遗留数据
c. 本地方法可以通过本地语言,编写应用程序中主动性能的部分,以提高系统的性能
3) 本地方法的缺点:
a. 本地方法不是安全的,使用本地方法的应用程序不能再免受内存毁坏错误的影响
b. 本地语言是与平台相关的,使用本地方法的应用程序也不再是可以自由移植的
c. 使用本地方法的应用程序也更难调试,在进入和退出本地代码时,需要相关的固定开销,性能影响,难于阅读
4) 使用本地方法前务必三思
11. 谨慎地进行优化 【Item 55】
1) 不要去计较效率上的一些小小的得失,在97%的情况下,不成熟的优化才是一切问题的根源
2) 要努力编写好的程序而不是快的程序
3) 好的程序体现了信息隐藏的原则:只要有可能,它们就会把设计决策集中在单个模块中,因此,可以改变单个决策,而不会影响到系统的其他部分,避免牵一发而动全身
4) 必须在设计的时候就考虑到性能问题,努力避免那些限制性能的设计决策
5) 在一个系统设计完成之后,最难以更改的组件是那些指定了模块之间交互关系以及模块与外界交互关系的组件
6)要考虑API设计决策的性能后果:
a. 使公有的类型成为可变的,这可能会导致大量的不必要的保护性拷贝
b. 在适合使用复合模式的公有类中使用继承,会把这个类与其它的超类永远的束缚在一起,从而人为的限制了子类的性能
c. 在API中使用实现类型而不是接口,会把你束缚在一个具体的实现上,即使将来出现更快的实现你也无法使用
12. 遵守普遍接受的命名惯例 【Item 56】
1) 命名的惯例分为两大类:字面的和语法的
2) 用户创建的包的名称绝不能以java和javax开头,因为标准类库和一些可选的包的名称会以java和javax开头
3) 包名称其它部分包括一个或多个描述该包的组成部分,这些短语应该尽量简短,通常不超过8个字符,可以以单词首字母代替一个单词的形式进行缩短
4) 类和接口的名称,包括枚举和注解类型的名称,都应该包括一个或者多个单词,每个单词首字母大写,应该尽量避免缩写,除非是一些首字母缩写和一些通用的缩写
5) 常量是唯一推荐使用下划线的情形
6) 类型参数名称通常由单个字母组成,这个字母通常有一下五种情形:
a. T表示任意的类型
b. E表示集合的元素类型
c. K和V表示映射的键和值类型
d. X表示异常
7) 当然如果长期养成的习惯用法与上述规则不同,不必盲目遵从这些命名惯例

阅读更多
想对作者说点什么?

博主推荐

换一批

没有更多推荐了,返回首页