5.10 static关键字

从前面讲解的概念中,如果使用一个类要分别开辟栈内存及堆内存,在堆内存中要保存对象中的属性,每个对象有每个对象自己的属性,如果现在有些属性希望所有对象共享,则就必须将其声明为static属性。如果一个类中的方法不想由对象,而是由类名称直接调用,则就可以声明为static方法。

5.10.1 使用static声明属性

如果在程序中使用static声明属性的话,则此属性称为全局属性(有些也称为静态属性),那么什么声明成全局属性到底有什么用呢?读者先来观察以下的代码。

【例5.50】观察以下代码

程序执行结果:

以上代码为了读者观察方便,暂时并没有使用private关键字进行封装。以上的程序是一个很简单的应用,程序的运行结果相信读者并不会很难理解。但是在此代码之中也有一些不妥之处。

实际上,如果现在假设此城市不叫“A城”了,而改为了“B城”,而且此类已经产生了5000个对象了,那么意味着,如果要修改这些对象的城市信息,则要把这5000个对象中的城市属性同时修改,要修改5000遍。这样肯定是不行的,那么该怎么解决呢?最好的做法是一次性修改之后,所有对象的城市信息都可以修改成功,所以,此时就可以把城市的属性使用static关键字进行声明,将其变为公共属性。

【例5.51】使用static声明城市属性

程序执行结果:

在程序中,为了读者观察方便,分别加入了“修改之前”和“修改之后”的信息提示,可以发现,只修改了一个对象的城市属性,则全部的对象的城市属性内容全部发生了变化,说明使用static声明的属性是所有对象共享的,上面程序的内存分布图,如图5-21所示。

图5-21 static属性保存的内存分配图

提示

Java中常用的内存区域。

在Java中主要存在4块内存空间,这些内存空间的名称及作用如下:

(1)栈内存空间:保存所有的对象名称(更准确的说是保存了引用的堆内存空间的地址)。

(2)堆内存空间:保存每个对象的具体属性内容。

(3)全局数据区:保存static类型的属性。

(4)全局代码区:保存所有的方法定义。

上面程序已经完成了基本的修改功能,进一步思考:一个类中的公共属性现在由一个对象修改了,这样操作合适吗?很明显不合适,类的公共属性应该由类进行修改是最合适的,因为用户不知道一个类到底有多少个对象产生。所以上面的代码在访问static属性时最好可以由类名称直接调用,那么有时也就把使用static声明的属性称为类属性。

【格式5-6 类属性调用】

所以,以上的代码访问country属性的时候最好使用如下代码。

【例5.52】使用类名称访问static属性

5.10.2 使用static声明方法

static既可以在声明属性的时候使用,也可以用其来声明方法,用它声明的方法有时也被称为“类方法”,可以由类名称直接调用,请看下面的范例。

【例5.53】使用static声明方法

程序执行结果:

在上面的程序中,Person类将所有的属性都进行了封装,所以要想设置此属性就必须使用setter()方法,但是这里方法是使用static声明的,所以可以直接使用类名称调用。

提示

关于static方法定义的说明。

在实际的开发之中,如果要定义一个类中的方法。一般都会以非static方法(普通方法)为主,而定义为static方法一般只有一种情况:此方法不希望通过实例化对象进行调用,那么此时会有两种因素:

(1)本类没有提供有普通属性,这样产生实例化对象没有意义;

(2)本类无法直接进行对象实例化,只能够利用static操作。

对于(2),在本书第6章讲解抽象类时读者会有所体会。

另外,在此处需要说明的是,非static声明的方法可以去调用static声明的属性或方法的。但是static声明的方法是不能调用非static类型声明的属性或方法的。

【例5.54】错误的代码,使用static方法调用非static方法及属性

程序编译时出错:

从程序编译时所报的错来看,static是不能调用任何非static内容的,因为在程序中所有的属性和方法必须在对象开辟堆内存之后才可以调用,而static类型的方法在对象未被实例化时就可以被类名所调用。

5.10.3 static的相关应用

下面为读者讲解两个static的相关应用实例,这两个应用将作为后续的程序讲解的基础。

从前面的讲解可以知道static属性是所有对象共享的,那么就可以使用static属性统计一个类到底产生了多少个实例化对象。

【例5.55】统计一个类产生了多少个实例化对象

程序执行结果:

从之前的定义中读者应该清楚,只要一有实例化对象产生,则一定会调用里面的构造方法,所以在构造方法中将static声明的属性进行count自增,这样就能够计算出一个类中有多少个实例化对象了。

可以使用static为对象进行自动的编名操作,此操作与上面代码类似。

【例5.56】为对象自动进行编名

程序执行结果:

上面代码的运行结果并不是很难理解,如果设置了name属性内容,则使用设置的名字;如果没有设置,则通过static属性可以为类中的属性进行自动的编名操作,这一点的应用在以后的多线程讲解中,读者会有所发现。

5.10.4 理解main()方法

读者可以发现,之前一直使用的main()方法的定义中,一直有static关键字的存在,那么main()方法每个参数的含义是什么呢?下面为读者列出了每个参数的含义:

(1)public:表示此方法可以被外部所调用。

(2)static:表示此方法可以由类名称直接调用。

(3)void:主方法是程序的起点,所以不需要任何的返回值。

(4)main:系统规定好默认调用的方法名称,执行的时候,默认找到main()方法名称。

(5)String args[]:表示的是运行时的参数。参数传递的形式为“Java类名称参数1参数2参数3……”。

【例5.57】验证参数传递,输入的必须是3个参数,否则程序退出。

程序执行结果(输入java StaticDemo08 one two three):

在上面程序中如果运行时没有输入参数,则会提示“输入参数的个数不足3个,程序退出”。程序中的System.exit(1)表示系统退出,只要在exit()方法中设置了一个非零的数字,则系统执行到此语句之后将退出程序。

如果读者在输入参数的时候希望参数中间加有空格,如“Hello World”、“Hello MLDN”等类的信息,则在输入参数的时候直接加上“"”即可进行整体的输入,程序如下所示:

【例5.58】程序执行命令:

程序运行结果:

因为使用了“"”括起来,所以即使中间出现了空格,也按照一个参数进行输入。

提示

使用static定义方法的补充。

在讲解数组与方法时,本书给过读者一个方法的基本格式,而且一直强调,如果一个方法要由主方法直接调用,则必须按以下格式声明:“public static方法的返回值类型方法名称(参数列表){}”。

相信读者应该对此还有印象,那么此定义读者现在应该可以理解了,这是因为主方法是静态方法,而静态方法是不能调用非静态方法的,所以之前的方法声明处才必须加上static关键字。