Effective Java学习笔记三

四十七.了解和使用类库
private static final Random rnd = new Random();
n是一个比较小的2的乘方,经过一段相当短的周期之后,它产生的随机数序列将会重复
如果n不是2的乘法,那么平均起来,有些数会比其他的数出现得更为频繁
但是这些缺点在Random.nextInt(int)中全部被解决了,你并不需要了解细节。
使用标准类库的好处是,它们的性能往往会随着时间的推移而不断提高,无需你做任何努力

四十八.如果需要精确的答案,请避免使用float和double
float和double中会出现精度损失,所以需要精确计算的时候请勿使用float和double
如果解决货币计算中的精度损失带来的问题, 可以使用BigDecimal, int或者long进行货币计算

四十九.基本类型优先于装箱基本类型
每个基本类型都有一个对应的引用类型,称作装箱基本类型
基本类型和装箱基本类型之间有三个主要区别:
①基本类型只有值,而装箱基本类型则具有与它们的值不同的同一性
②基本类型只有功能完备的值,装箱类型还有一个非功能值:null
③基本类型通常比装箱基本类型更节省时间和空间
first < second执行计算会自动拆箱
first == second 它在对象引用上执行同一性比较
当一项操作中混合使用基本类型和装箱基本类型时,装箱基本类型就会自动拆箱

五十.如果其他类型更适合,则尽量避免使用字符串
字符产不适合代替其他的值类型
字符串不适合代替枚举类型
字符创不适合代替聚集类型
字符串不适合代替能力表

五十一.当心字符串连接的性能
为连接n个字符串而重复使用字符串连接操作符,需要n的平方级的时间,当两个字符串连接在一起时,它们的内容都要被拷贝
为了获取可以接受的性能,请使用StringBuilder替代String
另外一种优化的方式是,使用字符数组

五十二.通过接口引用对象
如果有合适的接口类型存在,那么对于参数,返回值,变量和域来说,就都应该使用接口类型进行声明
如果坚持使用接口类型的话,你的程序将会变得更加灵活。
如果没有合适的接口存在,完全可以用类而不是接口来引用对象

五十三.接口优先于反射机制
泛型反射机制java.lang.reflect,提供了“通过程序来访问关于已装载的类的信息”
使用反射类型要付出的代价:
①丧失了编译时类型检查的好处
②执行反射访问所需要的代码非常笨拙和冗长
③性能损失
核心反射机制最初是为了基本组件的应用创建工具而设计的。

五十四.谨慎地使用本地方法
Java Native Interface(JNI),本地方法是指用本地程序设计语言来编写的特殊方法
本地方法有三种用途:
①访问特定于平台的机制
②提供了访问遗留代码库的能力
③本地方法可以通过本地语言,编写应用程序中注重性能的部分,提高系统的性能
本地方法有一些严重的缺点,因为本地语言不是安全的,所以使用本地方法的应用程序也不再能免受内存毁坏错误的影响

五十五.谨慎地进行优化
在优化方面,我们应该遵守两条规则
规则1:不要进行优化
规则2:还是不要进行优化——也就是说,在你还没有绝对清晰的来优化方案之前,请不要进行优化。
不要因为性能而牺牲合理的结构,要努力编写好的程序而不是快的程序。
并且一般而言,好的API设计也会带来好的性能,为了获得好的性能而对API进行包装,这是一种非常不好的想法

五十六.遵守普遍接受的命名惯例
Java建立了一套很好的命名惯例,这些命名惯例分为两大类,字面的和语法的
包的名称应该是层次状的,用分号分隔每个部分,每个部分都包括小写字母和数字,一般都用域名来进行定义
提醒:用户创建的包的名称决不能以java、javax开头,并且单词应用缩写形式
类和接口的名称,包括枚举和注解类型的名称,都应该包括一个或者多个单词,每个单词的首字母大写。
方法和域的名称与类和接口的名称一样,都遵守相同的字面惯例,只不过方法或者域的名称的第一个字母应该小写
常量域,它的名称应该包含一个或多个大写的字母,中间用下划线符号隔开,用final进行修饰
局部变量名称的字面命名惯例与成员名称类似,只不过它也允许缩写,单个字符和短字符序列的意义取决于局部变量所在的上下文环境
泛型的表示也是有规则的,T表示任意的类型,E表示集合的元素类型,K和V表示映射的键和值类型,X表示异常
对于返回boolean的方法,一般使用is开头
如果方法所在的类是Bean,要强制使用get(set),很多框架中默认调用的方法便是get加上属性名

五十七.只针对异常的情况才使用异常
异常的错误使用:
try {
int i = 0;
while(true) 
range[i++].climb();
}
catch(ArrayIndexOutOfBoundsException e) ..
之所以有人会使用这种方式,因为他们以为java的错误判断机制来提高性能
这种做法的错误之处在于:
①异常机制的设计是用于不正常的情形,JVM很少会对他进行优化
②把代码放在try-catch块中反而阻止了现代JVM实现本来可能要执行的某些特定优化
③对数组进行遍历的标准模式并不会导致冗余的检查
基于异常的模式要比标准模式慢很多

