EffectJava第三版
编码最基本原则
-
清晰性和简洁性最为重要 :组件的用 户永远也不应该被其行为所迷惑。
-
组件要尽可能小,但又不能太小 [ 本书中使用的术语“纽 件”( Component),是指任何可重用的软件元素,从单个方法,到包含多个包的复杂框架, 都可以是一个组件 ]。
-
代码应该被重用,而不是被拷贝 。
-
组件之间的依赖性应该尽可能地降 到最小。
-
错误应该尽早被检测出来,最好是在编译时就发现并解决。
清晰、正确、可用、健壮、灵活和可维护的程序 => 性能表现优异
Java 语言支持四种类型:接口 (包括注释)、 类(包括 enum)、数组和基本类型 。
-
接口 (包括注释)、 类(包括 enum)、数组被称为引用类型( reference type),类 实例和数组是对象( object),而基本类型的值则不是对象。
-
类的成员( member)由它的域 ( field)、方法( method)、 成员类 ( member class)和成员接口( member interface)组成。
-
方法的签名(signature) 由它的名称和所有参数类型组成; 签名不包括方法的返回类型。
创建和销毁对象
-
静态工厂方法替换构造器 public static Boolean success(){ return Boolean.TRUE;}
优点:
-
-
有名称能更确切地描述被返回的对象
-
不必每次调用重新创建可以利用缓存
-
可以返回原类型的任意子类型(更灵活)
-
返回的对象可随着参数变更每次调用而变化
-
方法返回的对象所属的类,可以在编写时不存在 服务提供者框架( Service Provider Framework)例如JDBC Spi DI(依赖注入)
-
缺点:
-
-
类不含有公有或受保护的构造器,就不能子类化
-
难以找到(解决方案, 类和接口的注释中关注,遵循命名规范 from/of/create/newInstance/valueOf/getType)
-
-
多个构造参数,考虑构造器
-
重叠构造器模式可行(多个参数不同的构造器),编写困难,阅读困难
-
JavaBeans 模式(先构造对象,再设置对象值),构造过程中对象状态不一致, 对象无法成为不可变(存在set方法)
-
Builder模式 build的设值方法返回 builder 本身,以便把调用链接起来,得到一个流式的 API。
-
-
私有构造器/枚举类型强化Singleton属性
-
公有静态实例 私有构造器被调用一次 (简单清晰)
-
私有静态实例 公有静态工厂方法 (灵活可修改, 泛型工厂 作为Supplier<XXXInstance>)
-
Singleton类序列化(transient且需要提供readResolve保证单例)
-
-
申明包含单元素的枚举类型 (通常是最方便的, 即利用enum实现了序列化 又保证了单例, 但只能用于扩展Enum不能extend其他类)
-
-
通过私有构造器强化不可实例化的能力
工具类 包含一个私有构造 器,它就不能被实例化, 同样无法被继承子类化
-
优先考虑依赖注人来引用资源(灵活性、 可重用性和可测试性)
-
静态工具类和 Singleton 类不适合于需要引用底层资源的类 。
-
当创建一个 新的实例时 , 就将该资源传到构造器中,就是DI
-
依赖注入的对象资源具有不可变性
-
依赖注入适用于构造器/静态工厂/构建器
-
把实例工厂传入构造器,也是一种依赖注入
-
将这些资源 或者工厂传给构造器(或者静态工厂,或者构建器),通过它们来创建类
-
-
避免创建不必要的对象
-
String = new String;; String = s 重用机制
-
优先使用静态工厂方法创建对象 而不是构造器
-
String.matches正则匹配时, 每次创建pattern实例的成本,(1 使用静态对象预编译pattern 2 延迟加载 使用时初始化)
-
对象不变时 要安全重用(map.keyset多次调用返回相同实例,不必创建多个实例)
-
自动拆装箱, 优先使用基本类型, 小对象的拆装箱应该以提升程序的清晰性、简洁性和功能性
-
重量级对象才值得使用池化思想,例如数据库连接,否则占用内存 损害性能 代码复杂
-
保护性拷贝,重用对象代价过高,可能引发Bug与安全漏洞, 不必要的创建只会影响程序风格与性能
-
-
消除过期的对象引用
-
Jvm会自动垃圾回收,同样需要考虑内存回收
-
内存泄漏会导致磁盘交换( Disk Paging),甚至导 致程序失败(OutOfMemoryError 错误)
-
栈的增长与收缩会保留对象的过期引用,正确做法 引用过期,清空设为null, 错误的引用也能抛出NPE
-
只要类是自己管理内存,程序员就应该警惕内存泄漏问
-
缓存常常发生过期引用的内存泄漏
-
监听器与其他回调只有注册没有取消,保存成 WeakHashMap中的键会变成弱引用,下个GC周期回收
-
-
避免使用终结方法和清除方法
-
终结方法( finalizer)通常是不可预测的
-
Java9中清除方法(cleaner)认识不可预测的
-
永远不应该依赖终结方法或者清除方法来更新重要的 持久状态
-
使用终结方法和清除方法有一个非常严重的性能损失
-
安全问题 finalizer attack,对象异常应该不再继续,实现AutoCloseable
-
正确用途
-
安全网,无法正常结束操作是,最终释放
-
本地对象的引用与释放 native peer object 本地对象的本地方法对象
-
除非是作为安全网,或者是为了终止非关键的本地资源,否则请不要使用 清除方法
-
-
-
try-with-resources优先于try-finally
-
try-finally过多嵌套 导致异常堆栈无法完全显示
-
java7的try-with-resources时代码简洁易懂,容易诊断
-
对象通用方法
-
覆盖equals的通用约定
期望结果
-
-
类的每个实例都唯一
-
类本身无需提供逻辑相等
-
超累覆盖equals,超类的行为对于这个类也适用
-
私有类/包级私有类可以确保equals方法永远不会调用
-
通用规范 实现逻辑相等时,覆盖equals方法
-
-
自反性 x . equals(x )必须返回 true
-
对称性 x . equals(y )则 y. equals(x )
-
传递性 x . equals(y )y . equals(z )则 x . equals(z )
-
一致性 多次调用 结果一致
-
非空性 所有对象都不等于null
-
诀窍
-
-
使用==检查是否为当前对象引用
-
使用instanceof检查参数类型正确性
-
将参数转换为正确的类型
-
对于该类中的每个关键域匹配检查significant
-
覆盖 equals 时总要覆盖 hashCode
-
不要企图让 equals 方法过于智能
-
不要将 equals 声明 中的 Object 对象替换为其他的类型
-
-
覆盖 equals 时总要覆盖 hashCode
-
相等的对象必须具有相等的散列码( hash code) 31 * i = = ( i < < 5 ) – i
-
不要试图从散列码计算中排除掉一个对象的关键域来提高性能
-
不要对 hashCode 方法的返回值做出具体的规定,提供修改灵活性
-
-
始终要覆盖 toString
-
提供好的 toString 实现可以便类用起来更加舒适,使用了这个类的系统 也更易于调试。
-
toString 方法应该返回对象中包含的所有值得关注的信息
-
无论是否决定指定格式,都应该在文档中明确地表明你的意图
-
为 toString 返回值中包含的所有信息提供一种可以通过编程 访问的途径(如json,具体格式)
-
-
谨慎地覆盖 clone
-
实现 Cloneable 接口的类是为了提供一个功能适 当的公有的 cl。ne 方法
-
x.clone() != x
-
x.clone().getζlass() == x.getClass()
-
x.clone() .equals(x)
-
不可变的类永远都不应该提供 clone 方法
-
clone 方法 就是另一个构造器; 必须确保它不会伤害到原始的对象, 并确保正确地创建被克隆对象中的 约束条件
-
Cloneable 架构与 引用可变对象的 final 域的正常用法是不相兼容的
-
公有的 clone 方法应该省略 throws 声明
-
对象拷贝的更好的办法是提供一个拷贝构造器( copy constructor)或拷贝工厂( copy factory )。
-
-
考虑实现 Comparable 接口 :自反性、 对称性和传递性 与 弱等同性测试
-
确保所有的 x 和 y 都满足 sgn(x.compareTo(y) ) == – sgn (y.compareTo (x ))
-
比较关系是可传递的: (x . compareTo ( y) > 0 & & y. compareTo (z) > 0 )暗示着 x . compareTo (z) > 0
-
确保 x. compareTo ( y) == 0 暗示着所有的 z 都满足 sgn(x .compareTo ( z) ) == sgn ( y . compare To ( z) )
-
建议(x . compare To (y) == 0) == (x . equals (y))
-
在装箱基本类型 的类中使用静态的 compare 方法,或者在 Comparator 接口中使用比较器构造方法(基础类型int hashcode可能溢出)
-
类和接口
-
使类和成员的可访问性最小化
-
满足封装的基本原则,有效解耦
-
尽可能地使每个类或者成员不被外界访问
-
公有类的实例域决不能是公有的,包含公有可变域的类通常并不是线程安全的
-
让类具有公有的静态 final 数组域,或者返回 这种域的访问方法,这是错误的
-
-
要在公有类而非公有域中使用访问方法
-
如果类可以在 它所在的包之外进行访问,就提供访问方法
-
如果类是包级私有的,或者是私有的嵌套类, 直接暴露它的数据域并没有本质的错误
-
让公有类暴露 不可变的域,其危害相对来说比较小
-
-
使可变性最小化,不可变类
-
不要提供任何会修改对象状态的方法
-
保证类不会被扩展
-
声明所有的域都是 final 的
-
声明所有的域都为私有的
-
确保对于任何可变组件的互斥访问
-
操作返回新的不可变对象
-
使类成为 final的 / ,让类的所有构造器都变成私有的或者包级私有的,并添加公有的静态工厂( static factory)来代替公有的构造器
-
-
复合优先于继承
-
继承打破了封装性
-
只有当子类和超类之间确实存在子类型关系时,使用继承才是恰当的
-
如果子 类和超类处在不同的包中,并且超类并不是为了继承而设计的,那么继承将会导致脆弱性
-
可以用复合和转发机制来代替继承,尤其是当存在适当 的接口可以实现包装类的时候。 包装类不仅比子类更加健壮,而且功能也更加强大
-
例如 MyHashSet extends HashSet 的继承 与 ForwardingSet implements Set{ set s}, MyHashSet extends ForwardingSet
-