
4.4 静态变量和静态方法
如果成员变量用static修饰,则该变量称为静态变量或类变量(class variable),否则称为实例变量(instance variable)。如果成员方法用static修饰,则该方法称为静态方法或类方法(class method),否则称为实例方法(instance method)。
4.4.1 静态变量
实例变量和静态变量的区别是:在创建类的对象时,Java运行时系统为每个对象的实例变量分配一块内存,然后可以通过该对象来访问该实例变量。不同对象的实例变量占用不同的存储空间,因此它们是不同的。而对于静态变量,Java运行且系统在类装载时为这个类的每个静态变量分配一块内存,以后再生成该类的对象时,这些对象将共享同名的静态变量,每个对象对静态变量的改变都会影响到其他对象。
下面的Counter类定义了一个静态变量y。
程序4.5 Counter.java

这里变量x是实例变量,y是静态变量。这意味着在任何时刻不论有多少个Counter类的对象都只有一个y。可能有一个、多个甚至没有Counter类的实例,总是只有一个y。静态变量y在类Counter被装载时就分配了空间。
对于静态变量通常使用类名访问,如下所示:

可以通过实例名访问静态变量,但这种方法可能产生混乱的代码,不推荐通过实例访问静态变量。下面代码说明了原因。

如果忽略了y是静态变量,可能认为c1.y的结果为100,实际上它的值为200,因为c1.y和c2.y引用的是同一个变量。
通常,static与final一起使用来定义类常量。例如,Java类库中的Math类中就定义了两个类常量:

可以通过类名直接使用这些常量。例如,下面语句可输出半径为10的圆的面积:

Java类库的System类中也定义了三个静态变量,分别是in、out和err,它们分别表示标准输入设备(通常是键盘)、标准输出设备(通常是显示器)和标准错误输出设备。
4.4.2 静态方法
静态方法和实例方法的区别是:静态方法属于类,它只能访问静态变量。实例方法可以对当前的实例变量进行操作,也可以对静态变量进行操作。静态方法通常用类名调用,也可以用实例变量调用,实例方法必须由实例来调用。注意,在静态方法中不能使用this和super关键字。
请看下面的程序。
程序4.6 SomeClass.java

这里,display()方法是静态的,可以访问类的静态成员y。但它不能访问x,因为x是实例变量,因此程序中后两行会导致编译器错误。因为x是非静态的,所以它必须通过实例访问。编译器的错误消息为:

通常使用类名访问静态方法。例如:

关于类的静态成员和实例成员总结如下:实例方法可以调用实例方法和静态方法,以及访问实例变量和静态变量。静态方法可以调用静态方法以及访问静态变量。静态方法不能调用实例方法或访问实例变量,因为静态方法和静态变量不属于某个特定对象。静态成员和实例成员的关系如图4-6所示。

图4-6 静态成员与实例成员的关系
在Java类库中也有许多类的方法定义为静态方法,因此可以使用类名调用。例如,Math类中定义的方法都是静态方法,下面是求随机数方法的定义:

从类成员的特性可以看出,可以用static来定义全局变量和全局方法。由于类成员仍然封装在类中,与C和C++相比,Java可以限制全局变量和全局方法的使用范围而防止冲突。
由于可以从类名直接访问静态成员,所以访问静态成员前不需要对它所在的类进行实例化。作为应用程序执行入口点的main()方法必须用static来修饰,也是因为Java运行时系统在开始执行程序前并没有生成类的一个实例,因此只能通过类名来调用main()方法开始执行程序。
4.4.3 单例模式
在Java类的设计中,有时希望一个类在任何时候只能有一个实例,这时可以将该类设计为单例模式(singleton)。要将一个类设计为单例模式,类的构造方法的访问修饰符应声明为private,然后在类中定义一个static方法,在该方法中创建类的对象。
程序4.7 Sun.java

程序输出结果为:

程序中将构造方法定义为private,外部不能直接使用构造方法创建Sun的实例,而必须通过getInstance()方法或INSTANCE常量返回唯一的实例。
4.4.4 递归
递归(recursion)是解决复杂问题的一种常见方法。其基本思想就是把问题逐渐简单化,最后实现问题的求解。例如,求正整数n的阶乘n!,就可以通过递归实现。n!可按递归定义如下:

按照上述定义,要求出n的阶乘,只要先求出n−1的阶乘,然后将其结果乘以n。同理,要求出n−1的阶乘,只要求出n−2的阶乘即可。当n为0时,其阶乘为1。这样计算n!的问题就简化为计算(n−1)!的问题,应用这个思想,n可以一直递减到0。
Java语言支持方法的递归调用。所谓方法的递归调用就是方法自己调用自己。设计算n!的方法为factor(n),则该算法的简单描述如下:

下面是完整的程序。
程序4.8 RecursionDemo.java

程序的运行结果为:

注意:如果n的值超过20,n!的值将超出long型数据的范围,此时将得不到正确的结果。若求较大数的阶乘,可以使用BigInteger类。关于BigInteger类的使用,请参阅8.3.7节“BigInteger和BigDecimal类”。