五十八.对可恢复的情况使用受检异常,对编程错误使用运行时异常
Java程序设计语言提供了三种可抛出结构:受检的异常,运行时异常和错误
使用受检的异常或是未受检的异常时,如果期望调用者能够适当地恢复,对于这种情况就应该使用受检的异常
大多数的运行时异常都表示前提违例,错误往往被JVM保留用于表示资源不足,约束失败
对于可恢复的情况,使用受检的异常,对于程序错误,则使用运行时异常。

五十九.避免不必要地使用受检的异常
受检的异常地Java程序设计语言的一项很好的特性,它们迫使程序员处理异常的条件
如果使用API的程序员无法做得比这更好,那么未受检的异常可能更为合适
异常受检的本质并没有为程序员提供任何好处,它反而需要付出努力,还是程序更为复杂
将受检异常变为未受检异常可能避免这些问题

六十.优先使用标准的异常
重用现有的异常有多方面的好处
①使得API更加易于学习和使用,因为它与程序员已经熟悉的习惯用法是一致的
②对于用到这些API的程序而言,它们的可读性会更好,因为它们不会出现很多程序员部首席的异常
③异常类越少,意味着内存印迹就越少,装载这些类的时间开销也越少
最经常被重用的异常是IllegalArgumentException,当调用者传递的参数值不合适的时候,往往就会抛出这个异常
IllegalStateException表示对象的状态而使调用非法,通常就会抛出这个异常
所有错误的方法调用都可以被归结为非法参数或者非法状态
UnsupportedOperationException表示对象不支持所请求的操作
ConcurrentModificationException表示一个对象被设计为专用于单线程或与外部同步机制配合使用

六十一.抛出与抽象相对应的异常
当低层的异常传到高层的时候,它抛出的异常也应该跟着发生变化
更高层次的实现应该捕获低层的异常,同时抛出可以按照高层抽象进行解释的异常。
一种特殊的异常转译形式称为异常链,如果低层的异常对于调试异常高层异常的问题非常有帮助,使用异常链就很合适。
大多数标准的异常都是支持链的构造器,对于没有支持链的异常,可以利用Rhrowable的initCause方法设置原因
这些异常应该高层的日志中进行记录,便于进行维护和检查。

六十二.每个方法抛出的异常都要有文档
描述一个方法所抛出的异常,是正确使用这个方法所需文档的重要组成部分,因此,花点时间仔细为每个方法抛出的异常建立文档是特别重要的
始终要单独地声明受检的异常,并且利用Javadoc的@throws标识,准确地记录下抛出每个异常的条件。
对于接口中的方法,在文档中记录下它可能抛出的未受检异常显得尤为重要,这份文档构成了该接口的通用约定的一部分。

六十三.在细节消息中包含能捕获失败的信息

在堆栈轨迹中包含该异常的字符串表示法,其中包含该异常的类名,紧随其后的是细节消息
为了捕获失败,异常的细节信息应该包含所有“对该异常有贡献”的参数和域的值
IndexOutOfBoundsException异常的细节消息应该包含上界,下界以及没有落在界内的下标值
异常的细节消息不应该与"用户层次的错误消息"混为一等,后者对于最终用户而言必须是可理解的
为异常的“失败捕获”信息提供一些访问方法是合适的,提供一些访问方法是合适的。

六十四.努力时失败保持原子性
抛出异常之后,我们希望对象仍然保持在一种良好的可用状态之中,及时失败发生在执行某个操作的过程中间。
一般而言,失败的方法调用应该能使对象保持在被调用之前的状态
获得失败原子性最常见的方法是:
①使用不可变的对象
②在执行操作之前检查参数的有效性,可以使得在对象的状态被修改之前,先抛出适当的异常。
③编写恢复代码,由它来拦截操作过程中发生的失败,是对象回滚到操作开始之前的状态上

六十五.不要忽略异常
空的catch块会使异常达不到应有的目的,至少catch块也应该包含一条说明,解释为什么可以忽略这个异常

六十六.同步访问共享的可变数据
synchronized可以保证在同一时刻,只有一个线程可以执行某一个方法,或者某一个代码块。
在读或写原子数据的时候,应该避免使用同步,为了在线程之间进行可靠的通信,也为了互斥访问,同步是必要的。
java类库中提供了Thead.stop方法,但是不推荐使用,因为这个方法可能导致数据遭到破坏,不要使用Thread.stop方法,但是不推荐使用,因为这个方法可能导致数据遭到破坏,不要使用Thread
while(!stopRequested)
if(!done) 
while(true)
 i++;
这种优化称作提供, 但是也可能造成代码出现bug
volatile修饰符不执行互斥访问,但它可以保证任何一个线程在读取该域的时候都将看到最近刚刚被写入的值
对于一个普通的Long变量,它的++操作也不是线程安全的,如果使用AtomicLong代替Long可以使用原子性的++操作
当多个线程共享可变数据的时候,每个读或者写操作的线程都必须执行同步。

