第十六条:符合优先于继承
继承打破了封装性。HashSet中addAll是基于add方法实现的。导致子类脆弱的一个相关原因是他们的超类在后续版本中可以获取新的方法。
委托:包装对象把自己传递给被包装对象。
java对象中违反这条规则的:stack不是vector,所以stack不应该扩展vector。如果在合适用复合的地方用了继承,会暴露实现细节。继承机制会把超类中所有缺陷传递到子类中,而复合则允许设计新的API来隐藏这些缺陷。
第十七条:要么为继承而设计,要么提供文档,要么禁止继承
构造器不能调用可被覆盖的方法,无论是直接调用还是间接调用。
对于readObject覆盖版本的方法是是在子类反序列化之前被运行。而对于clone,覆盖版本的方法是在子类的clone方法有机会修正被克隆对象之前先被运行。
第十八条:接口优于抽象类
抽象类允许包含某些方法的实现,而接口不允许。接口增强了类的安全(复合的方式),而继承相对来说比较脆弱,功能更差。但是抽象类的演变比接口容易。公有接口的修改增加新的方法,会破坏所有实现该接口的类,慎重。
第十九条:接口只用于定义类型
常量接口模式是对接口的不良使用。
第二十条:类层次优先于标签类
标签类过于冗长,容易出错,并且效率低下。switch(Class)枚举class。
用继承可以有效的反应类的层次关系。
设计的时候考虑下这个标签是否可以被取消,这个类是否可以用类层次来代替。
第二十一条:用函数对象表示策略
具体的策略类可以是宿主类的私有嵌套类
第二十二条:优先使用静态成员类
嵌套类是指定义在另一个类中的内部类。
静态成员类是最简单的一种嵌套类,他可以访问外围类的所有成员,包括申明私有的成员类。常见用法是作为公有的辅助类,仅当它与外围类一起使用的时候才有意义。
非静态成员类和静态成员类的区别是静态有static修饰。非静态成员类里面包含外围类的一个实例。
如果嵌套类的实例可以在它外围类的实例之外独立存在,这个嵌套类必须是静态成员类。
如果申明成员类不要求访问外围类的实例,就要始终把static修饰符放在它的声明中。如果不用static修饰,那么内部类会对外围类实例有个引用,即使外围类实例复合垃圾回收,但是仍然会保留。
私有静态成员类的常见用法:代表外围类所代表的对象组件。
匿名类适用性受诸多限制,无法将他们实例化,他们出现在表达式中,必须保持简短,10行或者更少,否则影响可读性。
第二十三条:不要在新代码中使用原生态类型
如果使用了原生态类型,就失掉了泛型在安全性和表述性方面的所有优势。
原生态List和参数化类型List,前者逃避了泛型检查。
泛型Set的无限通配符为Set读作某个集合的类型。
第二十四条:消除非受检警告
尽可能的消除非受检警告,尽可能在小范围里使用SuppressWarnings注解,通常是个变量声明,或者非常简短的方法或者构造器。不要在类上使用。每一条警告都表示可能在运行时抛出ClassCastException异常。
第二十五条:列表优于数组
数组是协变的,泛型是不可变的。数组是具体化的,只有在运行时才知道检查他们的元素类型。优先使用集合类型,换来的是高效的类型安全性和互用性。
第二十六条/二十七条:优先考虑泛型,泛型方法
使用泛型更加安全,更加容易,且不会破坏现有客户端。
第二十八条:利用有限制的通配符来提升API的灵活性
Iterable保证类型安全 E的子类集合 ,push
PECS Iterable E 的超类集合 ,pop
第二十九条:优先考虑类型安全的异构容器
将键参数化,而不是容器参数化。然后将参数化后的键提交给容器。
集合API限制你每个容器只能有固定数目的类型参数,可以将参数放在键上而不是容器上来避开这一限制。可以用Class对象作为键。
第三十条:用enum代替int常量
通过公有的静态final域为每个枚举常量导出实例额类。枚举类型是真正的final。
什么时候使用枚举:
需要一组固定的常量,枚举不需要显式的构造器和成员。枚举常量不一定要始终保持不变。多个枚举共享相同的行为时,可以考虑策略枚举。
第三十一条:用实例域代替序数
不要根据枚举的序数导出与它关联的值。而是要将他保存在一个实例域中。
第三十二条:用EnumSet代替位域
单个枚举类型中提取多个值。最好不要用序数来索引数组,而要使用EnumMap,如果你要表示的这种关系时多维的,就使用EnumMap<…,EnumMap<…>>
第三十四条:用接口模拟可伸缩的枚举
用法:比如不同规则的计算工资。枚举类型无法扩展,但是接口可以。
第三十五条:注解优于命名模式
文字拼写易出错;无法确保他们只用在相应的程序元素上;没有提供参数和元素关联的好方法。
定义注解格式的为原注解。
这种称为有参数的注解。表明用户可以自定义异常类。
第三十六条:坚持使用Override注解
表示被注解的方法覆盖了超类的一个声明。
如果你在编写一个没有标注为抽象的类,并且确信它覆盖了抽象的方法,这种情况下可以省略注解。使用这种注解,编译器可以帮你防范大量的错误。
第三十七条:用标记借口定义类型
标记接口是没有包含方法声明的接口。指明类实现了具有某种属性的接口。比如Serializable,clone等。可以用instanceof进行类型查询。
标记注解唯一的目的就是标记声明,确定标记是否存在可以使用isAnnotationPresent()方法来判断。
第三十八条:检查参数的有效性
非公有的方法通常使用断言来检查参数
第三十九条:必要时就行保护性拷贝
第四十条:谨慎设计方法签名
避免过长的参数列表:可以使用静态成员类来辅助。
对于参数类型,要优先使用接口,而不是类。(比如Map)
第四十一条:慎用重载
对于被覆盖的方法是动态的,覆盖机制是规范,重载机制是例外。能够重载方法并不意味着应该重载方法。对于多个具有相同参数数目的方法来说,应该避免重载方法。
第四十二条:慎用可变参数
可变参数的每次调用都会导致进行一次数组分配和初始化。比应该被过度使用。
第四十三条:返回零长度的数组和集合,而不是null
第四十四条:为所有导出的API元素编写文档注释
@param/@return/@throws
类的导出API有两个特征经常被人忽略,即线程安全性和可序列化性。
第四十五条:将局部变量的作用域最小化
可增强代码的可读性和可维护性,并降低出错的可能性。
每个局部变量的声明都应该包含一个初始化表达式。循环提供了特殊的机会将变量的作用域最小化。for 循环优于while循环。
第四十六条:for-each循环优于传统的for循环
for-each循环简洁,预防bug
第四十七条:了解使用类库
使用标准类库正确性,久经考验,不必浪费时间想解决方案。他们的性能往往随着时间推移而不断提高。
第四十八条:如果需要精确答案,请避免使用float和double
容易精度丢失,建议使用BigDecimal/int/long
第四十九条:基本类型优于装箱类型
节省空间。
对装箱类型使用==基本都是错误的
混合使用基本类型和装箱类型,装箱的基本类型就会自动拆箱。如果null对象拆箱,就会报NPE。
第五十条:如果其他类型更合适,避免使用字符串
字符串不适合代替枚举类型
字符串不适合代替聚集类型
也不适合代替能力表
第五十一条:当心字符串的连接性能
为连接n个字符串而重复使用字符串链接操作,需要n平方级的时间。(StringBuilder代替)
第五十二条:通过接口引用对象
这样会使程序更加灵活。决定更改实现时,只是改变构造器中的类的名字。
第五十三条:接口优于反射机制
反射机制:通过程序来访问关于已装载的类的信息,允许一个类使用另一个类。即使当编译的时候,前者更本不存在。代价:丧失编译时检查类型的好处;反射访问的代码笨拙和冗长;性能损耗。
第五十四条:谨慎使用本地方法(本地语言不是安全的)
第五十五条:谨慎的进行优化
第五十六条:遵守普遍的命名惯例便于公开和共享
第五十七条:只针对异常的情况下才使用异常
把代码放在try-catch反而会阻止jvm本实现的特定优化。不应该用到控制流。
第五十八条:对可恢复的情况使用受检异常,对编程错误的使用运行异常
异常可抛出结构:受检的异常/运行时的异常和错误(未受检)。
受检和未受检原则:如果期望受检者适当的恢复,应该使用受检异常。
用运行时异常表明程序错误
第五十九条:避免不必要的受检异常
第六十条:优先使用标准的异常
实现高度的代码重用。
第六十一条:抛出和抽象相应的异常
更高层的实现应该捕获底层的异常,同时抛出可以按照高层抽象进行解释的异常。
第六十二条:每个抛出的异常都要有文档
第六十三条:在细节消息中包含捕获失败的信息
第六十四条:努力使失败保持原子性
失败的方法调用应该使对象保持在被调用之前的状态。
第六十五条:不要忽略异常
catch中应该包含为什么不处理该异常
第六十六条:同步访问共享可变数据
第六十七条:避免过度同步
避免死锁和数据破坏,千万不要从同步区域内部调用外来方法。
第六十八条:exector和task优先于线程
newFixedThreadPool
第六十九条:并发工具优先于wait和notify
工具分为三类:Executor Framework,并发集合/同步器。并发集合(ConcurrentHashMap)可以提升性能。
有些集合接口已经通过了阻塞操作进行了扩展,他们会一直等待到成功执行为止。BlockingQueue
同步器是一些使线程能够等待另一个线程的对象,允许他们协调动作。常用的同步器CountDownLatch和Semaphore。
第七十条:线程安全性的文档化
不可变性:不需要外部的同步,String,Long
无条件的线程安全:实例可变,有足够的内部同步,Random和ConcurrentHashMap。
有条件的线程安全:要求外部同步。
非线程安全:实例可变,为了并发使用它们,客户必须利用自己选择的外部同步包围每个方法调用:ArrayList和HashMap。
线程对立:没有同步的修改静态数据。很少基本废除了。
第七十一条:慎用延迟初始化
如果域只是在实例部分被访问时才被调用,并且初始化这个域的开销很大,就值得延迟初始化。如果滥用,会破坏初始化循环。
第七十二条:不要依赖于线程调度器
依赖于线程调度器的程序很有可能是不可移植的
第七十三条:避免使用线程组
线程组过时了,无法统计线程组中(数组参数)的线程数量,有太多缺陷。用线程池来替代。
第七十四条:谨慎实现Serializable接口
代价是:1.一旦一个类被发布,就大大降低了“改变这个类实现”的灵活性。(默认的接受了它的序列化形式)
2.增加出现bug和安全漏洞的可能性
3.随着版本的发行,相关的测试负担也增加了
第七十五条:考虑用自定义的序列化形式
如果一个对象的物理表示法等同于它的逻辑内容。可能就适合于使用默认的序列化形式。确认后还需要一个readObject方法保证约束关系和安全性。
当两者不一致的时候,使用默认的会有以下缺点:
导出的API永远束缚在该类的内部表示法上。
会消耗更多的空间。
会消耗更多的时间。
引起栈溢出。
自定义的用transient来标记那些不需要序列化的域。
为自己编写的每个可序列化的类声明一个显示的序列版本UID。
第七十六条:保护性的编写readObject方法
当一个对象被反序列化的时候,对于客户端不应该拥有的对象引用,如果哪个域包含了遮这样的引用,就必须作保护性拷贝,非常重要。
第七十七条:对于实例控制,枚举类型优于readResolve
readResolve允许你用readObject 创建的实例代替另一个实例
如果把可序列化的实例受控的类编写为枚举,就可以绝对保证除了声明的常量外,只有一个实例,JVM提供了保障。
应该尽可能的使用枚举类型来实施实例控制的约束条件。
第七十八条:考虑用序列化代理代替序列化实例
为可序列化的类设计一个私有的静态嵌套类,精确的表示外围类的实例的逻辑状态
序列化代理模式两个局限:不能被客户端扩展的类兼容。不能和对象图中包含循环的类兼容。