何时以及如何正确使用静态方法

要知道何时以及如何正确使用静态方法,首先要搞清楚静态方法与实例方法的区别。静态(static)本身说明该行为是无状态的,无需实例化,调用时无需再格外分配内存来存放实例。所以,针对全局的单例场景、无状态的行为时,就可以考虑用使用静态方法。但是,静态方法有一个致命的问题,即它与具体类型是强耦合的。如果该行为可能存在变化,就要避免使用静态方法。

因此,有如下情形:

  • 对于确定不会变化的工具行为,使用静态方法;
  • 需要提供语法糖让代码更易于阅读,可以定义静态方法,因为它可以被static import。

例如,google的common库里面有一些工具类FilesPreconditions。这些工具类提供的方法通常是不会变的。如在Preconditions工具类中,非空检查的逻辑在将来并不会发生变化,该行为又没有状态,此时就可以用静态方法作为工具方法。

1
2
3
4
5
6
7
8
9
public static <T> T checkNotNull(T reference, @Nullable Object errorMessage) {
if(reference == null) {
throw new NullPointerException(String.valueOf(errorMessage));
} else {
return reference;
}
}

Preconditions.checkNotNull(name, "name is null");

对于第二种情形,例如单元测试时需要使用断言,为了提高测试代码的表现力,应力求测试更符合自然语言的阅读习惯。基于Java编写的AssertJ验证框架以拥有流畅的接口而著称。譬如说它提供的assertThat()方法,为了更好地体现DSL的特征,就被定义为静态方法,并通过static import隐藏类型信息,让方法调用变得更自然:

1
2
assertThat(fellowship).extracting("name")
.contains("Boromir", "Gandalf", "Frodo", "Legolas")

如果不满足这两个条件,就尽量不要用静态方法,因为静态方法不利于扩展,不利于Mock,因而也不利于编写测试。至于针对一些无状态的服务方法,例如电商系统中针对促销策略的变化封装OnSalePolicy类,由于促销策略随时发生变化,因此也不能使用静态方法。最佳选择是定义为服务接口,然后通过依赖注入(Dependency Injection)实现松耦合,拥抱变化。