一般来说,静态方法和单例模式的使用,大多使用在工具类/方法中,在需要的时候调用对应的静态方法/单例方法完成某项功能。也就是说,在功能上,它们之间是有一定的重合的,那么在实际开发的过程中,如何选择使用哪一种方式实现某个功能?要了解如何使用,首先应该了解的是它们之间的区别。

关于调用

二者都是可以直接通过类名调用,不同的是,静态方法的方式下,可以直接通过类名调用到这个类的静态方法,而对于单例,首先要通过类得到一个静态的实例,然后调用这个对象的方法。这一点对于使用上,没有很大的区别。

关于初始化

对于静态方法这种方式而言,一般使用一些静态变量保存方法执行所需的状态,这种情况下初始化有两种方式:

  1. 直接在类里增加一个static代码块,然后在这个代码块中执行一些初始化,但是此时的初始化是在类加载的时候自动完成的,开发者并不能在这种初始化里进行动态的初始化
  2. 在类里增加一个用于初始化的方法,类似的如 Android 中Looper.prepare()方法,通过调用一个初始化方法进行初始化的方法,那么我们就可以决定在何时初始化,以及可以通过给这个初始化方法增加参数完成动态的初始化

而对于单例模式的话,有「饱汉」和「饿汉」等初始化方式,它们与上述两种方式也是类似的。

但是,如果我们需要两个状态不同的工具类的话,使用静态方法就有点捉襟见肘了,因为它的所有状态都是通过静态变量保存的,所以同一时间只能是一种状态,如果还要进入另一状态,就需要再次初始化。但是对于单例而言,这不过就是两个单例的问题,只需要构造两个不同的单例,那么这两个单例的成员变量就是互不相关的,在这一点,应该是使用单例模式更加自由。

关于性质

还有一点,使用单例的时候,我们所有调用的方法都是这个单例对象的方法,也就是说这是一个彻彻底底的面对对象编程。Java 是一门面对对象的语言,Java 类拥有继承、接口、多态等特性,而这些特性在使用单例模式的时候都可以完美的继承这些特性,一个单例类,可以继承于已有的某个类,然后重写其中的某些方法,也可以实现某些接口,这就给了这个单例很大的自由。但是对于静态方法而言,由于所有的方法都是静态的,所有的成员变量也都是静态的,所以这在理论上并不能继承其他类(只能使用父类的静态方法),也不能实现接口(实现的接口的方法必然都不是静态的,也就无法使用)。

所以在面对对象这一性质上,还是单例更加自由。

关于资源引用

如果一个类需要使用某个系统资源进行初始化,那么在初始化的时候就需要将这个系统资源作为成员变量保存在类里面。如 Android 中最常用的系统资源Context对象,那么对于这两种方式来说:

  1. 如果使用静态方法,那么就需要将这个系统资源作为静态变量存储在类里面,在这种时候使用要极为小心,因为垃圾回收机制无法回收静态变量,所以一定要在适当的时候释放掉这个静态成员变量引用的资源,防止内存泄漏
  2. 如果使用单例模式,系统资源就会作为单例的成员变量保存,但是由于单例也是一个静态的,所以与上面一样,也需要在适当的时候手动释放资源

根据目前的分析,两种方法的使用是大致一致的。

关于回收

因为两种方式都是通过静态变量引用的(单例模式中,单例对象是静态的,静态方法中,成员变量也都是静态的),所以当我们已经不需要它们的时候,都不会自动释放内存,要释放它们占用的空间,可以专门给出一个释放空间的方法,将它们内部所引用的资源都置为 null 即可。所以在这一点上,它们是类似的。

总结

根据上面的分析,单例模式的优势主要还是在面对对象编程这一方面(如多个实例、继承、多态等),所以在涉及到这些东西的时候,显然是使用单例模式更有优势,并且这也更符合面对对象编程的思想:一切即对象。单例模式下,我们虽然使用类名得到对象,但我们调用的方法、使用的变量,终归还是这个单例对象的成员变量和方法,相比之下静态方法显得比较另类:它没有对象,只有类和类的静态方法、静态变量。

不过也正是没有对象,静态方法在某些时候执行起来更有效率,如:

public int add(int a, int b) {
    return a + b;
}

这种只需要用到方法参数,而无需类的成员变量参与的方法,大可以写成静态方法的形式,少了一层获取对象的过程,自然能加快方法的调用。对于其他情况的话,我觉得在开发中还是尽量使用单例模式,并且这也是大多 Android 开发者的选择。