Java程序设计教程
上QQ阅读APP看书,第一时间看更新

3.3 类的实例成员和静态成员

static称为静态修饰符,它可以修饰类中的字段和方法成员。类中使用static修饰的成员(字段和方法)称为静态成员,未使用static修饰符的则称为实例成员。实例成员属于类的对象,而静态成员则属于类。

3.3.1 Java变量的内存分配机制

在深入讨论类成员之前首先需要了解一下Java的内存分配机制。Java将内存分为栈内存和堆内存两种类型。在方法中定义的一些基本类型变量和对象引用类型变量都在方法的栈内存中分配所需空间,当超出变量的作用域后Java会自动释放掉该变量占用的空间。

堆内存用来存储通过new关键字创建的数组或对象,在堆中分配的内存空间由Java虚拟机的自动垃圾回收器来管理。

Java在堆中创建了一个数组或对象后,同时还会在栈中定义一个特殊的变量,该变量的值为数组或对象变量的堆内存地址(也称为数组或对象的“句柄”),它是数组或对象的引用变量。在程序中只能通过引用变量(栈变量)来实现对堆内存中数组或对象的访问。

需要注意的是,超出变量的作用域后引用变量在栈中占用的内存将自动释放,而存储在堆中的数组或对象值在对应的引用变量被释放后仍会继续存在,直至被Java虚拟机的垃圾回收器销毁。

3.3.2 实例成员

实例成员包括实例字段和实例方法。例如,下列代码声明了一个Circle类。其中,用于表示半径的r和表示高的h是实例字段,用于获取圆柱体体积的getVolume()方法是一个实例方法。

在程序中下列语句创建了两个不同的Circle类对象c1和c2,并为每个对象的r和h字段赋值,而后分别调用它们的getVolume()方法得到了圆的体积值。

1.实例字段

上述代码被执行后,c1和c2各自拥有了用于保存自己成员的,独立的存储空间,不与其他对象共享。其内存分配情况如图3-9所示。

可以看出,c1的相关数据保存在堆内存中以地址A为首地址的区域,而地址A的值又被保存在c1栈内存中。c2的相关数据保存在以地址B为首地址的区域,而地址B的值又被保存在c2的栈内存中。所以,修改c1的半径或高不会改变c2中的任何数据,反之亦然。

图3-9 不同对象的内存分配情况

2.实例方法

当类的字节码文件被加载到内存时,类的实例方法不会立即被分配内存空间,只有当该类的对象创建时系统才会跟随对象为实例方法分配内存空间。需要注意的是,对于实例方法系统为其分配的内存空间只有一个,不会为所有对象各自独立分配内存空间。该类所有不同的对象都共享该实例方法的地址。

3.3.3 静态字段

使用static修饰的字段称为静态字段,也称为静态变量或类变量。静态字段与前面介绍的实例字段相比主要有以下一些不同点。

1)最大的不同就是静态字段是属于类的,可以被所有该类的对象所共享。而实例字段则是属于对象的。所以静态字段只能声明在类中,不能声明在任何一个方法中。

2)静态字段对所有该类的对象而言是一个公共的存储单元,任何一个对象对它的读取都将得到一个相同的值,任何一个对象对它的修改,也会导致全局的变化。

3)静态字段不仅可以通过类的对象进行访问,也可以不必创建类的对象,而直接通过类进行访问。例如:

在外部程序中可以使用如下所示的语句访问静态字段。

【演练3-1】创建一个能表示同底三角形的Triangle类,该类具有表示底的double型字段bottom和表示高的double字段height,具有一个能返回double类型面积值的getArea()方法。要求在主方法中编写测试代码,观察静态字段共享于所有类对象的特点。程序运行结果如图3-10所示。

图3-10 程序运行结果

程序设计步骤如下。

1)问题分析:由于同底三角形的底边是相等的,所以可以将Triangle类的bottom字段设计成静态的,使之共享于所有Triangle类的对象。

2)在Eclipse环境中创建一个名为YL3_1的项目,并创建主类和主方法。在代码编辑窗口中编写如下所示的与主类同级的Triangle类代码。

3)在主方法中编写如下所示的测试代码。

3.3.4 静态方法

静态方法也称为类方法,指类中使用static修饰的方法成员。与静态字段相同,静态方法属于类而不属于任何该类的对象。当类被加载到内存时,系统就会为静态方法分配相应的内存空间,并且会一直保存到程序运行结束。所以,静态方法不仅可以被该类所有对象调用,也可以通过类名来调用。

由于类的所有对象都能共享静态方法,而且静态方法创建时实例字段还没有被创建(实例字段属于对象,随对象的创建而创建),所以其中出现的字段成员也必须为所有该类对象所共享。也就是说,静态方法中只能访问类的静态字段,不能访问实例字段。同样的道理,静态方法中也不能调用本类中其他实例方法。

例如,下列代码表现了在静态方法和实例方法中访问静态字段和实例字段的使用规则。

此外,无论是静态方法还是实例方法,当调用执行时方法中的局部变量才会被分配内存空间。方法执行完毕后,局部变量会立即被释放。如果在方法执行完毕前再次被调用,则系统会重新为局部变量分配新的内存空间,使每个执行进程互不干扰。

3.3.5 静态初始化器

静态初始化器是由static修饰的一对大括号括起来的语句块。它的作用与构造方法相似,都是用来初始化的,其语法格式如下:

静态初始化器与构造方法有以下几个不同点。

1)构造方法是用来初始化类对象的,而静态初始化器则是对类本身进行初始化,也就是为静态字段赋以初始值。

2)构造方法是在使用new关键字创建新对象时由系统自动调用的,而静态初始化器一般不能由程序代码调用,它在所属类被加载到内存时由系统自动调用执行。也就是说,构造方法由程序代码启动,由系统自动调用;静态初始化器则在类被加载时由系统调用。

3)代码中使用new关键字创建了多少个对象,构造方法就会被调用多少次。而静态初始化器则只有在类被加载到内存时被调用一次,与创建多少个对象无关。

4)不同于构造方法,静态初始化器不能理解成一种特殊的方法。它没有方法名,也没有返回值和参数。

5)与构造方法的重载相似,在一个类中可以包含有一个或多个静态初始化器。但不同于构造方法重载的是,在类被加载时这些静态初始化器会依次被调用执行。

【演练3-2】使用静态初始化器配合静态字段和构造方法实现Employee类中员工id(编号)实例字段的自动延续生成,也就是说每当使用new关键字创建一个新员工对象时,系统会自动为其分配一个自动延续的编号值(假设,已使用的最后一个员工编号为18000)。程序运行结果如图3-11所示。

图3-11 程序运行结果

程序设计步骤如下。

1)问题分析:在Employee类中可以使用一个静态字段lastID保存当前已使用的最后一个编号值,static初始化器中的代码负责为lastID赋以初始值(已使用的最后一个员工编号);将id字段设置为private类型,使其只能通过类的构造方法进行赋值。在构造方法中使id的值等于lastID值+1,即可实现员工编号的自动生成。

2)在Eclipse环境中创建一个Java项目YL3_2,并创建主类和主方法。

3)在代码窗口中按如下所示创建Employee类,并编写主方法中的测试代码。