Effective Java
Link : Effective Java 3rd
创建和销毁对象
- 考虑用静态工厂方法替换构造器
- 当遇到多个构造器参数时考虑用Builder替换
- 用私有构造器或者枚举类型强化单例属性
- 通过私有构造器强化不可实例化的能力
- 依赖注入优先硬连接资源
- 避免创建不必要的对象
- 消除过期的对象引用
- 避免使用终结方法和清理器
- try-with-resources优先try-finally
所有对象通用的方法
-
覆写equals时候遵守通用规定
-
覆写equals时候总要覆写hashCode
-
始终覆写toString
-
小心覆写clone
-
考虑实现Comparable接口
类和接口
-
使类和成员可见性最小
-
公有类中使用访问方法而非公有域
-
使可变性最小
-
组合优于继承
-
要么为继承而设计并提供文档,要么禁止继承
-
接口优于抽象类
-
为后代(posterity)设计接口
-
接口只用于定义类型
-
类继承优于标签类
-
优先考虑静态类而不是非静态
-
将源文件限制为单个顶级类
泛型
-
不要使用原生类型
-
消除非受检警告
-
list优于数组
-
优先考虑泛型
-
优先考虑泛型方法
-
使用有界通配符提升API的灵活性
-
小心组合泛型和可变参数 @
-
优先考虑类型安全的异构容器第
枚举和注解
-
用enum代替int常量
-
用实例域代替序数
-
用EnumSet代替位域
-
用EnumMap代替序数索引
-
用接口模拟可扩展的枚举
-
注解优于命名模式
-
统一使用Override注解
-
用标记接口定义类型
Lambda表达式和流 @
- Lambda表达式优于匿名类
- lambda的序列化
- 方法引用优于Lambda表达式
- 优先使用标准的函数式接口
- 小心使用流
- 流中优先使用无副作用的函数
- Collection优先流作为返回类型
- 当创建并行流的时候小心些
方法
- 检查参数的有效性
- 需要时进行保护性拷贝
- 小心设计方法签名
- 谨慎使用重载
- 谨慎可变参数
- 返回空集合或者数组,而不是null
- 谨慎返回Optionals @
- 为所有导出的API元素写文档注释
通用程序设计
- 最小化局部变量作用域
- for each优于传统for循环
- 了解和使用类库
- 如果需要精确答案,避免使用float和double
- 基本类型优于装箱类型
- 如果其他类型更合适,避免使用String
- 小心String连接性能
- 通过接口引用对象
- 接口优于反射
- 谨慎使用本地方法
- 谨慎优化
- 遵守普遍的命名规范
异常
- 只针对异常情况才使用异常
- 对可恢复的情况使用受检异常,对编程错误使用运行时异常
- 避免不必要使用受检异常
- 优先使用标准异常
- 抛出与抽象对应的异常
- 每个方法抛出异常要有文档
- 在细节信息中包含捕获失败的信息
- 努力使失败保持原子性
- 不要忽略异常
并发
-
同步访问共享可变数据
-
避免过度同步
- executors,task,stream优于线程 @
- 并发工具优于wait和notify
- 线程安全文档化
- 慎用延迟初始化
- 不要依赖线程调度器
序列化
-
考虑其他可选择优于Java序列化 @
-
考虑使用自定义序列化形式
-
谨慎实现Serializable接口
-
保护性编写readObject方法
-
对于实例控制,枚举优于readResolve
-
考虑序列化代理替换序列化实例
读书记录
-
消除过期的引用对象**:防止内存泄露(当自行管理缓存时)
-
- 实例:数组实现栈,pop的时候,删除的位置没有重新赋值为null;
-
对于缓存(缓存项生命周期由键的外部引用决定时),可与使用
WeakHashMap
解决; -
Equals方法覆盖:自反性、对称性、传递性、一致性,且需要重载
hashCode
方法;
无法在扩展可实例化的类的同时,既增加新的值组件,同时保留equals约定
- 传递性(ColorPoint和Point)和里氏替换原则无法同时满足;
- 可以通过组合而不是继承的方式进行扩展;
-
可以在抽象的子类中添加新的值组件;
-
CompareTo:
- 使用
Float.compare
和Double.compare
进行数值比较; -
建议实现
x.compareTo(y) == x.equals(y)
-
在公有类方法中使用公有方法而不是公有域
- 类是外部可以访问的,必须使用公有方法,不能对外提供共有域;
-
如果类是内部私有,或者私有的内部类,可以直接暴露其数据域(如果这些数据域确实描述该类提供的抽象)
-
复合优先继承
-
在包的内部使用继承是安全的,只有当子类真正是超类的子类型时;
-
使用第三方包提供服务,使用组合;
-
接口优于抽象类
泛型
- ? 表示任意类型
- Set\
- Set<?>是个通配符类型,表示只能包含某种未知对象类型的一个集合;
- 消除受检异常
- 最小化@SuppressWarnings("unchecked")的范围
- 列表优先数组
- new List\
[]是编译错误; - 数组是具体化、协变的,声明父类数组编译时无法发现问题;
- 泛型是不可变的,通过擦除实现,擦除使泛型可以与没有使用泛型的代码随意进行互用
- PECS: producer-extends, consumer-super
- 参数化类型表示一个T生产者,就使用<? extends T>;如果表示一个T消费者,就使用<? super T>;
-
staic \
E reduce(List<? extends E> list, function\ f, E initVal) -
不要用通配符 ? 作为返回类型
枚举和注解
- Enum 实现常量数的策略模式
- 在枚举类型中声明一个抽象的apply方法,并在特定于常量的类主体中覆盖;
-
等价于: 接口(抽象类) + 实现类 + 单例
-
使用EnumSet替代位域
-
使用EnumMap替代序数索引
-
坚持使用@Override注解
-
用标记接口定义类型
-
标记接口是没有包含方法声明的接口,如Serializable接口
方法
- 必要时进行保护性拷贝
- 假设类的客户端会尽其所能破坏类的约束条件;
- 保护性拷贝是在检查参数有效性之前,且针对的是拷贝之后的对象,避免多线程修改问题;
- 对于参数类型可以被不信任方子类化的参数,不要使用clone方法进行保护性拷贝;
-
类信任调用方或者类和客户端在同一个包,可以不进行保护性拷贝,但需要在类文档中说明清楚,调用者不能修改受影响的参数或返回值;
-
谨慎设计方法签名
-
对于boolean参数,优先使用两个元素的枚举类型,易懂且可扩展性好;
-
慎用重载
- 重载(overloading)是在编译时确定调用方法,根据声明的类型选择;
- 覆盖(overriding)是在运行时确定调用方法,根据实际类型选择;
- 返回零长度的数组或集合,而不是null
-
零长度的数组可以共享;
-
为所有导出的API元素编写文档注释
- 类、接口、构造器、方法、域;
- 方法:概要描述、详细描述、参数、返回、异常
- 类和方法的线程安全性;
- javadoc产生的HTML文件检查
通用程序设计
-
foreach优于for循环
-
基本类型优于装箱类型
-
精确答案时,用BigDecimal、int、long替代float和double
-
不用使用字符串连接操作符来合并多个字符串
- 谨慎进行优化
- 设计API、线路层协议和永久数据格式,需要考虑性能因素
- 专业的工具进行profile,找到关键点再进行优化
异常
-
不要用异常实现正常的控制流
-
对可恢复的情况使用受检异常,对编程错误使用运行时异常
-
抛出有抽象相对应的异常
-
高层实现捕获低层异常,同时抛出可按照高层抽象解释的异常;
-
异常链,底层的异常(cause)被传到高层的异常;
-
在细节消息中包含能捕获失败的信息
-
失败保持原子性
-
失败的方法调用应该使对象保持在被调用之前的状态 ;
-
调整计算顺序使可能会失败的部分在对象状态被修改前发生;恢复代码;临时拷贝
并发
Java语言规范保证读或写一个变量是原子的(atomic),除非变量类型是long或double。
- volatile保证线程可见性
- 用synchronized修饰只包含变量的读/写的方法,这里同步为了通信效果,而不是为了互斥;
- 避免过度同步
- 避免死锁和数据破坏,不要从同步区域内调用外来方法;
- executor和task优先于线程
- 并发工具优先于wait, notify
- Lock, CountDownLatch, Semaphore, CyclicBarrier, Condition
-
并发集合:ConcurrentHashMap, CopyOnWriteList,CopyOnWriteSet, AtomicXXX, Collections.synchronizedXXX, LinkedBlockingQueue, ConcurrentLinkedQueue
-
对于间歇式的定时,优先使用nanoTime
-
nanoTime不受系统的实时时钟的调整所影响
-
线程安全性的文档化说明
-
Immutable, ThreadSafe, NotThreadSafe。
-
不要让应用程序的正确性依赖线程调度器
-
不要依赖
Thread.yield
或者线程优先级 -
用线程池代替线程组(ThreadGroup)
序列化
- 显示指定序列化版本UID
- 内部类不可实现Serializable,静态成员类可以
- 内部类的默认序列化形式是定义不清楚的
- @serial 标签
- Javadoc会将文档信息放在序列化形式的特殊文档中
- 线程安全
-
如果在读取整个对象状态的任何其他方法上强制任何同步,则也必须在对象序列化上强制这种同步
-
保护性地编写readObject方法
-
防止通过直接构造byte[],通过默认序列化方式生成违反约束条件的实例;
-
readObject中添加保护性拷贝,防止通过“恶意编制的对象引用”引用并修改对象中的变量;
-
单例的序列化:枚举优先于readResolve
- 如果使用readResolve进行实例控制,带有对象引用类型的所有实例域则都必须被声明为transient;