第六章:方法
第二十三条:检查参数的有效性 1 对于公有的方法,使用Javadoc @throws标签,声明抛出异常。 2 非公有的方法使用assertions来检查它们的参数。 3 在一个方法执行之前,应该检查它的参数。 第二十四条:需要时使用保护性拷贝
1 对于构造函数的每个可变参数进行保护性拷贝(defensive copy)是必要的 eg: constructor(Type x){Type _x = new Type(x);} 2 保护性拷贝动作是在检查参数有效性之前进行的,并且有效性检查是针对拷贝之后的对象,而不是原始的对象。 3 对于“参数类型可以被不可信方子类化”的情形,请不要使用clone方法进行参数的保护性拷贝,因为clone方法有可能返回子类。 4 如果你不能容忍将要进入你的类内部的对象还会发生发生变化,那么就使用defensive copy。 5 把一个指向内部可变组件的引用返回给客户之前,应该考虑使用defensive copy。 6 非零长度的数组总是可变的。解决方案:返回该数组的一个非可变视图。 第二十五条:谨慎设计方法的原型
1 谨慎选择方法的名字:遵循标准的命名习惯,易于理解+大众认可 2 不要过于追求提供便利的方法:不要复杂化类设计。 3 避免长长的参数列表,类型相同的长参数序列尤其有害:解决办法是,把一个方法分解成多个方法;创建辅助类,用来保存参数的聚集(aggregate),往往是静态成员类。 4 对于参数类型,优先使用接口而不是类:提高将来的扩展性 5 谨慎使用函数对象:特定的模式下再使用,eg:strategy和visitor模式 第二十六条:谨慎地使用重载
重载方法(overloaded method):方法名相同,参数列表不同。 改写方法(overwritten method):子类中包含的方法声明与父类中的一个方法声明具有同样的原型。 内容: 1 对于重载方法的选择是静态的,而对于被改写方法的选择是动态的,即根据被调用所在对象的运行时类型。 2 改写机制是规范,而重载机制是例外,应该避免方法重载机制的混淆用法。 3 永远不要导出两个具有相同参数数目的重载方法:可以给方法起不同的名字。 4 一个类的多个构造函数总是重载的:可以使用静态工厂方法解决。 5 使用重载的时候注意,尽量保证参数列表中至少有一个对应的形参具有“根本不同”的类型(指一种类型的实例转换成另一种类型是不可能的) 6 当两个重载方法在同样的参数上被调用时,他们执行相同的功能,则重载就不会带来危害。 第二十七条:返回零长度的数组而不是null
内容: 没有理由从一个取数组值得方法中返回null,而不是返回一个零长度数组。 理由:简化方法调用者的返回值检查;在这个层次上担心性能问题是不明智的;对于不返回任何元素的调用,每次都返回同一个零长度数组是有可能的,因为零长度数组是非可变的。 一个惯用方法:一个零长度数组常量被传递给toArray方法,以指明所期望的返回类型。 第二十八条:为所有导出的API元素编写文档注释
内容:为了正确的编写API文档,你必须在每一个被导出的类,接口,构造函数,方法和域声明之前增加一个文档注释。 1 每一个方法的文档注释应该简洁地描述出它和客户之间的约定:所有前提条件和后置条件,以及它的副作用(sideeffect),和类的线程安全性。 2 每个文档注释的第一句话是该注释所属元素的概要描述,独立地描述目标实体的功能。 3 概要描述中不要有句号,也不要使用缩写和十进制小数。 4 对于方法和构造函数,概要描述应该是一个动词短语,描述了该方法所执行的动作。 5 对于类,接口和域,概要描述应该是一个名词短语,它描述了该类或者接口的实例,或者域本身所代表的事物。 第七章:通用程序设计 第二十九条:将局部变量的作用域最小化 好处:增加代码的可读性和可维护性,并降低出错的可能性。 规则: 1 在第一次使用的地方声明 2 尽量让每一个局部变量的声明都包含一个初始化表达式 3 如果在循环终止之后循环变量的内容不再被需要的话,则for循环优先于while循环 4 使方法小而集中 第三十条:了解和使用库
1 通过使用标准库,你可以充分利用专家知识,以及前人经验 2 不必浪费时间在底层的细节上 3 无需你做任何努力,它们的性能会不断提高 4 使自己的代码融入主流,更易懂,易维护 第三十一条:如果要求精确的答案,避免使用float和double
原因:让float或者double精确地表达10的任何负数次方值是不可能的。 解决办法:使用BigDecimal,int或者long进行计算。BigDecimal容许完全控制舍入。 第三十二条:如果其他类型更合适,则尽量避免使用字符串
1 字符串不适合代替其他的值类型:只有当数值本身是文本信息时,才是合理的,否则都应该被转换为适当的数值类型 2 字符串不适合代替枚举类型:使用类型安全枚举类型 3 字符串不适合代替聚集类型 4 字符串不适合代替能力表 第三十三条:了解字符串连接的性能
内容:不适合规模大的情形,为连接n个字符串而重复地使用字符串连接符要求n的平方级的时间,这是因为字符串是非可变的。 第三十四条:通过接口引用对象
内容:如果有合适的接口存在,那么对参数,返回值,变量和域的声明都应该使用接口类型,只有当你创建某个对象的时候,才真正需要引用这个对象的类。 好处:提高灵活性 不存在合适的接口类型的情形:1 一个具体类没有相关联的接口;2 对象属于一个框架,而框架的基本类型是类;3 一个类实现了一个接口,但是它提供了接口中不存在的额外方法。 总结:如果合适的接口存在,使用它,否则使用类层次结构中提供了所需功能的最高层的类 第三十五条:接口优先于映像机制
映像机制(reflection)容许一个类使用另一个类,即使当前者被编译的时候后者还根本不存在。 影响的代价:损失了编译时类型检查的好处;代码笨拙冗长;性能损失(2倍)。 内容:通常,普通应用在运行时刻不应该以映像方式访问对象,如果你编写的程序必须要与编译时刻未知的类一起工作,那么尽量使用映像机制实例化对象,而访问对象时使用编译时刻已知的某个接口或者超类。 第三十六条:谨慎地使用本地方法
本地方法:用本地语言(c c++)编写的特殊方法 本地方法的好处:提供了访问平台相关的设施的能力,提供了访问老式代码库的能力,实现性能关键部分。 本地方法的缺点:不安全,不可移植,较高的固定开销,代码难以阅读。 总结:随着1.3发行版本的推出,使用本地方法已经不提倡 第三十七条:谨慎地进行优化
内容: 1 优化更容易带来伤害,而不是好处,特别是不成熟的优化 2 努力编写好的程序而不是快的程序 3 努力避免那些限制性能的设计决定:特别注意模块之间交互关系的组件eg:API,永久数据格式 4 考虑你的API设计决定的性能后果 5 一旦你已经谨慎的设计了程序,产生了一个清晰,简明,结构良好的实现,那么就可以开始考虑优化了 第三十八条:遵守普遍接受的命名惯例
1 包的名字应该是层次状的,用句号分割每一部分,每一部分包括小写字母和数字:edu.sjtu 2 包名字的剩余部分应该包括一个或者多个描述该包的组成部分,鼓励有意义的缩写:util 3 类和接口的名字应该包括一个或者多个单词,每个单词的首字母大写:TimerTask 4 方法和域的命名类似类和接口,只是名字的第一个单词为小写:ensureCapacity 5 常量域包含一个或者多个大写形式的单词,中间由下划线符号分割开 6 类通常用一个名词或者名次短语命名:Timer 7 执行某个动作的方法通常用一个动词或者动词短语来命名:drawImage,isDigit 8 转换对象类型的方法,通常为toType:toString 9 返回一个视图的方法为asType:asList 10 返回一个与被调用对象同值的原语类型的方法为typeValue:intValue 11 静态工厂的常用名字:valueOf,getInstance 第八章:异常
第三十九条:只针对不正常的条件才使用异常 内容:异常只应该被用于不正常的条件,它们永远不应该被用于正常的控制流,一个设计良好的API不应该强迫它的客户为了正常的控制流而使用异常。 理由:创建,抛出和捕获异常的开销很昂贵;把代码块放在try-catch中会阻止JVM的优化 第四十条:对于可恢复的条件使用被检查的异常,对于程序错误使用运行时异常
1 如果期望调用者能够恢复,那么使用被检查的异常 2 用运行时异常来指明程序错误 3 错误往往被保留用来指示资源不足,约束失败。所以不要再实现任何新的Error类 4 你所实现的所有未被检查的抛出结构都应该是RuntimeException的子类 第四十一条:避免不必要地使用被检查的异常
理由:如果一个方法会抛出一个或者多个被检查的异常,那么调用该方法的代码必须在一个或者多个catch块中处理这些异常,或者它必须声明这些异常,以便让它们传播出去,这给程序员增添了麻烦。 使用被检查异常的条件:正确的使用API并不能阻止这种异常条件的产生;一旦产生了异常,使用API的程序员可以采取有用的动作。 第四十二条:尽量使用标准的异常
好处:使你的API更加易于学习和使用;可读性更好;异常类越少,意味着内存占用越少。 常用的异常:IllegalArgumentException, IllegalStateException, NullPointerException, IndexOutOfBoundsException 第四十三条:抛出的异常要适合于相应的抽象
内容: 1 异常转译(exception translation),高层的实现应该捕获底层的异常,同时抛出一个可以按照高层抽象进行解释的异常。 2 异常链接(exception chaining),底层的异常被高层的异常保存起来,并且高层的异常提供一个公有的访问方法来获得底层的异常。 3 尽量不要将异常从底层传播到高层,除非保证底层的异常对于高层也是合适的。 第四十四条:每个方法抛出的异常都要有文档
1 总是要单独的声明被检查的异常,并且利用Javadoc的@throws标记,准确地记录下每个异常被抛出的条件 2 每个方法的文档应该描述它的前提条件,在文档中记录下未被检查的异常是描述前提条件的最佳做法,虽然未被检查的异常并不要求被声明 3 使用Javadoc的@throws标签记录一个方法可能会抛出的每个未被检查的异常,但是不要使用throws关键字将未被检查的异常包含在方法的声明中 4 如果一个类中的许多方法出于同样的原因而抛出同一个异常,那么在该类的文档注释中对这个异常做文档,而不是为每一个方法单独做文档。 第四十五条:在细节消息中包含失败-捕获信息
1 为了捕获失败,一个异常的字符串表示应该包含所有对该异常有贡献的参数和域的值 2 为一个异常的失败捕获信息提供一些访问方法是合适的 第四十六条:努力使失败保持原子性
内容:一个失败的方法调用应该使对象保持它在被调用之前的状态。当一个方法抛出错误时,它不需要保持失败原子性。 方法: 1 设计一个非可变对象 2 在执行操作之前检查参数的有效性 3 对计算处理过程调整顺序,使得任何可能会失败的计算部分都发生在对象状态被修改之前 4 编写一段恢复代码,使对象的状态回滚到操作开始之前的状态 5 在对象的一份临时拷贝上执行操作,当操作完成之后再把临时拷贝中的结果复制给原来的对象 第四十七条:不要忽略异常
内容:空的catch块会使异常达不到应有的目的,至少catch也应该包含一个说明,用来解释为什么忽略掉这个异常,空的catch块会导致程序在遇到错误的情况下悄然地执行下去。 第九章:线程
第四十八条:对共享可变数据的同步访问 内容: 1 同步不仅可以阻止一个线程看到对象处于不一致的状态中,它还可以保证通过一系列看似顺序执行的状态转变序列,对象从一种一致的状态变迁到另一种一致的状态。 2 为了在线程之间可靠地通信,以及为了互斥访问,同步时需要的。 3 使用正确的同步方法来执行迟缓初始化。 4 无论何时当多个线程共享可变数据的时候,每个或者写数据的线程必须获得一把锁。 第四十九条:避免过多的同步
内容:过多的同步可能会导致性能降低,死锁,甚至不确定的行为。 1 为了避免死锁的危险,在一个被同步的方法或者代码块中,永远不要调用一个可被改写的公有或者受保护的方法 2 通常,在同步区域内应该做尽可能少的工作,获得锁,检查共享数据,根据需要转换数据,然后放掉锁。 3 在一个类的内部执行同步,一个很好的理由是它将被大量地并发使用,而且通过执行内部细粒度的同步操作可以获得很高的并发性 4 如果一个类或者一个静态方法依赖于一个可变的静态域,那么它必须要在内部执行同步 第五十条:永远不要在循环的外部调用wait
内容:总是使用wait循环模式来调用wait方法。 第五十一条:不要依赖于线程调度器
内容:任何依赖于线程调度器而达到正确性或者性能要求的程序,很有可能是不可移植的。 解决:尽可能确保在任何给定时刻只有少量的可运行线程,让每个线程做少量工作,然后使用Object.wait等待某个条件发生,或者使用sleep 第五十二条:线程安全性的文档化
1 在一个方法的声明中出现synchronized修饰符,这是一个实现细节,并不是到处的API的一部分 2 一个类为了可被多个线程安全地使用,必须在文档中清楚地说明它所支持的线程安全性级别(immutable,thread-safe,conditionally threda-safe,thread-compatible,thread-hostile) immutable,thread-safe:不需要外部同步;conditionally thread-safe:需要被顺序执行;thread-compatible:在每个方法调用的外围使用外部同步。thread-hostile:不能被多个线程安全使用。 第五十三条:避免使用线程组 内容:线程组已经过时。 第十章:序列化
序列化:将一个对象编码成一个字节流,相反的过程为反序列化。 第五十四条:谨慎地实现serializable 原因: 1 一旦一个类被发布,则改变这个类的实现的灵活性就大大降低,因为它的字节流编码成为它的导出API的一部分。 2 增加了错误和安全漏洞的可能性:对象创建机制 3 随着一个新版本的发行,相关的测试负担增加了 4 为了继承而设计的类应该很少实现serializable,接口也应该很少会扩展它,否则就会给扩展这个类的程序员背上沉重的负担 5 对于为了继承而设计的不可序列化的类,应该考虑提供一个无参数的构造函数 6 内部类应该很少实现serializable,除非是静态类 第五十五条:考虑使用自定义的序列化形式
1 若没有认真考虑默认序列化是否合适,则不要接受这种形式 2 如果一个对象的物理表示等同于它的逻辑内容,则默认的序列化形式是合适的 3 即使你确定了默认序列化形式是合适的,你仍然需要提供一个readObject方法以保证约束关系和安全性 4 当一个对象的物理表示与它的逻辑数据内容有实质区别时,不要使用默认序列化形式,因为:导出API约束在实现上;消耗过多空间;消耗时间;栈溢出; 5 在决定将一个域做成非transient之前,确信它的值将是该对象逻辑状态的一部分 6 为自己编写的每个可序列化的类声明一个显示的序列版本UID 第五十六条:保护性地编写readObject方法
内容:对于每一个可序列化的非可变类,如果它包含了私有的可变组件,那么在它的readObject方法中,必须要对这些组件进行保护性拷贝 指导原则: 1 对于对象引用域必须保持为私有的类,对将被保存到这些域中的对象进行保护性拷贝 2 对于具有约束条件的类,一定要检查约束条件是否满足 3 不要调用类中可被改写的方法 第五十七条:必要时提供一个readResolve方法
内容: 1 readResolve方法不仅对于singleton对象是必要的,而且对于所有其他的实例受控的类也是必需的 2 作为保护性的readObject方法的一种保守的替代选择 好习惯: 1 客户通过接口来引用被返回的对象,而不是通过实现类来引用被返回的对象。 2 用尽可能简单的方法使对象进入正常状态;如果可以的话,避免调用其它方法。在构造器中唯一能够安全调用的那些方法是基类中的final方法(包括private) 3 用继承表达行为间的差异,并用组合表达状态上的变化。 4 一旦一个元素<img src="<input type="image" src="<input type="radio" name="<input type="checkbox" name="<input type="checkbox" name="<input type="checkbox" name="<input type="checkbox" name="" value="">" value="">" value="">" value="">" value="">">">被释放掉,则该元素中包含的任何对象引用就应该被清空。 |
|
来自: ShangShujie > 《java》