3.9 关键字static

img

扫码看视频

3.9.1 静态字段

前面我们介绍了,类中的数据成员在该类的每个对象所在的内存中都有自己的一份独立拷贝。但有时候,我们需要在某个类的多个对象间共享同一份数据,比如,在程序中要统计Point对象创建的个数,为此,可以声明一个count字段,并在类型前面用static关键字进行修饰,如代码3.14所示。

img
img

在有参的构造方法中,我们让静态变量count自加一,因为在无参构造方法中调用了有参的构造方法,所以不管Point类的对象如何创建,count都会加一。

如果count字段不是静态成员变量,由于类的每个对象都有自己的数据成员,所以输出结果就应该是:

img

然而实际的输出结果是:

img

从结果中可以看到,pt和pt2对象访问的是同一个静态成员变量count,由于在先前创建pt对象时,count已经自加一,当pt2对象创建时,count再次加一,就变成了2。

为了更好地理解静态成员变量,读者可以在main方法的最后添加下面两句代码:

img

通过pt对象访问count变量,让其自加一,然后通过pt2访问count,将其值输出,结果是:3。

这就是静态数据成员和普通数据成员(我们可以称之为实例数据成员或对象数据成员)的区别,在类中定义的静态数据成员由该类的所有对象所共享,它们单独存放在一个内存区域。静态数据成员并不属于类的对象,而是属于类本身。

在访问静态数据成员时,一般不通过对象访问,虽然这是合法的,但并不是一个好的编码习惯,我们应该始终通过类名来访问类中的静态成员,形式为:类名.静态成员名,如下面的代码所示:

img

细心而又善于思考的读者可能已经发现,我们经常使用的System.out.println,其实out就是System类中的一个静态成员,该成员是一个对象,println是该对象的一个方法。

在C/C++中,可以在一个函数内部定义静态的局部变量,而在Java中,这是不允许的,静态变量只能作为类的静态成员而存在。下面的代码会引发编译错误。

img

提示:为了与静态成员变量以示区分,我们通常称非静态的成员变量为实例变量,代表这是对象所属的变量。

3.9.2 静态方法

既然有静态字段,自然也会有静态方法。在前面章节中我们接触的最多的静态方法就是main方法:

img

声明一个静态方法就是在方法声明时加入static关键字。注意,这个static关键字要放在返回值类型前面。如果main方法声明成:“public void static main(String[] args)”就错了。

在Point类中加入一个静态方法:

img

下面我们来看看如何调用静态方法。

img

两种调用方法均可以,不过Java还是推荐我们使用“类名称.静态方法名(参数列表)”的方式来调用静态方法。

既然可以通过对象来调用静态方法,那么在类的实例方法代码中,使用this变量来调用静态方法也是可以的。反之,在静态方法中,我们无法使用this变量。静态方法和静态字段一样,只属于类本身,与对象没有关系,在静态方法中的this变量并不知道它应该引用哪个对象,因此在静态方法中的this变量没有任何意义。

从生命周期的角度来看,类中的静态字段和静态方法在类加载的时候就已经分配好了内存,而类中的实例变量和实例方法,在创建对象时才完成变量内存分配和方法入口地址分配。程序代码归根结底,都是内存地址之间的访问,因此在实例方法中可以随意访问静态字段和静态方法,因为此时静态成员在内存中都已经存在;而在静态方法中,则不能访问非静态的方法(实例方法),也不能引用非静态的成员变量(实例变量),因为此时非静态的成员还未分配内存。

在什么情况下需要定义静态的数据成员呢?比如该类中的某些数据不依赖于某个特定对象而存在,需要在所有对象间共享,那么就可以将这些数据成员定义成静态的。

在什么情况下需要定义静态方法呢?比如这些方法并不访问类中的实例变量,或者该类根本就没有实例变量,所有需要计算的数据都是由外部通过方法参数传进来的,那么就可以将这些方法定义为类的静态方法。在代码3.4中,我们给出的MyMath类中的方法就非常适合定义成静态方法,该类中的方法主要用于完成与数学相关的计算,而这些方法依赖的数据都是通过方法参数传进来的。

3.9.3 static语句块

构造方法主要用于为类的对象定义初始化状态,在使用new关键字构造对象的时候调用,而static语句块则是在类加载时被调用,用于初始化一些全局的设置。下面我们通过代码3.14,来了解一下static语句块的运行时机。

img

程序运行的结果为:

img

从运行结果中可以看出,Java先执行了static语句块中的代码,输出“Initialize in static statement block.”,然后执行了main方法中的第一行代码,输出“Before create object.”,最后才是创建两个StaticStatementBlock类型的对象。

如果需要在类加载的时候执行某些代码,那么使用static语句块是一个很好的选择。