Practical Java,一部分值得注意的东西


读书笔记    <Practical Java>    68个改善代码的重要主题


-------------------------------------------------------------------------


一, 一般技术


实践1:参数总是以by value方式而非by reference方式传递

当方法的参数是一个对象(准确的说是对象的引用)时,实际上也是传值的方式,实参先把引用赋给形参(此时形参和实参就指向了同一个对象),然后函数里面的部分是在通过对形参引用的操作来改变对象的值。



实践2:对不变的date和object references使用final

关键词final 只能防止变量值的改变。如果被声明为final 的变量是个object
reference,那么该reference 不能被改变,必须永远指向同一个对象。然而
被指的那个对象可以随意改变。(注意是神马意思)



实践3:缺省情况下所有non-static函数都可被覆写

设计classes 时,你可以按此方式将final 当作一种提高性能的工具。在声明
某个class 为final 之前,请考虑清楚这对derived classes(派生类) 带来的隐含意义和限制。你也必须仔细考虑final 函数或non-final 函数对性能的潜在影响



实践4:在arrays(数组)和Vectors之间慎重选择

arrays 既可以容纳primitive types(基本型别)也可以容纳object references,每一个元素都将依据其自身型别而被赋予缺省值,如果array 的元素是object references,Java 便不会调用default 构造函数,而是将其初值设为null。

当更多元素被加入Vector 以至于超出其容量时,其体积会动态增长,这和array 有着显著的不同。Vector 的内部其实是以array 实现的,Vector 只能容纳object references,不能容纳基本型别(primitive types)。



实践5:多态(polymorphism)优于instanceof

不要将instanceof作为判断条件来实现一个if else功能的方法,应该使用多态。例如计算工资的方法,不要只实现一个计算函数(在里面用instanceof来判断这个对象是经理,还是普通职员,然后来决定不同的计算方式),应该将实现多个不同的计算方法,用参数类型的不同来实现多态。



实践6:必要时才使用instanceof

向下转型遇到的问题:对于不在一个类体系结构里的类型之间,强制转型在编译期就能够提示错误,然而在同一个类体系结构之间的类之间进行强制类型转换会在运行期间才抛出异常。特别是在Vector这样的集合中,从里面取出的元素类型都是Object的,向下转型无论转为什么类型,都不会在编译器提示错误,所以需要用instanceof来检查它的实际类型,然后在转型,这往往比catch来捕捉异常好。



实践7:一旦不再需要object references,就将它设为null

内存消耗


--------------------------------------------------------------------------


二,对象与相等性


实践8:区别reference型别和primitive型别

所有Java 对象都是通过Object references(引用) 来访问的,集合里面存放的是对象的引用,而不是对象本身



实践9: 区分==和equas()

操作符==只是[浅层地]测试相等性。看看操作符左侧的东西是不是和右侧的东西一
样,如果两个引用不是指向同一个对象,即使对象的值相等,==也不相等,因为它只是浅层的测试。

请使用==测试两个基本型别是否完全相同(identical), 或测试两个object references 是否指向同一个对象;请使用equals()比较两个对象是否一致(same)——基准点是其属性(attributes。译注:此处是指对象的实值内容,也就是数据值域,field)。



实践10:不要依赖equals()的缺省实现

1) 若要比较对象是否相等,其class 有责任提供一个正确的equals()。
2) 在[想当然的调用equals()]之前,应先检查并确保你所使用的class 的确
实现了equals()。
3) 如果你所使用的class 并未实现equals(),请判断java.lang.Object 的缺省
函数是否可胜任。
4) 如果无法胜任,就应该在某个外覆类(wrapper class)或subclass 中撰
写你自己的equals()。



实践11:实现equals()时必须深思熟虑

你想要让哪些classes 的对象与你的class 对象进行比较?你只打算让相同class 的对象之间进行比较吗?或者你允许derived class 对象和其base class 对象比较?一般来说我们实现equals()就是为了让不同内存空间上,但是值相同的对象相等。但是根据equals()的定义,你甚至可以让不同类类型的对象也可以相等,虽然这样没有什么意义。



实践12:实现equals()时优先考虑使用getClass()

equals()的最佳实现方式就是搭配getClass(),后者可确保只有相
同class 所产生的对象才有机会被视为相等。



实践13:调用super.equals()以唤起base class的相关行为

当你为一个derived class 撰写equals()时,你必须检查java.lang.Object 之外的所有base class,看看他们是否都实现有equals()。如果有,那么一定要调用super.equals()。然后仅比较新增的属性即可。



实践14:在equals()函数中谨慎使用instanceof

来实现derived class和base class对象的相等



