1. 创建和销毁对象
1.1 使用静态工厂方法创建对象
(1)使用场景
适合大多场景,除非有合适的理由选择构造器方式。
强烈推荐:当一个类需要多个带有相同签名的构造器时,就使用静态工厂方法代替构造器,并仔细选择名称以突出静态工厂方法之间的区别
(2)静态工厂方法的命名
1 2 3 4 5 6 7 8 9
| 静态工厂方法: from of valueOf instance或getInstance:根据参数值创建对象 create或newInstance:确保每次调用都返回一个新的实例时使用 getType newType type
|
(3)优点
- 当构造器过多时,用户不知道如何调用,这时候如果提供了带有名称的静态工厂方法,就提供了极大的便利
1 2 3
| User user = User.newUserByName("Mike") User user = User.newUserByAge(19) User user = User.newUser("Mike", 19)
|
(4)缺点
- 构造器私有化后,就无法被子类继承
- 程序员很难发现它们
1.2 遇到多个构造器参数时考虑使用建造者(Builder)模式
(1)使用场景
(2)示例代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52
| public class NutritionFacts { private final int servingSize; private final int servings; private final int calories; private final int fat; private final int sodium; private final int carbohydrate;
public static class Builder { private final int servingSize; private final int servings;
private int calories = 0; private int fat = 0; private int sodium = 0; private int carbohydrate = 0;
public Builder(int servingSize, int servings) { this.servingSize = servingSize; this.servings = servings; }
public Builder calories(int val) { calories = val; return this; } public Builder fat(int val) { fat = val; return this; } public Builder sodium(int val) { sodium = val; return this; } public Builder carbohydrate(int val) { carbohydrate = val; return this; }
public NutritionFacts build() { return new NutritionFacts(this); } }
private NutritionFacts(Builder builder) { servingSize = builder.servingSize; servings = builder.servings; calories = builder.calories; fat = builder.fat; sodium = builder.sodium; carbohydrate = builder.carbohydrate; }
public static void main(String[] args) { NutritionFacts cocaCola = new NutritionFacts.Builder(240, 8) .calories(100).sodium(35).carbohydrate(27).build(); } }
|
(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 2 3 4
| 不提供任何设值方法 保证类不会被扩展 声明所有的域是final的 声明所有的域是私有的
|
除非有令人信服的理由要让类成为可变的类,否则它就应该是不可变的
如果类不能被做成不可变的,就应该降低它的可变性
除非有令人信服的理由要使域变成非final的,否则每个域都应该是final的
构造器(或静态工厂)应该创建完全初始化的对象,并建立起所有的约束关系
3.3 复合优先于继承
要么设计继承并提供文档说明,要么禁止继承
3.4 接口优于抽象类
现有的类可以很容易被更新,以实现新的接口
接口允许构造非层次结构的类型框架
接口使安全地增强类的功能成为可能
3.5 静态内部类优于非静态内部类
4. 泛型
// TODO
5. 枚举和注解
5.1 使用enum替代int枚举模式
(1)使用场景
需要一组固定常量,并且在编译时就知道其成员
(2)使用实例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| enum RegistryType {
Zookeeper,
Redis;
static RegistryType getType(String name) { for (RegistryType registryType : RegistryType.values()) { if (registryType.name().equalsIgnoreCase(name)) { return registryType; } } throw new IllegalArgumentException("Not support registry type: " + name); } }
|
为了将数据与枚举常量关联起来,需要声明实例域,并编写一个带有数据并将数据保存在域中的构造器。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| public enum Planet { MERCURY(3.302e+23, 2.439e6), VENUS (4.869e+24, 6.052e6), EARTH (5.975e+24, 6.378e6), MARS (6.419e+23, 3.393e6), JUPITER(1.899e+27, 7.149e7), SATURN (5.685e+26, 6.027e7), URANUS (8.683e+25, 2.556e7), NEPTUNE(1.024e+26, 2.477e7);
private final double mass; private final double radius; private final double surfaceGravity;
private static final double G = 6.67300E-11;
Planet(double mass, double radius) { this.mass = mass; this.radius = radius; surfaceGravity = G * mass / (radius * radius); }
public double mass() { return mass; } public double radius() { return radius; } public double surfaceGravity() { return surfaceGravity; }
public double surfaceWeight(double mass) { return mass * surfaceGravity; } }
|
(3)注意
除非要将枚举方法导出,否则都应该声明为私有的,或包级私有的
5.2 坚持使用@Override
- 检查参数的有效性
- 返回零长度的数组或集合,而不是null
- 将局部变量的作用域最小化
1 2 3
| 在第一次使用局部变量时声明它 局部变量应包含初始化表达式 for循环优先于while循环
|
6. Lambda和Stream
// TODO
7. 方法
7.1 检查参数的有效性
7.2 谨慎设计方法签名
7.3 返回零长度的数组或集合,而不是null
1 2 3
| public List<Cheese> getCheeses(){ return cheesesInStock.isEmpty()?Collections.emptyList():new ArrayList<>(cheesesInStock) }
|
8. 通用编程
8.1 将局部变量的作用域最小化
在第一次使用局部变量的地方声明它
8.2 优先使用增强for循环
8.3 合适的命名
一旦发现有更好的名称,就换掉旧的
只要短名称足够清楚,就比长名称好
9. 异常
9.1 只针对异常的情况才使用异常
不要在正常的控制流中使用异常,例如捕获数据越界错误
9.2 对于可恢复的情况使用受检异常,对于编程错误使用运行时异常
1 2
| 如果期望调用者能够适当地恢复,应使用受检异常 用运行时异常来表明编程错误
|
9.3 避免滥用受检异常
1 2
| 使用受检异常的两个前提:①正确使用API并不能阻止异常的产生 ②调用者可以采用有用的动作 错误使用:将受检异常包装成运行时异常丢出去
|
9.4 优先使用标准的异常
1 2 3
| IllegalArgumentException:非null的参数值不正确,例如需要正数却传递了负数 IllegalStateException:不适合方法调用的对象状态 UnsupportedOperationException:对象不支持用户请求的方法
|
9.5 抛出与抽象对应的异常
1
| 更高层的实现应该捕获低层的异常,同时抛出可以按照高层抽象进行解释的异常
|
10. 并发
10.1
11. 序列化