PancrasL的博客

effective-java

2021-11-16

查看源图像

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)
  • 返回对象的时候可以返回同一个对象(缓存)

  • 可以返回原始类型的子类型对象

  • 返回的类对象可以使用动态加载机制创建(如SPI机制)

(4)缺点

  • 构造器私有化后,就无法被子类继承
  • 程序员很难发现它们

1.2 遇到多个构造器参数时考虑使用建造者(Builder)模式

(1)使用场景

1
4个或更多个参数

(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 {
// Required parameters
private final int servingSize;
private final int servings;

// Optional parameters - initialized to default values
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 registry type.
*/
Zookeeper,

/**
* Redis registry type.
*/
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; // In kilograms
private final double radius; // In meters
private final double surfaceGravity; // In m / s^2

// Universal gravitational constant in m^3 / kg s^2
private static final double G = 6.67300E-11;

// Constructor
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; // F = ma
}
}

(3)注意

除非要将枚举方法导出,否则都应该声明为私有的,或包级私有的

5.2 坚持使用@Override

1

  • 检查参数的有效性
  • 返回零长度的数组或集合,而不是null
  • 将局部变量的作用域最小化
1
2
3
在第一次使用局部变量时声明它
局部变量应包含初始化表达式
for循环优先于while循环
  • 优先使用增强for循环(?增强for循环基于迭代器,性能要低)

  • 如果其他类型更适合,则尽量避免使用字符串

6. Lambda和Stream

// TODO

7. 方法

7.1 检查参数的有效性

7.2 谨慎设计方法签名

  • 谨慎选择方法名称

  • 不要过于追求提供便利的方法

  • 避免过长的参数列表

  • 对于参数类型,优先使用接口而不是类

  • 对于boolean参数,要优先使用两个元素的枚举类型

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. 序列化

Tags: all