实践15: 实现equals () 时需要遵循某些规则

无论你选择使用getClass()或instanceof 来实现equals(),下面的规则适用于
所有的equals()函数:
1) 如果某个class 的两个对象即使占据不同的内存空间,也可被视为「逻
辑上相等」的话,那么你得为这个class 提供一个equals()。
2) 请检查是否等于this(参见实践12 ) 。
3) 比较这个class 中的相关属性(值域,fields),以判断两个对象是否相等
(参见实践12)。
4)  如果有java.lang.Object 以外的任何base class 实现了equals(), 那么就
应该调用super.equals()(参见实践13)。

在equals()函数中面对getClass()和instanceof 进行取舍时,你要仔细斟酌以
下问题:
1) 如果只允许同一个class 所产生的对象被视为相等,则通常使用
getClass()(参见实践12)。
2) 只有在不得不「对derived classes 对象与base classes 对象进行比较」的
场合中,才使用instanceof(参见实践14),而且你应该明白这样做带来
的可能问题和复杂性。
3) 如果使用instanseof,而且derived class 和base class 都实现有equals(),
则一定要知道,这种比较不会展现出所谓的「对称相等性」(symmetric
equality)。


--------------------------------------------------------------------------


三,异常处理


实践16:认识「异常控制流」(exception control flow)机制



实践17:绝对不可轻忽异常(Never ignore an exception)

当Java 程序产生异常,你能做些什么呢?
1. 捕捉并处理它,防止它进一步传播(propagate)。
2. 捕捉并再次抛出它,这么一来它会被传播给调用端。
3. 捕捉它,然后抛出一个新异常给调用端。
4. 不捕捉这个异常,听任它传播给调用端。



实践18:千万不要遮掩异常(Never hide an exception)

try、catch 和finally 区段可以彼此任意嵌套(nested)。此外,尽管从它们之中抛出了多个异常,但只有一个异常可被传播到外界。记住,最后被抛出的异常是惟一被调用端接受到的异常,其他异常都被掩盖而后遗失了。如果调用端需要知道造成失败的初始原因,程序之中就绝不能掩盖任何异常。可以建立一个Vector存放异常,保存所有产生过的异常,并返回给终端



实践19:明察throws子句的缺点

当一个方法新添加了throws异常,那么除非在这个方法体内自己处理,否则每个调用它的方法都将添加catch来捕捉异常,对系统影响很大,就请不要在开发周期的最后才添加异常的处理。请在一开始就设计好你的错误处理策略。当你在精心计划后仍然遇到这种情形时,你就会明白,throws 子句是一种使用而有益的语言特性,但如果你不够细心,它会令你头疼不已。



实践20:细致而全面地理解throws子句

1)提供throws 子句的用意在于,提醒函数调用者,告知可能发生的异常。

2) 当方法体内throw的异常种类A,B,C是继承关系时,即使你只throws最上层的异常,编译器也会通过,但是一般不推荐这样!

当你给出throws 子句的时候,要填写的完整无缺。虽然编译器不强求这样,但将函数可能抛出的所有异常统统列出是良好的编程习惯。这对调用它的方法捕捉和处理异常时有好处的。

3)覆写函数所抛出的异常,或者与superclass 的对应函数具有相同型别,或者是这些型别的特化(specializations;译注:即派生型别)
这意味着,如果你的覆写对象(某个superclass 函数)没有抛出任何异常,但你
的覆写函数因为增加了代码而可能引发异常,那么你必须在新函数中捕捉异常并就地处理。你不能将新函数所引发的异常传播给外界!!!!!



实践21:使用finally 避免资源泄露(resource leaks)

关闭socket,文件流等



实践22:不要从try区段中返回

try 区段代码一离开try 区段就会进入finally 区段,造成代码离开try 区段的情况包括:
1)抛出异常
2)Try 区段正常结束
3)在try 区段执行了return,break,或continue 语句,从而引发执行

注意return语句并不能阻止finally的执行。为了避免麻烦,尽量不要在try里使用return,经常这个返回值并不能返回给方法,而是被finally阻断了。



实践23:将try/catch 区段置于循环之外

基于效率的考虑



实践24:不要将异常用于流程控制

异常控制仅用于异常处理,不要用异常来控制程序的执行流程,涵义模糊,难以维护



实践25:不要每逢出错就使用异常

传统的错误处理和异常结合使用,共同构建高效代码



实践26:在构造函数(constructors)中抛出异常

构造函数没有返回值,在构造方法内跑出异常可以从得到构造函数内部失败的原因。



实践27:抛出异常之前先将对象恢复为有效状态


-------------------------------------------------------------------------


四,效率和优化技术



