创建和使用对象
理解什么是对象
典型的Java程序会创建大量对象,这些对象通过调用方法进行交互。通过这种对象交互,程序能够执行各种任务,例如实现图形用户界面、运行动画,或在网络上发送和接收信息。当对象完成其创建时的任务后,其资源将被回收供其他对象使用。
这是一个名为 CreateObjectDemo 的小程序,它创建了三个对象:一个 Point 对象和两个 Rectangle 对象。您需要所有三个源文件才能编译此程序。
public class CreateObjectDemo {
public static void main(String[] args) {
// Declare and create a point object and two rectangle objects.
Point originOne = new Point(23, 94);
Rectangle rectOne = new Rectangle(originOne, 100, 200);
Rectangle rectTwo = new Rectangle(50, 100);
// display rectOne's width, height, and area
IO.println("Width of rectOne: " + rectOne.width);
IO.println("Height of rectOne: " + rectOne.height);
IO.println("Area of rectOne: " + rectOne.getArea());
// set rectTwo's position
rectTwo.origin = originOne;
// display rectTwo's position
IO.println("X position of rectTwo: " + rectTwo.origin.x);
IO.println("Y position of rectTwo: " + rectTwo.origin.y);
// move rectTwo and display its new position
rectTwo.move(40, 72);
IO.println("X position of rectTwo: " + rectTwo.origin.x);
IO.println("Y position of rectTwo: " + rectTwo.origin.y);
}
}
Point 类
public class Point {
public int x = 0;
public int y = 0;
public Point(int x, int y) {
this.x = x;
this.y = y;
}
}
Rectangle 类
public class Rectangle {
public int width = 0;
public int height = 0;
public Point origin;
// four constructors
public Rectangle() {
origin = new Point(0, 0);
}
public Rectangle(Point p) {
origin = p;
}
public Rectangle(int w, int h) {
origin = new Point(0, 0);
width = w;
height = h;
}
public Rectangle(Point p, int w, int h) {
origin = p;
width = w;
height = h;
}
// a method for moving the rectangle
public void move(int x, int y) {
origin.x = x;
origin.y = y;
}
// a method for computing the area of the rectangle
public int getArea() {
return width * height;
}
}
该程序用于创建、操作和显示各类对象的相关信息。输出结果如下:
Width of rectOne: 100
Height of rectOne: 200
Area of rectOne: 20000
X position of rectTwo: 23
Y position of rectTwo: 94
X position of rectTwo: 40
Y position of rectTwo: 72
以下三个部分将通过上述示例阐述对象在程序中的生命周期。通过这些内容,您将学会如何编写代码在自己的程序中创建和使用对象,同时了解系统如何在对象生命周期结束时进行清理工作。
创建对象
众所周知,类为对象提供了蓝图;你从类中创建对象。以下摘自 CreateObjectDemo 程序的每条语句都会创建一个对象并将其赋值给变量:
Point originOne = new Point(23, 94);
Rectangle rectOne = new Rectangle(originOne, 100, 200);
Rectangle rectTwo = new Rectangle(50, 100);
第一行创建了 Point 类的对象,第二行和第三行分别创建了 Rectangle 类的对象。
每条陈述都包含三个部分(详见下文):
- Declaration: 粗体标注的代码均为变量声明,用于将变量名与对象类型关联。
- Instantiation: new 关键字是创建对象的 Java 运算符。
- Initialization: new 运算符之后紧跟构造函数调用,该构造函数用于初始化新对象。
声明一个变量指向一个对象
之前你学过,要声明一个变量,你写:
type name;
此声明告知编译器,您将使用名称name来引用类型为type的数据。对于原始变量,此声明还会为该变量预留适当的内存空间。
你也可以在单独一行声明引用变量。例如:
Point originOne;
若像这样声明 originOne,其值将保持未确定状态,直到实际创建对象并赋值给它。仅声明引用变量并不会创建对象。为此,你需要使用 new 运算符,具体方法将在下一节说明。在代码中使用 originOne 之前,必须先为其赋值。否则将引发编译器错误。
此状态下的变量当前未引用任何对象。
实例化一个类
new 运算符通过为新对象分配内存并返回该内存的引用来实例化类。new 运算符还会调用对象构造函数。
注意:“instantiating a class”与“creating an object.”是同义的。当你创建对象时,实际上是在创建类的“实例”,因此也实现了对类的“实例化”。
new 运算符需要一个后置参数:对构造函数的调用。构造函数的名称即为待实例化的类名。
new 操作符返回其创建的对象的引用。该引用通常会被赋值给相应类型的变量,例如:
Point originOne = new Point(23, 94);
new 操作符返回的引用不必赋值给变量,也可直接用于表达式中。例如:
int height = new Rectangle().height;
这个语句将在下一节中进行讨论。
初始化新对象
这里是 Point 类的代码:
public class Point {
public int x = 0;
public int y = 0;
//constructor
public Point(int a, int b) {
x = a;
y = b;
}
}
该类包含一个构造函数。构造函数的声明与类名相同且无返回类型,因此可通过此特征识别。Point 类的构造函数接受两个整数参数,代码声明为 Point(int a, int b)。以下语句为这些参数赋值23和94:
Point originOne = new Point(23, 94);
执行此语句的结果可通过下图说明:
以下是 Rectangle 类的代码,其中包含四个构造函数:
public class Rectangle {
public int width = 0;
public int height = 0;
public Point origin;
// four constructors
public Rectangle() {
origin = new Point(0, 0);
}
public Rectangle(Point p) {
origin = p;
}
public Rectangle(int w, int h) {
origin = new Point(0, 0);
width = w;
height = h;
}
public Rectangle(Point p, int w, int h) {
origin = p;
width = w;
height = h;
}
// a method for moving the rectangle
public void move(int x, int y) {
origin.x = x;
origin.y = y;
}
// a method for computing the area of the rectangle
public int getArea() {
return width * height;
}
}
每个构造函数都允许您为矩形的 origin、width和height提供初始值,既可使用基本类型,也可使用引用类型。如果一个类有多个构造函数,它们必须具有不同的签名。Java编译器根据参数的数量和类型来区分构造函数。当Java编译器遇到以下代码时,它会知道调用 Rectangle 类中需要一个 Point 参数后跟两个整数参数的构造函数:
Rectangle rectOne = new Rectangle(originOne, 100, 200);
这调用了矩形的一个构造函数,该函数将 origin 初始化为 originOne 。同时,构造函数将宽度设置为100,高度设置为200。现在存在两个指向同一个 Point对象的引用——一个对象可以拥有多个引用,如下图所示:
以下代码行调用了 Rectangle 类构造函数,该构造函数需要两个整数参数,用于提供宽度和高度的初始值。若检查构造函数内部的代码,你会发现它创建了一个新的 Point 对象,其x和y值初始化为0:
Rectangle rectTwo = new Rectangle(50, 100);
以下语句中使用的矩形构造函数不接受任何参数,因此称为无参数构造函数:
Rectangle rect = new Rectangle();
所有类至少包含一个构造函数。若类未显式声明构造函数,Java编译器会自动提供一个无参构造函数,称为默认构造函数。该默认构造函数会调用父类的无参构造函数;若该类无父类,则调用 Object 类的构造函数。若父类未定义构造函数(Object类确实存在),编译器将拒绝该程序。
使用对象
创建对象后,您可能需要使用它来完成某些操作。这可能包括:获取某个字段的值、修改某个字段,或是调用某个方法来执行特定操作。
引用对象的字段
对象字段通过其名称访问。必须使用明确无歧义的名称。
在类内部,字段可使用简单名称。例如,我们可以在 Rectangle 类中添加语句以打印 width 和 height:
IO.println("Width and height are: " + width + ", " + height);
在此情况下,width 和 height 是简单名称。
位于对象类外部的代码必须使用对象引用或表达式,后跟点(.)运算符,再接一个简单字段名,例如:
objectReferenace.fieldName
例如,CreateObjectDemo 类中的代码位于 Rectangle 类代码之外。
因此,要引用名为 rectOne 的 Rectangle 对象中的原点、宽度和高度字段,CreateObjectDemo 类必须分别使用名称 rectOne.origin、rectOne.width 和 rectOne.height。程序使用其中两个名称来显示 rectOne 的宽度和高度:
IO.println("Width of rectOne: " + rectOne.width);
IO.println("Height of rectOne: " + rectOne.height);
尝试在 CreateObjectDemo 类的代码中使用 width 和 height 这类简单名称是没有意义的——这些字段仅存在于对象内部——且会导致编译器报错。
随后,程序使用类似代码显示 rectTwo 的相关信息。同类型的对象各自拥有相同的实例字段副本。因此,每个矩形对象都包含名为origin、width和height的字段。当通过对象引用访问实例字段时,你实际引用的是该特定对象的字段。在 CreateObjectDemo 程序中,两个对象 rectOne 和 rectTwo 各自拥有不同的 origin、width 和 height 字段。
要访问字段,您可以使用对象的命名引用(如前例所示),也可以使用任何返回对象引用的表达式。请记住,new 运算符会返回对象的引用。因此,您可以使用 new 返回的值来访问新对象的字段:
int height = new Rectangle().height;
该语句创建了一个新的 Rectangle 对象,并立即获取其 height。本质上,该语句计算了 Rectangle 对象的默认高度。请注意,执行此语句后,程序不再持有创建的 Rectangle 对象的引用,因为程序从未将该引用存储在任何位置。该对象处于无引用状态,其资源可由Java虚拟机回收利用。
调用对象的方法
您还可通过对象引用调用对象的方法。在对象引用后添加方法的简单名称,中间用点运算符(.)连接。同时需在括号内提供方法的参数。若方法无需参数,则使用空括号。
objectReference.methodName(argumentList);
或者
objectReference.methodName();
Rectangle 类包含两个方法: getArea() 用于计算矩形面积,move() 用于改变矩形原点位置。以下是调用这两个方法的 CreateObjectDemo代码:
IO.println("Area of rectOne: " + rectOne.getArea());
...
rectTwo.move(40, 72);
第一条语句调用 rectOne 的 getArea() 方法并显示结果。第二行移动 rectTwo,因为 move() 方法为对象的 origin.x 和 origin.y 赋予了新值。
与实例字段类似,objectReference 必须是对象的引用。你可以使用变量名,也可以使用任何返回对象引用的表达式。new 运算符返回对象引用,因此你可以使用 new 返回的值来调用新对象的方法:
new Rectangle(100, 50).getArea()
表达式 new Rectangle(100, 50) 返回一个对象引用,该引用指向一个 Rectangle 对象。如所示,您可以使用点表示法调用新矩形的 getArea() 方法来计算新矩形的面积。
某些方法(如 getArea())会返回一个值。对于返回值的方法,您可以在表达式中使用方法调用。您可以将返回值赋给变量,用其进行决策,或控制循环。以下代码将 getArea() 返回的值赋给变量 areaOfRectangle:
int areaOfRectangle = new Rectangle(100, 50).getArea();
在此情况下,调用 getArea() 的对象是构造函数返回的矩形。
垃圾回收器
某些面向对象的语言要求你跟踪所有创建的对象,并在不再需要时显式销毁它们。显式管理内存既繁琐又容易出错。Java平台允许创建任意数量的对象(当然受系统承载能力的限制),且无需担心销毁问题。当Java运行时环境判定对象不再被使用时,会自动将其删除。这一过程称为垃圾回收。
当对象不再被任何引用时,该对象即可进入垃圾回收阶段。变量持有的引用通常会在变量作用域结束时自动释放。或者,您也可以通过将变量赋值为特殊值 null 来显式释放对象引用。请注意,程序中可能存在多个指向同一对象的引用;只有当所有引用都被释放后,该对象才具备进入垃圾回收的资格。
Java运行时环境配备了垃圾回收器,该机制会定期释放不再被引用的对象所占用的内存。当垃圾回收器判断时机成熟时,便会自动执行清理工作。