六十七.避免过度同步
要在同步区域内做尽可能少的工作
在一个可变的类内部进行同步,比外部锁定整个对象更高的并发性
为了避免死锁和数据破坏,千万不要从同步区域内部调用外来方法,要尽量限制同步区域内部的工作量。

六十八.executor和task优先于线程
java.util.concurrent中包含一个Executor Frameword, 这是一个很灵活的基于接口的任务执行工具
工作队列的生成:
ExecutorService executor = Executors.newSingleTheadExecutor()
executor.execute(runnable) 加入任务  executor.shutdown()  终止
现在工作单位和执行机制是分开的,现在关键的抽象是工作单元,称作任务(task)

六十九.并发工具优先于wait和notify
正确使用wait和notify比较困难,应该用更高级的并发工具代替
只要用并发Map代替老式的同步Map,就可以极大地提升并发应用程序的性能。
允许将阻塞队列用于工作队列,也称作生产做——消费者队列。

七十.线程安全性的文档化
线程安全性的几种级别:
不可变 无条件的线程安全 有条件的线程安全 非线程安全 线程对立
线程安全说明通常放在它的文档注释中,但是带有特殊线程安全属性的方法则应该在它们自己的文档注释中说明它们的属性
私有锁对象模式只能用在无条件的线程安全类上,有条件的线程安全类不能使用这种模式。

七十一.慎用延迟初始化
延迟初始化本身设计是一种为了防止加载数据太大的一种优化措施,但它也可以用来打破类和实例初始化中的有害循环
对于延迟初始化,建议“除非绝对必要,否则就不要这么做”
延迟初始化方法,对于实例域,使用双重检查模式,对于静态域,则使用lazy initialization holder class idiom

七十二.不要依赖于线程调度器
任何依赖于线程调度器达到正确性或性能要求的程序,很有可能都是不可移植的。
如果一个线程无法像其他线程那样获得足够的cpu时间,那么,不要企图通过调用Thread.yield来修正程序,yield在不同的JVM上实现有差异。
线程优先级可以用来提高一个已经能够正常工作的程序的服务质量,但永远不应该用来修正一个原本并不能工作的程序

七十三.避免使用线程组
线程组并没有提供太多有用的功能,并且他们提供的许多功能还都是有缺陷的。

七十四.谨慎地实现Serializable接口
最大代价是,一旦一个类被发布,就大大降低了“改变这个类的实现”的灵活性
如果你接受了默认的序列化形式,并且以后又要改变这个类的内部表示法,结果可能导致序列化形式的不兼容
流的唯一标识符,通常也被称为UID,每个可序列化的类都有一个唯一标识号与它相关联,
如果你没有在一个名为serialVersionUID的私有静态final的long域中显式指定该标识,
系统会自动根据这个类来调用一个复杂的运算过程,从而产生该标识号。(和你类中接口方法名称等有关),如果你在后面的版本类中加了成员域,
则自动产生的标识号会发生变化,然后造成兼容性问题
它增加了出现bug和安全漏洞的可能性,因为反序列化是隐藏的构造器
为了继承而设计的类,应该尽可能少地实现Serializable接口,用户的接口也应该尽可能少地继承Serializable接口,
可能为实现这个接口的程序员背负沉重的负担
Serializable接口的实现有Throwable类, Component和HttpServlet抽象类,这都是现实中需要的。

七十五.考虑使用自定义的序列化形式
如果一个对象的物理表示法等同于它的逻辑内容,可能就适合于使用默认的序列化形式
当一个对象的物理表示法与它的逻辑数据内容有实质性的区别时,使用默认序列化形式会有4个缺点:
①它使这个类的导出API永远要束缚在该类的内部表示法上。
②它会消耗过多的空间
③它会消耗过多的时间
④它会引起栈溢出
writeObject和readObject方法首要任务都是调用defaultReadObject方法
不管你选择了哪种序列形式,都是为自己编写的每个可序列化的类声明一个显示的序列版本UID

七十六.保护性地编写readObject方法
readObject方法实际上相当于一个公有的构造器,如同其他的构造器一样,他也要求注意同样的所有注意事项
当一个对象被反序列化的时候,对于客户端不应该拥有的对象引用,如果哪个域包含了这样的对象引用,就必须要坐保护性拷贝。
如果实体域被声明为final,为了实现可序列化可能需要去掉final
指导方针:
①对于对象引用必须保持为私有的类,要保护性拷贝这些域中的每个对象
②对于任何约束条件,如果检查失败,则抛出一个InvalidObjectException异常
③如果整个对象图在被反序列化之后必须进行验证,就应该使用ObjectInputValidation接口
④无论是直接还是间接方式,都不要调用类中任何可被覆盖的方法

七十七.对于实例控制,枚举类型优于readResovle方法
readResolve特性允许你用readObject创建的实例代替另一个实例
如果依赖readResolve进行实例控制,带有对象引用类型的所有实例域则都必要声明为transient

七十八.考虑用序列化代理代替序列化实例
这个readResolve方法仅仅利用它的公有API创建外围类的一个实例,这正是该模式的魅力所在。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值