实践28:先把焦点放在设计、数据结构和算法身上

实践29;不要倚编译期(compile-time)优化技术

实践30:理解运行期(runtime)代码优化技术

实践31:如欲进行字符串接合,stringbuffer 优于string

因为String类是不可变的,在使用String进行字符串结合时,实际上还是转化为了stringbuffer,最后转化为String。

用stringbuffer来实现可以减少创建对象的次数,提高效率。


实践32:将对象的创建成本(cre ation cost)降至最小


1. 从heap 之中分配内存,用以存放全部的instance 变量以及这个对象连同其superclasses 的实现专
届数据(implementation-specific data)。所谓[实现专属数据]包括指向“class and method data”的指
针。
2. 对象的instance 变量被初始化为其相应的缺省值。
3. 调用most derived class(最深层派生类)的构造函数(constructor)。构造函数做的第一件事就是调用
superclass 的构造函数。这个程序一直反复持续到java.1ang.object 构造函数被调用为止。一定要记住,java.1ang.object 是一切Java 对象的base class。
4. 在构造函数本体执行之前,所有instance 变量的初始值设定式(initializers) 和初始化区段(initialization blocks)先获得执行,然后才执行构造函数本体。于是base class 的构造函数最先执行,most derived class 的构造函数最后执行。这使得任何class 的构造函数都能放心大胆地使用其任何superclasses 的instance 变量。

既然创建一个对象要发生这么多事情, 建立一个轻型(lightweight) 对象就比建立一个重型(heavyweight)对象快很多。所谓轻型对象是指:既不具有长继承链(long inheritance chain),也不内含许多其他对象。重型对象恰恰相反。如果一个对象内含多个轻型对象,也可被视为重型对象。




实践33:慎防未用上的对象(unused objects)

在适当的地方创建对象。跟编码技巧有关



实践34:将同步化(synchronization)降至最低

一般来说,Vector的synchronized 版本比其non-synchronized 版本大约慢5~6 倍。



实践35:尽可能使用栈变量


实践36:使用static、final 和private 函数以促成inlining

inlined 函数只有在[被多次调用]的情况下,才会获得令人侧目的性能提升。这是因为当一个函数被inline 后,就不再需要负担函数调用的额外开销。因此,函数被调用愈多次,节省就愈多



实践37:instance 变量的初始化一次就好


实践38:使用基本型别(primitive types)是代码更快更小


实践39:不要使用Enumeration 或Ite rato r 来遍历Vector

用循环最快。


实践40:使用System.arraycopy()来复制arrays

比自己写循环拷贝更快,它是本机函数


实践41:优先使用array,然后才考虑Vector 和ArrayList


得知Vector 和ArrayList 的性能意义之后,当你面对性能至为关键(performance-criticl)的程序任务,请考虑使用array。如果你需要Vector 的功能,却又不需要它的同步特性(synchronization),请使用ArrayList。只有在你既需要Vector 的功能又挂面要它固有的同步特性时,才使用Vector。

不要仅仅因为手上有个数不定的数据需要存储,就毫无选择地使用Vector 或ArrayList。如果你无法确定需要保存的元素数量,可以考虑创建一个足以保存最大量数据的array。通常这可能浪费很多内存,但性能上的收益可能超过内存方面的代价。办有通过细致的性能评测( profiling)和对系统的详尽分析,你才能做出正确的选择。



实践42:尽可能复用(reuse)对象

编程技巧相关



实践43:使用缓式评估(延迟求值,lazy evaluation)

与语言无关,在策略上延缓那些可能用不上的工作的执行,知道不得已才去做


实践44:以手工方式将代码优化

实践45:编译为本机代码


实践46:面对instance 函数,synchronized 锁定的是对象(objects)而非函数
(methods)或代码(code)

关键词synchronized 可用作函数修饰符(modifier),亦可用作函数内的语句。其双重用法导致有些人无法弄清它的真实用意。它往往被描述为互斥器(mutex)或关键区(criticalsection)。这使得许多程序员误以为,由于代码被关键词synchronized 保护着,因此同一时刻只能有一个线程(thread)访问它。

对于instance 函数,关键词synchronized 其实并不锁定函数或代码,它锁定的是
对象(至于synchronized 对statics 的影响,请见实践47)。记住,每个对象只有一个lock(机锁)与之相关联。

当synchronized 被当作函数修饰符的时候,它所取得的lock 将被交给函数调用者
(某对象)。如果synchronized 用于object reference,则取得的lock 将被交给该reference所指对象



后面还有些多线程相关的,没有看~




posted @ 2011-06-05 14:45  jinmengzhe  阅读(701)  评论(0编辑  收藏  举报