
4.2 为对象定义类
面向对象编程就是使用对象进行程序设计。对象(object)代表现实世界中可以明确标识的一个实体。例如,一名学生、一部手机、一辆汽车、一个矩形、一个按钮甚至一笔贷款都可以看作是一个对象。每个对象都有自己独特的标识、状态和行为。
- 对象的状态(state),也称为特征(property)或属性(attribute),是由具有当前值的数据域来表示的。例如,员工对象具有数据域name和age,它们标识员工的属性。一个矩形对象具有数据域width和height,它们是描述矩形的属性。
- 对象的行为(behavior),也称动作(action)是由方法定义的。调用对象的一个方法就是要求对象完成一个动作。例如,可以为矩形对象定义一个名为getArea()和getPerimeter()方法,矩形对象可以调用getArea()返回矩形的面积,调用getPerimeter()方法返回矩形的周长。还可以在矩形对象上定义setWidth()、setHeight()等方法。
使用一个通用类定义同一类型的对象。类是一个模板或蓝图,用来定义对象的数据域是什么以及方法是做什么的。一个对象是类的一个实例(instance)。可以从一个类中创建多个实例。创建实例的过程称为实例化(instantiation)。对象和实例经常是可以互换的。类和对象之间的关系类似于汽车图纸和一辆汽车之间的关系。可以根据汽车图纸生产任意多的汽车。
类是组成Java程序的基本要素,封装了一类对象的状态和行为,是这一类对象的原形。定义一个新的类,就创建了一种新的数据类型;实例化一个类,就得到一个对象。
4.2.1 类的定义
可以说,Java程序一切都是对象。要想得到对象,首先必须定义类(也可以使用事先定义好的类),然后创建对象。
一个类的定义包括两个部分:类声明和类体的定义。
1.类声明
类声明的一般格式为:

说明:
(1)类的修饰符。
类的访问修饰符可以是public或缺省。若类用public修饰,则该类称为公共类,公共类可被任何包中的类使用。若不加public修饰符,类只能被同一包中的其他类使用。如果类使用abstract修饰符,则该类为抽象类;若用final修饰符,则该类为最终类。
(2)extends SuperClass。
如果一个类要继承某个类需使用extends指明该类的父类,SuperClass为父类名,即定义该类继承了哪个类。如果定义类的时候没有指明所继承的父类,那么它自动继承Object类。
(3)implements InterfaceNameList。
如果定义的类需要实现接口,则使用implements InterfaceNameList选项。一个类可以实现多个接口,若实现多个接口,接口名中间用逗号分开。
(4)类体。
类声明结束后是一对大括号,大括号括起来的部分称为类体(class body)。类体中通常包含三部分内容:构造方法、成员变量和成员方法。构造方法用于创建类实例,成员变量定义对象状态,成员方法定义对象行为。
下面代码定义一个名为Employee的类。
程序4.1 Employee.java

程序定义了一个Employee类表示员工,在该类中定义了三个变量,即name、age和salary,分别表示员工的姓名、年龄和工资。类中还定义了sayHello()方法和computeSalary()方法。该类定义一个无参数的构造方法。编译该程序可得到一个Employee.class类文件。
2.成员变量的定义
成员变量的声明格式为:

说明:
(1)变量的访问修饰符。
public|protected|private为变量的访问修饰符。用public修饰的变量为公共变量,公共变量可以被任何方法访问;用protected修饰的变量称为保护变量,保护变量可以被同一个包中的类或子类访问;没有使用访问修饰符,该变量只能被同一个包中的类访问;用private修饰的变量称为私有变量,私有变量只能被同一个类的方法访问。
(2)实例变量和静态变量。
如果变量用static修饰,则该变量称为静态变量,又称为类变量。没有用static修饰的变量称为实例变量。
(3)使用final修饰的变量叫作最终变量,也称为标识符常量。常量可以在声明时赋初值,也可以在后面赋初值,一旦为其赋值,就不能再改变了。
3.构造方法的定义
构造方法也叫构造器(constructor),是类的一种特殊方法。Java中的每个类都有构造方法,它的作用是创建对象并初始化对象的状态。下面代码定义一个不带参数的构造方法。

用户也可以定义带参数的构造方法。4.3.4节将详细介绍构造方法。
4.成员方法的定义
类体中另一个重要的成分是成员方法。该方法用来实现对象的动态特征,也是在类的对象上可完成的操作。Java的方法与C/C++中的函数类似,是一段用来完成某种操作的程序片段。但与C/C++语言不同的是,Java的方法必须定义在类体内,不能定义在类体外。
成员方法的定义包括方法的声明和方法体的定义,一般格式如下:

说明:
(1)方法返回值与方法名。
methodName为方法名,每个方法都要有一个方法名。returnType为方法的返回值类型,返回值类型可以是任何数据类型(包括基本数据类型和引用数据类型)。若一个方法没有返回值,则returnType应为全局变量。例如:

(2)方法参数。
在方法名的后面是一对括号,括号内是方法的参数列表,声明格式为:

type为参数的类型;paramName为参数名,这里的参数称为形式参数。方法可以没有参数,也可以有一个或多个参数。如果有多个参数,参数的声明中间用逗号分开。例如:

