Các đối tượng (Objects)
Khái niệm về đối tượng.
Một cách điển hình trong chương trình Java tạo nhiều đối tượng,
như bạn biết, tương tác bằng gọi các phương thức. Mặc dù tương tác với đối tượng,
trong chương trình cần quan tâm đến đầu ra của nó, như triển khai một GUI,
chạy một kịch bản, gửi và nhận thông tin của mạng. Một đối tượng có thể hoàn
thành công việc mà nó được tạo ra, tài nguyên nó được tái sử dụng với các đối
tượng khác.
Chương trình CreateObjectDemo, tạo ba đối tượng: một
đối tượng Point và hai đối tượng Rectangle. Bạn sẽ cần ba tệp để biên dịch
chương trình này.
Tệp CreateObjectDemo có nội dung:
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
System.out.println("Width of rectOne: " + rectOne.width);
System.out.println("Height of rectOne: " + rectOne.height);
System.out.println("Area of rectOne: " + rectOne.getArea());
// set rectTwo's position
rectTwo.origin = originOne;
// display rectTwo's position
System.out.println("X Position of rectTwo: " + rectTwo.origin.x);
System.out.println("Y Position of rectTwo: " + rectTwo.origin.y);
// move rectTwo and display its new position
rectTwo.move(40, 72);
System.out.println("X Position of rectTwo: " + rectTwo.origin.x);
System.out.println("Y Position of rectTwo: " + rectTwo.origin.y);
}
}
Lớp Point có nội dung:
public class Point {
public int x = 0;
public int y = 0;
// a constructor!
public Point(int a, int b) {
x = a;
y = b;
}
}
Lớp Rectangle có nội dung:
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;
}
}
Chương trình tạo, thao tác, và hiển thị thông tin của các đối
tượng khác nhau. Đầu ra của chương trình là:
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
Ba phần sau sử dụng ví dụ ở phía trên mô tả vòng đời của một
đối tượng trong một chương trình. Từ chúng, bạn sẽ học được cách viết mã nguồn
như tạo và sử dụng các đối tượng trong chương trình của bạn. Bạn cũng sẽ học được
làm thế nào làm sạch hệ thống trước một đối tượng khi vòng đời của nó kết thúc.
Tạo đối tượng.
Tạo đối tượng như thế nào:
Như bạn đã biết, một lớp cung cấp bản thiết kế
(blueprint) cho các đối tượng; bạn tạo một đối tượng từ một lớp. Mỗi một
câu lệnh sau đây, được lấy từ chương trình CreateObjectDemo ở trên tạo một
đối tượng và gán nó cho một biến:
Point originOne = new Point(23, 94);
Rectangle rectOne = new Rectangle(originOne, 100, 200);
Rectangle rectTwo = new Rectangle(50, 100);
Mỗi câu lệnh có ba thành phần (bàn chi tiết bên dưới):Dòng đầu tiên tạo một đối tượng của lớp Point, và dòng thứ
hai và thứ ba tạo một đối tượng của lớp Rectangle.
·
Khai báo (Declaration): Vế trái là tất cả
các biến được khai báo có liên kết với một tên biến và một kiểu đối tượng.
·
Cụ thể hóa (Instantiation): Từ khóa new
là toán tử dùng để tạo đối tượng.
·
Khởi tạo (Initialization): Toán tử new
cho phép gọi một hàm khởi tạo, gán cho đối tượng mới được tạo ra.
Khai báo một biến tham chiếu đến một đối tượng.
Ở phần trước, bạn đã học khai báo một biến theo cú pháp:
type name;
Khai báo cho trình biên dịch bạn sẽ sử dụng tên để
tham chiếu đến dữ liệu type. Với một biến nguyên thủy, khai báo này cũng dành số
lượng bộ nhớ thích hợp cho biến.
Bạn có thể khai báo biến tham chiếu như thế. Ví dụ:
Point originOne;
Nếu bạn khai báo biến originOne như cách này, giá trị
của nó sẽ không xác định cho đến khi một đối tượng thực sự được tạo ra và gán
cho nó. Khai báo đơn giản một biến tham chiếu không tạo đối tượng. Điều đó, bạn
cần sử dụng toán tử new, sẽ được mô tả trong phần tiếp theo. Bạn phải gán một đối
tượng cho originOne trước khi sử dụng nó trong mã nguồn của bạn. Nếu
không trình biên dịch sẽ bị lỗi.
Một biến trong trạng thái này, biến hiện tại không tham chiếu đến một đối tượng nào, có thể minh họa điều đó (tên biến, là originOne, cộng với một tham chiếu không xác định).
Khai báo biến tham chiếu mà không gán đối tượng
Khởi tạo một lớp.
Toán tử new khởi tạo một lớp bằng cách cấp vùng nhớ cho đối
tượng mới được tạo ra và trả về một tham chiếu đến vùng nhớ đó. Toán tử new
cũng gọi hàm tạo đối tượng.
Ghi nhớ: Câu “khởi tạo một lớp” có nghĩa như
là “tạo một đối tượng”. Khi bạn tạo một đối tượng, bạn đang tạo ra một “cái cụ
thể” của một lớp, do đó cũng “khởi tạo một lớp”.
Toán tử new là một từ đơn, đặt trước đối số: một lời gọi hàm
khởi tạo. Tên của hàm khởi tạo cung cấp tên của lớp khởi tạo.
Toán tử new trả về một tham chiếu đến đối tượng được tạo ra.
Tham chiếu này thường được gán cho biến có kiểu phù hợp như:
Point originOne = new Point(23, 94);
Khởi tạo một đối tượng.
Mã nguồn sau là lớp Point:
public class Point {
public int x = 0;
public int y = 0;
//constructor
public Point(int a, int b) {
x = a;
y = b;
}
}
Lớp này bao gồm một hàm khởi tạo đơn. Bạn có thể nhận ra một
hàm khởi tạo bởi vì nó được khai báo có tên giống với tên lớp và không có kiểu
trả về. Hàm khởi tạo trong lớp Point có hai đối số nguyên, được khai báo trong
đoạn code (int a, int b). Câu lệnh cung cấp số 23 và số 94 cho đối số:
Point originOne = new Point(23, 94);
Kết quả của thực thi là câu lệnh có thể minh họa trong dưới.
Minh họa thực thi câu lệnh khởi tạo đối tượng.
Mã nguồn của lớp Rectangle, bao gồm bốn hàm khởi dựng:
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;
}
}
Rectangle rectOne = new Rectangle(originOne, 100, 200);
Gọi một hàm khởi tạo Rectangle tạo origin thành originOne. Theo đó, hàm khởi tạo sẽ thiết lập width là 100 và height là 200. Bây giờ, có hai đối tượng Point tham chiếu đến cùng một đối tượng, một đối tượng có thể có nhiều tham chiếu đến nó, biểu hiện trong dưới.
Đối tượng Point có hai tham chiếu đến.
Theo dòng mã nguồn khởi tạo Rectangle yêu cầu hai đối
đố, cung giastrij cho width và height. Nếu bạn để ý dòng lệnh trong hàm khởi tạo,
bạn sẽ nhìn thấy tạo đối tượng Point có giá trị x và giá trị y được khởi
gán bằng 0.
Rectangle rectTwo = new Rectangle(50, 100);
Hàm khởi dựng Rectangle sử dụng dòng lệnh không có bất
kỳ đối số nào, nó được gọi là hàm khởi dựng không đối số:
Rectangle rect = new Rectangle();
Tất các các lớp có ít nhất một hàm khởi tạo. Nếu lớp đó
không có hàm này rõ ràng, trình biên dịch Java sẽ tự động cung cấp một hàm khởi
tạo không đối số, được gọi là hàm khởi tạo mặc định (default contructor). Hàm
khởi tạo mặc định này sẽ gọi hàm khởi tạo không tham số của lớp cha, hoặc đối
tượng khởi tạo Object nếu nó không có lớp cha. Nếu lớp cha không có hàm
khởi tạo (Lớp Object thì có một hàm), trình biên dịch sẽ từ chối chương
trình.
Sử dụng đối tượng.
Khi bạn tạo ra một đối tượng, bạn có thể muốn sử dụng nó vào
một vài việc. Bạn có thể cần sử dụng giá trị của các thuộc tính, thay đổi các
thuộc tính, hoặc gọi các phương thức để thực thi một hành động nào đó.
Thuộc tính đối tượng tham chiếu.
Các thuộc tính đối tượng được truy cập bởi tên của chúng. Bạn
phải sử dụng tên rõ ràng.
Bạn có thể sử dụng một tên đơn giản cho một thuộc tính bên
trong một lớp. Ví dụ, chúng ta có thể thêm một lệnh vào trong lớp Rectangle
để in ra width và height:
System.out.println("Width and height are: " + width + ", " + height);
Trong trường hợp này, width và heigh là những
tên đơn.
Mã nguồn bên ngoài lớp của đối tượng đó phải sử dụng một đối
tượng tham chiếu hoặc một biểu thức theo sau đó là toán tử dấu chấm (.), theo
sau đó nữa là tên thuộc tính đơn như ví dụ:
objectReference.fieldName
Ví dụ, mã nguồn của CreateObjectDemo là bên ngoài lớp
Rectangle. Do vậy tham chiếu đến các thuộc tinh origin, width,
và height của đối tượng rectOne của lớp Rectangle, lớp CreateObjectDemo
phải sử dụng các tên: rectOne.origin, rectOne.width, rectOne.height
tương ứng. Chương trình sử dụng hai trong số các tên này để hiển thị width và
height của đối tượng rectOne:
System.out.println("Width of rectOne: " + rectOne.width);
System.out.println("Height of rectOne: " + rectOne.height);
Cố gắng sử dụng tên width và height trong mã
nguồn của lớp CreateObjectDemo không có nghĩa, các trường này chỉ tồn tại
trong một đối tượng, và sẽ trả về một kết quả biên dịch lỗi.
Sau này, chương trình sử dụng tương tự mã nguồn hiển thị
thông tin về recTwo. Các đối tượng cùng loại có bản sao của riêng chúng
cuẩ các thuộc tính giống nhau. Như vậy, mỗi đối tượng Rectangle có các
thuộc tính origin, width, và height. Khi bạn truy cập vào
một thuộc tính cụ thể của đối tượng tham chiếu, bạn tham chiếu đến thuộc tính của
đối tượng. Hai đối tượng rectOne và rectTwo trong chương trình CreateObjectDemo
có các thuộc tính origin, width, và height khác nhau.
Để truy cập vào một thuộc tính, bạn có thể sử dụng tên được
tham chiếu cho một đối tượng, giống như trong ví dụ trước, hoặc bạn sử dụng một
biểu thức bất kỳ trả về một đối tượng tham chiếu. Gọi lại toán tử new trả
về tham chiếu của một đối tượng. Do vậy bạn có thể sử dụng giá trị trả về từ truy vâp vào các thuộc tính đối tượng mới:
int height = new Rectangle().height;
Câu lệnh này tạo một đối tượng Rectangle mới và lập tức
lấy chiều cao height. Bản chất câu lệnh tính toán giá trị mặc định chiều
cao height của một Rectangle. Lưu ý rằng, sau khi câu lệnh này được
thực thi xong, chương trình không còn tham chiếu đến đối tượng Rectangle
đã tạo, bởi vì chương trình không bao giờ lưu trữ tham chiếu ở bất kỳ đâu. Đối
tượng không được tham chiếu, tài nguyên đó được giải phóng và được tái sử dụng
bởi máy ảo Java (Java Vitual Machine).
Gọi các phương thức của một đối tượng.
Bạn cũng có thể sử dụng một đối tượng tham chiếu để gọi các
phương thức của đối tượng. Bạn nối tên đơn cho phương thức để tham chiếu đối tượng,
với sử dụng một toán tử chấm (.). Do vậy, bạn cung cấp, bên trong dấu ngoặc
đơn, bất kỳ một đối số nào cho phương thức. Nếu phương thức không yêu cầu một đối
số nào, sử dụng dấu mở đóng ngoặc, bên trong để trống.
objectReference.methodName(argumentList);
Hoặc:
objectReference.methodName();
Lớp Rectangle có hai phương thức: getArea() để
tính toán diện tích và move() để thay đổi hình chữ nhật ban đầu. Ở đây,
trong mã nguồn CreateObjectDemo đã gọi cả hai phương thức:
System.out.println("Area of rectOne: " + rectOne.getArea());
...
rectTwo.move(40, 72);
Cũng giống như các thuộc tính cụ thể, objectReference
phải được tham chiếu đến một đối tượng. Bạn có thể sử dụng bên biến, nhưng bạn
cũng có thể sử dụng bất kỳ biểu thức nào có trả về một đối tượng tham chiếu.
Toán tử new trả về một đối tượng tham chiếu, do vậy bạn có thể sử dụng
giá trị trả về từ việc gọi phương thức cho đối tượng mới:Câu lệnh đầu gọi phương thức getArea() ở đối tượng rectOne
và hiển thị kết quả. Câu lệnh thứ hai, di chuyển đối tượng rectTwo bởi vì
phương thức move() gán giá trị mới cho đối tượng là origin.x và origin.y.
new Rectangle(100, 50).getArea()
Biểu thức new Rectangle(100, 50) trả về một
đối tượng tham chiếu tham chiếu đến một đối tượng Rectangle. Như minh họa,
bạn có thể sử dụng dấu chấm để gọi phương thức getArea() cho đối tượng Rectangle
mới để tính toán diện tích hình chữ nhật mới này.
Một vài phương thức, như getArea(), trả về một giá trị.
Các phương thức trả về một giá trị, bạn có thể sử dụng để gọi trong một biểu thức.
Bạn có thể gán giá trị trả về cho một biến, sử dụng nó để quyết định, hoặc điều
khiển một vòng lặp. Mã nguồn này gán giá trị trả về của getArea() cho một biến là areaOfRectangle:
int areaOfRectangle = new Rectangle(100, 50).getArea();
Nhớ rằng, gọi một phương thức trong một đối tượng cụ thể là
sử dụng giống như gửi một thông điệp đến đối tượng đó. Trong trường hợp này, đối
tượng sẽ gọi phương thức getArea() của đối tượng hình chữ nhật được tạo
ra bởi hàm khởi tạo.
Dọn dẹp rác trong Java.
Một vài ngôn ngữ lập trình hướng đối tượng yêu cầu bạn phải
bám sát tất cả các đối tượng được tạo ra và giải phóng chúng khi không cần sử dụng
một thời gian dài. Quản lý bộ nhớ như vậy rất đơn điệu và dễ mắc lỗi. Nền tảng Java
cho phép bạn tạo ra rất nhiều đối tượng mà bạn muốn (tất nhiên là có giới hạn,
bởi vì hệ thống của bạn có thể kiểm soát chúng), bạn không thể cần lo lắng về
việc giải phóng chúng. Môi trường lập trình Java xóa các đối tượng khi
nó xác định chúng không dùng nữa. Tiến trình này được gọi là dọn dẹp rác trong
Java (garbage collection).
Một đối tượng đạt tiêu chuẩn để được dọn rác khi không còn
tham chiếu nào đến đối tượng đó. Các tham chiếu được hỗ trợ xóa đến một biến nếu
biến đó không còn không còn nằm trong phạm vi tham chiếu. Hoặc, bạn có thể xóa
một đối tượng tham chiếu bằng cách cài đặt biến đó có giá trị null. Nhớ
rằng, một chương trình có thể có nhiều tham chiếu đến một đối tượng, tất cả các
tham chiếu đến đối tượng đố phải được xóa trước khi đối tượng đó được dọn rác.
Môi trường lập trình Java có một cơ chế dọn rác, đó là định
kỳ làm sạch bộ nhớ được sử dụng bởi các đối tượng không được tham chiếu. Cơ chế
dọn rác làm việc tự động và quyết định thời gian phù hợp.