1. 创建和销毁对象
1.1 使用静态工厂方法创建对象
(1)使用场景
适合大多场景,除非有合适的理由选择构造器方式。
强烈推荐:当一个类需要多个带有相同签名的构造器时,就使用静态工厂方法代替构造器,并仔细选择名称以突出静态工厂方法之间的区别
(2)静态工厂方法的命名
1 | 静态工厂方法: |
(3)优点
- 当构造器过多时,用户不知道如何调用,这时候如果提供了带有名称的静态工厂方法,就提供了极大的便利
1 | User user = User.newUserByName("Mike") |
返回对象的时候可以返回同一个对象(缓存)
可以返回原始类型的子类型对象
返回的类对象可以使用动态加载机制创建(如SPI机制)
(4)缺点
- 构造器私有化后,就无法被子类继承
- 程序员很难发现它们
1.2 遇到多个构造器参数时考虑使用建造者(Builder)模式
(1)使用场景
1 | 4个或更多个参数 |
(2)示例代码
1 | public class NutritionFacts { |
(3)优点
JavaBean模式的成员变量不能是final的,而建造者模式下可以是
解决了构造参数过多时参数错配导致的编程错误
1.3 Singleton
(1)使用场景
表示无状态的对象
表示系统中本质上唯一的组件
(2)不适用的场景
Singleton不适用依赖底层资源的类
不要使用Singleton和静态工具类来实现一个或多个依赖底层资源的类
(3)实现方式
- 使用enum实现
- 私有构造器+INSTANCE静态变量实现
1.4 优先考虑依赖注入来引用资源
(1)使用场景
创建一个新实例时,将其依赖的资源传到构造器中
(2)模式变体
将资源工厂(Factory)传递给构造器
(3)不适用的场景
不要使用Singleton和静态工具类来实现一个或多个依赖底层资源的类,且该资源的行为会影响该类的行为
不要使用这个类来创建这些资源
将资源或者资源工厂传给构造器(或静态工厂),通过它们来创建类
1.5 消除过期的对象引用
(1)注意内存泄漏
只要是类自己管理内存,程序员就应该警惕内存泄漏问题
自己实现的Stack,在出栈后要置null
缓存中失效的数据需要及时清除
监听器和其他回调
2. 对象通用方法
2.1 equals
不覆盖equals时,类的每个实例仅和其自身相等。
除非有合适的理由,否则不要覆盖equals方法。
2.2 hashCode
覆盖equals方法时总要覆盖hashCode
2.3 toString
始终要覆盖toString,且应该返回对象中值得关注的信息
- 使系统易于调试
2.4 clone
谨慎地覆盖clone
2.5 Comparable
考虑实现Comparable接口
实现了Comparable接口地类表明它地实例具有内在地排序关系
2.5 finalize
不要使用finalize
3. 类和接口
3.1 使类和成员的可访问性最小化
(1)原则
尽可能使每个类或者成员不被外界访问
公有类的实例域决不能是公有的
3.2 使可变性最小
使可变性最小,优先考虑不可变对象(保留创建时的状态)
1 | 不提供任何设值方法 |
除非有令人信服的理由要让类成为可变的类,否则它就应该是不可变的
如果类不能被做成不可变的,就应该降低它的可变性
除非有令人信服的理由要使域变成非final的,否则每个域都应该是final的
构造器(或静态工厂)应该创建完全初始化的对象,并建立起所有的约束关系
3.3 复合优先于继承
要么设计继承并提供文档说明,要么禁止继承
3.4 接口优于抽象类
现有的类可以很容易被更新,以实现新的接口
接口允许构造非层次结构的类型框架
接口使安全地增强类的功能成为可能
3.5 静态内部类优于非静态内部类
4. 泛型
// TODO
5. 枚举和注解
5.1 使用enum替代int枚举模式
(1)使用场景
需要一组固定常量,并且在编译时就知道其成员
(2)使用实例
- 基本使用
1 | enum RegistryType { |
- 将其他数值类型和枚举进行关联
为了将数据与枚举常量关联起来,需要声明实例域,并编写一个带有数据并将数据保存在域中的构造器。
1 | public enum Planet { |
(3)注意
除非要将枚举方法导出,否则都应该声明为私有的,或包级私有的
5.2 坚持使用@Override
1 |
- 检查参数的有效性
- 返回零长度的数组或集合,而不是null
- 将局部变量的作用域最小化
1 | 在第一次使用局部变量时声明它 |
优先使用增强for循环(?增强for循环基于迭代器,性能要低)
如果其他类型更适合,则尽量避免使用字符串
6. Lambda和Stream
// TODO
7. 方法
7.1 检查参数的有效性
7.2 谨慎设计方法签名
谨慎选择方法名称
不要过于追求提供便利的方法
避免过长的参数列表
对于参数类型,优先使用接口而不是类
对于boolean参数,要优先使用两个元素的枚举类型
7.3 返回零长度的数组或集合,而不是null
1 | public List<Cheese> getCheeses(){ |
8. 通用编程
8.1 将局部变量的作用域最小化
在第一次使用局部变量的地方声明它
8.2 优先使用增强for循环
8.3 合适的命名
一旦发现有更好的名称,就换掉旧的
只要短名称足够清楚,就比长名称好
9. 异常
9.1 只针对异常的情况才使用异常
不要在正常的控制流中使用异常,例如捕获数据越界错误
9.2 对于可恢复的情况使用受检异常,对于编程错误使用运行时异常
1 | 如果期望调用者能够适当地恢复,应使用受检异常 |
9.3 避免滥用受检异常
1 | 使用受检异常的两个前提:①正确使用API并不能阻止异常的产生 ②调用者可以采用有用的动作 |
9.4 优先使用标准的异常
1 | IllegalArgumentException:非null的参数值不正确,例如需要正数却传递了负数 |
9.5 抛出与抽象对应的异常
1 | 更高层的实现应该捕获低层的异常,同时抛出可以按照高层抽象进行解释的异常 |