该方法声明了两个参数,在调用方法时必须提供相应的实际参数。
(3)访问修饰符。
public、protected和private为方法的访问修饰符。private方法只能在同一个类中被调用,protected方法可以在同一个类、同一个包中的类以及子类中被调用,而用public修饰的方法可以在任何类中调用。一个方法如果缺省访问修饰符,则称包可访问的,即可以被同一个类的方法访问和同一个包中的类访问。
(4)实例方法和静态方法。
没有用static修饰的方法称为实例方法,用static修饰的方法称为静态方法。关于static修饰符的使用,请参阅4.4节“静态变量和静态方法”。
(5)final和abstract方法。
用final修饰的方法称为最终方法,最终方法不能被覆盖。方法的覆盖与继承有关。用abstract修饰的方法称为抽象方法。
提示:在类体中,经常需要定义类的构造方法,构造方法用于创建新的对象。有些专家认为构造方法不是方法,它们也不是类的成员。
4.2.2 对象的使用
有了Employee类,就可以创建该类的实例,然后访问它的成员和调用它的方法完成有关操作,如调用computeSalary()方法计算员工的工资等。下面程序使用Employee类创建一个对象并访问它的变量和方法。
程序4.2 EmployeeDemo.java

程序运行结果为:

1.创建对象
为了使用对象,一般还要声明一个对象名,即声明对象的引用(reference),然后使用new运算符调用类的构造方法创建对象。对象声明格式如下:

TypeName为引用类型名,可以是类名也可以是接口名;objectName是对象名或引用名或实例名。例如,在EmployeeDemo类中的语句:

上述语句执行后的效果如图4-2所示。代码声明了一个Employee类的引用,实际上employee只保存着实际对象的内存地址。第二个语句执行后,程序创建了一个实际对象。这里使用new运算符调用Employee类的构造方法并把对该对象的引用赋给employee。创建一个对象也叫实例化,对象也称为类的一个实例。

图4-2 employee对象示意图
若要声明多个同类型的对象名,可用逗号分开。

也可以将对象的声明和创建对象使用一个语句完成。

若对象仅在创建处使用,也可以不声明引用名。例如,下面语句直接创建一个Employee对象,然后调用其sayHello()方法。

2.对象的使用
创建了一个对象引用后,就可以通过该引用来操作对象。使用对象主要是使用点号运算符(.)通过对象引用访问对象的成员变量和调用对象的成员方法。例如,在EmployeeDemo.java程序中,使用下面语句访问对象employee的成员变量name和age,调用对象employee的成员方法sayHello()。

3.对象引用赋值
对于基本数据类型的变量赋值,是将变量的值的一个副本赋给另一个变量。例如:

对于对象的赋值是将对象的引用(地址)赋值给变量。

上面的赋值语句执行结果是把emp1的引用赋值给了emp2,即emp1和emp2的地址相同,也就是emp1和emp2指向同一个对象,如图4-3所示。

图4-3 对象的引用赋值
由于引用变量emp1和emp2指向同一个对象,这时如果将emp1对象的name成员变量值修改为“李明”,那么输出emp2的name成员变量值也是“李明”,代码如下:

4.2.3 理解栈与堆
当Java程序运行时,JVM需要给数据分配内存空间。内存空间在逻辑上分为栈(stack)与堆(heap)两种结构。理解栈与堆对理解Java程序运行机制很有帮助。当Java程序运行时,被调用方法参数和方法中定义的局部变量都存储在内存栈中,当程序使用new运算符创建对象时,JVM将在堆中分配内存。
假设已经定义了Employee类,在main()方法中创建该类的一个对象,代码如下:

当main()方法执行时,JVM首先创建一个活动记录(activation record),它包括方法的参数args、方法中声明的局部变量employee,将其存储在栈中。在main()方法中创建了Employee对象,则该对象在堆中分配内存,上述代码执行后的栈与堆的情况如图4-4所示。如果在main()方法中调用了另一个方法,将创建另一个活动记录,并将其存入栈中。
当方法调用结束返回时,活动记录将从栈中弹出,也叫出栈。Java运行时系统将释放为活动记录中变量分配的空间。

图4-4 程序运行时栈和堆示意图
4.2.4 用UML图表示类
UML(unified modeling language)称为统一建模语言,是一种面向对象的建模语言,运用统一、标准化的标记和定义实现对软件系统进行面向对象的描述和建模。
在UML中可以用类图描述一个类。图4-5所示是Employee类的类图,它用长方形表示,一般包含三个部分:上面是类名;中间是成员变量清单;下面是构造方法和普通方法清单。有时为了简化类的表示,可能省略后两部分,只保留类名部分。
在一个UML类图中,可以包含有关成员访问权限的信息。public成员的前面加一个“+”,private成员前加“−”,protected成员前加“#”,不加任何前缀的成员被看作具有默认访问级别。关于类成员的访问权限将在7.2节讨论。
从图4-5可以看到,Employee类包含三个私有成员变量、两个构造方法和三个普通方法。在UML图中,成员变量和类型之间用冒号分隔。方法的参数列表放在括号中,参数需指定名称和类型,它的返回值类型写在一个冒号后面。

图4-5 Employee类的类图