【转】effective java 笔记4
from:http://blog.csdn.net/ilibaba/archive/2009/05/21/4207511.aspx
NO. 26 谨慎使用重载
一个常见的问题是:
public static String classify(Set s)
{ return “set”;}
public static String classify(List l)
{return “list”;}
public static String classify(Collection c)
{return “unknown collection”;}
public static void main(String[] args)
{ Collection[] tests=new Collection[]{
new HashSet(), new ArrayList(), new HashMap.values()};
for(int i=0;i<tests.length;i++){System.out.println(classify(test[i]));}
程序结果是print出三个unknown collection。因为classify被重载了,到底调用哪个重载函数叔编译时刻决定的,因为编译时,大家的参数类型都是Collection。
避免方法重载机制的混淆。永远不要导出两个具有相同参数数目的重载方法,是一个安全而保守的策略。
至少应该保证,当传递同样的参数时,所有的重载方法行为一致。
NO.27 返回零长度的数组而不是null
如果返回null,对于每次调用到该方法的时候都需要做null判断,否则很容易抛出空指针异常,推荐返回一个零长度的数组,在通常情况下,这样的做法对性能几乎没有影响。
NO.28 为所有导出的API元素编写文档注释
需要增加注释的地方:类、接口、构造函数、方法和域声明,
方法注释的内容:
调用该方法的前提条件;
调用后的后续处理(如捕获异常);
副作用(如方法启动线程后带来的安全性);
参数@param Describe;
返回@return Describe;
异常@throws if.....;
注意:注释中可以适用<p><code><tt>等HTML标签,但>,<等标签需要转义。
NO.29 讲局部变量的作用域最小化
在第一次适用局部变量的地方声明他;(不要过早地声明它)
要防止局部变量在“使用它的块”之外声明,这样会防止局部变量被意外使用;
几乎每一个局部变量的需要初始化,如果没有足够的信息来对一个变量进行有意义的初始化,那就推迟这个声明,直到可以初始化为止;
其他的方法,例如,把一个变量多的方法分成两个,每次操作一个方法,减少变量之间的干扰。
NO.30 了解和使用库
不要从头发明轮子,如果你要做的事情是很常见的,就去查下有没有这样的实现类,如果有,则使用它,这样会降低你实现相应功能的投入和代码的出错率。
NO.31 如果要求精确的答案,请避免使用 float和 double
float和double不适合表示货币,在平时的使用中应该避免。
例如:System.out.println(1.00-9*.10); //会输出0.09999999999999998
如果希望系统来处理十进制的小数点,可以使用 BigDecimal。如果不考虑小数的处理,数值范围没有超过9位的则可以用int来处理,如果不超过18位的,则可以用long来处理,超过18位的就必须用 BigDecimal处理。
NO.32 如果其他类型更适合,则尽量避免使用字符串
如果可以使用更加合适的数据类型,或者可以编写更加恰当的数据类型(如 DO、 POJO、枚举等),那么应该避免使用字符串来表示对象,若使用不当,字符串比其他类型更加笨拙,缺乏灵活性。
NO.33 了解字符串连接的性能
为连接 N个字符串而重复得地使用“ +”连接,要消耗N的平方级别的时间。为了获得更高的性能,请使用 StringBuffer代替 String。
NO.34 通过接口引用对象
应该优先使用接口而不是类来引用对象,如果有合适的接口存在,那么对参数的返回值、变量和域的声明都应该使用接口类型,如 Vector是List接口的实现,在声明时应该如下:
List subscribers = new Vector();
//而不是
Vector subscribers = new Vector();
例外的情况:
① 当没有合适的接口存在,可以用类而不是接口来引用一个对象,如: String、 Integer;
② 当一个对象是基本类型的类,而不是接口时,应该用相关的基类引用这个对象,如: java.util.TimerTask;
③ 当一个类实现了一个接口,但它提供了接口中不存在的额外方法,如果程序依赖于这些额外的方法,那么这样的类应该只被用来引用它的实例,永远不应该被用作参数类型。
NO.35 接口优先于映像机制(反射机制)
反射机制是 Java一项强大的功能,给定一个class实例,你可以获得constructor、method和field实例。对于一些特定复杂的程序设计中非常必要(如现在很流行的 spring框架),但在并非必须使用反射机制时,尽量避免使用反射,原因如下:
① 它在编译时不会进行类型检查;
② 实现代码冗长乏味,不易阅读;
③ 性能与一般的方法调用相比,要低下很多;
如果一个程序必须要与编译时未知的类一起工作,那么最好是用反射实例化对象,而访问对象时使用编译时刻已知的某个接口或者父类。
NO.36 谨慎地使用本地方法
尽量使用 Java自身提供的方法来代替本地方法(如用 Java提供的新功能来代替以前只有 C语言能实现个的功能),这样可以使系统变得更加安全,系统可移植性更高,也使代码变得更加容易阅读,如果一定要使用本地方法,请加强测试,并尽可能的少用。
NO.37 谨慎地进行优化
努力写好的程序而不是快的程序,程序要体现信息隐藏的原则,性能问题应该是设计阶段就考虑,要避免那些限制性能的设计决定,如:应该用复合模式的公有类使用继承,则该类的性能永久的受其父类性能的影响,为了获得好的性能而对 API进行修改并非是一个好的做法,通常,这些做法对性能并没什么多大的影响,如果一个系统有了清晰、简明、结构良好的实现,请谨慎对其进行优化,因为 80%的性能问题存在于 20%的代码中,找出影响性能的代码才是问题的关键,可以借助一些性能分析的工具。
第38条:遵守普遍接受的命名惯例
java的命名惯例分为两大类:字面的和语法的。 字面命名惯例涉及包、类、接口、方法和域。 包的名字是层次结构的,用句号分隔第一部分。每一部分的长度不要超过8,由小写字母和数字组成(数字少见用),鼓励使用有意义的缩写。除了java和javax外,一般以域名做开头,顺序是顶级域名放在最前面。 类和接口的名字应至少1至多个单词,每个单词的首字母大写(驼峰试),尽量避免缩写。
方法和域的名字与类和接口的名字遵守相同的字面惯例,只是第一个首字母要小写。常量域要全部字母都大写,词之间通过下划线区分。
语法命名惯例比字面惯例更灵活。
- 类通常用一个名词或名词短语,接口或者与类相同,或者以"-able"或"-ible"结尾的形容词。
- 执行某个动作的方法,常用一个动词或动词短语,
- 对于返回boolean类型的方法,名字常以“is"开头后加一个名词或形容词或短语,
- 如果返回的不是boolean,则常用一个名词/短语,或以"get"开头的动词短语。
- 如果一方法所在的类是一个Bean,则强制要求以get开头。
- 如果类包含对属性操作,常用setAttribute或getAttribute格式命名。
转换对象类型的方法,
- 如果返回不同类型的独立的对象,则称为toType
- 如果返回一个视图,则用asType,
- 如果返回与被调用对象同值的原语类型,称为typeValue
- 静态工厂的方法,常用valueOf或getInstance.
NO.39 只针对不正常的条件才使用异常
异常只应该被用于不正常的条件,它们永远不应被用于正常的控制流。
下面是一个用异常作遍历结束条件的滥用异常的例子:
- //horrible abuse of exceptions. Don't ever do this!
- try{
- int i=0;
- while(true)a[i++].f();
- }catch(ArrayIndexOutOfBoundsException e){
- ...
- }
其错有三:
1、创建、抛出和捕获异常的开销是很昂贵的。因为它的初衷是用于不正常的情形,少有jvm会它进行性能优化。
2、把代码放在try-catch中会阻止jvm实现本来可能要执行的某些特定的优化。
3、有些现代的jvm对循环进行优化,不会出现冗余的检查。
完全可以使用标准的实现方式:
- for(int i=0;i<a.length;i++){
- a[i].f();
- }
这条原则也适用于API设计。一个设计良好的API不应该强迫它的客户为了正常的控制流而使用异常。如果类中有一个”状态相关”的方法,即只有 特定的条件下可被调用的方法,则这个类也应有一个单独的“状态测试”方法,以为调用这个状态相关方法前的检查。如Collection类的next方法和 hasNext方法。
- for(Iterator i=collection.iterator();i.hasNext();){
- Foo foo=(Foo)i.next();
- ...
- }
第40条:对于可恢复的条件使用被检查的异常,对于程序错误使用运行时异常
java提供了三种可抛出的异常:被检查的异常(checked Exception)、运行时异常(run-time Exception)和错误(error)。 如果期望调用者在调用时出现的异常能够恢复,则应该使用被检查的异常,通过抛出一个被检查的异常,强迫调用者在catch中处理该异常,或者将异常传播到外面。
对于一个方法声明要抛出的每一个被检查的异常,它是对API用户的一种潜在指示:与异常相关联的条件是调用这次个方法的一种可能结果。
两种未被检查的可抛出结构:运行时异常和错误,在行为上相同的,它们都不需要、也不应该被捕获的抛出物。你所实现的所有未被检查的抛出结构都应是 RuntimeException的子类。定义一个非Exception、RuntimeException或Error子类的抛出物是可行的,但从行为 意义上它等同于普通的被检查异常(即Exception子类而非RuntimeException子类).
异常是个完全意义上的对象,在其上可以定义任意的方法。因被检查的异常往往指示了可恢复的条件,所以可通过定义方法,使调用者可获得一些有助于恢复的信息。具体可参见“Java异常的分类”http://blog.csdn.net/ilibaba/archive/2009/03/07/3965359.aspx
NO.41 避免不必要地使用被检查的异常
与返回代码不同,被检查的异常强迫程序处理例外的情况,从而大大地提高了程序的可靠性。而过分地使用被检查的异常,则增加了不可忽视的负担。如果正 确地使用API并不能阻止这种异常条件的产生,并且一旦产生了异常,使用API的程序可以采取有用的动作,那么这种负担被认为是正当的。
- try{
- ...
- }catch(TheCheckedException e){
- e.printStackTree();
- System.exit(1);
- }
如果使用API的程序员无法做得比这更好,那么未被检查的异常可能更为合适。在实践中,catch几乎总有断言失败的特征。
“把被检查的异常变成未被检查的异常”的一种技术是,把这个要抛出异常的方法分成两个方法,第一个方法返回一个boolean以指明是否要抛出异常,另一个执行真正的功能,如果条件不满足就抛异常。如下:
//Invocation with checked exception
- try{
- obj.action(args);
- }catch(TheCheckedException e){
- //Handle exception condition
- }
转换为:
- //Invocation with state-testing method and unchecked exception
- if(obj.actionPermitted(args)){
- obj.action(args));
- }else{
- //handle exception condition
- }
当然这种转换并不总是合适的,例如一对象将在缺少外部同步的情况下被并发访问,或者可被外界改变状态,那么这种转换将是不合适的。
NO.42 尽量使用标准的异常
Java平台库中讫今为止最常被重用的异常如下:
IllegalArgumentException 参数值不合适
IllegalStateException 对于这个方法调用而言,对象的状态不合适(如初始化不恰当)
NullPointerException 在null被禁止的情况下参数值为null
IndexOutOfBoundsException 下标越界
ConcurrentModificationException 在禁止并发修改的情况下,对象检测到并发修改
UnsupportedOperationException 对象不支持客户请求的方法
其它的异常也可以使用,只要确保抛出异常的条件与文档要求一致即可。
NO.43 抛出的异常要适合于相应的抽象
高层的实现,应该捕获低层的异常,同时抛出一个可以按照高层抽象进行解释的异常,这种做法叫做异常转译(exception translation)。即如:
- //exception translation!
- try{
- //use lowlevel abstraction to do our bidding
- ...
- }catch(LowerLevelException e){
- throw new HigherLevelException(...);
- }
低层的异常被高层的异常保存起来,且高层的异常提供一个公有的访问方法来获得低层的异常,这种做叫做异常链接(exception chaining)。
- //Exception chaining.
- try{
- //use lower-level abstraction to do our bindding
- ...
- }catch(LowerLevelException e){
- throw new HigherLevelException(e);
- }
异常链的实现非常简单,在1.4及以后版本中,可以通过Throwable来获得支持。
- //Exception chaining in release 1.4 or later
- HigherLevelException(Throwable t){
- super(t);
- }
处理来自低层的异常,
最好的做法是,在调用低层方法之前通过一些检查等手段来确保它们会成功执行;
其次的做法是,让高层处理这些异常,从而将高层方法的调用者与低层的问题隔离开;
一般的做法是使用异常转译;
如果低层方法的异常对高层也是合适的,则将其从低层传到高层。
NO.44 每个方法抛出的异常都要有文档
总是要单独地声明被检查的异常,并且利用javadoc的@throws标记,准确地记录下每个异常被抛出的条件。
使用javadoc的@throws标签积累下一个方法可能会抛出的每个未被检查的异常,但是不要使用throws关键字将未被检查的异常包含在方法的声明中。
如果一个类中的许多方法出于同样的原因而抛出同一个异常,那么在该类的文档注释中对这个异常做文档,而不是为每个方法单独写一个文档。
NO.45 在细节消息中包含失败 - 捕获信息
为了捕获失败,异常信息应该尽可能多的包含有意义的参数和域的值,如:IndexOutOfBoundsException应该包括上界、下界以及实际的下标。
NO.46 努力使失败保持原子性
一个失败的方法调用应该使对象保持“它在被调用之前的状态”,方法如下:
① 使用非可变对象;
② 在可变对象上的操作,应在执行前检查参数的有效性;
③ 写一段恢复代码,在执行失败的情况下,回滚到操作开始之前的状态(不常用);
④ 在对象的一份临时拷贝上执行操作,当完成之后,再把临时拷贝中的结果复制给原来的对象,如Collections.sort在执行排序之前,先把它的输入转储到一个数组中,这样既能降低内循环的开销,又能保证在排序失败的时候,输入列表将保持原样。
NO.47 不要忽略异常
除非有特殊的需求(如动画中的多帧图像播放),否则不要吃掉异常,至少在catch块中包含一条说明,用来解释为什么忽略这个异常。简单地将一个未被检查的异常传播到外界至少会使程序迅速地失败,从而保留了有助于调试该失败条件信息,比异常被忽略后的一个不可预测的时刻程序失败这种情况要强。