Với đặc tính xây dựng đợc những ứng dụng dễ hiểu, dễ sửa và có thể dùng lại, lập trình hớng đối tợng (OOP) đã và đang phát triển mạnh, thu hút sự chú ý của nhiều ngời trong thập kỷ vừa qua. Java ra đời trong giai đoạn này, do đó nó đợc xây dựng dới dạng một ngôn ngữ hớng đối tợng hoàn toàn. Để có thể hiểu và sử dụng Java một cách hữu hiệu, chúng ta cần phải nắm đợc các khái niệm cơ bản trong OOP.
1. Phơng pháp lập trình bình thờng:
Từ trớc đến nay, chúng ta thờng dùng các bản ghi để lu trữ thông tin, đồng thời xây dựng các hàm bên ngoài để điều chỉnh, xử lý các bản ghi đó. Để có đợc bản ghi nh vậy, đầu tiên phải tạo ra một "mẫu" cho nó:
struct Flight { // một bản ghi định nghĩa bằng C
int altitude; int heading; int speed; float latitude; float longitude; }
Khi tạo ra một bản ghi từ mẫu trên, chúng ta phải khai báo biến trong chơng trình:
struct Flight myFlight;
Để thay đổi độ cao (biến altitude) cho myFlight, chúng ta phải viết hàm điều chỉnh độ cao:
void changeAltitude(Flight &aFlight, int height) { // thay đổi độ cao
aFlight.altitude = height;
// kiểm tra độ cao, nếu lớn hơn giá trị // cho phép thì sửa lại:
if (aFlight.altitude>=100000)
aFlight.altitude = 100000; }
Khi muốn thay đổi độ cao, chúng ta gọi hàm:
changeAltitude(myFlight, 10200);
Các hàm tơng tự cho việc chuyển hớng bay, hạ cánh.. cũng cần đợc phát triển đầy đủ tơng tự nh trên.
Kế đến, bạn cần các bản ghi cho máy bay dân dụng. Một "mẫu" mới đợc tạo ra cho các bản ghi kiểu mới:
struct CommercialFlight {
// các biến riêng cho chuyến bay thơng mại
int flightNumber;
int passengers;
// các biến cũ giống nh của Flight
int altitude;
int heading;
int speed;
float longitude; }
Cũng giống nh trên, để tạo một bản ghi từ kiểu này, chúng ta khai báo biến:
struct CommercialFlight myCommercialFilght;
Tuy nhiên, chúng ta không đợc phép dùng lại hàm đã viết ở trên của Flight cho CommercialFlight. Nghĩa là trình biên dịch sẽ báo lỗi khi gặp phát biểu:
changeAltitude(myCommercialFlight, 10200);
Chúng ta có thể thấy rằng với mỗi kiểu bản ghi khác nhau cần khai báo kiểu dữ liệu cũng nh các hàm điều khiển khác nhau. Điều này dẫn đến việc quản lý, bảo trì và nâng cấp một chơng trình ứng dụng rất phức tạp: Thứ nhất, các cấu trúc dữ liệu khai báo trong lập trình kiểu cũ khó dùng đợc đến lần thứ hai: nếu có ai đó muốn lấy một đoạn mã trong chơng trình của bạn để phát triển tiếp; họ phải đọc, nghiên cứu lại đoạn mã của bạn để đa những đoạn mã mới vào chỗ thích hợp.
Thứ hai, tất cả các phần khác nhau trong chơng trình của bạn đều có thể truy cập đến dữ liệu bên trong một bản ghi, do đó không loại trừ khả năng một đoạn mã nào đó trong chơng trình thay đổi dữ liệu nội tại của bản ghi và gây ra lỗi. Bình thờng, chơng trình gọi đến hàm changeAltitude() để thay đổi độ cao chuyến bay, tuy nhiên có thể tồn tại ở đâu đó câu lệnh:
// tăng độ cao lên 200
myFlight.altitude = myFlight.altitude + 200; // rất có thể lệnh này làm độ cao của
// chuyến bay lên trên giá trị cho phép // là 100.000 meter
2. Lập trình hớng đối tợng (Object - Oriented Programming):
Với lập trình hớng đối tợng, các thờng trình xử lý dữ liệu và bản thân dữ liệu đợc gắn liền với nhau thành một tổng thể (bao gói - encapsulated). Một tổng thể nh vậy trong Java gọi là một lớp (class):
class Flight { // các dữ liệu (biến thành phần) int altitude; int heading; int speed; float latitude; float longitude;
// hàm thay đổi độ cao
void changeAltitude(int height) { // thay đổi độ cao
altitude = height;
// kiểm tra độ cao, nếu lớn hơn giá trị // cho phép thì sửa lại:
if (altitude>=100000) altitude = 100000; }
// in thông tin chuyến bay void printFlight() {
+"/"+speed); }
}
Hàm changeAltitude() là một ví dụ về hàm thành phần (member function, đôi khi còn gọi là method), nghĩa là hàm thuộc về cấu trúc dữ liệu đó. Bạn thấy rằng đoạn mã trong hàm đơn giản hơn chút đỉnh, bởi các biến trong đó không cần phải quy chiếu tới aFlight - một biến giả nh ở trên. Chúng ta thấy rằng các hàm thành phần tự hiểu dữ liệu bên trong lớp đó.
Cũng giống nh kiểu bản ghi ở trên, đây chỉ là "mẫu" của một đối tợng
(object). Để tạo đợc một bản sao (instance) của lớp, chúng ta dùng khai báo:
Flight myFlight;
Biến myFlight sau khai báo mang giá trị null (không chứa gì). Để tạo một đối tợng, chúng ta còn phải dùng toán tử new để định vị một vùng nhớ:
myFlight = new Flight();
Lúc này chúng ta có thể truy cập đến biến nội tại của đối tợng đó:
myFlight.altitude = 1000; if (myFlight.speed>100) ...
Chúng ta cũng có thể coi đối tợng là một thực thể với các hành vi riêng của mình. Một chuyến bay có hành vi nâng độ cao, thay đổi tốc độ.. Chúng ta ra lệnh cho chuyến bay tự nâng độ cao của mình lên 10200 meter:
myFlight.changeAltitude(10200);
Java là ngôn ngữ lập trình hớng đối tợng thực sự, với các đặc điểm thể hiện nh:
a. Che dấu dữ liệu (data hiding):
Việc đóng gói dữ liệu và hàm với nhau nh vậy còn cho phép che dấu dữ liệu, nghĩa là ngăn cản các phần khác nhau trong chơng trình truy nhập đến các biến thành phần nh altitude, speed.. Khi đó chơng trình phải thay đổi thành:
class Flight {
// các dữ liệu (biến thành phần)
priva t e int altitude;
int heading; int speed; float latitude; float longitude; // phần còn lại vẫn thế ! ... }
Biến altitude bây giờ đợc "che chắn", nghĩa là các phần khác ngoài khai báo lớp không đợc phép truy cập đến biến này. Để thay đổi biến, bạn buộc phải gọi đến hành vi thay đổi độ cao của lớp (changeAltitude()), trong đó có đầy đủ các kiểm tra cần thiết để tránh việc biến altitude nhận các giá trị ngoài khoảng.
b. Thừa kế (Inheritance):
Sử dụng các lớp thay vì kiểu bản ghi sẽ tránh đợc vấn đề trùng các mã. Chúng ta đã thấy trong phần trên, kiểu CommercialFlight chứa những biến altitude, speed.. đã có sẵn trong kiểu Flight, dẫn đến trùng lặp mã lệnh. Trong phần này, chúng ta sẽ xây dựng CommercialFlight (lớp con) trên cơ sở lớp Flight (lớp cha):
class CommercialFlight extends Flight { // các biến riêng cho lớp này
// không cần khai báo lại các biến có trong lớp cha
int flightNumber;
int passengers;
// nạp chồng hàm:
// in thông tin chuyến bay void printFlight() {
System.out.println("Máy bay dân dụng: "+altitude+"/" +heading+"/"+speed);
} }
Lớp CommercialFlight là lớp con của lớp Flight, nó kế thừa tất cả những gì lớp Flight có. Do vậy chúng ta có thể viết:
CommercialFlight flight = new CommercialFlight();
flight.speed = 100; // thay đổi tốc độ
flight.changeAltitude(10200); // thay đổi độ cao
flight.passengers = 24; // số hành khách
Cách làm nh vậy giúp chơng trình đơn giản đi rất nhiều. Hơn thế, khi một lớp con không muốn dùng hàm thành phần của lớp cha, nó có thể nạp chồng hàm của riêng mình. Trong ví dụ trên, chúng ta đã thấy lớp con đã nạp chồng hàm printFlight() của lớp cha để đa ra thông tin riêng của mình.
Flight f1 = new Flight();
CommercialFlight f2 = new CommercialFlight(); f1.speed = 200;
f2.speed = 300;
f1.printFlight(); // gọi hàm của lớp Flight
f2.printFlight(); // gọi hàm của lớp CommercialFlight
Đôi khi, lớp cơ sở rất trừu tợng, không thể mô tả ngay đợc, ngời ta phải dùng đến các lớp ảo (abstract class). Lớp ảo đợc khai báo nh bình thờng, nhng có thêm từ khoá abstract để báo cho trình biên dịch biết lớp này cha đợc viết hoàn chỉnh, ngời sử dụng không đợc phép tạo đối tợng trực tiếp từ lớp kiểu này:
abstract class Animal { String name;
// methods
abstract void Eat(); abstract void Breath(); abstract void Sleep(); }
Không đợc phép:
Chú ý: Một lớp chỉ cần có một hàm thành phần khai báo kiểu abstract, nó sẽ tự động đợc coi là abstract.
c. Tính đa hình (polymorphism):
Tính đa hình đặc biệt quan trọng đối với lập trình hớng đối tợng. Nó cho phép trừu tợng hoá các đối tợng, làm chơng trình trở nên đơn giản và dễ hiểu. Hãy tởng tợng chúng ta có thể viết một hàm print() nhận và in ra tham số bất kỳ thay vì phải viết ba hàm riêng biệt printInt(), printStr(), printFloat() để in ra số nguyên, xâu ký tự và số thực. Java cho phép thực hiện tính đa hình theo ba cách:
• Thừa kế (inheritance) : Một hàm nếu chấp nhận kiểu lớp cha sẽ chấp nhận tất cả các
lớp con của lớp cha đó.
• Nạp chồng (overloading) : Thực hiện các hàm cùng tên nhng khác nhau về tham số
trong cùng một lớp.
• Giao diện (interfacing) : Thực hiện đợc các hàm thành phần cùng tên, cùng tham số
nhng nằm trên các lớp khác nhau.
d. Constructor và Finalizer:
Hàm dựng và hàm huỷ là hai hàm thành phần đợc gọi đến khi đối tợng đợc tạo ra và huỷ đi. Nếu nh bạn không định nghĩa hàm dựng, Java sẽ tự động tạo ra hàm dựng không có tham số.
Hàm dựng đợc định nghĩa trùng với tên lớp, Java cho phép định nghĩa nhiều hơn một hàm dựng:
class Cat extends Animal { String kind;
// các hàm dựng của lớp đó public Cat (String Name) {
name = Name; }
public Cat (String Name, String Kind) { Cat(Name);
kind = Kind; }
}
Để tạo ra một đối tợng từ lớp Cat, chúng ta dùng từ khoá new và một trong các hàm dựng ở trên:
Cat meo = new Cat("Lyly", "Xiem");
Hàm huỷ chỉ có một, đợc định nghĩa với cấu trúc sau:
protected void finalizer() {
// các thủ tục cần thiết để "thu dọn" đối tợng ...
}
Hàm finalizer() đợc tự động gọi đến bởi quá trình thu dọn rác (Garbage collection).
Nh vậy, Java là ngôn ngữ hớng đối tợng thực sự. Th viện Core API của Java chứa hơn 250 lớp "tạo sẵn", chúng ta có thể dùng trực tiếp hoặc kế thừa các lớp này để phục vụ cho mục đích riêng, từ việc xử lý ảnh đến tạo ra các kết nối trên mạng.. Phần kế tiếp đây sẽ trình bày ngôn ngữ Java một cách kỹ càng hơn.