-
创建和销毁对象
-
1. 考虑用静态工厂方法代替构造器
- 优势1:有名称
- 优势2:不必在每次调用它的时候都创建一个新的对象
- 优势3:可以返回子类型对象
- 优势4:在创建参数化类型的时候,可以是代码变得更加简洁
- 静态工厂方法缺点1:类如果不含公有的或者受保护的构造器,就不能被子类话
- 静态工厂方法缺点2:它们与其他的静态方法实际上没有任何区别
-
2. 遇到多个构造器参数的时候考虑用构造器
-
多个构造参数的问题
- 重叠构造器,难写难读
- JavaBean模式在构造过程中可能处于不一致的状态
- JavaBean模式阻止了把类做成不可变的可能
-
builder模式
- 如果类的构造器或者静态工厂有多个参数,设计这种类的时候,Builder模式就是不错的选择
- 3. 用私有的构造器或者枚举类型强化Singleton属性
- 4. 通过私有构造器强化不可实例化的能力
-
5. 避免创建不必要的对象
- 如果对象是不可变的,他就始终可以被重用
-
6. 清除过期的对象引用
- 只要类是自己管理内存,程序员应该警惕内存泄露问题
- 内存泄露的另一个常见来源是缓存
- 内存泄露的第三个常见来源是监听器和其他回调
-
7. 避免使用终结方法
- 除非是作为安全网或者为了终结非关键的本地资源,否则不要使用终结方法
- 使用终结方法,就要调用super.finalize
- 终结方法与公有的非final类关联起来,考虑使用终结方法守卫者
-
对于所有对象都通用的方法
-
8. 覆盖equals时请遵守通用约定
-
不用覆盖equals方法的情况
- 类的每个实例本质上都是唯一的
- 不关心类是否提供了“逻辑相等”的测试功能
- 超类已经覆盖了equals,从超类继承过来的行为对于子类也是合适的
- 类是私有的或者是包级私有的,可以确定它的equals方法不会被调用
-
约定
- 自反省
- 对称性
- 传递性
- 一致性
- 任何非null的引用x,x.equals(null)必须返回false
-
高质量equals的诀窍
- 使用==操作符检查“参数是否为这个对象的引用”
- 使用instanceof操作符检查“参数是否为正确的类型”
- 把参数转化为正确的类型
-
对于该类中的每个“关键域”,检查参数中的域是否与该对象中对应的域相匹配
- float和double域使用Float.compare和Double.compare方法
- 可为空的对象引用域的比较 (field == null ? o.field == null : field.equal(o.field) 或者(field == o.field || (field != null && field.equals(o.field)))
-
当你编写完成了equal方法之后,应该问自己三个问题:它是否对称的,传递的,一致的
- 覆盖equal方法时总要覆盖hashCode
- 不要企图让equals方法过于智能
- 不要将equals声明中的Object对象替换为其他的类型,这样是重载,不是覆盖
-
9. 覆盖equals时总要覆盖hashCode
-
相等的对象必须拥有相等的散列码
- result = result * 31 + c
- hashCode()开销大时可以缓存
- 不要试图从散列码计算中排除掉一个对象的关键部分来提高性能
-
10. 始终要覆盖toString()方法
- toString()方法应该返回所有值得关注的信息
- 对象太大的时候返回一个摘要信息
- 无论你是否要指定格式,都要应该在文档中明确的表明你的意图,要指定格式,就要严格地这样去做
- 无论是否指定格式,都为toString方法返回值中包含的所有信息提供一种编程式的访问途径
-
11. 谨慎的覆盖clone
- 同构造器一样,clone方法不应该在构造的过程中调用任何非final的方法
- 12. 考虑实现Comparable接口
-
类和接口
- 13.使类和成员的可访问性最小化
- 14.在公有类中使用访问方法而非公有域
-
15. 使可变性最小化
-
不可变类
- 不提供任何会修改对象状态的方法
- 保证类不会被扩展
- 使所有的域都是final的
- 使所有的域都成为私有的
-
确保对于任何可变组件的互斥访问
- 保护性拷贝
-
16.复合优先于继承
-
安全的继承
- 同一个包下,由同一个程序员控制
- 专门为了继承而设计,并且有很好的文档
-
继承打破了封装性
- 自用性
- 超类升级
-
复合/转发
-
包装类
- decorator
-
17.要么为继承而设计,要么提供文档说明,要么就禁止继承
-
文档说明可覆盖的方法的自用性
- 方法或构造器调用了那些可覆盖的方法,调用顺序,调用结果,后续影响
- 那些情况下会调用可覆盖的方法
- 对于为了继承而设计的类,唯一的测试方法就是编写子类
- 构造器决不可调用可被覆盖的方法
-
18.接口优于抽象类
- 现有的类很容易被更新,以实现新的接口
- 接口是定义mixin的理想选择
- 接口允许我们构造非层次结构的类型框架
-
!抽象类的演变要比接口容易的多
- 接口一旦被发行,并且已经被广泛实现,再想改变这个接口几乎是不可能的
-
19.接口只用于定义类型
- 常量接口会污染命名空间
- 20.类层次优于标签类
- 21.用函数对象表示策略
-
22.优先使用静态成员类
-
四种嵌套类(nested class)
- 静态成员类
- 非静态成员类
- 匿名类
- 局部类
-
泛型
-
23.请不要在新代码中使用原生态类型
- 声明中有一个或多个类型参数(type parameter)的类或者接口,就是泛型(generic)类或者接口
- 无限制的通配符类型(?),不能讲任何元素(null除外)放到Collection<?>中,否则会有编译时异常
-
例外
-
类文字(class literal)中必须使用原生态类型
- List.class, String[].class, int.class合法,List<String>.class,List<?>.class不合法
- instanceof 操作符
- 24.消除非首检警告
-
25.列表优于数组
- 数组是协变的,泛型则是不可变的
-
26.优先考虑泛型
- 使用泛型比使用需要在客户端进行转换的类型来的更加安全,也更加容易。在设计新的类型的时候,要确保它们不需要这种转换就可以使用。
-
27.优先使用泛型方法
- 静态工具方法尤其适合于泛型化
- 泛型单例工厂
- 递归类型限制
-
28. 利用有限制的通配符来提升API的灵活性
- PECS
- 29. 优先考虑安全的异构容器
-
枚举和注解
-
30. 用enum代替int常量
- int枚举模式类型不安全,不方便使用
- String枚举类型不安全,有性能问题
- 枚举类型可以带构造器,带方法,可以带抽象方法还可以嵌套
- 31. 用实例域代替序数
-
32. 用EnumSet代替位域
- Enum.ordinal很少用到
- 33. 用EnumMap代替序数索引
- 34. 用接口模拟可伸缩的枚举
-
35. 注解优于命名模式
- JUnit新旧版本
-
命名方式缺点
- 文字拼写错误会导致失败
- 无法确保它们只用于相应的程序元素上
- 没有提供将参数值与程序元素关联起来的好方法
-
注解
- 元注解
- 标记注解
- 36. 坚持使用Override注解
- 37. 用标记接口定义类型
-
方法
-
38. 检查参数的有效性
- 公有的方法,参数限制要写文档,参数无效抛出IllegalArgumentException,IndexOutOfBoundsException,NullPointerException的异常
- 非公有的方法通过断言来检查他们的参数
- 构造器的参数有效性检查是非常重要的
- 例外:检查昂贵,不切实际,隐含在计算过程中
-
39. 必要时进行保护性拷贝
- TOCTOU攻击
- 对于参数类型可以被不可信任方子类化的参数,请不要使用clone方法进行保护性拷贝
-
40. 谨慎的设计方法签名
- 谨慎的选择方法的名称
- 不要过于追求提供便利的方法
-
避免过长的参数列表
- 方法分解
- 辅助类
- 从对象创建到方法调用都采用builder模式
- 对于参数类型,要优先使用接口而不是类
- 对于boolean类型的参数,要优先使用两个元素的枚举类型
- 41. 慎用重载
-
42. 慎用可变参数
- printf和反射机制从变长参数中大大的受益
- 会影响一些性能
- 43. 返回0长度的数组或集合,而不是null
- 44. 为所有导出的api编写文档注释
-
通用程序设计
-
45.将局部变量的作用域最小化
- 在第一次使用它的地方声明
-
46.for-each循环优于传统的for循环
- for-each可以遍历集合和数组以及任何实现了iterable接口的对象
-
无法使用for-each的地方
- 过滤
- 转换
- 平行迭代
-
47.了解和使用类库
- java.lang
- java.util
-
48.如果想要精确的答案,请避免使用float和double
- 货比计算使用BigDecimal,int或者long
- 49.基本类型优于装箱基本类型
-
50.如果其他类型更适合,则尽量避免使用字符串
- 字符串不适合代替其他值类型
- 字符串不合适代替枚举类型
- 字符串不适合代替聚集类型
- 字符串不适合代替能力表
- 51.当心字符串连接的性能
- 52.通过接口引用对象
-
53.接口优于反射机制
- 丧失了编译时类型检查的好处
- 执行反射访问所需要的代码非常笨拙和冗长
- 性能损失
- 54.谨慎的使用本地方法
-
55.谨慎的进行优化
- 很多计算上的过失都被归咎于效率(没有必要达到的效率),而不是任何其他的原因--甚至包括盲目的做傻事 --William A. Wulf
- 不要去计较效率上的一些小小的损失,在97%的情况下,不成熟的优化才是一切问题的根源 --Donald E. Knuth
- 在优化方面,我们应该遵守两条规则:规则1:不要进行优化。 规则二:(仅针对专家):还是不要进行优化--也就是说,在你还没有绝对清晰的未优化方案之前,请不要进行优化 --M. A. Jackson
- 不要费力去编写快的程序,应该努力编写好的程序
- 56.遵守普遍接受的命名惯例
-
异常
-
57. 只针对异常的情况才使用异常
- 永远不应该用于正常的控制流
- 设计良好的API不应该强迫它的客户端为了正常的控制流而使用异常
-
58. 对可恢复的情况使用受检异常,对编程错误使用运行时异常
- 大多数运行时异常都表示提前违例
- 59. 避免不必要的使用受检异常
-
60. 优先使用标准的异常
-
常用异常
- IllegalArgumentException
- IllegalStateException
- NullPointerException
- IndexOutOfBoundsException
- ConcurrentModificationException
- UnsupportedOperationException
-
61. 抛出与抽象对应的异常
- 异常转义,异常链
-
62. 每个方法抛出的异常都要有文档
- 要为每个受检异常提供单独的throws子句,不要为未受检异常提供throws子句
- 63. 在细节信息中包含能补获失败的信息
-
64. 努力使失败保持原子性
- 不可变对象
- 执行操作之前检查参数有效性,可能会失败的计算部分都在对象状态被修改之前发生
- 编写恢复代码,临时拷贝
- 65. 不要忽略异常
-
并发
-
66. 同步访问共享的可变数据
-
同步
- 互斥
-
通信
- volatile
- i++不是原子操作
- java.util.concurrent.atomic.AtomicLong
- 两种失败
- 活性失败(程序无法进行)
- 安全性失败(程序计算出错误的结果)
-
67. 避免过度同步
- 为了避免活性失败或者安全性失败,在一个被同步的方法或者代码块中,永远不要放弃对客户端的控制
-
应该在同步区域内做尽可能少的工作
- 获得锁,检查共享数据,根据需要转换数据,然后放掉锁
- 如果一个可变的类要并发使用,应该使这个类变成事线程安全的,通过内部同步,你还可以获得明显比外部锁定整个对象更高的并发性。否则,就不要在内部同步,让客户在必要的时候从外部同步
- 在同步区域之外被调用的外来方法被称作“开发调用”
-
68. executor和task优先于线程
-
Executor Framework
- java.util.concurrent.Executors
-
69. 并发工具优先于wait和notify
-
java.util.concurrent中的工具分成三类
- Executor Framework
- Concurrent Collection(并发集合)
- Synchronizer(同步器)
-
70. 线程安全性的文档化
-
线程安全级别
-
不可变的
- String
- Long
- BigInteger
-
无条件的线程安全
- Random
- ConcurrentHashMap
-
有条件的线程安全
- Collections.synchronized包装返回的集合,他们的迭代器要求外部同步
-
非线程安全
- ArrayList
- HashMap
- 线程对立的
-
71. 慎用延迟初始化
- 对于实例域,使用双重检查模式
- 对于静态域,使用Lazy initialization holder class idiom
- 对于可以接受重复初始化的实力域,也可以考虑使用单重检查模式
-
72. 不要依赖线程调度器
- 任何依赖于线程调度器来达到正确性或者性能要求的程序,很有可能都是不可移植的
- 73. 避免使用线程组
-
序列化
-
74. 谨慎的实现Serializable接口
-
三个代价
- 大大降低了“改变这个类的实现”的灵活性
- 增加了bug和安全漏洞的可能性
- 随着类发行新的版本,相关的测试负担也增加了
- Date,BigInteger这样的值类应该实现Serializable,大多数的集合类也应该如此。
- 代表活动实体的类,比如线程池,一般不应该实现Serializable
-
75. 考虑使用自定义的序列化形式
- 如果一个对象的物理表示法等同于它的逻辑内容,可能就适合于使用默认的序列化形式。
-
当一个对象的屋里表示法与他的逻辑数据内容有实质性的区别时,使用默认的序列化形式会有以下4个缺点
- 它使这个类的导出API永远的束缚在该类的内部表示上
- 它会消耗过多的空间
- 它会消耗过多的时间
- 它会引起栈溢出
-
不使用默认的序列化形式
- writeObject
- readObject
- transient
- 不管选择了那种序列化的形式,都要为自己编写的每个可序列花的类声明一个显式的序列版本UID
- 76. 保护性的编写readObject方法
-
77. 对于实例控制,枚举类型优于readResolve
- 对于一个正在被反序列化的对象,如果它的类定义了一个readResolve方法,并且具备正确的声明,那么在反序列化之后,新建对象上的readResolve方法就会被调用,然后,该方法返回的对象引用将被返回,取代新建的对象
- 如果依赖readResolve进行实例控制,带有对象引用类型的所有实力域都必须为transient。
-
78. 考虑序列化代理代替序列化实例
- writeReplace方法