+ All Categories
Home > Documents > LẬP TRÌNH HƯỚNG ĐỐI TƯỢNG - Tailieuhoctap.vn

LẬP TRÌNH HƯỚNG ĐỐI TƯỢNG - Tailieuhoctap.vn

Date post: 24-Apr-2023
Category:
Upload: khangminh22
View: 0 times
Download: 0 times
Share this document with a friend
260
HC VIN CÔNG NGHBƯU CHÍNH VIN THÔNG CƠ STI TP. HCHÍ MINH Bài ging: L L P P T T R R Ì Ì N N H H H H Ư Ư N N G G Đ Đ I I T T Ư Ư N N G G Tài liu dùng cho hĐại hc ngành Công NghThông Tin Đã được Hi đồng khoa hc khoa Thông qua ngày 18/09/2010 Biên son :Ths. Bùi Công Giao LƯU HÀNH NI BTp. HChí Minh – Năm 2010
Transcript

HỌC VIỆN CÔNG NGHỆ BƯU CHÍNH VIỂN THÔNG

CƠ SỞ TẠI TP. HỒ CHÍ MINH

Bài giảng:

LLẬẬPP TTRRÌÌNNHH HHƯƯỚỚNNGG ĐĐỐỐII TTƯƯỢỢNNGG

Tài liệu dùng cho hệ Đại học ngành Công Nghệ Thông Tin

Đã được Hội đồng khoa học khoa

Thông qua ngày 18/09/2010

Biên soạn :Ths. Bùi Công Giao

LƯU HÀNH NỘI BỘ

Tp. Hồ Chí Minh – Năm 2010

Lời nói đầu

Lập trình là một kỹ năng rất cần thiết cho sinh viên ngành Công nghệ thông tin. Sau khi đã học qua lập trình căn bản với ngôn ngữ C, sinh viên sẽ được tiếp cận với một phương pháp lập trình theo tư duy mới, gần với thế giới thực hơn, phù hợp với các dự án phần mềm lớn, đó là lập trình hướng đối tượng.

Nội dung môn học này bao gồm những khái niệm, mô hình về đối tượng và phương pháp lập trình hướng đối tượng bằng ngôn ngữ lập trình Java. Các đặc trưng quan trọng của lập trình hướng đối tượng như đóng gói, kế thừa, đa hình…và các quan hệ lớp; xử lý vào/ra, xử lý ngoại lệ, lập trình luồng xử lý và giao diện sẽ được thể hiện trong Java.

Bài giảng này được biên soạn chủ yếu dựa vào hai quyển sách : Beginning Java Objects: From Concepts to Code, Second Edition, Apress, 2005 của Jacquie Barker và Bài giảng Lập trình Hướng đối tượng, Học viện Công nghệ Bưu chính Viễn thông của Trần Đình Quế và Nguyễn Mạnh Hùng, Người soạn chân thành cảm ơn các tác giả trên.

Mục lục

Chương 1.  Tổng quan về lập trình hướng đối tượng ................... 1 

1.1 Phương pháp tiếp cận của lập trình truyền thống ......................... 1 

1.1.1 Lập trình tuyến tính ............................................................................. 1 

1.1.2 Lập trình cấu trúc ................................................................................ 1 

1.2 Phương pháp tiếp cận lập trình hướng đối tượng .......................... 3 

1.3 Các đặc trưng của lập trình hướng đối tượng ................................ 3 

1.3.1 Tính đóng gói dữ liệu .................................................................................. 3 

1.3.2 Tính kế thừa ................................................................................................ 4 

1.3.3 Tính đa hình ................................................................................................ 4 

1.4 Trừu tượng hóa ................................................................................. 4 

1.4.1 Tổ chức trừu tượng theo sự phân cấp lớp ......................................... 4 

1.4.2 Trừu tượng hóa – cơ sở của phát triển phần mềm ........................... 5 

1.5 Xu hướng phát triển của lập trình hướng đối tượng ..................... 6 

1.5.1 Lập trình hướng thành phần (Component-oriented programming-COP) ................................................................................................................ 6 

1.5.2 Lập trình hướng tác nhân ................................................................... 7 

1.5.3 Lập trình hướng khía cạnh ................................................................. 7 

Chương 2.  Cơ bản ngôn ngữ lập trình Java ................................. 9 

2.1 Đặc trưng của ngôn ngữ Java .......................................................... 9 

2.2 Kiến trúc chương trình và cơ chế thực thi của Java .................... 14 

2.3 Các kiểu dữ liệu cơ bản và biến ..................................................... 18 

2.3.1 Kiểu dữ liệu cơ bản ............................................................................ 18 

2.3.2 Biến ...................................................................................................... 19 

2.3.2.1 Khai báo biến ......................................................................................... 19 

2.3.2.2 Phạm vi hoạt động của biến .................................................................. 20 

2.3.2.3 Khởi tạo biến .......................................................................................... 20 

2.3.2.4 Ép kiểu .................................................................................................... 20 

2.4 Các toán tử và biểu thức ................................................................. 21 

2.4.1 Các toán tử .......................................................................................... 21 

2.4.2 Biểu thức ............................................................................................. 25 

2.5 Các cấu trúc lệnh ............................................................................ 26 

2.5.1 Lệnh if-else .......................................................................................... 26 

2.5.2 Lệnh switch-case ................................................................................ 27 

2.5.3 Vòng lặp while .................................................................................... 28 

2.5.4 Vòng lặp do-while ............................................................................... 29 

2.5.5 Vòng lặp for ........................................................................................ 29 

2.6 Phong cách lập trình ....................................................................... 31 

2.7 Case Study ....................................................................................... 33 

Chương 3.  Đối tượng và lớp ......................................................... 38 

3.1 Phân rã phần mềm theo cách tiếp cận hướng đối tượng ............. 38 

3.2 Khái niệm đối tượng ....................................................................... 39 

3.3 Khái niệm lớp .................................................................................. 42 

3.4 Khái niệm đóng gói ......................................................................... 43 

3.5 Biến tham chiếu ............................................................................... 44 

3.6 Khởi tạo đối tượng .......................................................................... 45 

Chương 4.  Tương tác giữa các đối tượng.................................... 50 

4.1 Cộng tác giữa các đối tượng ........................................................... 50 

4.2 Thuộc tính ........................................................................................ 51 

4.3 Phương thức .................................................................................... 52 

4.3.1 Khai báo phương thức ....................................................................... 52 

4.3.2 Biến this ............................................................................................... 53 

4.3.3 Gọi phương thức ................................................................................ 53 

4.3.4 Nạp chồng phương thức .................................................................... 54 

4.3.5 Phương thức xây dựng....................................................................... 54 

4.3.6 Che dấu thông tin ............................................................................... 56 

4.4 Truyền thông điệp giữa các đối tượng .......................................... 57 

Chương 5.  Quan hệ giữa các đối tượng ....................................... 61 

5.1 Kết hợp và liên kết .......................................................................... 61 

5.2 Kế thừa ............................................................................................ 63 

5.3 Đa hình ............................................................................................. 64 

5.4 Lớp trừu tượng ............................................................................... 71 

5.5 Giao tiếp ........................................................................................... 74 

5.6 Tính chất tĩnh .................................................................................. 76 

5.7 Kiểu liệt kê ....................................................................................... 79 

5.8 Case Study ....................................................................................... 83 

Chương 6.  Tập đối tượng ............................................................. 95 

6.1 Khái niệm tập đối tượng ................................................................. 95 

6.2 Ba kiểu tập đối tượng cơ bản ......................................................... 95 

6.3 Mảng ................................................................................................ 97 

6.4 Các loại tập đối tượng thường gặp .............................................. 100 

6.4.1 LinkedList ......................................................................................... 100 

6.4.2 HashMap ........................................................................................... 102 

6.4.3 TreeMap ............................................................................................ 104 

6.4.4 HashSet.............................................................................................. 106 

6.4.5 TreeSet .............................................................................................. 107 

6.5 Tạo kiểu tập hợp ........................................................................... 108 

6.6 Phương thức trả về kiểu tập hợp ................................................. 110 

Chương 7.  Xử lý ngoại lệ ............................................................ 113 

7.1 Giới thiệu ngoại lệ ......................................................................... 113 

7.2 Cơ chế xử lý ngoại lệ ..................................................................... 115 

7.2.1 Khối try ............................................................................................. 115 

7.2.2 Khối catch ......................................................................................... 115 

7.2.3 Khối finally ....................................................................................... 119 

7.3 Bắt các ngoại lệ .............................................................................. 121 

7.4 Phân cấp lớp ngoại lệ .................................................................... 126 

7.5 Các điểm cần lưu ý thêm về ngoại lệ ........................................... 129 

7.5.1 Bắt ngoại lệ tổng quát ...................................................................... 129 

7.5.2 Trình biên dịch Java yêu cầu phải có xử lý ngoại lệ ..................... 129 

7.5.3 Tận dụng xử lý ngoại lệ để làm rõ lỗi phát sinh ............................ 131 

7.5.4 try/catch lồng nhau .......................................................................... 131 

7.5.5 Kiểu ngoại lệ do người dùng định nghĩa ........................................ 132 

7.5.6 Ném nhiều kiểu ngoại lệ .................................................................. 134 

7.6 Case Study ..................................................................................... 134 

Chương 8.  Xử lý vào/ra .............................................................. 137 

8.1 Luồng vào/ra ................................................................................. 137 

8.1.1 Giới thiệu luồng vào/ra .................................................................... 137 

8.1.2 Luồng byte ........................................................................................ 138 

8.1.3 Luồng ký tự ....................................................................................... 140 

8.1.4 Luồng bộ đệm ................................................................................... 141 

8.2 Scanning và Formatting ............................................................... 143 

8.2.1 Scanning ............................................................................................ 143 

8.2.2 Formatting ........................................................................................ 146 

8.3 Vào ra từ chế độ dòng lệnh .......................................................... 148 

8.3.1 Vào ra qua luồng chuẩn .................................................................. 148 

8.3.2 Vào ra qua Console .......................................................................... 154 

8.4 Làm việc với CSDL ....................................................................... 157 

8.4.1 JDBC ................................................................................................. 157 

8.4.1.1 Giới thiệu JDBC .................................................................................. 157 

8.4.1.2 Kiến trúc JDBC ................................................................................... 157 

8.4.2 MySQL và Java ................................................................................ 158 

8.4.2.1 Cài đặt Connector/J - JDBC Driver của MySQL ............................ 158 

8.4.2.2 Kiểm tra Connector/J .......................................................................... 159 

8.4.2.3 Thực hiện các câu truy vấn ................................................................. 160 

Chương 9.  Lập trình giao diện ................................................... 166 

9.1 Giao diện với các đối tượng cơ bản ............................................. 166 

9.1.1 Các đối tượng container cơ bản ...................................................... 166 

9.1.1.1 Frame .................................................................................................... 166 

9.1.1.2 Panel ...................................................................................................... 167 

9.1.1.3 Dialog .................................................................................................... 168 

9.1.2 Các đối tượng component cơ bản ................................................... 169 

9.1.2.1 Label ..................................................................................................... 169 

9.1.2.2 TextField và TextArea ........................................................................ 170 

9.1.2.3 Button ................................................................................................... 172 

9.1.3 Xử lý sự kiện ..................................................................................... 173 

9.2 Giao diện với các đối tượng Multimedia ..................................... 180 

9.2.1 Ô đánh dấu và nút chọn .................................................................. 180 

9.2.2 Lựa chọn ........................................................................................... 182 

9.2.3 Danh sách .......................................................................................... 184 

9.3 Các kỹ thuật trình bày .................................................................. 186 

9.3.1 Trình bày Flow Layout .................................................................... 186 

9.3.2 Trình bày Grid Layout .................................................................... 187 

9.3.3 Trình bày Border Layout ................................................................ 189 

9.3.4 Trình bày GridBag Layout ............................................................. 190 

9.3.5 Trình bày Null Layout ..................................................................... 192 

9.4 Applet ............................................................................................. 193 

9.4.1 Cấu trúc của một Applet ................................................................. 194 

9.4.2 Sử dụng Applet ................................................................................. 195 

9.4.3 Truyền tham số cho Applet ............................................................. 199 

9.5 SWING ........................................................................................... 200 

9.5.1 Mở rộng các đối tượng component ................................................. 201 

9.5.2 Mở rộng các đối tượng container ................................................... 202 

9.6 Case Study ..................................................................................... 210 

Chương 10.  Luồng xử lý .............................................................. 219 

10.1 Giới thiệu luồng ........................................................................... 219 

10.2 Tạo và quản lý luồng .................................................................. 220 

10.2.1 Tạo luồng ........................................................................................ 220 

10.2.2 Phương thức của lớp luồng ........................................................... 223 

10.2.3 Quyền ưu tiên của luồng ................................................................ 225 

10.2.4 Luồng ngầm .................................................................................... 226 

10.2.5 Đa luồng với Applet ....................................................................... 227 

10.3 Nhóm luồng ................................................................................. 229 

10.3.1 Giới thiệu nhóm luồng ................................................................... 229 

10.3.2 Sự đồng bộ luồng ............................................................................ 233 

10.3.3 Mã đồng bộ ..................................................................................... 233 

10.3.4 Sử dụng khối đồng bộ .................................................................... 235 

10.3.5 Kỹ thuật đợi – thông báo ............................................................... 237 

10.3.6 Sự bế tắt .......................................................................................... 240 

10.4 Thu rác ......................................................................................... 242 

Tham khảo ...................................................................................... 247 

Danh sách hình

Hình 1.1 Sơ đồ phân cấp trừu tượng của các đối tượng tự nhiên. .................................. 5 

Hình 2.1 Cách biên dịch truyền thống .......................................................................... 10 

Hình 2.2 Dịch chương trình Java .................................................................................. 11 

Hình 2.3 Máy ảo Java thực hiện mã bytecode độc lập môi trường hoạt động ............. 12 

Hình 2.4 Cùng mã bytecode có thể được hiểu bởi hai máy ảo ..................................... 13 

Hình 2.5 Phân tích một chương trình Java đơn giản .................................................... 14 

Hình 3.1 Phân rã ứng dụng từ trên xuống. ................................................................... 38 

Hình 3.2 Gắn ứng dụng từ dưới lên. ............................................................................. 39 

Hình 3.3 Sử dụng một biến tham chiếu để chỉ tới đối tượng trong bộ nhớ. ................. 45 

Hình 3.4 Hai biến tham chiếu tới cùng một đối tượng. ................................................ 46 

Hình 3.5 Chuyển y tham chiếu tới Student thứ hai. ..................................................... 46 

Hình 3.6 Chuyển x tham chiếu tới Student thứ hai. ..................................................... 47 

Hình 3.7 Đối tượng Student mồ côi. ............................................................................. 47 

Hình 4.1 Các đối tượng trong hệ thống phải cộng tác với nhau để hoàn tất sứ mệnh SRS ............................................................................................................................... 51 

Hình 4.2 Thông điệp được truyền qua lại giữa các đối tượng Course và Student ....... 58 

Hình 4.3 Người yêu cầu chỉ thấy chi tiết bên ngoài của việc trao đổi thông điệp. ...... 58 

Hình 5.1 Kết hợp ba ngôi ............................................................................................. 62 

Hình 5.2 Một thể hiện tương đương bằng ba kết hợp hai ngôi .................................... 62 

Hình 6.1 Hầu hết tập hợp tự động co lại khi một phần tử bị lấy ra .............................. 96 

Hình 6.2 Dùng khoá để truy cập trực tiếp đối tượng trong tập hợp từ điển ................. 96 

Hình 6.3 Các tập hợp không phải là bộ cho phép nhiều tham chiếu tới cùng một đối tượng. ............................................................................................................................ 97 

Hình 7.1 Khi máy ảo Java ném một ngoại lệ, như phát ra một pháo để thông báo cho ứng dụng một vấn đề phát sinh. .................................................................................. 114 

Hình 7.2 Nếu không có ngoại lệ được ném ra trong khối try, tất cả các khối catch được bỏ qua. ........................................................................................................................ 117 

Hình 7.3 Nếu một ngoại lệ phát sinh, khồi catch đầu tiên chặn bắt, nếu có, được thực thi, và phần còn lại được bỏ qua. ................................................................................ 118 

Hình 7.4 Máy ảo Java theo dõi việc thứ tự các phương thức được gọi bằng cách tạo chồng gọi (call stack). ................................................................................................. 123 

Hình 7.5 Khi một phương thức chấm dứt, nó bị lấy ra khỏi chồng. .......................... 123 

Hình 7.6 Khối lệnh try/catch trong trong phương thức methodY nhận biết ngoại lệ trong mức hiện hành của chồng gọi. .......................................................................... 123 

Hình 7.7 Ngoại lệ thoát khỏi p.methodY() và chuyển xuống s.methodX() xử lý ...... 124 

Hình 7.8 NullPointerException thông qua chồng gọi được chuyển tới phương thức main. ........................................................................................................................... 125 

Hình 7.9 Nếu ứng dụng bỏ qua xử lý ngoại lệ, máy ảo Java chấm dứt ứng dụng và báo cáo ngoại lệ tới cửa cửa sổ dòng lệnh cho người sử dụng để quan sát. ..................... 126 

Hình 7.10 Lớp java.lang.Exception có nhiều lớp con ................................................ 127 

Hình 7.11 Cây thừa kế của kiểu ngoại lệ java.sql.DataTruncation ............................ 127 

Hình 7.12 Kết quả demo của NumberOfDigits.java .................................................. 135 

Hình 8.1 Đọc dữ liệu vào chương trình. ..................................................................... 137 

Hình 8.2 Viết dữ liệu từ chương trình. ....................................................................... 138 

Hình 8.3 Luồng byte vào ra đơn giản ......................................................................... 139 

Hình 8.4 Các thành phần đặc tả qui cách. .................................................................. 148 

Hình 9.1 Kết quả demo Frame ................................................................................... 167 

Hình 9.2 Kết quả demo Panel ..................................................................................... 168 

Hình 9.3 Kết quả demo Dialog ................................................................................... 169 

Hình 9.4 Kết quả demo Label ..................................................................................... 170 

Hình 9.5 Kết quả demo Text ...................................................................................... 172 

Hình 9.6 Kết quả demo Button ................................................................................... 173 

Hình 9.7 Kết quả demo sự kiện .................................................................................. 179 

Hình 9.8 Kết quả demo Radio Button ........................................................................ 182 

Hình 9.9 Kết quả demo Choice Button ...................................................................... 184 

Hình 9.10 Kết quả demo Listbox ............................................................................... 186 

Hình 9.11 Kết quả demo Flow layout ........................................................................ 187 

Hình 9.12 Kết quả demo Grid layout ......................................................................... 188 

Hình 9.13 Kết quả demo Border layout..................................................................... 190 

Hình 9.14 Kết quả demo Gridbag layout.................................................................... 192 

Hình 9.15 Kết quả demo Null layout .......................................................................... 193 

Hình 9.16 Kết quả demo Applet ................................................................................. 195 

Hình 9.17 Kết quả demo Applet bảng tính ................................................................. 198 

Hình 9.18 Kết quả demo Applet có tham số .............................................................. 200 

Hình 9.19 Kết quả demo JButton ............................................................................... 202 

Hình 9.20 Kết quả demo gắn các đối tượng vào các tầng .......................................... 205 

Hình 9.21 Kết quả demo JMenu ................................................................................. 210 

Hình 9.22 Kết quả demo Case Study .......................................................................... 216 

Hình 10.1 Vòng đời của luồng ................................................................................... 220 

Hình 10.2 Đa luồng với Applet .................................................................................. 228 

Danh sách bảng

Bảng 2.1 Các toán tử số học ......................................................................................... 22 

Bảng 2.2 Các toán tử bit ............................................................................................... 23 

Bảng 2.3 Các toán tử quan hệ. ...................................................................................... 24 

Bảng 2.4 Các toán tử logic ........................................................................................... 24 

Bảng 2.5 Thứ tự ưu tiên các toán tử ............................................................................. 25 

Bảng 4.1 Phạm vi truy cập của các thành viên của lớp ................................................ 52 

Bảng 7.1 Danh sách một số ngoại lệ .......................................................................... 128 

Bảng 10.1 Các phương thức của một lớp luồng ......................................................... 224 

Thuật ngữ viết tắt

API Application Programming Interface

CSDL Cơ sở dữ liệu

CTDL Cấu trúc dữ liệu

GUI Graphic User Interface

JDBC Java DataBase Connectivity

JFC Java Foundation Class

JVM Java Virtual Machine

LIFO Last in, First out

ODBC Open Database Connectivity

RDBMS Relational Database Management Systems

SRS Student Registration System

Chương 1: Tổng quan về lập trình hướng đối tượng

Biên soạn : Bùi Công Giao --------------------------------------- 1 --------------------------------------

Chương 1. Tổng quan về lập trình hướng đối tượng

Nội dung chương này nhằm giới thiệu một cách tổng quan về cách tiếp cận hướng đối tượng. Nội dung trình bày bao gồm:

Giới thiệu về cách tiếp cận của lập trình truyền thống.

Giới thiệu cách tiếp cận của lập trình hướng đối tượng.

Sự trừu tượng hóa là cơ sở của phát triển phần mềm hướng đối tượng.

Nêu các đặc trưng của lập trình hướng đối tượng.

Xu hướng hiện nay của lập trình hướng đối tượng.

1.1 Phương pháp tiếp cận của lập trình truyền thống

Lập trình truyền thống đã trải qua hai giai đoạn:

Giai đoạn sơ khai, khi khái niệm lập trình mới ra đời, là lập trình tuyến tính.

Giai đoạn tiếp theo, là lập trình hướng cấu trúc.

1.1.1 Lập trình tuyến tính

Đặc trưng cơ bản của lập trình tuyến tính là tư duy theo lối tuần tự. Chương trình sẽ được thực hiện tuần tự từ đầu đến cuối, lệnh này kế tiếp lệnh kia cho đến khi kết thúc chương trình.

Đặc trưng

Lập trình tuyến tính có hai đặc trưng:

Đơn giản: chương trình được tiến hành đơn giản theo lối tuần tự, không phức tạp.

Đơn luồng: chỉ có một luồng (thread) công việc duy nhất, và các công việc được thực hiện tuần tự trong luồng đó.

Tính chất

Ưu điểm: Do tính đơn giản, lập trình tuyến tính có ưu điểm là chương trình đơn giản, dễ hiểu. Lập trình tuyến tính được ứng dụng cho các chương trình đơn giản.

Nhược điểm: Với các ứng dụng phức tạp, người ta không thể dùng lập trình tuyến tính để giải quyết.

Ngày nay, lập trình tuyến tính chỉ tồn tại trong phạm vi các mô đun nhỏ nhất của các phương pháp lập trình khác. Ví dụ trong một chương trình con của lập trình cấu trúc, các lệnh cũng được thực hiện theo tuần tự từ đầu đến cuối chương trình con.

1.1.2 Lập trình cấu trúc

Trong lập trình hướng cấu trúc, chương trình chính được chia nhỏ thành các chương trình con và mỗi chương trình con thực hiện một công việc xác định. Chương trình

Chương 1: Tổng quan về lập trình hướng đối tượng

Biên soạn : Bùi Công Giao --------------------------------------- 2 --------------------------------------

chính sẽ gọi đến chương trình con theo một giải thuật, hoặc một cấu trúc được xác định trong chương trình chính.

Các ngôn ngữ lập trình cấu trúc phổ biến là Pascal, C và C++. Riêng C++ ngoài việc có đặc trưng của lập trình cấu trúc do kế thừa từ C, còn có đặc trưng của lập trình hướng đối tượng. Cho nên C++ còn được gọi là ngôn ngữ lập trình nửa cấu trúc, nửa hướng đối tượng.

Đặc trưng

Đặc trưng cơ bản nhất của lập trình cấu trúc thể hiện ở mối quan hệ:

Chương trình = CTDL + Giải thuật

Trong đó:

CTDL là cách tổ chức dữ liệu, cách mô tả bài toán dưới dạng ngôn ngữ lập trình

Giải thuật là một quy trình để thực hiện một công việc xác định

Trong chương trình, giải thuật có quan hệ phụ thuộc vào CTDL:

Một CTDL chỉ phù hợp với một số hạn chế các giải thuật.

Nếu thay đổi CTDL thì phải thay đổi giải thuật cho phù hợp.

Một giải thuật thường phải đi kèm với một CTDL nhất định.

Tính chất

Mỗi chương trình con có thể được gọi thực hiện nhiều lần trong một chương trình chính.

Các chương trình con có thể được gọi đến để thực hiện theo một thứ tự bất kỳ, tuỳ thuộc vào giải thuật trong chương trình chính mà không phụ thuộc vào thứ tự khai báo của các chương trình con.

Các ngôn ngữ lập trình cấu trúc cung cấp một số cấu trúc lệnh điều khiển chương trình.

Ưu điểm

Chương trình sáng sủa, dễ hiểu, dễ theo dõi.

Tư duy giải thuật rõ ràng.

Nhược điểm

Lập trình cấu trúc không hỗ trợ việc sử dụng lại mã nguồn: Giải thuật luôn phụ thuộc chặt chẽ vào CTDL, do đó, khi thay đổi CTDL, phải thay đổi giải thuật, nghĩa là phải viết lại chương trình.

Không phù hợp với các phần mềm lớn: tư duy cấu trúc với các giải thuật chỉ phù hợp với các bài toán nhỏ, nằm trong phạm vi một mô đun của chương trình. Với dự án phần mềm lớn, lập trình cấu trúc tỏ ra không hiệu quả trong việc giải quyết mối quan hệ vĩ mô giữa các mô đun của phần mềm.

Chương 1: Tổng quan về lập trình hướng đối tượng

Biên soạn : Bùi Công Giao --------------------------------------- 3 --------------------------------------

Vấn đề

Vấn đề cơ bản của lập trình cấu trúc là bằng cách nào để phân chia chương trình chính thành các chương trình con cho phù hợp với yêu cầu, chức năng và mục đích của mỗi bài toán.

1.2 Phương pháp tiếp cận lập trình hướng đối tượng

Xuất phát từ hai hạn chế chính của phương pháp lập trình cấu trúc:

Không quản lý được sự thay đổi dữ liệu khi có nhiều chương trình cùng thay đổi một biến chung. Vấn đề này đặc biệt nghiêm trọng khi các ứng dụng ngày càng lớn, người ta không thể kiểm soát được sự truy nhập đến các biến dữ liệu chung.

Không tiết kiệm được tài nguyên con người: Giải thuật gắn liền với CTDL, nếu thay đổi CTDL, sẽ phải thay đổi giải thuật, và do đó, phải viết lại mã chương trình từ đầu.

Để khắc phục được hai hạn chế này khi giải quyết các bài toán lớn, người ta xây dựng một phương pháp tiếp cận mới, là phương pháp lập trình hướng đối tượng, với hai mục đích chính:

Đóng gói dữ liệu để hạn chế sự truy nhập tự do vào dữ liệu, không quản lý được.

Cho phép sử dụng lại mã nguồn, hạn chế việc phải viết lại mã từ đầu cho các chương trình.

Ngôn ngữ lập trình hướng đối tượng phổ biến hiện nay là Java và C++. Tuy nhiên, C++ mặc dù cũng có những đặc trưng cơ bản của lập trình hướng đối tượng nhưng vẫn không phải là ngôn ngữ lập trình thuần hướng đối tượng. Java thật sự là một ngôn ngữ lập trình thuần hướng đối tượng.

1.3 Các đặc trưng của lập trình hướng đối tượng

1.3.1 Tính đóng gói dữ liệu

Dữ liệu luôn được tổ chức thành các thuộc tính của lớp đối tượng. Việc truy nhập đến dữ liệu phải thông qua các phương thức của đối tượng lớp.

Trong một đối tượng, dữ liệu hay thao tác hay cả hai có thể là riêng (private) hoặc chung (public) của đối tượng đó. Thao tác hay dữ liệu riêng là thuộc về đối tượng đó chỉ được truy cập bởi các thành phần của đối tượng, điều này nghĩa là thao tác hay dữ liệu riêng không thể truy cập bởi các phần khác của chương trình tồn tại ngoài đối tượng. Khi thao tác hay dữ liệu là chung, các phần khác của chương trình có thể truy cập nó mặc dù nó được định nghĩa trong một đối tượng. Các thành phần chung của một đối tượng dùng để cung cấp một giao diện có điều khiển cho các thành thành riêng của đối tượng.

Cơ chế đóng gói (encapsulation) là phương thức tốt để thực hiện cơ chế che dấu thông tin so với các ngôn ngữ lập trình cấu trúc.

Chương 1: Tổng quan về lập trình hướng đối tượng

Biên soạn : Bùi Công Giao --------------------------------------- 4 --------------------------------------

1.3.2 Tính kế thừa

Cho phép sử dụng lại mã nguồn. Các lớp đối tượng có thể kế thừa (inheritance) từ các lớp đối tượng khác. Khi đó, trong các lớp kế thừa, có thể sử dụng các phương thức hoạt động của các lớp bị kế thừa, mà không cần phải định nghĩa lại.

Việc cho phép sử dụng lại mã nguồn được thực hiện thông qua cơ chế kế thừa trong lập trình hướng đối tượng. Theo đó:

Các lớp có thể được kế thừa nhau để tận dụng các thuộc tính, các phương thức của nhau.

Trong lớp dẫn xuất (lớp kế thừa) có thể sử dụng lại các phương thức của lớp cơ sở (lớp bị lớp khác kế thừa) mà không cần thiết phải cài đặt lại mã nguồn.

Ngay cả khi lớp dẫn xuất định nghĩa lại các phương thức cho mình, lớp cơ sở cũng không bị ảnh hưởng và không phải sửa lại bất kỳ một đoạn mã nguồn nào.

1.3.3 Tính đa hình

Khả năng để cho một thông điệp có thể thay đổi cách thực hiện của nó theo lớp cụ thể của đối tượng nhận thông điệp. Khi một lớp dẫn xuất được tạo ra, nó có thể thay đổi cách thực hiện các phương thức nào đó mà nó thừa hưởng từ lớp cơ sở của nó. Một thông điệp khi được gởi đến một đối tượng của lớp cơ sở, sẽ dùng phương thức đã định nghĩa cho nó trong lớp cơ sở. Nếu một lớp dẫn xuất định nghĩa lại một phương thức thừa hưởng từ lớp cơ sở của nó thì một thông điệp có cùng tên với phương thức này, khi được gởi tới một đối tượng của lớp dẫn xuất sẽ gọi phương thức đã định nghĩa cho lớp dẫn xuất.

Như vậy đa hình (polymorphism) là khả năng cho phép gởi cùng một thông điệp đến những đối tượng khác nhau có cùng chung một đặc điểm, nói cách khác thông điệp được gởi đi không cần biết thực thể nhận thuộc lớp nào, chỉ biết rằng tập hợp các thực thể nhận có chung một tính chất nào đó. Chẳng hạn, thông điệp vẽ hình được gởi đến cả hai đối tượng hình hộp và hình tròn. Trong hai đối tượng này đều có chung phương thức vẽ hình, tuy nhiên tuỳ theo thời điểm mà đối tượng nhận thông điệp, hình tương ứng sẽ được vẽ lên.

1.4 Trừu tượng hóa

1.4.1 Tổ chức trừu tượng theo sự phân cấp lớp

Thậm chí mặc dù nảo con người giỏi ở sự trừu tượng các khái niệm như bản đồ đường đi và cảnh quan, vẫn còn số lượng rất lớn các công việc trừu tượng hoá riêng lẻ mà chúng ta thường gặp trong cuộc sống. Để đối phó sự phức tạp này con người phải sắp xếp thông tin một cách có hệ thống theo các tiêu chuẩn nào đó; Xử lý này gọi là sự phân lớp (classification).

Ví dụ như khoa học đã phân cấp theo lớp tất cả đối tượng tự nhiên thuộc về động vật, hoặc cây, hoặc khoáng sản. Để được phân là động vật, nó phải thoả điều kiện sau:

Chương 1: Tổng quan về lập trình hướng đối tượng

Biên soạn : Bùi Công Giao --------------------------------------- 5 --------------------------------------

Vật thể sống.

Tự di chuyển.

Phản ứng khi bị kích thích.

Qui luật cho một đối tượng là cây thì hơi khác:

Vật thể sống (như động vật).

Không có hệ thần kinh.

Có màn cellulose.

Sau khi phân lớp đơn giản như trên ta có thể thêm vài qui luật để có các loại động vật cụ thể hơn cho đến khi có một sự phân cấp theo sự trừu tượng từ cao (đỉnh) xuống thấp (đáy). Sơ đồ phân cấp trừu tượng của các đối tượng tự nhiên được thể hiện như hình 1.1.

Hình 1.1 Sơ đồ phân cấp trừu tượng của các đối tượng tự nhiên.

1.4.2 Trừu tượng hóa – cơ sở của phát triển phần mềm

Khi ghi nhận các yêu cầu cho một dự án phần mềm, chúng ta thường bắt đầu bằng cách thu thập thông tin chi tiết về tình hình thực tế mà trên đó hệ thống sẽ hoạt động. Những chi tiết này thường là một sự tổng hợp của

Thông tin từ những người sử dụng hệ thống được chúng ta phỏng vấn.

Những hoạt động mà chúng ta quan sát từ nghiệp vụ hàng ngày của người sử dụng hệ thống.

Kế đến chúng ta phải chọn lọc ra những chi tiết nào liên quan tới mục đích sử dụng hệ thống. Việc này là cần thiết vì chúng ta không thể tự động hoá tất cả hệ thống nghiệp vụ bằng phần mềm! Việc bao gồm quá nhiều chi tiết sẽ làm hệ thống phức tạp và khó

Các đối tượng tự nhiên

Cây Động vật

Khoáng sản

Động vật có vú

Cá Chim Bò sát Lưởng cư Côn trùng

Chó Bò Khỉ …

Chương 1: Tổng quan về lập trình hướng đối tượng

Biên soạn : Bùi Công Giao --------------------------------------- 6 --------------------------------------

khăn hơn trong việc thiết kế, kiểm tra chương trình, dò lỗi, tạo sưu liệu, bảo trì và mở rộng chương trình sau này.

Sự trừu tưởng hoá sẽ giúp người phân tích hệ thống có được các nét đặc trưng của hệ thống trong miền (domain) bài toán, tập trung vào vấn đề của hệ thống dự định phát triển. Như khi thể hiện một người trong chương trình, màu mắt của họ có quan trọng không? Về gien ? Lương ? Sở thích ? Câu trả lời là bất kỳ đặc tính nào của người có thể liên quan hay không liên quan tuỳ thuộc vào hệ thống mà ta định phát triển là

Chương trình tính lương

Chương trình tiếp thị theo tuổi tác

CSDL bệnh nhân nhãn khoa

Hệ thống theo dõi những kẻ bị truy nã

Một khi đã xác định các đặc trưng quan trọng của một tình huống, ta cần chuẩn bị một mô hình hoá cho nó. Mô hình hoá là xử lý nhằm phát triển một khuôn mẫu để tạo ra một thứ gì đó; ví dụ như bản thiết kế ngôi nhà, sơ đồ mạch điện, một khuôn bánh. Một mô hình đối tượng của hệ thống phần mềm là một mẫu như vậy. Mô hình hoá và trừu tượng hoá luôn đi với nhau vì một mô hình là sự mô tả đồ hoạ hay vật lý của trừu tượng; Trước khi mô hình vật gì đó một cách có hiệu quả, ta phải xác định các chi tiết cần thiết của chủ thể cần được mô hình.

1.5 Xu hướng phát triển của lập trình hướng đối tượng

1.5.1 Lập trình hướng thành phần (Component-oriented programming-COP)

Xuất phát từ lập trình hướng đối tượng, tư duy lập trình hướng thành phần theo ý tưởng:

Giải quyết bài toán bằng cách xây dựng một tập các thành phần (component) có tính độc lập tương đối với nhau. Mỗi thành phần đảm nhiệm một phần công việc nhất định.

Sau đó, người ta ghép các thành phần với nhau để thu được một phần mềm thoả mãn một tập các yêu cầu xác định.

Với lập trình hướng thành phần, người ta có thể tiến hành lập trình theo phương pháp sau:

Xây dựng một thư viện các thành phần, mỗi thành phần thực hiện một công việc xác định.

Khi cần phát triển một phần mềm cụ thể, người ta chỉ cần chọn những thành phần có sẵn trong thư viện để ghép lại với nhau. Người lập trình chỉ phải phát triển thêm các thành phần mình cần mà chưa có trong thư viện.

Chương 1: Tổng quan về lập trình hướng đối tượng

Biên soạn : Bùi Công Giao --------------------------------------- 7 --------------------------------------

Phương pháp này có những ưu điểm rất lớn:

Lập trình viên có thể chia sẻ với nhau những thành phần mình đã xây dựng cho nhiều người khác dùng chung.

Khi cần, lập trình viên có thể lắp ghép các thành phần có sẵn khác nhau để tạo thành các chương trình có chức năng khác nhau. Tất cả chỉ cần dựa trên công nghệ lắp ghép thành phần, tiết kiệm được rất nhiều công sức lập trình.

Trong xu hướng lập trình hướng thành phần, một số phương pháp lập trình khác đã nảy sinh và đang phát triển mạnh mẽ:

Lập trình hướng tác nhân (Agent-Oriented Programming)

Lập trình hướng khía cạnh (Aspect-Oriented Programming-AOP)

1.5.2 Lập trình hướng tác nhân

Lập trình hướng agent có thể xem là một mức trừu tượng cao hơn của lập trình hướng thành phần.

Trong đó, các agent là các thành phần có khả năng hoạt động độc lập, tự chủ để hoàn thành công việc của mình. Hơn nữa, các agent có khả năng chủ động liên lạc với các agent khác để có thể phối hợp, cộng tác hay cạnh tranh nhau để hoàn thành nhiệm vụ.

Lập trình hướng agent có hai đặc trưng cơ bản:

Thứ nhất là khả năng tự chủ của mỗi agent để hoàn thành nhiệm vụ riêng của nó.

Thứ hai là tính tổ chức xã hội giữa các agent, cho phép các agent phối hợp, cộng tác, cạnh tranh nhau để hoàn thành nhiệm vụ chung của toàn hệ thống.

1.5.3 Lập trình hướng khía cạnh

Phương pháp lập trình hướng khía cạnh là phương pháp lập trình phát triển tư duy tách biệt các mối quan tâm khác nhau thành các mô đun khác nhau. Ở đây, một mối quan tâm thường không phải một chức năng nghiệp vụ cụ thể và có thể đóng gói mà là một khía cạnh (thuộc tính) chung mà nhiều mô đun phần mềm trong cùng hệ thống nên có, ví dụ lưu vết thao tác và lỗi (error logging).

Với AOP, chúng ta có thể cài đặt các mối quan tâm chung cắt ngang hệ thống bằng các mô đun đặc biệt gọi là aspect thay vì dàn trải chúng trên các mô đun nghiệp vụ liên quan. Các aspect sau đó được tự kết hợp với các mô đun nghiệp vụ khác bằng quá trình gọi là đan (weaving) bằng bộ biên dịch đặc biệt.

AspectJ là một công cụ AOP cho ngôn ngữ lập trình Java. Trình biên dịch AspectJ sẽ đan xen chương trình Java chính với các aspect thành các tập tin bytecode chạy trên máy ảo Java.

TỔNG KẾT CHƯƠNG 1

Nội dung chương 1 đã trình bày các vấn đề tổng quan liên quan đến phương pháp tiếp cận hướng đối tượng trong lập trình:

Chương 1: Tổng quan về lập trình hướng đối tượng

Biên soạn : Bùi Công Giao --------------------------------------- 8 --------------------------------------

Các phương pháp tiếp cận truyền thống: lập trình tuyến tính và lập trình cấu trúc.

Phương pháp tiếp cận hướng đối tượng với các đặc trưng cơ bản: đóng gói dữ liệu, tính đa hình và sử dụng lại mã nguồn.

Lập trình hướng đối tượng trên nền tảng là trừu tượng hoá là cơ sở lập trình hiện đại.

Hiện nay, lập trình hướng thành phần, lập trình hướng agent và lập trình hướng aspect tiến hoá từ lập trình hướng đối tượng đang là xu hướng phát triển mạnh mẽ.

BÀI TẬP

1. Vẽ một sơ đồ phân cấp lớp cho các lớp sau đây: táo, chuối, thịt bò, nước giải khát, phó mát, thứ có thể ăn uống, sản phẩm bơ sửa, đồ ăn, trái cây, đậu xanh, thịt, sửa, thịt heo, rau muống, rau.

2. Những đặc điểm nào của tivi mà chúng cần thiết để được trừu tượng từ góc nhìn của

Một khách hàng?

Một kỹ sư thiết kế?

Người bán?

Nhà sản xuất?

3. Chọn một bài toán mà bạn muốn mô hình hoá từ cách tiếp cận hướng đối tượng. Bạn nên chọn các lĩnh vực mà bạn quan tâm hay bạn thường gặp trong công việc hàng ngày. Giả sử bạn sẽ viết một chương trình để tự động vài chức năng của bài toán. Đầu tiên viết một trang giấy mô tả yêu cầu của chương trình. Đoạn văn đầu tiên mô tả tổng quát hệ thống dự định. Kế đến nêu rõ yêu cầu chức năng mà người sử dụng bình thường mô tả hệ thống, tránh dùng các từ kỹ thuật như, “Hệ thống này phải chạy trên môi trường Windows NT, và dùng giao thức TCP/IP để . . .”

Chương 2: Cơ bản ngôn ngữ lập trình Java

Biên soạn : Bùi Công Giao --------------------------------------- 9 --------------------------------------

Chương 2. Cơ bản ngôn ngữ lập trình Java

Trước khi tìm hiểu ngôn ngữ Java người đọc cần biết về lịch sử hình thành ngôn ngữ lập trình này.

Java được khởi đầu bởi James Gosling và bạn đồng nghiệp ở Sun Microsystems năm 1991. Ban đầu ngôn ngữ này được gọi là Oak (có nghĩa là cây sồi; do bên ngoài cơ quan của ông Gosling có trồng nhiều loại cây này), họ dự định ngôn ngữ đó thay cho C++. Java được sử dụng chủ yếu trên môi trường mạng, Internet và lập trình Game trên thiết bị di động. Không nên lẫn lộn Java với JavaScript, hai ngôn ngữ đó chỉ giống tên và loại cú pháp như C. Công ty Sun Microsystems đang giữ bản quyền và phát triển Java thường xuyên.

Java được tìm hiểu trong chương này như sau:

Kiến trúc tổng quát một chương trình xây dựng trên Java

Các toán tử và các CTDL cơ bản trên Java

Các cấu trúc lệnh của Java

2.1 Đặc trưng của ngôn ngữ Java

Java có những đặc trưng cơ bản sau:

Đơn giản

Hướng đối tượng

Độc lập phần cứng và hệ điều hành

Mạnh mẽ

Bảo mật

Phân tán

Đa luồng

Linh động

Đơn giản

Những người thiết kế mong muốn phát triển một ngôn ngữ dễ học và quen thuộc với đa số người lập trình. Do vậy Java loại bỏ các đặc trưng phức tạp của C và C++ như:

Loại bỏ thao tác con trỏ, thao tác định nghĩa chồng toán tử (operator overloading)…

Không cho phép đa kế thừa (multi-inheritance) mà sử dụng các giao diện (interface)

Không sử dụng lệnh goto cũng như file header (.h).

Loại bỏ cấu trúc struct và union.

Chương 2: Cơ bản ngôn ngữ lập trình Java

Biên soạn : Bùi Công Giao --------------------------------------- 10 --------------------------------------

Hướng đối tượng

Java là ngôn ngữ lập trình hoàn toàn hướng đối tượng:

Mọi thực thể trong hệ thống đều được coi là một đối tượng, tức là một thể hiện cụ thể của một lớp xác định.

Tất cả các chương trình đều phải nằm trong một lớp nhất định.

Không thể dùng Java để viết một chức năng mà không thuộc vào bất kỳ một lớp nào. Tức là Java không cho phép định nghĩa dữ liệu và hàm tự do trong chương trình.

Hình 2.1 Cách biên dịch truyền thống

Độc lập phần cứng và hệ điều hành

Đối với các ngôn ngữ lập trình truyền thống như C/C++, phương pháp biên dịch được thực hiện như sau (Hình 2.1): với mỗi một nền phần cứng khác nhau, có một trình biên dịch khác nhau để biên dịch mã nguồn chương trình cho phù hợp với nền phần cứng

// Chương trình C

#include <stdio.h>

main() {

printf(“Hello!”);

}

Mã nguồn viết trên C phụ thuộc môi trường hoạt động.

Trình biên dịch C cho Solaris

Trình biên dịch C cho Windows

Trình biên dịch C cho Linux

Phiên bản Solaris Phiên bản Windows Phiên bản Linux

… tạo ra

Mã nhị phân phụ thuộc môi trường hoạt động.

Chương 2: Cơ bản ngôn ngữ lập trình Java

Biên soạn : Bùi Công Giao --------------------------------------- 11 --------------------------------------

ấy. Do vậy, khi chạy trên một nền phần cứng khác, bắt buộc phải biên dịch lại mã nguồn.

Hình 2.2 Dịch chương trình Java

Đối các chương trình viết bằng Java, trình biên dịch javac sẽ biên dịch mã nguồn thành dạng mã bytecode. Sau đó, khi chạy chương trình trên các nền phần cứng khác nhau, máy ảo Java (JVM) dùng trình thông dịch Java để chuyển mã bytecode thành dạng chạy được trên các môi trường tương ứng. Do vậy, khi thay đổi môi trường (phần cứng hoặc hệ điều hành), không phải biên dịch lại mã bytecode. Hình 2.2 minh hoạ quá trình biên dịch mã nguồn Java.

Máy ảo Java là một chương trình đặc biệt để thông dịch và thực hiện mã nguồn Java. Khi chương trình Java chạy nó sẽ nằm dưới sự quản lý của máy ảo này, và máy ảo thì chịu sự quản lý của hệ điều hành của một loại máy tính cụ thể. Máo ảo sẽ thông dịch mã bytecode thành ngôn ngữ máy mà máy tính có thể hiểu được. Như vậy sẽ có nhiều máy ảo Java cho từng môi trường cụ thể. Hình 2.3 minh hoạ quá trình thực thi của mã nguồn Java.

// Chương trình Java

public class Hello {

public static void main( String[ ] args) {

System.out.println(“Hello!”);

}

}

Mã nguồn Java độc lập môi trường hoạt động.

Trình biên dịch Java cho Solaris

Trình biên dịch Java cho Windows

Trình biên dịch Java cho Linux

Mã bytecode độc lập môi trường hoạt động.

… tạo ra

Chương 2: Cơ bản ngôn ngữ lập trình Java

Biên soạn : Bùi Công Giao --------------------------------------- 12 --------------------------------------

Do chương trình Java phải chạy dưới máy ảo nên tốc độ thực hiện của nó hợi chậm hơn các chương trình được biên dịch và chạy trực tiếp trên hệ điều hành. Tuy nhiên tốc độ máy tính ngày càng cao thì sự chênh lệch về tốc độ không đáng kể.

Khi chuyển mã bytecode từ môi trường này sang môi trường khác như từ Windows sang Linux thì ta không cần phải biên dịch lại chương trình. Hình 2.4 minh hoạ điều này.

Hình 2.3 Máy ảo Java thực hiện mã bytecode độc lập môi trường hoạt động

Mạnh mẽ

Java là ngôn ngữ yêu cầu chặt chẽ về kiểu dữ liệu:

Kiểu dữ liệu phải được khai báo tường minh.

Java không sử dụng con trỏ và các phép toán con trỏ.

Java kiểm tra việc truy nhập đến mảng, chuỗi khi thực thi để đảm bảo rằng các truy nhập đó không ra ngoài giới hạn kích thước mảng.

Quá trình cấp phát, giải phóng bộ nhớ cho biến được thực hiện tự động, nhờ dịch vụ thu rác những đối tượng không còn sử dụng nữa.

Mã nhị phân đã được biên dịch viết bằng C hay C++ chạy trực tiếp trên một kiến trúc phần cứng cụ thể (dưới sự quản lý của hệ điều hành)

Máy ảo Java chạy trên một kiến trúc phần cứng cụ thể (dưới sự quản lý của hệ điều hành)

Mã bytecode độc lập môi trường hoạt động chạy dưới sự quản lý của máy ảo

Chương 2: Cơ bản ngôn ngữ lập trình Java

Biên soạn : Bùi Công Giao --------------------------------------- 13 --------------------------------------

Cơ chế bẫy lỗi của Java giúp đơn giản hóa quá trình xử lý lỗi và hồi phục sau lỗi.

Hình 2.4 Cùng mã bytecode có thể được hiểu bởi hai máy ảo

Bảo mật

Java cung cấp một môi trường quản lý thực thi chương trình với nhiều mức để kiểm soát tính an toàn:

Ở mức thứ nhất, dữ liệu và các phương thức được đóng gói bên trong lớp. Chúng chỉ được truy xuất thông qua các giao diện mà lớp cung cấp.

Ở mức thứ hai, trình biên dịch kiểm soát để đảm bảo mã là an toàn, và tuân theo các nguyên tắc của Java.

Mức thứ ba được đảm bảo bởi trình thông dịch. Chúng kiểm tra xem mã bytecode có đảm bảo các qui tắc an toàn trước khi thực thi.

Mức thứ tư kiểm soát việc nạp các lớp vào bộ nhớ để giám sát việc vi phạm giới hạn truy xuất trước khi nạp vào hệ thống.

Máy ảo Java phụ thuộc môi trường hoạt động B

Máy ảo Java phụ thuộc môi trường hoạt động A

A B

Chương 2: Cơ bản ngôn ngữ lập trình Java

Biên soạn : Bùi Công Giao --------------------------------------- 14 --------------------------------------

Phân tán

Java được thiết kế để hỗ trợ các ứng dụng chạy trên mạng bằng các lớp mạng (java.net). Hơn nữa, Java hỗ trợ nhiều nền chạy khác nhau nên chúng được sử dụng rộng rãi như là công cụ phát triển trên Internet, nơi sử dụng nhiều nền khác nhau.

Đa luồng

Chương trình Java cung cấp giải pháp đa luồng (multi threading) để thực thi các công việc cùng đồng thời và đồng bộ giữa các luồng.

Linh động

Java được thiết kế như một ngôn ngữ động để đáp ứng cho những môi trường mở. Các chương trình Java chứa rất nhiều thông tin thực thi nhằm kiểm soát và truy nhập đối tượng lúc chạy. Điều này cho phép khả năng liên kết động mã.

2.2 Kiến trúc chương trình và cơ chế thực thi của Java

Dạng cơ bản của một tập tin mã nguồn Java có cấu trúc như hình 2.5 sau :

Hình 2.5 Phân tích một chương trình Java đơn giản

Một tập tin chứa mã nguồn Java có thể có ba phần chính:

o Phần khai báo tên gói (khối) bằng từ khoá package.

o Phần khai báo thư viện tham khảo bằng từ khoá import.

o Phần khai báo nội dung lớp bằng từ khoá class.

Khai báo package

Package được dùng để đóng gói các lớp trong chương trình lại với nhau thành một khối. Đây là một cách hữu hiệu để lưu trữ các lớp gần giống nhau hoặc có cùng một mô đun thành một khối thống nhất.

Cú pháp khai báo tên gói bằng từ khoá package:

package <Tên gói>;

// Một chương trình Java đơn giản

package packageName;

import java.awt.*;

class ClassName {

int i; // khai báo thuộc tính

public void methodName() {

statements; // lệnh thực hiện

}

}

Đây là dòng ghi chú

Khai báo lớp Khai báo phương thức

Khai báo tên gói, nếu có

Khai báo tên thư viện sẵn có, nếu cần dùng

Chương 2: Cơ bản ngôn ngữ lập trình Java

Biên soạn : Bùi Công Giao --------------------------------------- 15 --------------------------------------

Ưu điểm của package:

Cho phép nhóm các lớp vào với nhau thành các đơn vị nhỏ hơn. Việc thao tác trên các đơn vị khối sẽ gọn hơn thao tác trên một tập các lớp.

Tránh việc xung đột khi đặt tên lớp. Vì các lớp không cùng gói có thể đặt tên trùng nhau. Khi số lượng lớp của chương trình quá lớn ta có thể tránh phải đặt tên khác nhau cho các lớp bằng cách đặt chúng vào các gói khác nhau.

Cho phép bảo vệ các lớp. Khi chương trình lớn, việc chia nhỏ chương trình thành các gói sẽ thuận lợi hơn cho việc quản lý và phát triển.

Tên gói còn được dùng để định danh lớp trong ứng dụng.

Lưu ý:

Dòng lệnh khai báo tên gói phải được đặt đầu tiên trong tập tin mã chương trình.

Chỉ được khai báo tối đa một tên gói đối với mỗi tập mã nguồn Java.

Các tập tin của các lớp nằm cùng gói ứng dụng phải được lưu trong cùng một thư mục (tên thư mục là tên gói) theo cấu trúc của dự án.

Tên gói nên đặt theo chữ thường vì tên gói sẽ là tên thư mục tương ứng trong ổ đĩa, tránh nhầm lẫn với tên các tập tin là tên các lớp của chương trình.

Khi không nhóm các lớp trong gói (do chương trình đơn giản), thì không cần thiết phải khai báo tên gói ở đầu tập tin. Khi đó lớp ở gói mặc định không có tên.

Khai báo thư viện

Khai báo thư viện để chỉ ra những thư viện đã được định nghĩa sẵn mà chương trình sẽ tham khảo tới. Cú pháp khai báo thư viện với từ khoá import như sau:

import <Tên thư viện>;

Java chuẩn cung cấp một số thư viện như sau:

java.lang: cung cấp các hàm thao tác trên các kiểu dữ liệu cơ bản, xử lý lỗi và ngoại lệ, xử lý vào ra trên các thiết bị chuẩn như bàn phím và màn hình.

java.applet: cung cấp các hàm cho xây dựng các applet (sẽ trình bày trong Chương 9).

java.awt: cung cấp các hàm cho xây dựng các ứng dụng đồ hoạ với các thành phần giao diện multimedia (sẽ trình bày trong Chương 9).

java.io: cung cấp các hàm xử lý vào/ra trên các thiêt bị chuẩn và các thiết bị ngoại vi (sẽ trình bày trong Chương 8).

java.util: cung cấp các hàm tiện ích trong xử lý liên quan đến các kiểu dữ liệu có cấu trúc như ArrayList, HashMap, TreeMap,… (sẽ trình bày trong Chương 6)

Chương 2: Cơ bản ngôn ngữ lập trình Java

Biên soạn : Bùi Công Giao --------------------------------------- 16 --------------------------------------

Lưu ý:

Nếu muốn khai báo tham khảo nhiều thư viện, phải khai báo tham khảo mỗi thư viện với một từ khoá import.

Nếu chỉ tham khảo một vài lớp trong một thư viện, nên chỉ rõ tham khảo lớp nào, thay vì phải khai báo tham khảo cả gói (bằng kí hiệu “*”) vì tham khảo cả gói sẽ tăng kích cỡ tập tin class sau khi biên dịch.

Nếu không tham khảo thư viện nào, không cần thiết phải khai báo các tham khảo với từ khoá import.

Khai báo lớp

Phần này luôn bắt buộc phải có đối với một tập tin Java. Phần này có dạng như sau:

<tính chất> class <Tên lớp> {

}

Trong Java tên lớp thường đặt theo kiểu Pascal (các ký tự đầu tiên của từ là chữ in) như SimpleProgram, FirstClass,..

Lớp có ba tính chất đặc trưng bởi ba từ khoá:

public: lớp có thể được truy cập từ các gói khác. Nếu không khai báo public thì ngầm định lớp chỉ có thể được truy xuất trong cùng một gói.

final: khai báo lớp hằng, lớp này không thể tạo dẫn xuất; Tức là không có lớp nào kế thừa được từ các lớp có tính chất final.

abstract: khai báo lớp trừu tượng, lớp này chứa các phương thức trừu tượng. Không thể tạo các thể hiện của các lớp trừu tượng bằng toán tử new như các lớp thông thường.

Lưu ý:

Chỉ có tính chất public hoặc không khai báo tính chất (package) có thể sử dụng cho lớp. và lớp này có cùng tên tập tin. Ví dụ nếu tập tin là Xyz.java thì phải có lớp Xyz trong tập tin này.

Nếu có nhiều hơn một lớp public trong một tập tin, trình biên dịch sẽ báo lỗi.

Để sử dụng lớp từ một gói khác, ta phải viết tên gói trước tên lớp. Ví dụ chuong2.LopMinhHoa

Ngoài ba phần cơ bản của một tập tin Java như trên, có những điều ta cần phải biết như sau:

Ghi chú

Java hỗ trợ ba kiểu ghi chú: truyền thống, kết thúc dòng, và ghi chú sưu liệu Java (Java documentation comments)

Ghi chú truyền thống: ghi chú bắt đầu bằng /* kết thúc là */ như

/* Giải phương trình bậc hai

Chương 2: Cơ bản ngôn ngữ lập trình Java

Biên soạn : Bùi Công Giao --------------------------------------- 17 --------------------------------------

ax2+bx+c=0 */

Ghi chú kết thúc dòng: ghi chú bắt đầu bằng // như

// Giải phương trình bậc hai

// ax2+bx+c=0

Ghi chú sưu liệu Java : ghi chú được tạo ra bởi tiện ích javadoc

Phương thức main bắt buộc phải có với mọi chương trình Java và là điểm bắt đầu thực hiện chương trình, cú pháp của phương thức này:

public static void main( String [ ] args) {

// các câu lệnh

}

Đây là phương thức chính, từ đây chương trình bắt đầu việc thực thi của mình. Tất cả các ứng dụng Java đều sử dụng một phương thức main này.

Từ khoá public là một chỉ định truy xuất. Nó cho biết thành viên của lớp có thể được truy xuất từ bất cứ đâu trong chương trình.

Từ khoá static cho phép main được gọi tới mà không cần tạo ra một thể hiện (instance) của lớp. Nó không phụ thuộc vào các thể hiện của lớp được tạo ra.

Từ khoá void thông báo cho máy tính biết rằng phương thức sẽ không trả lại bất cứ giá trị nào khi thực thi chương trình.

args là danh sách tham số cho chương trình.

Ví dụ sau minh hoạ cách sử dụng tham số

class PassArgument {

public static void main(String args[ ]) {

System.out.println(“Danh sách tham số phương thức main nhận được:”);

System.out.println(args[0]);

System.out.println(args[1]);

System.out.println(args[2]);

}

}

Phương thức in ra màn hình có thể sử dụng System.out.println (in xong xuống dòng) và System.out.print(in không xuống dòng)

Thực thi của Java

Chương trình Java có được soạn thảo bằng notepad trong Windows, vi trong Linux hay các IDE như eclipse, Netbean, Jcreator Pro, Jbuilder… Nếu chạy trong cửa sổ dòng lệnh của Windows, ta có thể biên dịch chương trình Java ra mã bytecode như sau:

javac sourcecode_filename

Chương 2: Cơ bản ngôn ngữ lập trình Java

Biên soạn : Bùi Công Giao --------------------------------------- 18 --------------------------------------

ví dụ:

javac PassArgument.java

javac Foo.java Bar.java Another.java

hoặc sử dụng *:

javac *.java

Nếu biên dịch thành công các tập tin .class chứa mã bytecode

Để chạy mã bytecode dùng lệnh sau:

java bytecode_filename (bỏ qua đuôi tập tin.class)

ví dụ

java PassArgument

sẽ cho kết quả như sau:

Đây là danh sách tham số phương thức main nhận được A

123

B1

Chú ý khi chạy trong một IDE, việc biên dịch và chạy chương trình Java được thực hiện bằng cách chọn lệnh trong menu.

2.3 Các kiểu dữ liệu cơ bản và biến

2.3.1 Kiểu dữ liệu cơ bản

byte: Dùng để lưu dữ liệu kiểu số nguyên có kích thước một byte (8 bit). Phạm vi biểu diễn giá trị từ -128 đến 127. Giá trị mặc định là 0.

char: Dùng để lưu dữ liệu kiểu kí tự hoặc số nguyên không âm có kích thước 2 byte (16 bit). Phạm vi biểu diễn giá trị từ \u0000 đến \uFFFF. Giá trị mặc định là 0.

boolean: Dùng để lưu dữ liệu chỉ có hai trạng thái đúng hoặc sai (độ lớn chỉ có 1 bit). Phạm vi biểu diễn giá trị là true, false. Giá trị mặc định là false.

short: Dùng để lưu dữ liệu có kiểu số nguyên, kích cỡ 2 byte (16 bit). Phạm vi biểu diễn giá trị từ - 32768 đến 32767. Giá trị mặc định là 0.

int: Dùng để lưu dữ liệu có kiểu số nguyên, kích cỡ 4 byte (32 bit). Phạm vi biểu diễn giá trị từ -2,147,483,648 đến 2,147,483,647. Giá trị mặc định là 0.

float: Dùng để lưu dữ liệu có kiểu số thực, kích cỡ 4 byte (32 bit). Giá trị mặc định là 0.0f.

double: Dùng để lưu dữ liệu có kiểu số thực có kích thước lên đến 8 byte. Giá trị mặc định là 0.00d.

long: Dùng để lưu dữ liệu có kiểu số nguyên có kích thước lên đến 8 byte. Giá trị mặc định là 0l.

Chương 2: Cơ bản ngôn ngữ lập trình Java

Biên soạn : Bùi Công Giao --------------------------------------- 19 --------------------------------------

2.3.2 Biến

2.3.2.1 Khai báo biến

Cú pháp khai báo biến:

dataType varName;

Trong đó, dataType là kiểu dữ liệu của biến, varName là tên biến. Trong Java, việc đặt tên biến phải tuân theo các quy tắc sau:

Chỉ được bắt đầu bằng một kí tự (chữ), hoặc một dấu gạch dưới, hoặc một kí tự $

Không có khoảng trắng giữa tên

Bắt đầu từ kí tự thứ hai, có thể dùng các kí tự (chữ), chữ số, dấu $, dấu gạch dưới

Không trùng với các từ khoá

Dòng lệnh sau không biên dịch vì public là một từ khoá Java:

int public;

Trình biên dịch sẽ báo lỗi đoạn lệnh sau:

not a statement

int public;

^

';' expected

int public;

^

Có phân biệt chữ hoa chữ thường

Cách đặt tên biến nên theo kiểu camel (ký tự đầu tiên là chữ thường, các ký tự đầu tiên của từ kế tiếp là chữ in) như

int grade;

double averageGrade;

String myPetRat;

boolean weAreFinished;

Các biến sau đây là hợp lệ:

int simple;

int _under;

int more$money_is_2much;

Các biến sau đây không hợp lệ:

int 1bad;

int number#sign;

Chương 2: Cơ bản ngôn ngữ lập trình Java

Biên soạn : Bùi Công Giao --------------------------------------- 20 --------------------------------------

int foo-bar;

int plus+sign;

int x@y;

int dot.notation;

2.3.2.2 Phạm vi hoạt động của biến

Một biến có phạm vi hoạt động trong toàn bộ khối lệnh mà nó được khai báo. Một khối lệnh bắt đầu bằng dấu { và kết thúc bằng dấu }

Nếu biến được khai báo trong một cấu trúc lệnh điều khiển, biến đó có phạm vi hoạt động trong khối lệnh tương ứng.

Nếu biến được khai báo trong một phương thức (không nằm trong khối lệnh nào), biến đó có phạm vi hoạt động trong phương thức tương ứng: có thể được sử dụng trong tất cả các khối lệnh của phương thức.

Nếu biến được khai báo trong một lớp (không nằm trong trong một phương thức nào), biến đó có phạm vi hoạt động trong toàn bộ lớp tương ứng: có thể được sử dụng trong tất cả các phương thức của lớp.

2.3.2.3 Khởi tạo biến

Không nhất thiết phải gán giá trị cho biến khi nó vừa được khai báo, nhưng biến phải có giá trị xác định khi chúng lần đầu được sử dụng. Ví dụ sau hai biến nguyên được khai báo, biến foo được gán giá trị, biến bar thì không.

int foo;

int bar;

foo = 3;

foo = foo + bar; // Dòng lệnh này không biên dịch.

Trình biên dịch sẽ báo lỗi đoạn lệnh sau:

variable bar might not have been initialized

foo = foo + bar;

^

2.3.2.4 Ép kiểu

Ví dụ, nhiều khi gặp tình huống cần cộng một biến có dạng int với một biến có dạng float. Để xử lý tình huống này, Java sử dụng tính năng ép kiểu (type casting) của C/C++. Đoạn mã sau đây thực hiện phép cộng một giá trị dấu phẩy động với một giá trị nguyên.

float c = 35.8f;

int b = (int)c + 1;

Đầu tiên giá trị dấu phảy động c được đổi thành giá trị nguyên 35. Sau đó nó được cộng với 1 và kết quả là giá trị 36 được lưu vào b.

Chương 2: Cơ bản ngôn ngữ lập trình Java

Biên soạn : Bùi Công Giao --------------------------------------- 21 --------------------------------------

Trong Java có hai loại ép kiểu dữ liệu:

Nới rộng (widening): quá trình làm tròn số từ kiểu dữ liệu có kích thước nhỏ hơn sang kiểu có kích thước lớn hơn. Kiểu biến đổi này không làm mất thông tin. Ví dụ chuyển từ int sang float. Chuyển kiểu loại này có thế được thực hiện ngầm định bởi trình biên dịch.

Thu hẹp (narrowwing): quá trình làm tròn số từ kiểu dữ liệu có kích thước lớn hơn sang kiểu có kích thước nhỏ hơn. Kiểu biến đổi này có thể làm mất thông tin như ví dụ ở trên.

Chuyển kiểu loại này không thể thực hiện ngầm định bởi trình biên dịch, người dùng phải thực hiện chuyển kiểu tường minh.

2.3.2.5 Kiểu String

Kiểu chuổi trong Java là String. Nếu khai báo

string s = "foo"; // báo lỗi.

Thông báo lỗi

cannot find symbol symbol: string

Để ghép chuỗi với nhau hay chuỗi với số i nhau dùng toán tử +

Ví dụ:

String x = "foo";

String y = "bar";

int no = 33;

String z = x + y + "!" + no; // z được gán giá trị "foobar! 33" (giá trị của x và y không đổi)

Để gán chuỗi rỗng

String s = "";

Để gán giá trị null (chưa được gán giá trị lần nào)

String s = null;

2.4 Các toán tử và biểu thức

2.4.1 Các toán tử

Java cung cấp các dạng toán tử sau:

Toán tử số học

Toán tử bit

Toán tử quan hệ

Toán tử logic

Toán tử điều kiện

Chương 2: Cơ bản ngôn ngữ lập trình Java

Biên soạn : Bùi Công Giao --------------------------------------- 22 --------------------------------------

Toán tử gán

Toán tử số học

Các toán hạng của các toán tử số học phải ở dạng số. Các toán hạng kiểu boolean không sử dụng được, song các toán hạng ký tự cho phép sử dụng loại toán tử này. Một vài kiểu toán tử được liệt kê trong bảng dưới đây.

Toán tử Mô tả

+ Cộng

- Trừ

* Nhân

/ Chia

% Lấy số chia dư, ví dụ 10%3=1

++ Tăng giá trị biến lên 1

-- Giãm giá trị biến lên 1

+= Cộng và gán giá trị

Cộng các giá trị của toán hạng bên trái vào toán hạng bên phải và gán giá trị trả về vào toán hạng bên trái. Ví dụ c += a tương đương c = c + a

-= Trừ và gán giá trị

Trừ các giá trị của toán hạng bên trái vào toán toán hạng bên phải và gán giá trị trả về vào toán hạng bên trái. Ví dụ c -= a tương đương với

c = c - a

*= Nhân và gán

Nhân các giá trị của toán hạng bên trái với toán toán hạng bên phải và gán giá trị trả về vào toán hạng bên trái. Ví dụ c *= a tương đương với

c = c*a

/= Chia và gán

Chia giá trị của toán hạng bên trái cho toán toán hạng bên phải và gán giá trị trả về vào toán hạng bên trái. Ví dụ c /= a tương đương với c = c/a

%= Lấy số dư và gán

Chia giá trị của toán hạng bên trái cho toán toán hạng bên phải và gán giá trị số dư vào toán hạng bên trái. Ví dụ c %= a tương đương với

c = c%a

Bảng 2.1 Các toán tử số học

Toán tử Bit

Các toán tử dạng bit cho phép ta thao tác trên từng bit riêng biệt trong các kiểu dữ liệu nguyên thuỷ.

Chương 2: Cơ bản ngôn ngữ lập trình Java

Biên soạn : Bùi Công Giao --------------------------------------- 23 --------------------------------------

Toán tử Mô tả

~ Phủ định bit (NOT)

Trả về giá trị phủ định của một bit.

& Toán tử AND bit

Trả về giá trị là 1 nếu các toán hạng là 1 và 0 trong các trường hợp khác.

| Toán tử OR bit

Trả về giá trị là 1 nếu một trong các toán hạng là 1 và 0 trong các trường hợp khác.

^ Toán tử Exclusive OR bit

Trả về giá trị là 1 nếu chỉ một trong các toán hạng là 1 và trả về 0 trong các trường hợp khác.

>> Dịch sang phải bit

Chuyển toàn bộ các bit cuả một số sang phải một vị trí, giữ nguyên dấu của số âm. Toán hạng bên trái là số bị dịch còn số bên phải chỉ số vị trí mà các bit cần dịch.

<< Dịch sang trái bit

Chuyển toàn bộ các bit cuả một số sang trái một vị trí, giữ nguyên dấu cuả số âm. Toán hạng bên trái là số bị dịch còn số bên phải chỉ số vị trí mà các bit cần dịch.

Bảng 2.2 Các toán tử bit

Các toán tử quan hệ

Các toán tử quan hệ kiểm tra mối quan hệ giữa hai toán hạng. Kết quả của một biểu thức có dùng các toán tử quan hệ là những giá trị boolean (true hoặc false). Các toán tử quan hệ được sử dụng trong các cấu trúc điều khiển.

Toán tử Mô tả

= = So sánh bằng

Toán tử này kiểm tra sự tương đương của hai toán hạng

!= So sánh khác

Kiểm tra sự khác nhau của hai toán hạng.

> Lớn hơn

Kiểm tra giá trị của toán hạng bên phải lớn hơn toán hạng bên trái hay không

< Nhỏ hơn

Kiểm tra giá trị của toán hạng bên phải có nhỏ hơn toán hạng bên trái hay không

>= Lớn hơn hoặc bằng

Chương 2: Cơ bản ngôn ngữ lập trình Java

Biên soạn : Bùi Công Giao --------------------------------------- 24 --------------------------------------

Kiểm tra giá trị của toán hạng bên phải có lớn hơn hoặc bằng toán hạng bên trái hay không

<= Nhỏ hơn hoặc bằng

Kiểm tra giá trị của toán hạng bên phải có nhỏ hơn hoặc bằng toán hạng bên trái hay không.

Bảng 2.3 Các toán tử quan hệ.

Các toán tử logic

Các toán tử logic làm việc với các toán hạng Boolean. Một vài toán tử kiểu này được chỉ ra dưới đây

Toán tử Mô tả

&& Và (AND)

Trả về một giá trị true nếu chỉ khi cả hai toán tử có giá trị true

|| Hoặc (OR)

Trả về giá trị true nếu ít nhất một giá trị là true.

^ XOR

Trả về giá trị true nếu và chỉ nếu chỉ một trong các giá trị là true, các trường hợp còn lại cho giá trị false

! Toán hạng đơn tử NOT. Chuyển giá trị từ true sang false và ngược lại.

Bảng 2.4 Các toán tử logic

Các toán tử điều kiện

Toán tử điều kiện là một loại toán tử đặc biệt vì nó bao gồm ba thành phần cấu thành biểu thức điều kiện.

Cú pháp:

<biểu thức 1> ? <biểu thức 2> : <biểu thức 3>;

biểu thức 1: biểu thức logic. Trả về giá trị true hoặc false

biểu thức 2: là giá trị trả về nếu <biểu thức 1> xác định là true

biểu thức 3: là giá trị trả về nếu <biểu thức 1> xác định là false

Toán tử gán

Toán tử gán (=) dùng để gán một giá trị vào một biến và có thể gán nhiều giá trị cho nhiều biến cùng một lúc. Ví dụ đoạn lệnh sau gán một giá trị cho biến var và giá trị này lại được gán cho nhiều biến trên một dòng lệnh đơn.

int var = 20;

int p,q,r,s;

p=q=r=s=var;

Dòng lệnh cuối cùng được thực hiện từ phải qua trái. Đầu tiên giá trị ở biến var được gán cho s, sau đó giá trị của s được gán cho r và cứ tiếp như vậy.

Chương 2: Cơ bản ngôn ngữ lập trình Java

Biên soạn : Bùi Công Giao --------------------------------------- 25 --------------------------------------

Thứ tự ưu tiên của các toán tử

Các biểu thức được viết ra nói chung gồm nhiều toán tử. Thứ tự ưu tiên quyết định trật tự thực hiện các toán tử trên các biểu thức. Bảng dưới đây liệt kê thứ tự thực hiện các toán tử trong Java

Thứ tự Toán tử

1 Các toán tử đơn như +, -, ++, --

2 Các toán tử số học và các toán tử dịch như *, /, +, -, <<, >>

3 Các toán tử quan hệ như >, <, >=, <=, ==, !=

4 Các toán tử logic và bit như &&, ||, &, |, ^

5 Các toán tử gán như =, *=, /=, +=, -=

Bảng 2.5 Thứ tự ưu tiên các toán tử

Thay đổi thứ tự ưu tiên

Để thay đổi thứ tự ưu tiên trên một biểu thức, ta có thể sử dụng dấu ngoặc đơn ():

Phần được giới hạn trong ngoặc đơn được thực hiện trước.

Nếu dùng nhiều ngoặc đơn lồng nhau thì toán tử nằm trong ngoặc đơn phía trong sẽ thực thi trước, sau đó đến các vòng phía ngoài.

Trong phạm vi một cặp ngoặc đơn thì quy tắc thứ tự ưu tiên vẫn giữ nguyên tác dụng.

Ví dụ:

int x = 1;

int y = 2;

int z = 3;

int answer = ((8 * (y + z)) + y) * x;

sẽ được lượng giá như sau:

((8 * (y + z)) + y) * x

((8 * 5) + y) * x

(40 + y) * x

42 * x

42

2.4.2 Biểu thức

Một biểu thức đơn giản trong Java là

Hằng: 7, false

Một ký tự: ‘A’, ‘3’

Một chuỗi: “foo”, “Java”

Tên của biến: myString, x

Chương 2: Cơ bản ngôn ngữ lập trình Java

Biên soạn : Bùi Công Giao --------------------------------------- 26 --------------------------------------

Sự kết hợp của hai toán hạng bởi một toán tử hai ngôi: x + 2

Một biến và toán tử một ngôi đi kèm: i++

Biểu thức trong cặp dấu (): (x + 2)

Biểu thức có thể là sự kết hợp của các biểu thức khác được lồng nhau trong các cặp ():

((((4/x) + y) * 7) + z).

Kiểu của biểu thức là kiểu giá trị trả về sau khi lượng giá biểu thức

Ví dụ 1 :

double x = 3.0 ;

double y = 2.0 ;

if ((x > 2.0) && (y != 4.0)) { … }

biểu thức (x > 2.0) && (y != 4.0) lượng giá tới true, vì vậy biểu thức này có kiểu boolean.

Ví dụ 2 :

int x = 1 ;

int y = 2 ;

int z = 3 ;

int answer = ((8 * (y + z)) + y) * x ;

biểu thức ((8 * (y + z)) + y) * x lượng giá tới 4, vì vậy biểu thức này có kiểu int.

2.5 Các cấu trúc lệnh

Java cung cấp hai loại cấu trúc điều khiển:

Điều khiển rẽ nhánh

if-else

swich-case

Vòng lặp (loop)

while

do-while

for

2.5.1 Lệnh if-else

Lệnh if-else kiểm tra giá trị dạng boolean của điều kiện. Nếu giá trị điều kiện là true thì chỉ có khối lệnh sau if sẽ được thực hiện, nếu là false thì chỉ có khối lệnh sau else được thực hiện.

Cú pháp:

if (điều kiện) {

khối lệnh 1;

} else {

Chương 2: Cơ bản ngôn ngữ lập trình Java

Biên soạn : Bùi Công Giao --------------------------------------- 27 --------------------------------------

khối lệnh 2;

}

điều kiện: Biểu thức boolean như toán tử so sánh.

khối lệnh 1: Khối lệnh được thực thi khi giá trị điều kiện là true.

khối lệnh 2: Khối lệnh được thực thi nếu điều kiện trả về giá trị false.

Đoạn chương trình sau kiểm tra xem các số có chia hết cho 5 hay không.

int num=10;

If (num%5 == 0)

System.out.println (num + “chia hết cho 5!”);

else

System.out.println (num + ”không chia hết cho 5!”);

Ở đoạn chương trình trên num được gán giá trị nguyên là 10. Trong câu lệnh if-else điều kiện num%5 trả về giá trị 0 và điều kiện thực hiện là true. Thông báo 10 chia hết cho 5 được in ra. Lưu ý rằng vì chỉ có một câu lệnh được viết trong đoạn if và else, bởi vậy không cần thiết phải được đưa vào dấu ngoặc móc { và }.

2.5.2 Lệnh switch-case

Khối lệnh switch-case có thể được sử dụng thay thế câu lệnh if-else trong trường hợp một biểu thức cho ra nhiều kết quả.

Cú pháp:

swich (biểu thức) {

case ‘giá trị 1’: khối lệnh 1;

break;

case ‘giá trị 2’: khối lệnh 2;

break;

…………………

case ‘giá trị N’: khối lệnh N;

break;

default: khối lệnh mặc định;

}

biểu thức : Biến chứa một giá trị xác định.

giá trị 1, giá trị 2, …. giá trị N: Các giá trị hằng số phù hợp với giá trị trên biểu thức.

khối lệnh 1, khối lệnh 1… khối lệnh N: Khối lệnh được thực thi khi trường hợp tương ứng có giá trị true.

break: Từ khoá được sử dụng để bỏ qua tất cả các câu lệnh sau đó và chuyển điều khiển tới cấu trúc sau switch.

Chương 2: Cơ bản ngôn ngữ lập trình Java

Biên soạn : Bùi Công Giao --------------------------------------- 28 --------------------------------------

default: Từ khóa tuỳ chọn được sử dụng để chỉ rõ các câu lệnh nào được thực hiện chỉ khi tất cả các trường hợp nhận giá trị false.

khối lệnh mặc định: Khối lệnh được thực hiện chỉ khi tất cả các trường hợp nhận giá trị false Đoạn chương trình sau xác định giá trị trong một biến nguyên và hiển thị ngày trong tuần được thể hiện dưới dạng chuỗi. Để kiểm tra các giá trị nằm trong khoảng từ 0 đến 6, chương trình sẽ thông báo lỗi nếu nằm ngoài phạm vi trên.

Ví dụ sau xét in ra ngày:

int day = 2;

switch(day) {

case 0 : System.out.println(“Sunday”);

break;

case 1 : System.out.println(“Monday”);

break;

case 2 : System.out.println(“Tuesday”);

break;

case 3 : System.out.println(“Wednesday”);

break;

case 4 : System.out.println(“Thursday”);

break;

case 5: System.out.println(“Friday”);

break;

case 6 : System.out.println(“Satuday”);

break;

default: System.out.println(“Invalid day of week”);

}

Đoạn lệnh sẽ hiển thị Tuesday.

2.5.3 Vòng lặp while

Vòng lặp while thực thi khối lệnh khi điều kiện vẫn là true và dừng lại khi điều kiện thực thi nhận giá trị false. Cú pháp:

while(điều kiện) {

khối lệnh;

}

điều kiện: có giá trị boolean; vòng lặp sẽ tiếp tục cho nếu điều kiện vẫn có giá trị true.

khối lệnh: Khối lệnh được thực hiện nếu điều kiện nhận giá trị true

Đoạn chương trình sau tính tổng của 5 số tự nhiên đầu tiên dùng cấu trúc while.

int a = 5, sum = 1;

while (a >= 1) {

Chương 2: Cơ bản ngôn ngữ lập trình Java

Biên soạn : Bùi Công Giao --------------------------------------- 29 --------------------------------------

sum +=a;

a--;

}

System.out.println(“The sum is ”+sum);

Ở ví dụ trên, vòng lặp được thực thi cho đến khi điều kiện a>=1 là true. Biến a được khai báo bên ngoài vòng lặp và được gán giá trị là 5. Cuối mỗi vòng lặp, giá tri của a giảm đi 1. Sau năm vòng giá trị của a bằng 0. Điều kiện trả về giá trị false và vòng lặp kết thúc. Kết quả sẽ được hiển thị The sum is 15

2.5.4 Vòng lặp do-while

Vòng lặp do-while thực thi khối lệnh khi mà điều kiện là true, tương tự như vòng lặp while, ngoại trừ do-while thực hiện lệnh ít nhất một lần ngay cả khi điều kiện là false. Cú pháp:

do {

khối lệnh;

} while(điều kiện);

điều kiện: Biểu thức boolean; vòng lặp sẽ tiếp tục khi mà điều kiện vẫn có giá trị true.

khối lệnh: Khối lệnh luôn được thực hiện ở lần thứ nhất, từ vòng lặp thứ hai, chúng được thực hiện khi điều kiện nhận giá trị true.

Ví dụ sau tính tổng của 5 số tự nhiên đầu tiên dùng cấu trúc do-while.

int a = 1,sum = 0;

do {

sum += a;

a++;

} while (a <= 5);

System.out.println(“Sum of 1 to 5 is ”+sum);

Biến a được khởi tạo với giá trị 1, sau đó nó vừa được dùng làm biến chạy (tăng lên 1 sau mỗi lần lặp) vừa được dùng để cộng dồn vào biến sum. Tại thời điểm kết thúc, chương trình sẽ in ra

Sum of 1 to 5 is 15.

2.5.5 Vòng lặp for

Vòng lặp for cung cấp một dạng kết hợp tất cả các đặc điểm chung của tất cả các loại vòng lặp: giá trị khởi tạo của biến chạy, điều kiện dừng của vòng lặp và lệnh thay đổi giá trị của biến chạy.

Cú pháp:

for(khối lệnh khởi tạo; điều kiện; khối lệnh thay đổi biến chạy) {

khối lệnh;

Chương 2: Cơ bản ngôn ngữ lập trình Java

Biên soạn : Bùi Công Giao --------------------------------------- 30 --------------------------------------

}

khối lệnh khởi tạo: khởi tạo giá trị ban đầu cho các biến chạy, các lệnh khởi tạo được phân cách nhau bởi dấu phẩy và chỉ thực hiện duy nhất một lần vào thời điểm bắt đầu của vòng lặp.

điều kiện: Biểu thức boolean; vòng lặp sẽ tiếp tục cho đến khi nào điều kiện có giá trị false.

khối lệnh thay đổi biến chạy: các câu lệnh thay đổi giá trị của biến chạy. Các lệnh này luôn được thực hiện sau mỗi lần thực hiện khối lệnh trong vòng lặp. Các lệnh phận biệt nhau bởi dấu phẩy.

Đoạn chương trình sau hiển thi tổng của 5 số đầu tiên dùng vòng lặp for.

int sum = 0;

for (int i=1; i<=5; i++)

sum += i;

System.out.println(“The sum is ”+sum);

Ở ví dụ trên, i và sum là hai biến được gán các giá trị đầu là 1 và 0 tương ứng. Điều kiện được kiểm tra và khi nó còn nhận giá trị true, câu lệnh tác động trong vòng lặp được thực hiện. Tiếp theo giá trị của i được tăng lên 2 để tạo ra số chẵn tiếp theo. Một lần nữa, điều kiện lại được kiểm tra và câu lệnh tác động lại được thực hiện. Sau năm vòng, i tăng lên 6, điều kiện trả về giá trị false và vòng lặp kết thúc. Thông báo: The sum is 15 được hiển thị.

Để thoát khỏi vòng lặp ta sử dụng lệnh break. Ví dụ:

// x chạy từ 1 tới 4 ...

for (int x = 1; x <= 4; x++) {

// ... nhưng khi x bằng 3, thì thoát khỏi dòng lặp bằng lệnh break

if (x == 3) break;

System.out.println(x);

}

System.out.println("Lặp kết thúc");

Kết quả của đoạn lệnh:

1

2

Lặp kết thúc

Để quay lại vòng lặp ta sử dụng lệnh continue. Ví dụ:

// x chạy từ 1 tới 4 ...

Chương 2: Cơ bản ngôn ngữ lập trình Java

Biên soạn : Bùi Công Giao --------------------------------------- 31 --------------------------------------

for (int x = 1; x <= 4; x++) {

// ... nhưng khi x bằng 3, thì thoát khỏi dòng lặp bằng lệnh break

if (x == 3) continue;

System.out.println(x);

}

System.out.println("Lặp kết thúc");

Kết quả của đoạn lệnh trên:

1

2

4

Lặp kết thúc

2.6 Phong cách lập trình

Người lập trình giỏi thì chương trình viết phải cho dễ đọc, sao cho những người lập trình chung dễ theo dõi và bảo trì. Phong cách lập trình sau đây giúp tạo nên các chương trình sáng sủa:

Canh lề cho từng khối lệnh, ví dụ:

public class IndentationExample {

public static void main(String[] args) {

for (int i = 1; i <= 4; i++) {

System.out.print(i);

if ((i == 2) || (i == 4)) {

if (i == 2)

System.out.print("Two ");

else

System.out.print("Four ");

System.out.println("is an even number");

} else if ((i == 1) || (i == 3)) {

if (i == 1)

System.out.print("One ");

else

System.out.print("Three ");

System.out.println("is an odd number");

}

}

Chương 2: Cơ bản ngôn ngữ lập trình Java

Biên soạn : Bùi Công Giao --------------------------------------- 32 --------------------------------------

}

}

Sử dụng ghi chú thích hợp trong các trường hợp sau:

o Nếu có các đoạn lệnh nào ghi ngờ

o Canh lề ghi chú cùng với khối lệnh của nó

o Ghi chú phải có ý nghĩa, không nên phát biểu lại nội dung câu lệnh, ví dụ

// Khai báo x là số nguyên, và gán bằng 3.

int x = 3;

Cách đặt { và } có hai cách:

o Đặt { ở cuối dòng lệnh bắt đầu khối lệnh. Mỗi } đặt ở dòng riêng, canh lề với ký tự đầu tiên của dòng lệnh bắt đầu trong khối lệnh, ví dụ:

public class Test {

public static void main(String[] args) {

for (int i = 0; i < 3; i++)

System.out.println(i);

}

}

o Đặt { ở mỗi dòng riêng, canh lề với dòng ngay trên nó. Mỗi } đặt ở dòng riêng, canh lề với { tương ứng, ví dụ:

public class Test

{

public static void main(String[] args)

{

for (int i = 0; i < 3; i++)

System.out.println(i);

}

}

Cà hai cách trên đều được chấp nhận, nhưng cách đầu thì gọn hơn và sử dụng rộng rãi hơn. Dẫu người lập trình chọn cách nào thì phải giữ đúng kiểu trong xuyên suốt dự án phần mềm của mình để đảm bảo tính nhất quán.

Đặt tên biến phải có ý nghĩa.

Tránh đặt tên biến quá vắn tắt, khó hiểu như:

int grd;

Chương 2: Cơ bản ngôn ngữ lập trình Java

Biên soạn : Bùi Công Giao --------------------------------------- 33 --------------------------------------

Phải là

int grade;

Tránh đặt tên quá dài, như:

double averageThirdQuarterReturnOnInvestment;

một gợi ý thay thế:

double avg3rdQtrROI;

2.7 Case Study

Bây giờ, áp dụng các nội dung đã học trong chương này để viết một chương trình tính chu vi và diện tích của một hình chữ nhật có kích thước x,y với yêu cầu:

Kích thước x, y nhập từ tham số dòng lệnh.

Phải kiểm tra x, y là các số nguyên dương hay không trước khi tính toán.

In kết quả tính toán ra màn hình.

Đây là chương trình thực hiện bài toán này.

package chuong2;

class RectangleDemo {

public static void main(String args[]) {

// khai báo các biến lưu giữ kích thước của hình chữ nhật

int x = 0, y = 0;

// đọc các kích thước từ tham số dòng lệnh

nếu truyền đủ hai tham số thì mới tính tiếp */

if (args.length >= 2) {

//chuyển kiểu từ String sang integer

x = Integer.parseInt(args[0]);

y = Integer.parseInt(args[1]);

}

/*Tính chu vi và diện tích hình chữ nhật

nếu cả hai tham số đều dương thì mới tính */

if (x>0 && y>0) {

// tính chu vi

int chuvi = 2*(x + y);

System.out.println (“Chu vi là “ + chuvi);

// tính diện tích

int dientich = x*y;

Chương 2: Cơ bản ngôn ngữ lập trình Java

Biên soạn : Bùi Công Giao --------------------------------------- 34 --------------------------------------

System.out.println (“Diện tích là “ + dientich);

} else

System.out.println (“Các tham số không đúng!“);

}

}

Sau khi biên dịch chương trình trên có tên là RectangleDemo.java, ta chạy từ cửa sổ dòng lệnh:

>java RectangleDemo 10 20

Sẽ thu được kết quả:

Chu vi là: 60

Diện tích là: 200

Nếu chỉ gõ ở cửa sổ dòng lệnh:

>java RectangleDemo

Thì sẽ nhận được một thông báo lỗi:

Các tham số không đúng!

TỔNG KẾT CHƯƠNG 2

Nội dung chương này đã trình bày các nội dung cơ bản về cú pháp ngôn ngữ lập trình Java:

Tất cả các lệnh của Java phải được tổ chức vào trong một lớp nhất định. Tên tập tin mã nguồn phải trùng với tên lớp.

Từ khoá class được dùng để khai báo tên lớp

Tên lớp, tên phương thức, tên hằng và tên biến trong Java phải tuân theo quy tắc đặt tên của Java.

Ứng dụng Java có một lớp chứa phương thức main. Các tham số có thể được truyền vào phương thức main nhờ các tham số lệnh (command line parameters).

Java cung cấp các dạng toán tử:

- Các toán tử số học

- Các toán tử bit

- Các toán tử quan hệ

Chương 2: Cơ bản ngôn ngữ lập trình Java

Biên soạn : Bùi Công Giao --------------------------------------- 35 --------------------------------------

- Các toán tử logic

- Toán tử đìều kiện

- Toán tử gán

Java cung cấp các cấu trúc điều khiển lệnh:

- if-else

- switch

- for

- while

- do while

Một vài qui tắc làm chương trình sáng sủa.

BÀI TẬP

1. Vào website của Sun: http://java.sun.com và tìm hiểu các ưu điểm của Java chưa được đề cập tới trong chương này.

2. Xem xét các sản phẩm phần mềm của các công ty IBM, Oracle, Microsoft, và Apple có liên quan đến ngôn ngữ Java.

3. So sánh Java và các ngôn ngữ lập trình khác mà bạn đã biết?

4. Trong các tên sau, tên nào có thể dùng làm tên biến trong Java:

_123

a$

1abc

class

vi du

$123

5. Muốn lưu giữ một biến số nguyên dương mà có giá trị lớn nhất là một triệu thì dùng kiểu dữ liệu nào là tiết kiệm bộ nhớ nhất?

6. Muốn lưu giữ một biến số nguyên âm mà có giá trị nhỏ nhất là âm một tỉ thì dùng kiểu dữ liệu nào là tiết kiệm bộ nhớ nhất?

7. Trong cấu trúc lệnh if-else đơn (một if và một else) thì có ít nhất một khối lệnh (của if hoặc của else) được thực hiện. Đúng hay sai?

8. Trong cấu trúc lệnh switch-case, khi không dùng default thì có ít nhất một khối lệnh được thực hiện. Đúng hay sai?

9. Trong cấu trúc lệnh while, khối lệnh được thực hiện ít nhất một lần ngay cả khi điều kiện có giá trị false. Đúng hay sai?

10. Trong cấu trúc lệnh do-while, khối lệnh được thực hiện ít nhất một lần ngay cả khi điều kiện có giá trị false. Đúng hay sai?

Chương 2: Cơ bản ngôn ngữ lập trình Java

Biên soạn : Bùi Công Giao --------------------------------------- 36 --------------------------------------

11. Trong cấu trúc lệnh for, khối lệnh được thực hiện ít nhất một lần ngay cả khi điều kiện có giá trị false. Đúng hay sai?

12. Lượng giá biểu thức sau:

int a = 1;

int b = 1;

int c = 1;

((((c++ + --a) * b) != 2) && true)

13. Cho biết kết quả thu đươc khi thực hiện đoạn chương trình sau?

class Me {

public static void main(String args[]) {

int sales = 820;

int profit = 200;

System.out.println((sale +profit)/10*5);

}

}

14. Cho biết đoạn chương trình sau thực hiện vòng lặp bao nhiêu lần và kết quả in ra là gì?

class Me {

public static void main(String args[]) {

int i = 0;

int sum = 0;

do {

sum += i;

i++;

} while(i <= 10);

System.out.println(sum);

}

}

15. Cho biết đoạn chương trình sau thực hiện vòng lặp bao nhiêu lần và kết quả in ra là gì?

class Me {

public static void main(String args[]) {

int i = 5;

int sum = 0;

do {

sum += i;

i++;

Chương 2: Cơ bản ngôn ngữ lập trình Java

Biên soạn : Bùi Công Giao --------------------------------------- 37 --------------------------------------

} while(i < 5);

System.out.println(sum);

}

}

16. Cho biết hai đoạn chương trình sau in ra kết quả giống hay khác nhau?

class Me1{

public static void main(String args[]) {

int i;

int sum = 0;

for(i=0; i<5; i++)

sum += i;

System.out.println(sum);

}

}

và:

class Me2 {

public static void main(String args[ ]) {

int i = 0;

int sum = 0;

for( ; i<5; i++)

sum += i;

System.out.println(sum);

}

}

17. Viết chương trình tính tổng các số chẵn nằm trong khoảng 1 đến 100.

18. Viết chương trình hiển thị tổng các bội số của 7 nằm giữa 1 và 100.

19. Viết chương trình tìm giai thừa của n (n>0), n nhập từ tham số dòng lệnh.

20. Viết chương trình tìm bội số chung nhỏ nhất của m và n (m, n>0), m và n được nhập từ tham số dòng lệnh.

21. Viết chương trình tìm ước số chung lớn nhất của m và n (m, n>0), m và n được nhập từ tham số dòng lệnh.

22. Viết chương trình tìm số Fibonaci thứ n (n>2), n nhập từ tham số dòng lệnh. Biết rằng số Fibonaci được tính theo công thức: F(n) = F(n-1) + F(n-2) với n>=2 và F(0) = F(1) = 1.

23. Viết chương trình in ra số chẵn từ 2 tới 10 bằng cách dùng

a. Một vòng lặp for và một câu lệnh continue

b. Một vòng lặp while và một biến làm cờ hiệu (variable as a flag).

Chương 3: Đối tượng và lớp

Biên soạn : Bùi Công Giao --------------------------------------- 38 --------------------------------------

Chương 3. Đối tượng và lớp

Nội dung chương này nhằm giới thiệu:

Ưu điểm của lập trình hướng đối tượng so sánh với lập trình không đối tượng truyền thống

Dùng lớp để đặc tả dữ liệu và cách cư xử của đối tượng như thế nào

Tạo đối tượng vào lúc chạy chương trình như thế nào

Cách khai báo biến tham chiếu tới đối tượng

Cách biến tham chiếu đối tượng trong bộ nhớ

Thu rác bộ nhớ

3.1 Phân rã phần mềm theo cách tiếp cận hướng đối tượng

Như đã đề cập ở phần 1.1.2, đối với lập trình theo hướng chức năng ta phân rã phần mềm từ trên xuống (top down), sau khi các chức năng cụ thể ở các nút lá được hoàn thành chúng được gắn kết lại để ra ứng dụng hoàn chỉnh theo hướng từ dưới lên. Ví dụ như hệ thống chương trình đăng ký sinh viên SRS được minh hoạ bằng hình 3.1 và 3.2 như sau:

Hình 3.1 Phân rã ứng dụng từ trên xuống.

Cách phân rã như vậy thì chức năng được chú ý đầu tiên sau đó mới đến dữ liệu cần cho chức năng. Dữ liệu được gửi qua lại giữa các chức năng và khi có sự thay đổi dữ liệu ngoài ý muốn thì chúng ta khó biết được nơi xảy qua. Hơn nữa khi CTDL bị thay đổi ở một môđun thì có thể các môđun không hiểu gây ra sự gãy đổ hệ thống phần mềm.

Đăng ký Sinh viên

Hiển thị danh sách khoá học

Sinh viên chọn khoá học

Kiểm tra Sinh viên có đủ điều kiện để đăng ký

… … …

Chương 3: Đối tượng và lớp

Biên soạn : Bùi Công Giao --------------------------------------- 39 --------------------------------------

Phân rã phần mềm theo cách tiếp cận hướng đối tượng sẽ khắc phục các nhược điểm này như sau:

Thiết kế CTDL, sau đó mới tới chức năng hệ thống.

Dữ liệu được bao bọc bên trong đối tượng, như vậy CTDL chỉ được hiểu bởi đối tượng có chứa dữ liệu đó.

Hình 3.2 Gắn ứng dụng từ dưới lên.

Nếu CTDL của đối tượng phải thay đổi sau khi ứng dụng được cài đặt thì chỉ ảnh hưởng tới đối tượng đó.

Mỗi đối tượng chịu trách nhiệm cho việc bảo đảm tính toàn vẹn của dữ liệu của nó.

3.2 Khái niệm đối tượng

Trong lập trình hướng đối tượng, tất cả các thực thể trong hệ thống đều được coi là các đối tượng cụ thể. Đối tượng là một thực thể hoạt động khi chương trình đang chạy.

Ví dụ:

1. Trong bài toán quản lý buôn bán xe hơi của một cửa hàng kinh doanh, mỗi chiếc xe đang có mặt trong cửa hàng được coi là một đối tượng. Chẳng hạn, một chiếc xe nhãn hiệu Ford, màu trắng, giá 5000$ là một đối tượng.

2. Trong bài toán quản lý nhân viên của một văn phòng, mỗi nhân viên trong văn phòng được coi là một đối tượng. Chẳng hạn, nhân viên tên là “Vinh”, 25 tuổi làm ở phòng hành chính là một đối tượng.

Một đối tượng là một thực thể đang tồn tại trong hệ thống và được xác định bằng ba yếu tố:

Định danh đối tượng: xác định duy nhất cho mỗi đối tượng trong hệ thống, nhằm phân biệt các đối tượng với nhau.

Trạng thái của đối tượng: là sự tổ hợp của các giá trị của các thuộc tính mà đối tượng đang có.

Đăng ký Sinh viên

Hiển thị danh sách khoá học

Sinh viên chọn khoá học

Kiểm tra Sinh viên có đủ điều kiện để đăng ký

… … …

Chương 3: Đối tượng và lớp

Biên soạn : Bùi Công Giao --------------------------------------- 40 --------------------------------------

Hoạt động của đối tượng: là các hành động mà đối tượng có khả năng thực hiện được.

Trạng thái hiện tại của đối tượng qui định tính chất đặc trưng của đối tượng. Ví dụ, nhân viên trong ví dụ trên có trạng thái là:

Tên là Vinh

Tuổi là 25

Vị trí làm việc là phòng hành chính.

Trong khi đó, trạng thái của chiếc xe trong cửa hàng là:

Nhãn hiệu xe là Ford

Màu sơn xe là trắng

Giá bán xe là 5000$

Mỗi đối tượng sẽ thực hiện một số hành động. Ví dụ, đối tượng xe hơi có khả năng thực hiện những hành động sau:

Khởi động.

Dừng lại.

Chạy.

Để biểu diễn đối tượng trong lập trình hướng đối tượng, người ta trừu tượng hoá đối tượng để tạo nên khái niệm lớp đối tượng.

Trừu tượng hoá đối tượng theo chức năng

Trừu tượng hoá đối tượng theo chức năng chính là quá trình mô hình hoá phương thức của lớp dựa trên các hành động của các đối tượng. Quá trình này được tiến hành như sau:

Tập hợp tất cả các hành động có thể có của các đối tượng.

Nhóm các đối tượng có các hoạt động tương tự nhau, loại bỏ bớt các hoạt động cá biệt, tạo thành một nhóm chung.

Mỗi nhóm đối tượng đề xuất một lớp tương ứng.

Các hành động chung của nhóm đối tượng sẽ cấu thành các phương thức của lớp tương ứng.

Ví dụ, trong bài toán quản lý cửa hàng bán ô tô. Mỗi ô tô có mặt trong của hàng là một đối tượng. Mặc dù mỗi chiếc xe có một số đặc điểm khác nhau về nhãn hiệu, giá xe, màu sắc… nhưng có chung các hành động của một chiếc xe ô tô là:

Có thể khởi động máy.

Có thể chạy.

Có thể dừng lại.

Có thể tắt máy.

Chương 3: Đối tượng và lớp

Biên soạn : Bùi Công Giao --------------------------------------- 41 --------------------------------------

Ngoài ra, một số ít xe có thể thực hiện một số hành động cá biệt như:

Có thể giấu đèn pha

Có thể tự bật đèn pha

Có thể tự động phát tín hiệu báo động.

Tuy nhiên, không phải xe nào cũng thực hiện được các hành động này. Cho nên ta loại bỏ các hành động cá biệt của một số xe, chỉ giữ lại các hành động chung nhất, để mô hình thành các phương thức của đối tượng xe ô tô tương ứng với các hành động chung nhất của các xe ô tô.

Lớp Xe ô tô

Phương thức:

Khởi động xe

Chạy xe

Dừng xe

Tắt máy

Trừu tượng hoá đối tượng theo dữ liệu

Trừu tượng hoá đối tượng theo dữ liệu chính là quá trình mô hình hoá các thuộc tính của lớp dựa trên các thuộc tính của các đối tượng tương ứng. Quá trình này được tiến hành như sau:

Tập hợp tất cả các thuộc tính có thể có của các đối tượng.

Nhóm các đối tượng có các thuộc tính tương tự nhau, loại bỏ bớt các thuộc tính cá biệt, tạo thành một nhóm chung.

Mỗi nhóm đối tượng đề xuất một lớp tương ứng.

Các thuộc tính chung của nhóm đối tượng sẽ cấu thành các thuộc tính tương ứng của lớp được đề xuất.

Ví dụ, trong bài toán quản lý cửa hàng bán ô tô. Mỗi ô tô có mặt trong của hàng là một đối tượng. Mặc dù mỗi chiếc xe có một số đặc điểm khác nhau về nhãn hiệu, giá xe, màu sắc… nhưng có chung các thuộc tính của một chiếc xe ô tô là:

Các xe đều có nhãn hiệu.

Các xe đều có màu sắc

Các xe đều có giá bán

Các xe đều có công suất động cơ

Ngoài ra, một số ít xe có thể có thêm các thuộc tính:

Có xe có thể có dàn nghe nhạc

Có xe có thể có màn hình xem ti vi

Có xe có lắp kính chống nắng, chống đạn…

Chương 3: Đối tượng và lớp

Biên soạn : Bùi Công Giao --------------------------------------- 42 --------------------------------------

Tuy nhiên, đây là các thuộc tính cá biệt của một số đối tượng xe, nên không được đề xuất thành thuộc tính của lớp ô tô. Do đó, ta mô hình lớp ô tô với các thuộc tính chung nhất của các ô tô.

Lớp Xe ô tô

Thuộc tính:

Nhãn hiệu xe

Màu xe

Giá xe

Công suất xe (mã lực)

3.3 Khái niệm lớp

Trong lập trình hướng đối tượng, đối tượng là một thực thể cụ thể, tồn tại trong hệ thống. Trong khi đó, lớp là một khái niệm trừu tượng, dùng để chỉ một tập hợp các đối tượng có mặt trong hệ thống.

Ví dụ:

1. Trong bài toán quản lý buôn bán xe hơi của một cửa hàng kinh doanh, mỗi chiếc xe đang có mặt trong cửa hàng được coi là một đối tượng. Nhưng khái niệm Xe hơi là một lớp đối tượng dùng để chỉ tất cả các loại xe hơi của của hàng.

2. Trong bài toán quản lý nhân viên của một văn phòng, mỗi nhân viên trong văn phòng được coi là một đối tượng. Nhưng khái niệm Nhân viên là một lớp đối tượng dùng để chỉ chung chung các nhân viên của văn phòng.

Lưu ý:

o Lớp là một khái niệm, mang tính trừu tượng, dùng để biểu diễn một tập các đối tượng.

o Đối tượng là một thể hiện cụ thể của lớp, là một thực thể tồn tại trong hệ thống.

Lớp được dùng để biểu diễn đối tượng, cho nên lớp cũng có thuộc tính và phương thức:

Thuộc tính của lớp tương ứng với thuộc tính của các đối tượng.

Phương thức của lớp tương ứng với các hành động của đối tượng.

Ví dụ, lớp xe ô tô được mô tả bằng các thuộc tính và phương thức:

Lớp Xe ô tô

Thuộc tính:

Nhãn hiệu xe

Màu xe

Giá xe

Công suất xe (mã lực)

Chương 3: Đối tượng và lớp

Biên soạn : Bùi Công Giao --------------------------------------- 43 --------------------------------------

Phương thức:

Khởi động xe

Chạy xe

Dừng xe

Tắt máy

Lưu ý:

Một lớp có thể có một trong các khả năng sau:

Hoặc chỉ có thuộc tính, không có phương thức.

Hoặc chỉ có phương thức, không có thuộc tính.

Hoặc có cả thuộc tính và phương thức, trường hợp này là phổ biến nhất.

Đặc biệt, lớp không có thuộc tính và phương thức nào là các lớp trừu tượng. Các lớp này không có đối tượng tương ứng.

Lớp và Đối tượng

Lớp và đối tượng, mặc dù có mối liên hệ tương ứng lẫn nhau, nhưng bản chất lại khác nhau:

Lớp là sự trừu tượng hoá của các đối tượng. Trong khi đó, đối tượng là một thể hiện của lớp.

Đối tượng là một thực thể cụ thể, có thực, tồn tại trong hệ thống. Trong khi đó, lớp là một khái niệm trừu tượng, chỉ tồn tại ở dạng khái niệm để mô tả các đặc tính chung của một số đối tượng.

Tất cả các đối tượng thuộc về cùng một lớp có cùng các thuộc tính và các phương thức.

Một lớp là một nguyên mẫu của một đối tượng. Nó xác định các hành động khả thi và các thuộc tính cần thiết cho một nhóm các đối tượng cụ thể.

Nói chung, lớp là khái niệm tồn tại khi phát triển hệ thống, mang tính khái niệm, trừu tượng. Trong khi đó, đối tượng là một thực thể cụ thể tồn tại khi hệ thống đang hoạt động.

3.4 Khái niệm đóng gói

Đóng gói là cơ chế gộp thuộc tính và phương thức của một đối tượng trong một tổ chức duy nhất.

Xét ví dụ bài toán quản lý nhân viên văn phòng với lớp Nhân viên như sau:

Lớp Nhân viên

Thuộc tính:

Tên

Ngày sinh

Giới tính

Chương 3: Đối tượng và lớp

Biên soạn : Bùi Công Giao --------------------------------------- 44 --------------------------------------

Phòng ban

Hệ số lương

Phương thức:

Tính lương nhân viên

Khi đó, cách tính lương cho nhân viên là khác nhau đối với mỗi người:

<Tiền lương> = <Hệ số lương> * <Lương cơ bản> * <Tỉ lệ phần trăm>

Trong đó, tỉ lệ phần trăm là khác nhau cho mỗi phòng ban, ví dụ:

Phòng kế hoạch là 105%

Phòng hành chính là 100%

Phòng nhân sự là 110%

Khi đó, tuỳ vào thuộc tính phòng ban khác nhau mà ta phải dùng công thức tỉ lệ khác nhau để tính lương cho mỗi nhân viên.

Tuy nhiên, cách tính cụ thể này là công việc bên trong của phương thức tính tiền lương của lớp Nhân viên. Với mỗi ứng dụng, khi tạo một đối tượng cụ thể của lớp nhân viên, ta chỉ cần truyền các tham số thuộc tính cho đối tượng, sau đó gọi phương thức tính tiền lương cho đối tượng nhân viên đó, ta sẽ biết được tiền lương của nhân viên. Cách gọi phương thức tính tiền lương là hoàn toàn giống nhau cho tất cả các đối tượng nhân viên của văn phòng.

Sự giống nhau về cách sử dụng phương thức cho các đối tượng của cùng một lớp, mặc dù bên trong phương thức có các cách tính toán khác nhau với các đối tương khác nhau, được gọi là tính đóng gói dữ liệu của lập trình hướng đối tượng. Ưu điểm của đóng gói đã được trình bày trong Chương 1.

3.5 Biến tham chiếu

Trong ngôn ngữ lập trình C, khai báo biến

int x;

nghĩa rằng

x là một tên hình thức được dùng để chỉ tới một giá trị nguyên được lưu trong bộ nhớ máy tính. Chúng ta không quan tâm nơi bộ nhớ đó ở đâu.

Bất cứ khi nào ta muốn truy xuất giá trị nguyên đó, ta chỉ cần gọi tới ký hiệu hình thức x, ví dụ:

if (x > 17) x = x + 5;

Trong ngôn ngữ lập trình hường đối tượng như Java, chúng ta có thể định nghĩa một lớp như Student, và khai báo một biến kiểu Student như sau:

Student y;

Chương 3: Đối tượng và lớp

Biên soạn : Bùi Công Giao --------------------------------------- 45 --------------------------------------

nghĩa rằng

y là một tên hình thức được dùng để chỉ tới một đối tượng Student (nghĩa là một thể hiện của lớp Student) mà được lưu trong bộ nhớ máy tính. Chúng ta không quan tâm nơi bộ nhớ đó ở đâu.

Bất cứ khi nào ta muốn truy xuất thuộc tính hay phương thức của đối tượng này, ta chỉ cần gọi tới ký hiệu hình thức y và tên thuộc tính hay phương thức, ví dụ:

if (y.registerForCouse())

System.out.println ("Accepted");

Có sự giống nhau giữa y là Student và x là int trong minh hoạ trên. Chỉ bởi vì int là kiểu đã định nghĩa trước trong Java (và các ngôn ngữ khác), lớp Student là kiểu người dùng định nghĩa. Vì y tham chiếu tới một thể hiện (đối tượng) của lớp Student, nên y là biến tham chiếu. Trái lại các biến thuộc tám kiểu cơ bản của Java—int, double, float, byte, short, long, char, và boolean—không là biến tham chiếu, nghĩa là chúng không tham chiếu các đối tượng.

3.6 Khởi tạo đối tượng

Khi khai báo biến như

Student y;

Đối tượng chưa thật sự được tạo trong bộ nhớ, ta chỉ tạo biến tham chiếu y. Nếu ta muốn khởi tạo đối tượng cho y tham chiếu tới, dùng lệnh sau:

y = new Student();

Hình 3.3 minh hoạ điều này.

Hình 3.3 Sử dụng một biến tham chiếu để chỉ tới đối tượng trong bộ nhớ.

Nếu người đọc đã quen thuộc với khái niệm con trò trong ngôn ngữ C và C++, một biến tham chiếu tương tự như biến con trỏ chỉ tới một vùng nhớ hay chứa giá trị là địa chỉ nơi đối tượng được lưu giử Tuy nhiên biến tham chiếu không thể áp dụng các phép toán số học như biến con trỏ.

Đối tượng Student

y

Chương 3: Đối tượng và lớp

Biên soạn : Bùi Công Giao --------------------------------------- 46 --------------------------------------

Để hiểu hơn về biến tham chiếu ta xét các đoạn lệnh sau:

// Đoạn lệnh 1

// Vừa khởi tạo x vừa tạo một đối tượng Student cho x tham chiếu tới

Student x=new Student();

// x và y cùng tham chiếu tới một đối tượng Student. Hình 3.4 minh hoạ điều này.

Student y = x;

Hình 3.4 Hai biến tham chiếu tới cùng một đối tượng.

// Đoạn lệnh 2

// Vừa khởi tạo x vừa tạo một đối tượng Student cho x tham chiếu tới

Student x=new Student();

// x và y cùng tham chiếu tới một đối tượng Student

Student y = x;

Student z=new Student();

// z và y cùng tham chiếu tới đối tượng Student thứ hai. Hình 3.5 minh hoạ điều này.

y = z; 

Hình 3.5 Chuyển y tham chiếu tới Student thứ hai.

Đối tượng Student

x y

Đối tượng Student

z

Đối tượng Student

x y

Chương 3: Đối tượng và lớp

Biên soạn : Bùi Công Giao --------------------------------------- 47 --------------------------------------

// Đoạn lệnh 3

// Vừa khởi tạo x vừa tạo một đối tượng Student cho x tham chiếu tới

Student x=new Student();

// Vừa khởi tạo z vừa tạo một đối tượng Student cho z tham chiếu tới

Student z=new Student();

/* z và x cùng tham chiếu tới đối tượng Student thứ hai. đối tượng Student đầu tiên không còn bị tham chiếu trờ thành “mồ côi”. Hình 3.6 minh hoạ điều này. */

x= z;

Hình 3.6 Chuyển x tham chiếu tới Student thứ hai.

// Đoạn lệnh 4

// Vừa khởi tạo x vừa tạo một đối tượng Student cho x tham chiếu tới

Student x=new Student();

/* x giờ không chỉ tới một đối tượng Student nào cả do bị gán null. Đối tượng Student không còn bị tham chiếu trờ thành “mồ côi”. Hình 3.7 minh hoạ điều này. */

x= null; 

Hình 3.7 Đối tượng Student mồ côi.

Đối tượng Student

x

Đối tượng Student

x

Đối tượng Student

z

Chương 3: Đối tượng và lớp

Biên soạn : Bùi Công Giao --------------------------------------- 48 --------------------------------------

Để giải quyết các trường hợp đối tượng không có biến nào tham chiếu như trên, máy ảo Java tạo ra cơ chế thu rác (garbage collection) như sau:

Công việc thu rác xảy ra khi ứng dụng còn ít bộ nhớ tự do hoặc khi máy ảo Java rảnh. Vì vậy trong một thời điểm nào các đối tượng mồ côi vẫn còn ở trong bộ nhớ nhưng ta không thể truy xuất chúng.

Nếu muốn thu rác xảy ra ngay lập tức thì gọi phương thức sau:

Runtime.getRuntime().gc();

Thu rác sẽ được đề cập kỹ hơn trong phần 10.4

TỔNG KẾT CHƯƠNG 3

Trong chương này các vấn đề sau được trình bày:

Một đối tượng là sự trừu tượng hoá phần mềm của một đối tượng vật lý hay khái niệm trong thế giới, .

Một lớp là khuôn mẫu cho tạo ra đối tượng. Đặc biệt một lớp định nghĩa dữ liệu hay thuộc tính mà một đối tượng có, và cách cư xử/dịch vụ mà đối tượng thực hiện, còn gọi là phương thức/hoạt động của đối tượng.

Một đối tượng là thể hiện của lớp.

Lớp là kiểu người dùng định nghĩa.

Sử dụng kiểu liệt kê như thế nào

BÀI TẬP

1. Trong số các nhận định sau, cái nào đúng, cái nào sai:

a. Đối tượng là một thực thể cụ thể, tồn tại thực tế trong các ứng dụng.

b. Đối tượng là một thể hiện cụ thể của lớp.

c. Lớp là một khái niệm trừu tượng dùng để biểu diễn các đối tượng.

d. Lớp là một sự trừu tượng hoá của đối tượng.

e. Lớp và đối tượng có bản chất giống nhau.

f. Trừu tượng hoá đối tượng theo chức năng tạo ra các thuộc tính của lớp.

g. Trừu tượng hoá đối tượng theo chức năng tạo ra các phương thức của lớp.

h. Trừu tượng hoá đối tượng theo dữ liệu tạo ra các thuộc tính của lớp.

i. Trừu tượng hoá đối tượng theo dữ liệu tạo ra các phương thức của lớp.

j. Kế thừa cho phép hạn chế sự trùng lặp mã nguồn.

k. Kế thừa cho phép tăng khả năng sử dụng lại mã nguồn.

l. Đóng gói hạn chế khả năng truy nhập dữ liệu.

m. Đóng gói hạn chế việc phải sửa đổi mã nguồn.

n. Đa hình cho phép thực hiện cùng một thao tác trên nhiều đối tượng khác nhau.

Chương 3: Đối tượng và lớp

Biên soạn : Bùi Công Giao --------------------------------------- 49 --------------------------------------

2. Trong các khai báo lớp sau, khai báo nào là đúng:

a) class myClass

b) Class myClass

c) public class MyClass

d) Public class MyClass

3. Có phải Màu thì thích hợp cho việc tạo lớp để trừu tượng hoá khái niệm này hay không ?

4. Liệt kê tất cả các thuộc tính và hành động của đối tượng Xe ô tô. Đề xuất lớp Car.

5. Liệt kê tất cả các thuộc tính và hành động của đối tượng lớp Bus.

6. Từ hai lớp Car và Bus của 2 bài trên. Đề xuất một lớp Engine cho hai lớp trên kế thừa, để tránh trùng lặp dữ liệu giữa hai lớp Car và Bus.

7. Xây dựng thuộc tính của các lớp sau

o Classroom

o Departments

o Student

o Professor

Viết chương trình tạo đối tượng của các lớp trên.

Chương 4: Tương tác giữa các đối tượng

Biên soạn : Bùi Công Giao --------------------------------------- 50 --------------------------------------

Chương 4. Tương tác giữa các đối tượng

Nội dung chương này nhằm giới thiệu:

Sử dụng phương thức để đặc tả cách hành xử của đối tượng.

Đối tượng công bố phương thức của nó như là dịch vụ tới đối tượng khác và việc yêu cầu sự phục vụ được thực hiện như thế nào

Đối tượng bảo trì và bảo đảm tính toàn vẹn dữ liệu của nó như thế nào thông qua cơ chế dấu thông tin

Cách tạo phương thức xây dựng để khởi tạo đối tượng ban đầu

4.1 Cộng tác giữa các đối tượng

Qui trình phát triển phần mềm hướng đối tượng bao gồm bốn bước cơ bản:

1. Thiết lập đúng các yêu cầu chức năng và sứ mệnh tổng quát của một ứng dụng.

2. Thiết lập các lớp thích hợp bao gồm CTDL, cách cư xử và mối quan hệ với lớp khác.

3. Tạo số lượng các thể hiện đối tượng từ các lớp này để thoả mãn các yêu cầu và sứ mệnh của ứng dụng..

4. Thiết lập môi trường hoạt động của đối tượng để sẳn sàng phản ứng với các sự kiện kích hoạt từ bên ngoài

Xét hệ thống đăng ký sinh viên SRS có môi trường hoạt động như sau:

Việc click chuột vào một nút (button) trên giao diện của ứng dụng SRS hàm ý rằng sinh viên muốn đăng ký một khoá học.

Nhận thông tin từ một hệ thống tự động khác như SRS nhận danh sách sinh viên đã đóng tiền từ hệ thống thu cước

Ngay khi một sự kiện kích hoạt được nhận biết từ hệ thống hướng đối tượng, các đối tượng thích hợp phản ứng và thực hiện các dịch vụ của chính nó hay yêu cầu các dịch vụ của các đối tượng khác trong dây chuyền phản ứng, cho đến khi đạt được mục đích. Ví dụ yêu cầu đăng ký khoá học của sinh liên quan đến nhiều đối tượng như hình 4.1

Các đối tượng trong sơ đồ tương tác này:

Một đối tượng Student (trừu tượng hoá của một sinh viên thật)

Một đối tượng DegreeProgram để đảm bảo rằng khoá học đăng ký phù hợp với sinh viên này.

Một đối tượng Course để đảm bảo còn chổ cho sinh viên đăng ký môn học này.

Một đối tượng Classroom thể hiện phòng học tổ chức môn học và kiểm tra khả năng chứa của nó.

Chương 4: Tương tác giữa các đối tượng

Biên soạn : Bùi Công Giao --------------------------------------- 51 --------------------------------------

Một đối tượng Transcript để đảm bảo người sinh viên này đã học các môn học cần thiết cho môn học đang đăng ký.

Hình 4.1 Các đối tượng trong hệ thống phải cộng tác với nhau để hoàn tất sứ mệnh SRS

4.2 Thuộc tính

Khai báo thuộc tính của lớp:

<tính chất> <kiểu dữ liệu> <tên thuộc tính>;

Tính chất: các thuộc tính và phương thức của lớp có các tính chất được đặc trưng bởi các từ khoá sau:

o public: có thể được truy cập từ bên ngoài lớp định nghĩa.

o protected: chỉ được truy cập từ lớp định nghĩa và các lớp kế thừa từ lớp đó.

o private: chỉ được truy cập trong phạm vi bản thân lớp định nghĩa.

o không khai báo: ngầm định là package, chỉ được truy cập từ cùng package

o static: được dùng chung cho một thể hiện của lớp, có thể được truy cập trực tiếp bằng <tên lớp>.<tên thuộc tính> mà không cần khởi tạo một thể hiện của lớp. (phần này được bàn kỹ hơn trong 5.6)

o abstract: định nghĩa một thuộc tính trừu tượng. Thuộc tính này không thể truy nhập trong lớp nhưng có thể bị định nghĩa chồng ở các lớp kế thừa.

o final: một thuộc tính hằng, không bị định nghĩa chồng ở các lớp kế thừa.

o native: dùng cho phương thức khi cài đặt phụ thuộc môi trường trong một ngôn ngữ khác, như C hay hợp ngữ.

Một DegreeProgram

(chương trình học)

Một Student (sinh viên)

Một Transcript (bản điểm)

Một Class (lớp học)

Một Course (môn học)

Click chuột trong giao diện ứng dụng

Một sự kiện bên ngoài tác động vào hệ thống yêu cầu các đối tượng nói chuyện với nhau

Chương 4: Tương tác giữa các đối tượng

Biên soạn : Bùi Công Giao --------------------------------------- 52 --------------------------------------

o synchronized: dùng cho phương thức tới hạn, nhằm ngăn các tác động của các đối tượng khác khi phương thức đang được thực hiện.

Kiểu dữ liệu: có thể là các kiểu dữ liệu cơ bản sẵn có của Java, có thể là các lớp do người dùng tự định nghĩa.

Tên thuộc tính: được đặt tên theo quy tắc đặt tên biến của Java. Thông thường tên thuộc tính đặt theo kiểu camel như name, studentId, courseLoad.

Định nghĩa lớp Person sau có hai thuộc tính name, và age.

class Person {

public String name;

public int age;

}

4.3 Phương thức

4.3.1 Khai báo phương thức

<tính chất> <kiểu trả về> <tên phương thức> ([<các tham số>]) [throws <các ngoại lệ>] {

}

Tính chất: đặc trưng bởi các từ khoá tương tự như tính chất của thuộc tính.

Kiểu trả về: Kiểu dữ liệu trả về của phương thức, có thể là kiểu dữ liệu sẵn có của Java hoặc là kiểu do người dùng tự định nghĩa.

Tên phương thức: tuân theo qui tắc đặt tên biến của Java. Thông thường tên phương thức đặt theo kiểu camel như registerForCourse, printTranscript.

Các ngoại lệ: là một đối tượng đặc biệt được tạo ra khi chương trình gặp lỗi. Java sẽ trả lại cho chương trình ngoại lệ này theo từ khoá throws. Các ngoại lệ, nếu có, được phân cách nhau bởi dấu phẩy.

Các tham số: các tham số của phương thức, được liệt kê theo cặp

<kiểu tham số> <tên tham số>, các tham số được phân biệt bởi dấu phẩy.

Dưới đây là bảng tóm tắt cách truy cập cho các thành viên của lớp (Y là được truy cập)

Tính chất Lớp Lớp con Gói Bất kỳ ở đâu

private Y

protected Y Y Y

public Y Y Y Y

(không khai báo) Y Y Y

Bảng 4.1 Phạm vi truy cập của các thành viên của lớp

Chương 4: Tương tác giữa các đối tượng

Biên soạn : Bùi Công Giao --------------------------------------- 53 --------------------------------------

Đoạn lệnh sau mô tả việc khai báo phương thức show() để hiển thị thông tin cá nhân của lớp Person.

class Person {

public String name;

public int age;

public void show() {

System.out.println( name + “ is ” + age + “ years old!”);

}

}

4.3.2 Biến this

Biến this là một biến ẩn đặc biệt luôn tồn tại trong các lớp Java: một lớp có đúng một biến ẩn this. Biến này được sử dụng trong khi chạy và nó trỏ đến bản thân đối tượng đang thể hiện lớp đó. Biến this thường được sử dụng trong các hàm xây dựng của lớp.

Đoạn lện sau khai báo một lớp Person hoàn toàn giống với lớp Person đã được khai báo trước, nhưng chỉ khác là có dùng biến this trong hàm xây dựng của lớp.

class Person {

public String name;

public int age;

// Phương thức xây dựng

public Person(String name, int age) {

this.name = name;

this.age = age;

}

public void show() {

System.out.println( name + “ is ” + age + “ years old!”);

}

}

Ta chú ý đến hàm xây dựng của lớp, hàm này có hai biến cục bộ là name và age, trùng với các biến của lớp. Do đó, trong phạm vi hàm này, biến this.name và this.age sẽ chỉ các biến của lớp, còn các biến name và age sẽ chỉ các biến cục bộ của hàm.

4.3.3 Gọi phương thức

Chương trình PersonDemo minh hoạ cách dùng lớp Person mà chúng ta vừa định nghĩa. Chương trình này sẽ tạo ra một đối tượng myPerson của lớp Person với các thuộc tính có giá trị khởi tạo: name = “Minh” và age = “21”. Sau đó, chương trình sử dụng phương thức show() của đối tượng myPerson để in ra dòng thông báo :

Minh is 21 years old!.

Chương 4: Tương tác giữa các đối tượng

Biên soạn : Bùi Công Giao --------------------------------------- 54 --------------------------------------

package chuong4;

class PersonDemo {

public static void main(String args[]) {

Person myPerson = new Person(“Minh”, 21);

myPerson.show();

}

}

4.3.4 Nạp chồng phương thức

Java cho phép trong cùng một lớp, có thể khai báo nhiều phương thức có cùng tên nhưng khác nhau về tính chất; khi đó ta gọi đây là nạp chồng (overloading) phương thức. Xét chi tiết hơn thì các phương thức này:

Hoặc danh sách các tham số khác nhau

Hoặc kiểu trả về khác nhau

Hoặc kết hợp hai điều kiện trên.

Nếu không, Java sẽ không phân biệt được chúng. Ví dụ nếu trong cùng một lớp:

// hợp lệ

public int add(int x, int y){…}

public float add(float x, int y){…}

// không hợp lệ

public int add(int x, int y){…}

public int add(int x, int y){…}

4.3.5 Phương thức xây dựng

Phương thức xây dựng (constructor) của lớp được dùng để khởi tạo một thể hiện cụ thể của một lớp, nghĩa là gán các giá trị khởi đầu cho các thuộc tính, nếu có, và tạo ra một đối tượng cụ thể.

Lưu ý:

Phương thức xây dựng phải có tên trùng với tên của lớp

Phương thức xây dựng không có giá trị trả về

Phương thức xây dựng có tính chất public hoặc package (không khai báo kiểu truy nhập)

Có thể có nhiều phương thức xây dựng của cùng một lớp

Chương trình sau minh hoạ hai phương thức xây dựng của lớp Person

package chuong4;

class Person {

public String name;

Chương 4: Tương tác giữa các đối tượng

Biên soạn : Bùi Công Giao --------------------------------------- 55 --------------------------------------

public int age;

// Phương thức xây dựng không tham số

public Person() {

name = “”;

age = 0;

}

// Phương thức xây dựng có hai tham số

public Person(String name, int age) {

this.name = name;

this.age = age;

}

public void show() {

if (name.equals(“”))

System.out.println( “name is empty”);

else

System.out.println( name + “ is ” + age + “ years old!”);

}

}

public class TestPerson {

public static void main( String args[]) {

// gọi phương thức xây dựng không tham số

Person p1 = new Person();

p1.show();

// gọi phương thức xây dựng có tham số

Person p2 = new Person(“Tam”, 31 );

p2.show();

}

}

Kết quả chương trình:

name is empty

Tam is 31 years old!

Chương 4: Tương tác giữa các đối tượng

Biên soạn : Bùi Công Giao --------------------------------------- 56 --------------------------------------

4.3.6 Che dấu thông tin

Lớp che dấu thông tin của nó để bên ngoài không thể thấy bằng tính chất private cho thuộc tính và phương thức. Ví dụ định nghĩa lớp Student sau :

public class Student {

// vài thuộc tính

private String name;

private String ssn;

private double totalLoans, tuitionOwed;

public void printStudentInfo() {

// Truy xuất thuộc tính của lớp Student

System.out.println("Name: " + name);

System.out.println("Student ID: " + ssn);

}

public boolean allBillsPaid() {

boolean answer = false;

// Truy xuất phương thức của lớp Student

double amt = moneyOwed();

if (amt == 0.0)

answer = true;

else

answer = false;

return answer;

}

private double moneyOwed() {

return totalLoans + tuitionOwed;

}

}

Trong định nghĩa lớp Student, hai thuộc tính name, ssn và phương thức moneyOwed chỉ được thấy bên trong lớp còn phương thức allBillsPaid có thể được thấy từ bên ngoài

Để cung cấp cách truy nhập thuộc tính loại private từ bên ngoài lớp, cặp phương thức get/set được đề nghị như sau:

public class Student {

private String name;

// Bên ngoài dùng phương thức đọc (get) giá trị thuộc tính name

Chương 4: Tương tác giữa các đối tượng

Biên soạn : Bùi Công Giao --------------------------------------- 57 --------------------------------------

public String getName() {

return name;

}

// Bên ngoài dùng phương thức ghi (set) giá trị thuộc tính name

public void setName(String newName) {

name = newName;

}

//….

}

public class TestStudent {

public static void main( String args[]) {

Student s = new Student ();

s.setName(“Huong”);

System.out.println( “Name : ”+ s.getName();

}

}

4.4 Truyền thông điệp giữa các đối tượng

Chúng ta xét một ví dụ truyền thông điệp giữa hai đối tượng có kiểu đã định nghỉa là Student và Course và các phương thức sau.

Với lớp Student

boolean successfullyCompleted(Course c)

Cho một đối tượng c kiểu Course, ta yêu cầu đối tượng kiểu Student nhận thông điệp này và xác nhận sinh viên này đã hoàn tất môn học và đã nhận một điểm đậu.

Với lớp Course:

boolean register(Student s)

Cho một tham chiếu s tới một đối tượng Student, ta yêu cầu đối tượng Course nhận thông điệp để làm bất cứ điều gì cần thiết để xem xét sự đăng ký học của sinh viên s. Ta mong chờ Course trả về true hay false để báo hiệu đăng ký thành công hay thất bại.

Hình 4.2 phản ánh trao đổi thông điệp giữa một đối tượng c kiểu Course và một đối tượng s kiểu Student. Đường mũi tên liên tục thể hiện thông điệp được truyền thông qua lời triệu hồi phương thức; Đường mũi tên đứt quảng thể hiện giá trị trả về từ phương thức.

Các bước thứ tự trong sơ đồ được diễn giải như sau

1. Đối tượng c kiểu Course nhận một thông điệp

c.register(s);

Chương 4: Tương tác giữa các đối tượng

Biên soạn : Bùi Công Giao --------------------------------------- 58 --------------------------------------

c.register(s)

true

Đối tượng c

(một Course)

với s là một đối tượng Student.

Để đối tượng c quyết định chính thức liệu s nên hay không nên cho phép được đăng ký, c gửi thông điệp s.successfullyCompleted(c2) tới s với c2 thể hiện một tham chiếu tới một đối tượng khác có kiểu Course mà là môn học cần học trước c. Lưu ý có thể không có hay có một hay nhiều c2 như vậy.

2. s trả lời cho c là true chỉ rằng s đã hoàn tất c2.

3. Được thuyết phục rằng s đã đáp ứng điều kiện cần cho khoá học, c hoàn tất công việc đăng ký cho sinh viên s bằng cách gửi giá trị true tới nơi yêu cầu phục vụ.

Hình 4.2 Thông điệp được truyền qua lại giữa các đối tượng Course và Student

Ví dụ này tương đối đơn giản; trong thực tế c phải nói chuyện với các đối tượng khác như:

Một đối tượng Classroom (phòng mà tổ chức môn học này để chắc chắn rằng còn đủ chổ cho sinh viên s)

Một đối tượng DegreeProgram (để đảm bảo rằng khoá học đăng ký phù hợp với sinh viên s)

Hình 4.3 Người yêu cầu chỉ thấy chi tiết bên ngoài của việc trao đổi thông điệp.

Sự uỷ nhiệm

Chương 4: Tương tác giữa các đối tượng

Biên soạn : Bùi Công Giao --------------------------------------- 59 --------------------------------------

Nếu có yêu cầu tới đối tượng A để làm gì đó và đến phiên nó, A yêu cầu sự trợ giúp từ một đối tượng khác là B thì việc này gọi là sự uỷ nhiệm (delegation) bởi A tới B.

Sự uỷ nhiệm xảy ra giữa các đối tượng thì thông thường trong suốt với đối tượng có yêu cầu đầu tiên. Xét ví dụ trước, đối tượng c kiểu Course uỷ nhiệm một phần công việc đăng ký khoá học của đối tượng s kiểu Student cho s khi c yêu cầu s kiểm tra xem đã hoàn tất các khoá học bắt buộc hay không. Từ góc nhìn của người phát ra yêu cầu đăng ký ban đầu —c.register(s);—điều này dường như là một tương tác giản đơn, nghĩa là người yêu cầu đòi hỏi c đăng ký một sinh viên và nó đã thực hiện điều này! Tất cả công việc chi tiết mà c thực hiện thì bị che đậy với người yêu cầu (xem hình 4.3).

TỔNG KẾT CHƯƠNG 4

Trong chương này các vấn đề sau được trình bày:

Cách tạo và sử dụng phương thức như thế nào.

Các đối tượng thường cộng tác với nhau để thực hiện một chức năng hệ thống như đăng ký môn học cho sinh viên.

Đối tượng A có thể liên lạc với đối tượng B nếu A có tham chiếu tới B.

Các lớp che dấu đặc tính của chúng như thế nào.

Tạo phương thức truy xuất get/set để cung cấp việc truy xuất các thuộc tính private của đối tượng.

Cách tạo phương thức xây dựng

Nạp chồng cho phép tạo nhiều phương thức cùng tên và cách tạo các hàm xây dựng khác nhau.

BÀI TẬP

1. Cho lớp Book có các thuộc tính sau:

Author author;

String title;

int noOfPages;

boolean fiction;

viết các phương thức get/set cho mỗi thuộc tính này và biên dịch lớp Book trên.

2. Cho đoạn mã chương trình như sau:

Student s;

Professor p;

boolean b;

String x = "Math";

s.setMajor(x);

if (!s.hasAdvisor())

Chương 4: Tương tác giữa các đối tượng

Biên soạn : Bùi Công Giao --------------------------------------- 60 --------------------------------------

b = s.designateAdvisor(p);

Đặc tính -thuộc tính và phương thức- nào được hàm ý cho lớp Student và Professor qua đoạn lệnh trên? Hãy xác định

Khả năng truy cập của từng đặc tính

Các đặc tính được khai báo như thế nào

3. Mở rộng lớp Student và Professor trong bài tập 6 của chương 3 như sau:

Bao gồm phương thức get/set cho mỗi thuộc tính.

Có các phương thức xây dựng cho mỗi lớp.

Tạo phương thức

public void printAllAttributes()

để hiển thị tất cả thuộc tính như sau

Student Name: John Smith

Student ID: 123-45-6789

Rồi thay đổi phương thức main của để sử dụng phương thức xây dựng cho từng kiểu đối tượng.

4. Đoạn lệnh sau đây có gì sai:

public class Building {

private String address;

public int numberOfFloors;

void GetnumberOfFloors() {

return numberOfFloors;

}

private void SetNoOfFloors(float n) {

NumberOfFloors = n;

}

public void display() {

System.out.println("Address: " + address);

System.out.println("No. of Floors: " + numberOfFloors);

}

Chương 5: Quan hệ giữa các đối tượng

Biên soạn : Bùi Công Giao --------------------------------------- 61 --------------------------------------

Chương 5. Quan hệ giữa các đối tượng

Nội dung chương này nhằm giới thiệu:

Các loại quan hệ khác nhau giữa các lớp xác định các đối tượng liên kết như thế nào trong thời gian chạy chương trình.

Tính thừa kế giúp tạo lớp mới bằng cách nêu các đặc điểm khác so với lớp có sẵn.

Các qui luật cho những gì có thể và không thể làm khi tạo các lớp con qua tính thừa kế.

Các phương thức xây dựng được tạo như thế nà

Khả năng truy xuất các đặc trưng qua tính thừa kế.

Một kiểu lớp đặc biệt gọi là enum dùng để liệt kê các giá trị định sẳn mà là miền giá trị cho một biến đặc biệt

5.1 Kết hợp và liên kết

Quan hệ giữa các lớp là sự kết hợp (association).Trong hệ thống SRS có vài kết hợp:

Lớp Student có quan hệ đăng ký học với lớp Course.

Lớp Professor có quan hệ dạy với lớp Course.

Lớp DegreeProgram có quan hệ bao gồm với lớp Course.

Một liên kết (link) là quan hệ giữa hai hay nhiều đối tượng. Xét quan hệ một Student đăng ký một Course, ta có các liên kết sau:

Tâm (một đối tượng loại Student) đăng ký môn Mạng máy tính (một đối tượng loại Course).

Sơn (một đối tượng loại Student) đăng ký môn Lập trình Agent (một đối tượng loại Course).

Hà (một đối tượng loại Student) đăng ký môn Mạng máy tính (một đối tượng loại Course giống như đối tượng mà Tâm đăng ký học).

Như vậy ta có thể tóm tắt như sau:

Một kết hợp là một quan hệ tiềm năng giữa các đối tượng của các lớp.

Một liên kết là quan hệ thực sự giữa các đối tượng của các lớp.

Hầu hết kết hợp là giữa hai lớp khác nhau, gọi là kết hợp hai ngôi (binary association). Một kết hợp một ngôi (unary association) là giữa các thể hiện của cùng một lớp, ví dụ

Một Course là môn học bắt buộc phải hoàn tất trước một Course khác.

Một Person có quan hệ vợ/chồng với một Person khác.

Chương 5: Quan hệ giữa các đối tượng

Biên soạn : Bùi Công Giao --------------------------------------- 62 --------------------------------------

Kết hợp có số ngôi cao hơn có thể xảy ra như kết hợp ba ngôi (ternary association) bao gồm ba lớp. Ví dụ như: một Student học một Course được dạy bởi một Professor (hình 5.1).

Ta có thể phân rã kết hợp có nhiều ngôi thành nhiều kết hợp hai ngôi.

Ví dụ ta thể hiện kết hợp ba ngôi ở hình 5.1 thành các kết hợp hai ngôi như sau (hình 5.2):

Student tham dự Course.

Professor giảng dạy Course.

Professor dạy Student.

Hình 5.1 Kết hợp ba ngôi

Hình 5.2 Một thể hiện tương đương bằng ba kết hợp hai ngôi

Số liên kết (multiplicity)

Cho một kết hợp giữa A và B, số liên kết hàm ý số đối tượng kiểu A có thể kết hợp với một đối tượng kiểu B.

Có ba loại số liên kết: một-một, một-nhiều và nhiều-nhiều. Các ví dụ sau minh hoạ loại liên kết:

Một đối tượng Student chỉ có một đối tượng Transcript, đây là liên kết một-một.

Student

Professor Course

dạy tham dự

giảng dạy

Chương 5: Quan hệ giữa các đối tượng

Biên soạn : Bùi Công Giao --------------------------------------- 63 --------------------------------------

Một đối tượng Professor có thể hướng dẫn nhiều Student, nhưng một đối tượng Student chỉ có một Professor hướng dẫn, đây là liên kết một-nhiều.

Một đối tượng Student tham dự nhiều Course, một Course dạy cho nhiều Student, đây là liên kết nhiều-nhiều

Ngoài quan hệ liên kết, giữa các lớp còn có các dạng quan hệ cơ bản như sau:

Khái quát hóa (Generalization): mối quan hệ giữa một lớp có các đặc trưng mang tính khái quát cao hơn và một lớp có tính chất đặc biệt hơn. Mối quan hệ khái quát hóa chính là sự kế thừa của một lớp từ lớp khác. Ví dụ lớp Person có khái quát hoá cao hơn lớp Student và Employee, ngược lại hai lớp Student và Employee có tính chất đặc biệt hơn lớp Person.

Quan hệ cộng hợp (Aggregation): dạng quan hệ mô tả một lớp A là một phần của lớp B và lớp A có thể tồn tại độc lập. Ví dụ lớp Student là một phần của lớp Class và lớp Student có thể tồn tại độc lập.

Quan hệ gộp (Composition): biểu diễn một quan hệ kiểu tổng thể-bộ phận. Lớp A có quan hệ gộp với lớp B nếu lớp A là một phần của lớp B và sự tồn tại của đối tượng lớp B điều khiển sự tồn tại của đối tượng lớp A. Ví dụ lớp Country là tổng thể và lớp Province là bộ phận. Lớp Province là một phần của lớp Country.

Quan hệ phụ thuộc (Dependency): mối quan hệ phụ thuộc giữa hai lớp đối tượng: một lớp đối tượng A có tính độc lập và một lớp đối tượng B phụ thuộc vào A; một sự thay đổi của A sẽ ảnh hưởng đến lớp phụ thuộc B. Ví dụ một đối tượng của lớp Order (hoá đơn) phụ thuộc vào đối tượng DiscountPolicy (Chính sách chiết khấu).

5.2 Kế thừa

Sự kế thừa được sử dụng khi muốn tạo một lớp mới từ một lớp đã biết. Khi đó, tất cả các thuộc tính và phương thức của lớp cũ đều trở thành thuộc tính và phương thức của lớp mới. Lớp cũ được gọi là lớp cha, lớp mới được gọi là lớp con.

Khai báo lớp kế thừa

<thuộc tính> <tên lớp con> extends <tên lớp cha> {

…..

}

Đoạn lệnh sau minh hoạ việc tạo một lớp Employee được kế thừa từ lớp Person đã được xây dựng.

classs Employee extends Person {

public float salary;

// Phương thức xây dựng

public Employee(String name, int age, float salary) {

Chương 5: Quan hệ giữa các đối tượng

Biên soạn : Bùi Công Giao --------------------------------------- 64 --------------------------------------

super(name, age);

this.salary = salary;

}

}

Khi đó, đoạn chương trình của chương trình EmployeeDemo1 vẫn in ra dòng thông báo “Minh is 21 years old!” vì khi đó đối tượng myEmployee gọi đến phương thức show() được kế thừa từ lớp Person.

package chuong4;

class EmployeeDemo1 {

public static void main(String args[]) {

Employee myEmployee = new Employee(“Minh”, 21, 300f);

myEmployee.show();

}

}

Quy tắc truy nhập trong kế thừa

Các quy tắc này quy định khả năng truy nhập của lớp con đối với các thuộc tính và phương thức của lớp cha:

private: chỉ được truy nhập trong phạm vi lớp cha, lớp con không truy nhập được. Tất cả các lớp ngoài lớp cha đều không truy nhập được.

protected: lớp con có thể truy nhập được. Tất cả các lớp không kế thừa từ lớp cha đều không truy nhập được.

final: lớp con có thể sử dụng nhưng không thể khai báo ghi đè được (chỉ đúng với phương thức).

public: lớp con có thể sử dụng và nạp chồng được. Tất cả các lớp bên ngoài đều sử dụng được.

5.3 Đa hình

Đa hình là việc triệu gọi đến các phương thức nạp chồng của đối tượng (đã học tại phần IV của chương 4). Khi một phương thức nạp chồng được gọi, chương trình sẽ dựa vào kiểu các tham số và kiểu trả về để gọi phương thức của đối tượng cho phù hợp.

Chương trình Operator minh hoạ việc khai báo nhiều hàm add() để cộng hai số hoặc cộng hai chuỗi ký tự.

package chuong5;

public class Operator {

// Cộng hai số nguyên

Chương 5: Quan hệ giữa các đối tượng

Biên soạn : Bùi Công Giao --------------------------------------- 65 --------------------------------------

public int add(int x, int y) {

return (x + y);

}

Chương 5: Quan hệ giữa các đối tượng

Biên soạn : Bùi Công Giao --------------------------------------- 66 --------------------------------------

// Cộng hai số thực

public float add(float x, float y) {

return (x + y);

}

// Cộng hai chuỗi kí tự

public String add(String a, String b) {

return (a + b);

}

public static void main(String args[]) {

Operator myOperator = new Operator();

System.out.println(“The (5+19) is ” + myOperator.add(5, 19));

System.out.println(“The (\”ab\” + \”cd\”) is \”” + myOperator.add(“ab”, “cd”) + “\””);

}

}

Chương trình sẽ hiển thị ra hai dòng thông báo:

The (5+19) is 24

The (‘ab’ + ‘cd’) is ‘abcd’

Trong lớp Operator có hai phương thức cùng tên và cùng có hai tham số đầu vào là add(). Khi chương trình thực thi lệnh myOperator.add(5, 19), chương trình sẽ tự đối chiếu các kiểu tham số, thấy 5 và 19 có dạng gần với kiểu int nhất, nên phương thức add(int, int) sẽ được gọi và trả về giá trị là 24.

Khi chương trình thực thi lệnh myOperator.add(“ab”, “cd”), chương trình sẽ tự đối chiếu các kiểu tham số, thấy “ab” và “cd” có dạng gần với kiểu String nhất, nên phương thức add(String, String) sẽ được gọi và trả về giá trị là “abcd”.

Lưu ý:

Khi gọi hàm với các kiểu dữ liệu khác với các hàm đã được khai báo, sẽ có sự chuyển đổi kiểu ngầm định diễn ra. Khi không thể thực hiện chuyển đổi kiểu ngầm định, Java sẽ phát sinh một thông báo lỗi.

Chẳng hạn, trong chương trình Operator, nếu ta thực thi lệnh myOperator.add(4.0f, 5) có dạng add(float, int), chương trình sẽ chuyển ngầm định số nguyên 5 thành float (chuyển từ kiểu int sang float thuộc diện nới rộng kiểu, là kiểu chuyển ngầm định

Chương 5: Quan hệ giữa các đối tượng

Biên soạn : Bùi Công Giao --------------------------------------- 67 --------------------------------------

trong Java) để có thể sử dụng dạng được khai báo add(float, float) và kết quả sẽ là 9.0f.

Nếu ta thực thi lệnh myOperator.add(“ab”, 5) có dạng add(String, int), vì int không thể chuyển ngầm định thành String nên lệnh này sẽ phát sinh lỗi. Để tránh lỗi này, phải chuyển đổi kiểu tường minh cho số 5 thành kiểu String bằng một trong các cách sau:

myOperator.add(“ab”, (new Integer(5)).toString());

myOperator.add(“ab”, 5 + ””);

Java sử dụng kết nối sau (late-binding) để hỗ trợ tính đa hình. Nghĩa là quyết định phương thức nào được gọi sẽ bị trì hoãn cho tới lúc chạy. Tính chất này là ghi đè (overriding) phương thức.

Ghi đè phương thức

Phương thức của lớp con có cùng tên và đặc điểm như phương thức của lớp cha ( có cùng danh sách tham số và có cùng kiểu trả về).

Khi phương thức ghi đè được triệu hồi từ lớp con, ta luôn có phiên bản phương thức ở lớp con được thực hiện. Xét chương trình Override sau:

package chuong5;

class A {

int i, j;

A(int a, int b) {

i = a;

j = b;

}

// hiển thị i và j

void show() {

System.out.println("i and j: " + i + " " + j);

}

}

class B extends A {

int k;

B(int a, int b, int c) {

super(a, b);

k = c;

}

Chương 5: Quan hệ giữa các đối tượng

Biên soạn : Bùi Công Giao --------------------------------------- 68 --------------------------------------

// hiển thị k – phương thức này ghi đè show() của A

void show() {

System.out.println("k: " + k);

}

}

public class Override {

public static void main(String args[]) {

B subOb = new B(1, 2, 3);

subOb.show(); // this calls show() in B

}

}

Kết quả chương trình:

k: 3

Ghi đè phương thức xây dựng

Trong phần 4.3.5 ta đã xem xét phương thức xây dựng; trong chương này do tính chất kế thừa mà phương thức xây dựng có thêm các đặc tính sau:

Khi một lớp con dẫn xuất từ lớp cha, nếu lớp con không có phương thức xây dựng nhưng lớp cha có phương thức xây dựng không tham số thì phương thức xây dựng này sẽ được gọi.

Ví dụ:

package chuong5;

class LopCha {

LopCha() {

System.out.println("Khởi tạo lớp cha");

}

}

class LopCon extends LopCha {

}

public class TestConstruction1 {

public static void main(String args[]) {

Chương 5: Quan hệ giữa các đối tượng

Biên soạn : Bùi Công Giao --------------------------------------- 69 --------------------------------------

LopCon c = new LopCon();

}

}

Kết quả chương trình:

Khởi tạo lớp cha

Nếu lớp con có phương thức xây dựng và trong phương thức này nó sử dụng phương thức xây dựng của lớp cha thì lệnh super được sử dụng.

Ví dụ:

package chuong5;

class LopCha {

LopCha() {

System.out.println("Khởi tạo lớp cha");

}

}

class LopCon extends LopCha {

LopCon() {

super();

System.out.println("Khởi tạo lớp con");

}

}

public class TestConstruction2 {

public static void main(String args[]) {

LopCon c = new LopCon();

}

}

Kết quả chương trình:

Khởi tạo lớp cha

Khởi tạo lớp con

Chương 5: Quan hệ giữa các đối tượng

Biên soạn : Bùi Công Giao --------------------------------------- 70 --------------------------------------

Lưu ý: super phải là lệnh đầu tiên của phương thức xây dựng lớp con.

Nếu lớp cha chỉ có một phương thức xây dựng có tham số và lớp con sử dụng lại phương thức xây dựng này bằng lệnh super thì phải truyền cho nó đúng tham số, nếu không sẽ có lỗi.

Ví dụ:

package chuong5;

class LopCha {

LopCha( int i) {

System.out.println("Giá tri i : “+i);

}

}

class LopCon extends LopCha {

LopCon() {

super();

System.out.println("Khởi tạo lớp con");

}

}

public class TestConstruction3 {

public static void main(String args[]) {

LopCon c = new LopCon();

}

}

Chương trình sẽ có lỗi tại lệnh super() và ta phải điều chỉnh chương trình như sau:

package chuong5;

class LopCha {

LopCha( int i) {

System.out.println("Giá tri i : “+i);

}

}

class LopCon extends LopCha {

Chương 5: Quan hệ giữa các đối tượng

Biên soạn : Bùi Công Giao --------------------------------------- 71 --------------------------------------

LopCon() {

super(3);

System.out.println("Khởi tạo lớp con");

}

}

public class TestConstruction3 {

public static void main(String args[]) {

LopCon c = new LopCon();

}

}

Kết quả chương trình:

Giá trị i : 3

Khởi tạo lớp con

Nếu lớp cha có phương thức xây dựng không tham số và phương thức xây dựng có tham số thì lớp con mặc định sử dụng phương thức xây dựng không tham số với lệnh super(). Nếu muốn phương thức xây dựng có tham số thì phải truyền cho nó đúng tham số một cách tường minh trong lớp con như ví dụ TestConstruction3.

5.4 Lớp trừu tượng

Lớp trừu tượng là một dạng lớp đặc biệt, trong đó có phương thức chỉ được khai báo ở dạng khuôn mẫu (template) mà không được cài đặt chi tiết. Việc cài đặt chi tiết các phương thức chỉ được thực hiện ở các lớp con kế thừa lớp trừu tượng đó.

Lớp trừu tượng được sử dụng khi muốn định nghĩa một lớp mà không thể biết và định nghĩa ngay được vài phương thức của nó.

Khai báo lớp trừu tượng

Lớp trừu tượng được khái báo như cách khai báo các lớp thông thường, ngoại trừ có thêm từ khoá abstract trong phần tính chất:

[public] abstract class <Tên lớp> {

….

}

Tính chất: mặc định là public.

Tên lớp: tuân thủ theo quy tắc đặt tên lớp thông thường của Java.

Chương 5: Quan hệ giữa các đối tượng

Biên soạn : Bùi Công Giao --------------------------------------- 72 --------------------------------------

Lưu ý:

Lớp trừu tượng cũng có thể kế thừa một lớp khác, nhưng lớp cha cũng phải là một lớp trừu tượng. (Khai báo kế thừa thông qua từ khoá extends như khai báo kế thừa thông thường).

Khai báo phương thức của lớp trừu tượng

[public] abstract <kiểu dữ liệu trả về> <Tên phương thức> ([<các tham số>]) [throws <các ngoại lệ>];

Tính chất: tính chất của một phương thức trừu tượng của lớp trừu tượng luôn là public. Nếu không khai báo tường minh thì giá trị mặc định cũng là public.

Kiểu dữ liệu trả về: có thể là các kiểu cơ bản của Java, cũng có thể là kiểu do người dùng tự định nghĩa (kiểu đối tượng).

Tên phương thức: tuân thủ theo quy tắc đặt tên phương thức của lớp

Các tham số: nếu có thì mỗi tham số được xác định bằng một cặp <kiểu tham số> <tên tham số>. Các tham số được phân cách nhau bởi dấu phẩy.

Các ngoại lệ: nếu có thì mỗi ngoại lệ được phân cách nhau bởi dấu phẩy.

Lưu ý:

• Tính chất của phương thức trừu tượng không được là private hay static. Vì phương thức trừu tượng chỉ được khai báo chi tiết (nạp chồng) trong các lớp dẫn xuất (lớp kế thừa) của lớp trừu tượng. Do đó, nếu phương thức là private thì không thể nạp chồng, nếu phương thức là static thì không thể thay đổi trong lớp dẫn xuất.

• Phương thức trừu tượng chỉ được khai báo dưới dạng khuôn mẫu nên không có cặp dấu { } và kết thúc bằng dấu ;

Đoạn lệnh sau khai báo hai phương thức của lớp trừu tượng Animal:

Phương thức getName() trả về tên loài động vật, dù chưa biết tên cụ thể loài nào nhưng kiểu trả về là String. Phương thức getFeet() trả về số chân của loài động vật, cũng chưa biết cụ thể là bao nhiêu chân nhưng kiểu trả về là int.

abstract class Animal {

abstract String getName();

abstract int getFeet();

public void show() {

System.out.println(“Show me”);

}

}

Sử dụng lớp trừu tượng

Lớp trừu tượng được sử dụng thông qua các lớp dẫn xuất của nó. Vì chỉ có các lớp dẫn xuất mới cài đặt cụ thể các phương thức được khai báo trong lớp trừu tượng.

Chương 5: Quan hệ giữa các đối tượng

Biên soạn : Bùi Công Giao --------------------------------------- 73 --------------------------------------

Đoạn lệnh sau khai báo lớp về lớp Bird kế thừa từ lớp Animal. Lớp Bird cài đặt chi tiết hai phương thức đã được khai báo trong lớp Animal: phương thức getName() sẽ trả về tên loài là “Bird”; phương thức getFeet() trả về số chân của loài chim là 2.

public class Bird extends Animal {

// Trả về tên loài chim

public String getName() {

return “Bird”;

}

// Trả về số chân của loài chim

public int getFeet() {

return 2;

}

}

Đoạn lệnh sau khai báo lớp Cat cũng kế thừa từ lớp Animal. Lớp này cài đặt chi tiết hai phương thức đã được khai báo trong lớp Animal: phương thức getName() sẽ trả về tên loài là Cat; phương thức getFeet() trả về số chân của loài mèo là 4.

public class Cat extends Animal {

// Trả về tên loài mèo

public String getName() {

return “Cat”;

}

// Trả về số chân của loài mèo

public int getFeet() {

return 4;

}

}

Chương trình AnimalDemo sử dụng lại hai lớp Bird và Cat đã định nghĩa.

package chuong5;

public class AnimalDemo {

public static void main(String args[]) {

Chương 5: Quan hệ giữa các đối tượng

Biên soạn : Bùi Công Giao --------------------------------------- 74 --------------------------------------

Bird myBird = new Bird();

System.out.println(“The ” + myBird.getName() + “ has ” + myBird.getFeet() + “ feets”);

Cat myCat = new Cat();

System.out.println(“The ” + myCat.getName() + “ has ” + myCat.getFeet() + “ feets”);

}

}

Chương trình này sẽ hiển thị hai dòng thông báo:

The Bird has 2 feets

The Cat has 4 feets

5.5 Giao tiếp

Nhằm tránh những nhập nhằng của tính chất đa kế thừa của C++, Java không cho phép kế thừa trực tiếp từ nhiều hơn một lớp cha. Nghĩa là Java không cho phép đa kế thừa trực tiếp, nhưng cho phép cài đặt nhiều giao tiếp (Interface) để có thể thừa hưởng thêm các thuộc tính và phương thức của các giao tiếp đó.

Khai báo giao tiếp

[public] interface <Tên giao tiếp> [extends <danh sách giao tiếp>] {

}

Tính chất: tính chất của một giao tiếp luôn là public. Nếu không khai báo tường minh thì giá trị mặc định cũng là public.

Tên giao tiếp: tuân thủ theo quy tắc đặt tên biến của java.

Danh sách các giao tiếp: danh sách các giao tiếp cha đã được định nghĩa để kế thừa, các giao tiếp cha được phân cách nhau bởi dấu phẩy. (Phần trong ngoặc vuông [ ] là tuỳ chọn).

Lưu ý: một giao tiếp chỉ có thể kế thừa từ các giao tiếp khác mà không thể được kế thừa từ các lớp sẵn có.

Khai báo phương thức của giao tiếp

[public] <kiểu giá trị trả về> <Tên phương thức> ([<các tham số>]) [throws <danh sách ngoại lệ>];

Tính chất: tính chất của một thuộc tính hay phương thức của giao tiếp luôn là public. Nếu không khai báo tường minh thì giá trị mặc định cũng là public. Đối với thuộc tính, thì luôn phải thêm là hằng (final) và tĩnh (static).

Chương 5: Quan hệ giữa các đối tượng

Biên soạn : Bùi Công Giao --------------------------------------- 75 --------------------------------------

Kiểu giá trị trả về: có thể là các kiểu cơ bản của Java, cũng có thể là kiểu do người dùng tự định nghĩa (kiểu đối tượng).

Tên phương thức: tuân thủ theo quy tắc đặt tên phương thức của lớp

Các tham số: nếu có thì mỗi tham số được xác định bằng một cặp <kiểu tham số> <tên tham số>. Các tham số được phân cách nhau bởi dấu phẩy.

Các ngoại lệ: nếu có thì mỗi ngoại lệ được phân cách nhau bởi dấu phẩy.

Lưu ý:

Các phương thức của giao tiếp chỉ được khai báo dưới dạng mẫu mà không có cài đặt chi tiết (có dấu ; ngay sau khai báo và không có phần cài đặt trong dấu { }). Phần cài đặt chi tiết của các phương thức chỉ được thực hiện trong các lớp sử dụng giao tiếp đó.

Các thuộc tính của giao tiếp luôn có tính chất là final, static và public. Do đó, cần gán giá trị khởi đầu ngay khi khai báo thuộc tính của giao tiếp.

Đoạn lệnh sau minh hoạ việc khai báo một thuộc tính và một phương thức của giao tiếp Product, thuộc tính lưu nhãn hiệu của nhà sản xuất sản phẩm; phương thức dùng để truy xuất giá bán của sản phẩm.

public interface Product {

public static final String MARK = “Adidas”;

public float getCost();

}

Sử dụng giao tiếp

Vì giao tiếp chỉ được khai báo dưới dạng các phương thức mẫu và các thuộc tính hằng nên việc sử dụng giao tiếp phải thông qua một lớp có cài đặt giao tiếp đó. Việc khai báo một lớp có cài đặt giao tiếp được thực hiện thông qua từ khoá implements như sau:

<tính chất> class <tên lớp> implements <các giao tiếp> {

….

}

Tính chất và tên lớp được sử dụng như trong khai báo lớp thông thường.

Các giao tiếp: một lớp có thể cài đặt nhiều giao tiếp. Các giao tiếp được phân cách nhau bởi dấu phẩy. Khi đó, lớp phải cài đặt cụ thể tất cả các phương thức của tất cả các giao tiếp mà nó sử dụng.

Lưu ý:

Một phương thức được khai báo trong giao tiếp phải được cài đặt cụ thể trong lớp có cài đặt giao tiếp nhưng không được phép khai báo chồng. Nghĩa là số lượng các tham số của phương thức trong giao tiếp phải được giữ nguyên khi cài đặt cụ thể trong lớp.

Chương 5: Quan hệ giữa các đối tượng

Biên soạn : Bùi Công Giao --------------------------------------- 76 --------------------------------------

Chương trình Shoe minh hoạ việc cài đặt một Shoe thực hiện giao tiếp Product với các thuộc tính và phương thức đã được định nghĩa.

package chuong5;

public class Shoe implements Product {

// Cài đặt phương thức được khai báo trong giao tiếp

public float getCost() {

return 10f;

}

// Phương thức truy nhập nhãn hiệu sản phẩm

public String getMark() {

return MARK;

}

// Phương thức main

public static void main(String args[]) {

Shoe myShoe = new Shoe();

System.out.println(“This shoe is ” + myShoe.getMark() + “ having a cost of $” + myShoe.getCost());

}

}

Chương trình sẽ in ra dòng: “This shoe is Adidas having a cost of $10”. Hàm getMark() sẽ trả về nhãn hiệu của sản phẩm, là thuộc tính đã được khai báo trong giao tiếp. Hàm getCost() là cài đặt riêng của lớp Shoe đối với phương thức đã được khai báo trong giao tiếp Product mà nó sử dụng, cài đặt này trả về giá trị 10 đối với lớp Shoe.

5.6 Tính chất tĩnh

Có những trường hợp mà ta muốn tất cả các đối tượng thuộc một lớp chia sẻ một giá trị của một biến cụ thể thay vì phải từng đối tượng duy trì bản sao của chính mình của biến đó như là một thuộc tính. Ngôn ngữ Java đáp ứng nhu cầu này thông qua các thuộc tính tĩnh (static) có liên quan đến tất cả đối tượng của lớp đó hơn là với các cá nhân đối tượng riêng lẽ.

Chương 5: Quan hệ giữa các đối tượng

Biên soạn : Bùi Công Giao --------------------------------------- 77 --------------------------------------

Xét định nghĩa lớp Student sau:

public class Student {

private static int totalStudents;

// ….

public int getTotalStudents() {

return totalStudents;

}

public void setTotalStudents(int x) {

totalStudents = x;

}

public int reportTotalEnrollment() {

System.out.println("Total Enrollment: " + getTotalStudents());

}

public void incrementEnrollment() {

setTotalStudents(getTotalStudents() + 1);

}

}

Khi đó đoạn lệnh sau tạo các đối tượng của lớp Student:

// tạo ba sinh viên, tăng số ghi danh mỗi lần cho mỗi sinh viên

Student s1 = new Student();

s1.incrementEnrollment();

// ...

Student s2 = new Student();

s2.incrementEnrollment();

// ...

Student s3 = new Student();

s3.incrementEnrollment();

Mỗi lần chúng ta gọi incrementEnrollment đối với một Student, những người khác sẽ được hưởng lợi, bởi vì tất cả họ đều chia sẻ giá trị totalStudents. Để chứng minh điều này, hãy yêu cầu mỗi đối tượng Student báo cáo về tổng số ghi danh:

s1.reportTotalEnrollment();

s2.reportTotalEnrollment();

s3.reportTotalEnrollment();

Đây là kết quả xuất ra:

Total Enrollment: 3

Chương 5: Quan hệ giữa các đối tượng

Biên soạn : Bùi Công Giao --------------------------------------- 78 --------------------------------------

Total Enrollment: 3

Total Enrollment: 3

Giống như thuộc tính tĩnh, phương thức tĩnh liên quan đến tất cả đối tượng của lớp và về bản chất nó thuộc lớp chứ không thuộc từng đối tượng.

Định nghĩa lớp Student sau có ba phương thức tĩnh sử dụng biến tĩnh totalStudents getTotalStudents, setTotalStudents, và reportTotalEnrollment:

public class Student {

private static int totalStudents;

// phương thức xây dựng...

public Student(...) {

// …

// tự động tăng số sinh viên mỗi lần tạo một đối tượng Student.

totalStudents++;

}

// ba phương thức tĩnh sau đây thuộc về bản chất của lớp Student

public static int getTotalStudents() {

return totalStudents;

}

public static void setTotalStudents(int x) {

totalStudents = x;

}

public static int reportTotalEnrollment() {

System.out.println("Total Enrollment: " + getTotalStudents());

}

// …

}

Ta có thể gọi các phương thức tĩnh này qua tên lớp

Student.reportTotalEnrollment();

Hoặc qua tên đối tượng của lớp đó

s.reportTotalEnrollment();

và kết quả là giống nhau.

Tuy nhiên khi sử dụng phương thức tĩnh, ta cần chú ý đến điểm hạn chế của nó: phương thức tĩnh không được phép truy xuất đến các đặc tính không tĩnh (nonstatic)

Chương 5: Quan hệ giữa các đối tượng

Biên soạn : Bùi Công Giao --------------------------------------- 79 --------------------------------------

của lớp chứa nó. Xét ví dụ sau: ta tạo phương thức tĩnh print cho lớp Student mà truy xuất đến phương thức không tĩnh getName.

public class Student {

private static int totalStudents;

// …

public static int getTotalStudents() {

return totalStudents;

}

//…

public static void print() {

System.out.println(getName() + " is one of " + getTotalStudents() +

"students.");

}

}

}

Trình biên dịch sẽ báo lỗi:

non-static method getName() cannot be referenced from a static context

Lý do là đặc tính (thuộc tính hay phương thức) không tĩnh thuộc về đối tượng của lớp, do đó chúng chỉ tồn tại khi đối tượng được tạo ra. Khi phương thức tĩnh dùng đặc tính không tĩnh thì có thể đặc tính không tĩnh chưa có.

5.7 Kiểu liệt kê

Kiểu liệt kê (Enumeration) chứa một danh sách các giá trị định sẵn và là miền giá trị cho biến hay thuộc tính đặc biệt. Kiểu này do người dùng định nghĩa có dạng như sau:

public enum EnumName { … }

Kiểu liệt kê là loại lớp đơn giản bao gồm

Chỉ có một thuộc tính như value có kiểu cơ bản:

private final type value;

Một danh sách liệt kê giá trị có thể có cho thuộc tính được biểu diễn theo dạng danh sách các cặp tên hình thức(giá trị) cách nhau bởi dấu ,

symbolicName1(value1),

symbolicName2(value2),

symbolicNameN(valueN);

Chương 5: Quan hệ giữa các đối tượng

Biên soạn : Bùi Công Giao --------------------------------------- 80 --------------------------------------

Một phương thức xây dựng dùng để tạo thuộc tính value có dạng:

EnumName(type v) {

value = v;

}

Nhận xét rằng phương thức xây dựng của kiểu liệt kê không được khai báo là public vì nó không được gọi từ bên ngoài, nó chỉ dùng bên trong kiểu liệt kê này.

Một phương thức truy nhập value() được gọi từ bên ngoài để lấy giá trị của thuộc tính.

Kết hợp lại dạng mẫu tổng quát cho việc khai báo một kiểu liệt kê như sau:

public enum EnumName {

symbolicName1(value1),

symbolicName2(value2),

symbolicNameN(valueN);

private final type value;

EnumName(type v) {

value = v;

}

public type value() {

return value;

}

}

Xét một ví dụ sau: có 5 khoa mà sinh viên có thể theo học, tên khoa kiểu String:

// Major.java

public enum Major {

Math(“Mathematics”),

Bio(“Biology”),

Chem(“Chemistry”),

CS(“Computer Science”),

PhysEd(“Physical Education”);

/* Do vậy kiểu của thuộc tính value, kiểu của tham số chuyển tới phương thức xây dựng Major, và kiểu trã về của phương thức value() tất cả là String */

private final String value;

Chương 5: Quan hệ giữa các đối tượng

Biên soạn : Bùi Công Giao --------------------------------------- 81 --------------------------------------

Major(String v) {

value = v;

}

public String value() {

return value;

}

}

Bây giờ ta có định nghĩa lớp Student có sử dụng kiểu liệt kê trên

public class Student {

private String name;

private Major major;

// …

public Student(String name, Major major) {

this.setName(name);

this.setMajor(major);

}

public void setName(String n) {

name = n;

}

public void setMajor(Major m) {

major = m;

}

// …

public void display() {

// tận dụng phương thức value() của Major

System.out.println(name + “ is a “ + major.value() + “ major.”);

}

}

Sau đây là chương trình minh hoạ cách dùng kiểu liệt kê Major:

package chuong5;

public class EnumExample {

public static void main(String[] args) {

Chương 5: Quan hệ giữa các đối tượng

Biên soạn : Bùi Công Giao --------------------------------------- 82 --------------------------------------

/* Tạo đối tượng Student và dùng kiểu liệt kê Major để gán một trong 5 giá trị hợp lệ cho thuộc tính major của đối tượng này, Major.CS hàm ý một giá trị như vậy */

Student s = new Student(“Fred Schnurd”, Major.CS);

s.display();

}

}

Kết quả chương trình:

Fred Schnurd is a Computer Science major.

Với tên hình thức Major.CS đại diện cho chuỗi “Computer Science”. Điều này có được do lời gọi tới value() trong phương thức display() của Student. Nếu ta thay đổi display() như sau:

public void display() {

// in ra major trực tiếp

System.out.println(name + “ is a “ + major + “ major.”);

}

Thì tên hình thức của kiểu liệt kê Major được in ra

Fred Schnurd is a CS major.

Lưu ý : ta không thể gán bất kỳ giá trị nào khác hơn 5 giá trị đã định nghĩa trong kiểu liệt kê Major cho thuộc tính major. Nếu vi phạm Java sẽ báo lỗi lúc biên dịch.

Một ví dụ khác về kiểu liệt kê Grade :

public enum Grade {

// các giá trị là hằng số kiểu double

A(4.0),

B(3.0),

C(2.0),

D(1.0),

F(0.0);

// thuộc tính value có kiểu double.

private final double value;

Chương 5: Quan hệ giữa các đối tượng

Biên soạn : Bùi Công Giao --------------------------------------- 83 --------------------------------------

Grade(double v) { // tham số kiểu double.

value = v;

}

public double value() { // trã về kiểu double

return value;

}

}

Chương trình minh hoạ cách dùng kiểu liệt kê Grade:

public class GradeDemo {

public static void main(String[] args) {

Grade grade;

grade = Grade.A;

System.out.println(grade);

System.out.println(grade.value());

}

}

Kết quả xuất ra :

A

4.0

5.8 Case Study

Trong phần này, chúng ta sẽ viết một chương trình quản lý nhân viên của một công ty. Bao gồm các lớp chính:

Lớp Human là một lớp trừu tượng, chỉ có một phương thức duy nhất là show().

Lớp Person là lớp kế thừa từ lớp Human, có hai thuộc tính là name và age. Để đóng gói dữ liệu các thuộc tính này có dạng private và các phương thức truy nhập chúng (get và set). Ngoài ra lớp này còn cài đặt phương thức show() kế thừa từ lớp trừu tượng Human.

Lớp Employee là lớp kế thừa từ lớp Person, có thêm thuộc tính là salary. Thuộc tính này cũng có dạng private để đóng gói dữ liệu và cần các phương thức truy nhập get/set. Lớp này cài đặt lại phương thức show(). Hơn nữa, lớp Employee còn có thêm hai phương thức addSalary() và addSalary(float) để tính tăng lương cho nhân viên: một phương thức tăng lương theo tỉ lệ mặc định là 10% (không cần tham số), và một phương thức tăng theo giá trị cụ thể đưa vào (cần tham số).

Chương 5: Quan hệ giữa các đối tượng

Biên soạn : Bùi Công Giao --------------------------------------- 84 --------------------------------------

Các phần tiếp theo sẽ trình bày phần cài đặt chi tiết cho các lớp này.

Lớp Human là một lớp trừu tượng, chỉ có một phương thức duy nhất là show().

Đây là nội dung tập tin Human.java.

package chuong5;

abstract class Human {

abstract void show();

}

Lớp Person là lớp kế thừa từ lớp Human:

• Có hai thuộc tính là name và age có dạng private

• Các phương thức truy nhập các thuộc tính name (getName() và setName(String)) và age (getAge() và setAge(int)).

• Cài đặt chồng phương thức show() kế thừa từ lớp trừu tượng Human.

Đây là nội dung tập tin Person.java.

package chuong5;

class Person extends Human {

private String name;

private int age;

// Phương thức xây dựng không có tham số

public Person() {

super();

name = “”;

age = 0;

}

// Phương thức xây dựng có tham số

public Person(String name, int age) {

this.name = name;

this.age = age;

}

// Phương thức truy nhập thuộc tính name

public String getName() {

return name;

}

Chương 5: Quan hệ giữa các đối tượng

Biên soạn : Bùi Công Giao --------------------------------------- 85 --------------------------------------

public void setName(String name) {

this.name = name;

}

// Phương thức truy nhập thuộc tính age

public int getAge() {

return age;

}

public void setAge(int age) {

this.age = age;

}

// Khai báo nạp chồng

public void show() {

System.out.println( name + “ is ” + age + “ years old!”);

}

}

Lớp Employee là lớp kế thừa từ lớp Person:

Có thêm thuộc tính là salary cũng có dạng private để đóng gói dữ liệu và cần các phương thức truy nhập get/set.

Lớp này cài đặt lại phương thức show().

Có thêm hai phương thức addSalary() và addSalary(float) để tính tăng lương cho nhân viên: phương thức addSalary() tăng lương theo tỉ lệ mặc định là 10% (không cần tham số), phương thức addSalary(float) tăng theo giá trị cụ thể đưa vào (cần tham số).

Sau đây là nội dung tập tin Employee.java

package chuong5;

class Employee extends Person {

private float salary;

// Phương thức xây dựng không có tham số

public Employee() {

super();

salary = 0f;

}

Chương 5: Quan hệ giữa các đối tượng

Biên soạn : Bùi Công Giao --------------------------------------- 86 --------------------------------------

// Phương thức xây dựng có tham số

public Employee(String name, int age, float salary) {

super(name, age);

this.salary = salary;

}

/* Phương thức truy nhập thuộc tính salarry */

public float getSalary() {

return salary;

}

public void setSalary(float salary) {

this.salary = salary;

}

// Khai báo nạp chồng

public void show() {

System.out.println( getName() + “ is ” + getAge() + “ years old having a salary of $” + salary + “/month!”);

}

/* Phương thức tăng lương */

public void addSalary() {

salary *= 1.1f;

}

public void addSalary(float addition) {

salary += addition;

}

}

Lưu ý: Trong phương thức nạp chồng show() của lớp Employee, ta phải dùng các phương thức public được kế thừa từ lớp Person là getName() và getAge() để truy nhập đến thuộc tính name và age mà không thể truy xuất trực tiếp.

Chương trình CaseStudy5 chứa hàm main để chạy minh hoạ việc sử dụng các lớp Person và Employee.

Đây là nội dung của tập tin CaseStudy5.java

Chương 5: Quan hệ giữa các đối tượng

Biên soạn : Bùi Công Giao --------------------------------------- 87 --------------------------------------

package chuong5;

public class CaseStudy5 {

public static void main(String args[]) {

// Sử dụng lớp Person

Person myPerson = new Person(“Vinh”, 25);

myPerson.show();

// Sử dụng lớp Employee

Employee myEmployee = new Employee(“Sơn”, 25, 300f);

myEmployee.show();

// Tăng lương theo mặc định

myEmployee.addSalary();

myEmployee.show();

// Tăng lương lên $50

myEmployee.addSalary(50f);

myEmployee.show();

}

}

Chương trình sẽ hiển thị nội dung như sau:

Vinh is 25 years old!

Sơn is 25 years old having a salary of $300/month!

Sơn is 25 years old having a salary of $330/month!

Sơn is 25 years old having a salary of $380/month!

Dòng thứ nhất in ra dòng thông báo theo phương thức show() của lớp Person. Dòng thứ hai cũng hiển thị thông báo theo phương thức show() của lớp Employee đã được khai báo nạp chồng. Dòng thứ ba hiển thị thông báo sau khi đã tăng lương theo mặc định (10%) từ 300$ lên 330$. Dòng thứ tư hiển thị thông báo sau khi tăng lương thêm một lần nữa với lượng tăng là 50$ từ 330$ lên 380$.

Chương 5: Quan hệ giữa các đối tượng

Biên soạn : Bùi Công Giao --------------------------------------- 88 --------------------------------------

TỔNG KẾT CHƯƠNG 5

Trong chương này các vấn đề sau được trình bày:

Một sự kết hợp mô tả mối quan hệ giữa các lớp, nghĩa là mối quan hệ tiềm năng giữa các đối tượng của hai lớp, trong khi đó một liên kết mô tả một quan hệ thực sự giữa hai đối tượng của các lớp đó.

Xét sự kết hợp giữa lớp X và Y, multiplicity là là số đối tượng kiểu X liên kết tới một đối tượng kiểu Y và ngược lại; có thể là 1:1, 1:m, và m:m. Trong tất cả trường hợp sự tham gia của đối tượng ở hai đầu mối quan hệ có thể là tuỳ thích hoặc bắt buộc.

Như thế nào để tạo lớp mới từ lớp đang có qua tính thừa kế, và điều gì nên và không nên khi tạo lớp mới. Hơn nữa chúng ta có thể mở rộng lớp cha và chuyên biệt nó bằng việc thêm vào các đặc trưng hay ghi đè phương thức.

Sự phân cấp lớp phát triển theo thời gian như thế nào, và điều gì ta có thể làm để tránh các hiệu ứng xấu tới ứng dụng khi sự phân cấp lớp thay đổi theo yêu cầu thực tế.

Vài điều phức tạp của hàm xây dựng liên quan tới tính thừa kế.

Tại sao đa thừa kế có thể gây rắc rối cho việc diễn giải ngôn ngữ hướng đối tượng.

BÀI TẬP

1. Nêu mối quan hệ?

Dược sỹ – Toa thuốc

Toa thuốc – Thuốc

2. Cho các ví dụ minh hoạ các dạng quan hệ cơ bản.

3. Trong các khai báo phương thức của một lớp (không trừu tượng) sau, khai báo nào là đúng:

a. String getName{return name;}

b. String getName(){return name;}

c. String getName(){return name;};

d. String getName();

4. Trong các khai báo phương thức trừu tượng của một lớp trừu tượng sau, khai báo nào là đúng:

a. abstract String getName{return name;}

b. abstract String getName(){return name;}

c. abstract String getName(){return name;};

d. abstract String getName();

5. Trong các khai báo một giao tiếp sau, khai báo nào là đúng:

a. public abstract MyInterface{…}

Chương 5: Quan hệ giữa các đối tượng

Biên soạn : Bùi Công Giao --------------------------------------- 89 --------------------------------------

b. public class MyInterface{…}

c. public interface MyInterface{…}

d. public interface MyInterface();

6. Trong các khai báo kế thừa lớp sau, giả sử Aclass và Bclass là các lớp có sẵn, khai báo nào là đúng:

a. public class MyClass extends Aclass{…}

b. public class MyClass Extends Aclass{…}

c. public class MyClass extends Aclass, Bclass{…}

d. public class MyClass extends Aclass, extends Bclass{…}

7. Trong các khai báo kế thừa giao tiếp sau, giả sử Ainterface và Binterface là các giao tiếp có sẵn, khai báo nào là đúng:

a. public interface MyInterface extends Ainterface{…}

b. public interface MyInterface Extends Ainterface{…}

c. public interface MyInterface extends Ainterface, Binterface{…}

d. public interface MyInterface extends Ainterface, extends Binterface{…}

8. Trong các khai báo một lớp sử dụng giao tiếp sau, giả sử Ainterface và Binterface là các giao tiếp có sẵn, khai báo nào là đúng:

a. public class MyClass extends Ainterface{…}

b. public class MyClass implements Ainterface{…}

c. public class MyClass implements Ainterface, Binterface{…}

d. public class MyClass implements Ainterface, implements Binterface{…}

9. Xét đoạn chương trình cài đặt một lớp sử dụng một giao tiếp sau:

public interface MyInterface {

public void show(String a);

}

// Khai báo lớp sử dụng giao tiếp

public class MyClass implements MyInterface {

}

Khi đó, lớp MyClass chỉ có duy nhất một phương thức cài đặt chi tiết phương thức show của giao tiếp MyInterface. Trong các cách cài đặt sau, cách nào là đúng:

a. public int show(String a){ System.out.println(a);}

b. public void show(){ System.out.println(“Show!”);}

c. public void show(String a){ System.out.println(a);}

d. public void show(String a, String b){ System.out.println(a+b);}

10. Xét đoạn chương trình cài đặt phép toán cộng với các số hạng có kiểu khác nhau:

public class MyOperator {

Chương 5: Quan hệ giữa các đối tượng

Biên soạn : Bùi Công Giao --------------------------------------- 90 --------------------------------------

public static int add(int a, int b) {

return a+b;

}

public static float add(float a, float b) {

return a+b;

}

public static double add(double a, double b) {

return a+b;

}

public static String add(String a, String b) {

return a+b;

}

}

Khi ta gọi lệnh MyOperator.add(-5, 100), chương trình sẽ gọi phương thức nào?

a. add(int, int)

b. add(float, float)

c. add(double, double)

d. add(String, String)

e. Thông báo lỗi

11. Cũng với lớp MyOperator trong bài 9, khi ta gọi lệnh MyOperator.add(5.0d, 100f), chương trình sẽ gọi phương thức nào?

a. add(int, int)

b. add(float, float)

c. add(double, double)

d. add(String, String)

e. Thông báo lỗi

12. Cũng với lớp MyOperator trong bài 9, khi ta gọi lệnh MyOperator.add(“abcd”, 100f), chương trình sẽ gọi phương thức nào?

a. add(int, int)

b. add(float, float)

c. add(double, double)

d. add(String, String)

e. Thông báo lỗi

13. Về sự khác nhau giữa lớp và giao tiếp trong Java, những nhận định nào sau đây là đúng:

a. Lớp được kế thừa tối đa từ một lớp khác, giao tiếp có thể kế thừa từ nhiều giao tiếp khác.

Chương 5: Quan hệ giữa các đối tượng

Biên soạn : Bùi Công Giao --------------------------------------- 91 --------------------------------------

b. Lớp có thể kế thừa từ giao tiếp, nhưng giao tiếp không kế thừa từ các lớp.

c. Thuộc tính của lớp có thể có tính chất bất kỳ, thuộc tính của giao tiếp phải là hằng số và tĩnh.

d. Phương thức của lớp có thể được cài đặt chi tiết, phương thức của giao tiếp chỉ được khai báo ở dạng khuôn mẫu.

14. Xét đoạn chương trình khai báo hai lớp kế thừa nhau như sau:

public class Person {

public void show() {

System.out.println(“This is a person!”);

}

}

và:

public class Employee extends Person {

public void show(int x) {

System.out.println(“This is the employee ” + x);

}

}

Khi đó, đoạn chương trình sau sẽ hiển thị thông báo nào?

public class Demo1 {

public static void main(String args[]) {

Employee myEmployee = new Employee();

myEmployee.show();

}

}

a. This is a person!.

b. This is the employee 0.

c. Thông báo lỗi.

d. Không hiển thị gì cả.

15. Cũng với hai lớp Person và Employee trong bài trên. Đoạn chương trình sau sẽ hiển thị thông báo nào?

public class Demo2 {

public static void main(String args[]) {

Employee myEmployee = new Employee();

myEmployee.show(10);

}

}

Chương 5: Quan hệ giữa các đối tượng

Biên soạn : Bùi Công Giao --------------------------------------- 92 --------------------------------------

a. This is a person!.

b. This is the employee 10.

c. Thông báo lỗi.

d. Không hiển thị gì cả.

16. Viết một chương trình khai báo một lớp Rectangle. Lớp này có hai thuộc tính cục bộ là chiều rộng và chiều dài của hình. Viết hai phương thức xây dựng tường minh cho lớp này: Một khởi tạo với một tham số kiểu int, khi đó, chiều rộng và chiều dài được thiết lập thành giá trị tham số đưa vào (hình vuông). Một khởi tạo với hai tham số kiểu int tương ứng là chiều rộng và chiều dài.

17. Viết một giao tiếp khai báo các phép toán cộng hai số, cộng hai chuỗi, cộng chuỗi và số. Sau đó viết một lớp cài đặt giao tiếp đó.

18. Viết một lớp trừu tượng về các hình phẳng, trong đó khai báo các phương thức tính chu vi và diện tích của hình. Sau đó viết ba lớp kế thừa từ lớp trừu tượng đó là: lớp hình vuông, lớp hình chữ nhật và lớp hình tròn. Tự thiết lập các thuộc tính cục bộ cần thiết cho mỗi lớp con và khai báo nạp chồng hai phương thức ban đầu trong mỗi lớp con.

19. Xét bài 3 của chương 1 hãy nêu các mối quan hệ của các lớp trong hệ thống đề xuất.

20. Xét lớp FeatureFilm có các phương thức như sau:

public void update(Actor a, String title)

public void update(Actor a, Actor b, String title)

public void update(String topic, String title)

Các phương thức nào sau đây là hợp lệ khi được thêm vào lớp FeatureFilm ?

public boolean update(String category, String theater)

public boolean update(String title, Actor a)

public void update(Actor b, Actor a, String title)

public void update(Actor a, Actor b)

21. Tạo lớp FeatureFilm của bài trước để kiểm tra các phương thức. Tạo lớp Actor rỗng như sau:

public class Actor { }

22. Xác định phương thức nào nạp chồng, ghi đè của bốn lớp sau—Vehicle, Automobile, Truck, và SportsCar:

public class Vehicle {

String name;

public void fuel(String fuelType) {

// bỏ qua chi tiết ...

Chương 5: Quan hệ giữa các đối tượng

Biên soạn : Bùi Công Giao --------------------------------------- 93 --------------------------------------

}

public boolean fuel(String fuelType, int amount) {

// bỏ qua chi tiết ...

}

}

public class Automobile extends Vehicle {

public void fuel(String fuelType, String timeFueled) {

// bỏ qua chi tiết ...

}

public boolean fuel(String fuelType, int amount) {

// ...

}

}

public class Truck extends Vehicle {

public void fuel(String fuelType) {

// ...

}

}

public class SportsCar extends Automobile {

public void fuel(String fuelType) {

// ...

}

public void fuel(String fuelType, String timeFueled) {

// ...

}

}

Có bao nhiêu phương thức Fuel mà bốn lớp nhận diện? Hãy liệt kê chúng.

23. Xét các lớp FarmAnimal, Horse, và Cow:

public class FarmAnimal {

private String name;

public String getName() {

Chương 5: Quan hệ giữa các đối tượng

Biên soạn : Bùi Công Giao --------------------------------------- 94 --------------------------------------

return name;

}

public void setName(String n) {

name = n;

}

public void makeSound() {

System.out.println(getName() + " makes a sound ...");

}

}

public class Cow extends FarmAnimal {

public void makeSound() {

System.out.println(getName() + " goes Moooooo ...");

}

}

public class Horse extends FarmAnimal {

public void setName(String n) {

super.setName(n + " [a Horse]");

}

}

Đoạn lệnh sau sẽ in ra câu gì?

Cow c = new Cow();

Horse h = new Horse();

c.setName("Elsie");

h.setName("Mr. Ed");

c.makeSound();

h.makeSound();

24. Tạo các kiểu liệt kê sau

Kiểu liệt kê Weekday đại diện bảy ngày trong tuần có kiểu String.

Kiểu liệt kê SolarSystem đại diện 9 hành tinh trong hệ mặt trời: Mercury, Venus, Earth, Mars, Jupiter, Saturn, Uranus, Neptune, và Pluto. Để hỗ trợ kiểu liệt kê này hãy khai báo lớp Planet có tính chất sau: một thuộc tính kiểu String chứa tên hành tinh, một phương thức xây dựng có tên hành tinh như tham số và một phương thức toString trã về chuỗi "Planet: planetName".

Chương 6: Tập đối tượng

Biên soạn : Bùi Công Giao --------------------------------------- 95 --------------------------------------

Chương 6. Tập đối tượng

Nội dung chương này nhằm giới thiệu:

Thao tác trên mảng

Thuộc tính của ba kiểu tập hợp thông thường : danh sách có thứ tự, bộ, tự điển.

Tập hợp giúp mô hình các vấn đề rất phức tạp trong thế giới thực

Kỹ thuật thiết kế các kiểu tập hợp riêng

6.1 Khái niệm tập đối tượng

Tập đối tượng dùng để tập hợp các đối tượng có cùng tính chất nào đó để quản lý chúng như là một nhóm và thao tác trên chúng chung với nhau, và cùng có thể truy cập đến từng đối tượng khi cần thiết.

Ví dụ:

Một giáo sư có thể lướt qua tất cả các đối tượng sinh viên đăng ký một khóa học mà giáo sư giảng dạy để vào điểm cuối kỳ.

Hệ thống đăng ký sinh viên duyệt qua tất cả đối tượng khoá học trước khi cho mở hay huỷ khoá học vì không đủ sinh viên đăng ký

Khai báo một biến tham chiếu tới một kiểu tập hợp:

CollectionType<elementType> x;

Biến x lúc này chưa xác định. Kế tiếp ta dùng toán tử new để triệu hồi phương thức xây dựng của kiểu tập hợp đó.

X = new CollectionType<elementType>();

Ví dụ:

ArrayList<Student> x; // ArrayList là một trong các kiểu tập hợp có sẵn

x = new ArrayList<Student>();

6.2 Ba kiểu tập đối tượng cơ bản

Danh sách có thứ tự (ordered list): kiểu tập hợp cho phép chèn các phần tử vào vị trí có sắp thứ tự và sau đó truy xuất chúng theo cùng thứ tự đó. Các đối tượng đặc biệt có thể truy xuất theo vị trí của chúng trong danh sách (ví dụ chúng có thể lấy theo thứ tự đầu tiên, cuối cùng hay thứ n).

Hầu hết các kiểu tập hợp-bao gồm danh sách có thứ tự- không cần gán khả năng chứa lúc khởi tạo ban đầu. Tập hợp tự động mở rộng khi thêm vào phần tử và khi phần tử bị lấy ra tập hợp tự động khít lại như hình 6.1.

Vài lớp Java là danh sách có thứ tự như: ArrayList, LinkedList, Stack,Vector.

Chương 6: Tập đối tượng

Biên soạn : Bùi Công Giao --------------------------------------- 96 --------------------------------------

Khi một phần tử bị xoá từ một tập hợp

Lỗ trống tự động bị bịt lại

Hình 6.1 Hầu hết tập hợp tự động co lại khi một phần tử bị lấy ra

Một từ điển (dictionary), hay một bản đồ (map), dùng để lưu từng tham chiếu đối tượng cùng với một khoá tìm kiếm duy nhất (unique lookup key) của đối tượng để sau đó khoá này được dùng để truy xuất nhanh chóng đối tượng này.

Khoá thường được tạo ra dựa trên một hay nhiều giá trị thuộc tính của đối tượng.Ví dụ, mã sinh viên là khoá của của Student vì giá trị này là duy nhất cho mỗi Student (hình 6.2).

Vài lớp Java là từ điển như HashMap, Hashtable, và TreeMap.

 

 

 

 

Hình 6.2 Dùng khoá để truy cập trực tiếp đối tượng trong tập hợp từ điển

Một bộ (set) là một tập hợp không sắp thứ tự, nghĩa rằng không thể truy xuất một phần tử của bộ bằng vị trí của nó trong bộ kể từ khi nó được thêm vào.

Bộ không cho phép các phần tử giống nhau. Ví dụ nếu ta tạo một bộ tham chiếu các đối tượng Student và một tham chiếu tới một đối tượng đã có trong bộ thì một

 

Khoá Tham chiếu

đối tượng

Dùng khoá để truy xuất chính xác đối tượng trong tự điển

Chương 6: Tập đối tượng

Biên soạn : Bùi Công Giao --------------------------------------- 97 --------------------------------------

tham chiếu thứ hai tới cùng đối tượng đó không thể được thêm vào bộ; bộ sẽ lờ đi yêu cầu thêm này. Nhưng đối với danh sách có thứ tự hay tự điển ta có thể làm điều này như hình 6.3.

Vài lớp Java là bộ như HashSet và TreeSet.

Hình 6.3 Các tập hợp không phải là bộ cho phép nhiều tham chiếu tới cùng một đối tượng.

6.3 Mảng

Mảng là kiểu đơn giản của danh sách có thứ tự

Khai báo một biến tham chiếu tới một kiểu tập hợp:

datatype[] x;

ví dụ:

int[] x;

// khai báo biến y như là một tham chiếu tới một mảng lưu trữ 20 đối tượng Student

Student[] y = new Student[20];

hay

// khai báo một mảng chứa 3 giá trị kiểu double

double[] data = new double[3];

// gán 4.7cho phần tử đầu tiên (thứ zero)

data[0] = 4.7;

// lấy giá trị phần tử cuối cùng

double temp = data[2];

Khởi tạo nội dung mảng chuỗi

String[] names = { "Steve", "Jacquie", "Chloe", "Shylow", "Baby Grode" };

Một cách khác (dài hơn)

Chương 6: Tập đối tượng

Biên soạn : Bùi Công Giao --------------------------------------- 98 --------------------------------------

String[] names = new String[5];

names[0] = "Steve";

names[1] = "Jacquie";

names[2] = "Chloe";

names[3] = "Shylow";

names[4] = "Baby Grode";

Thao tác trên mảng đối tượng

studentBody[0] = new Student("Fred");

studentBody[1] = new Student("Mary");

hay

Student s = new Student("Fred");

studentBody[0] = s;

// sử dụng lại s

s = new Student("Mary");

studentBody[1] = s;

ArrayList

Đây là kiểu tập hợp mảng mở rộng

Xét chương trình ArrayListExample sau:

package 6;

import java.util.*;

public class ArrayListExample {

public static void main(String[] args) {

// khởi tạo tập hợp students

ArrayList<Student> students = new ArrayList<Student>();

// tạo vài đối tượng Student

Student a = new Student("Herbie");

Student b = new Student("Klem");

Student c = new Student("James");

// lưu tham chiếu ba Student này vào trong tập hợp

students.add(a);

students.add(b);

students.add(c);

// ... duyệt tập hợp này để in tên sinh viên

Chương 6: Tập đối tượng

Biên soạn : Bùi Công Giao --------------------------------------- 99 --------------------------------------

for (Student s : students)

System.out.println(s.getName());

}

}

Kết quả xuất ra:

Herbie

Klem

James

Chép nội dung của ArrayList vào một mảng

Xét ví dụ tạo một ArrayList tên là students và thêm vào 3 đối tượng Student

ArrayList<Student> students = new ArrayList<Student>();

students.add(new Student("Herbie"));

students.add(new Student("Klemmie"));

students.add(new Student("James"));

Kế đến tạo một mảng kiểu Student có kích thước bằng mảng students

Student[] copyOfStudents = new Student[students.size()];

Để chép nội dụng students vào copyOfStudents ta gọi phương thức toArray trên students

students.toArray(copyOfStudents);

Cuối cùng ta kiểm tra nội dung 2 mảng xem có giống nhau không

System.out.println("The ArrayList contains the following students:");

for (Student s : students)

System.out.println(s.getName());

System.out.println();

System.out.println("The array contains the following students:");

for (int i = 0; i < copyOfStudents.length; i++)

System.out.println(copyOfStudents[i].getName());

Chương 6: Tập đối tượng

Biên soạn : Bùi Công Giao --------------------------------------- 100 --------------------------------------

Đây là kết quả:

The ArrayList contains the following students:

Herbie

Klemmie

James

The array contains the following students:

Herbie

Klemmie

James

6.4 Các loại tập đối tượng thường gặp

6.4.1 LinkedList

Là một danh sách mà mỗi phần tử có một liên kết tới phần tử kế tiếp.

Có danh sách liên kết tuyến tính và danh sách liên kết tuyến tính quay vòng. Danh sách liên kết tuyến tính kết thúc ở một điểm nào đó còn danh sách liên kết tuyến tính quay vòng có phần tử cuối cùng chỉ tới phần tử đầu tiên để tạo một chu trình.

Chương trình LinkListDemo sau đây minh hoạ danh sách liên kết:

package chuong6;

import java.util.*;

public class LinkedListDemo {

public static void main(String[] args) {

LinkedList link = new LinkedList();

// thêm vào 3 phần tử khác kiểu

link.add("a");

link.add("b");

link.add(new Integer(10));

System.out.println("The contents of array is " + link);

System.out.println("The size of an linkedlist is " + link.size());

// thêm phần tử đầu

link.addFirst(new Integer(20));

System.out.println("The contents of array is" + link);

System.out.println("The size of an linkedlist is " + link.size());

Chương 6: Tập đối tượng

Biên soạn : Bùi Công Giao --------------------------------------- 101 --------------------------------------

// thêm phần tử cuối

link.addLast("c");

System.out.println("The contents of array is " + link);

System.out.println("The size of an linkedlist is " + link.size());

// thêm phần tử thứ ba

link.add(2, "j");

System.out.println("The contents of array is " + link);

System.out.println("The size of an linkedlist is " + link.size());

// thêm phần tử thứ hai

link.add(1, "t");

System.out.println("The contents of array is " + link);

System.out.println("The size of an linkedlist is " + link.size());

// xoá phần tử thứ tư

link.remove(3);

System.out.println("The contents of array is " + link);

System.out.println("The size of an linkedlist is " + link.size());

}

}

Đây là kết quả:

The contents of array is [a, b, 10]

The size of an linkedlist is 3

The contents of array is[20, a, b, 10]

The size of an linkedlist is 4

The contents of array is [20, a, b, 10, c]

The size of an linkedlist is 5

The contents of array is [20, a, j, b, 10, c]

The size of an linkedlist is 6

The contents of array is [20, t, a, j, b, 10, c]

The size of an linkedlist is 7

The contents of array is [20, t, a, b, 10, c]

Chương 6: Tập đối tượng

Biên soạn : Bùi Công Giao --------------------------------------- 102 --------------------------------------

The size of an linkedlist is 6

6.4.2 HashMap

Là kiểu tập hợp từ điển, HashMap cho phép truy xuất trực tiếp tới một đối tượng bằng khoá duy nhất của nó. Cả hai khoá và đối tượng có thể thuộc bất cứ kiểu nào.

Xét chương trình HashMapExample sau đây:

package chuong6;

public class Student {

private String idNo;

private String name;

public Student(String i, String n) {

idNo = i;

name = n;

}

public String getName() {

return name;

}

public String getIdNo() {

return idNo;

}

}

import java.util.HashMap;

public class HashMapExample {

public static void main(String[] args) {

/* khởi tạo tập hợp students có kiểu HashMap với String là kiểu khoá và Student là kiểu giá trị */

HashMap<String, Student> students = new HashMap<String, Student>();

// tạo 3 Student

Student s1 = new Student("12345-12", "Fred");

Student s2 = new Student("98765-00", "Barney");

Student s3 = new Student("71024-91", "Wilma");

// chèn 3 Student vào students, dùng idNo như khoá

students.put(s1.getIdNo(), s1);

Chương 6: Tập đối tượng

Biên soạn : Bùi Công Giao --------------------------------------- 103 --------------------------------------

students.put(s2.getIdNo(), s2);

students.put(s3.getIdNo(), s3);

// lấy Student theo một ID đặc biệt.

String id = "98765-00";

System.out.println("Let's try to retrieve a Student with ID = " + id);

Student x = students.get(id);

// nếu giá trị trả về về bởi get khác null thì ta đã tìm thấy một Student ...

if (x != null)

System.out.println("Found! Name = " + x.getName());

else

// không có Student nào như vậy

System.out.println("Invalid ID: " + id);

System.out.println();

// thử một ID không hợp lệ

id = "00000-00";

System.out.println("Let's try to retrieve a Student with ID = " + id);

x = students.get(id);

if (x != null)

System.out.println("Found! Name = " + x.getName());

else

System.out.println("Invalid ID: " + id);

System.out.println();

System.out.println("Here are all of the students:");

System.out.println();

// duyệt HashMap này để xử lý tất cả Student.

for (Student s : students.values()) {

System.out.println("ID: " + s.getIdNo());

System.out.println("Name: " + s.getName());

System.out.println();

}

}

}

Chương 6: Tập đối tượng

Biên soạn : Bùi Công Giao --------------------------------------- 104 --------------------------------------

Kết quả xuất ra:

Let's try to retrieve a Student with ID = 98765-00

Found! Name = Barney

Let's try to retrieve a Student with ID = 00000-00

Invalid ID: 00000-00

Here are all of the students:

ID: 12345-12

Name: Fred

ID: 98765-00

Name: Barney

ID: 71024-91

Name: Wilma

6.4.3 TreeMap

Là kiểu tập hợp từ điển khác trong Java. TreeMap tương tự như HashMap, với một điểm khác biệt sau:

Khi ta duyệt qua TreeMap, đối tượng được truy xuất tới theo thứ tự tăng dần.

Khi ta duyệt qua HashMap, không gì đảm bảo rằng các phần tử được truy xuất theo thứ tự xác định trước.

Xét chương trình TreeHash sau đây:

package chuong6;

import java.util.*;

public class TreeHash {

public static void main(String[] args) {

/* khởi tạo 2 tập hợp, một HashMap và một TreeMap với String là kiểu khoá và là kiểu đối tượng */

HashMap<String, String> h = new HashMap<String, String>();

TreeMap<String, String> t = new TreeMap<String, String>();

// chèn vài chuỗi vào HashMap, chuỗi chèn vừa là khoá vừa là giá trị

h.put("FISH", "FISH");

h.put("DOG", "DOG");

h.put("CAT", "CAT");

Chương 6: Tập đối tượng

Biên soạn : Bùi Công Giao --------------------------------------- 105 --------------------------------------

h.put("ZEBRA", "ZEBRA");

h.put("RAT", "RAT");

// chèn chuỗi giống như vậy theo cùng thứ tự vào TreeMap

t.put("FISH", "FISH");

t.put("DOG", "DOG");

t.put("CAT", "CAT");

t.put("ZEBRA", "ZEBRA");

t.put("RAT", "RAT");

// duyệt qua HashMap để lấy tất cả chuỗi ...

System.out.println("Retrieving from the HashMap:");

for (String s : h.values())

System.out.println(s);

System.out.println();

// ... và rồi duyệt qua TreeMap

System.out.println("Retrieving from the TreeMap:");

for (String s : t.values())

System.out.println(s);

}

}

Kết quả xuất ra:

Retrieving from the HashMap:

ZEBRA

CAT

FISH

DOG

RAT

Retrieving from the TreeMap:

CAT

DOG

FISH

RAT

Chương 6: Tập đối tượng

Biên soạn : Bùi Công Giao --------------------------------------- 106 --------------------------------------

ZEBRA

6.4.4 HashSet

Đây là kiểu bộ dùng bảng băm (hash table) để lưu trữ. Bảng băm là CTDL lưu thông tin bằng việc ánh xạ khoá của từng phần tử dữ liệu vào một vị trí hay chỉ mục của mảng.

Chương trình NamsSet sau đây minh hoạ HashSet:

package chuong6;

import java.util.*;

public class NamesSet {

public static void main(String args[]) {

HashSet objSet = new HashSet();

// thêm 5 phần tử vào bảng băm

objSet.add("Patrick");

objSet.add("Bill");

objSet.add("Gene");

objSet.add("Daniel");

objSet.add("Claire");

System.out.println("Size of the set : " + objSet.size());

// in các phần tử của bảng băm

System.out.println("Contents of the set :");

System.out.println(objSet);

System.out.println("\nContents of the set after adding 2 elements :");

objSet.add("Rubio");

objSet.add("Yang Sun");

System.out.println(objSet);

System.out.println("Size of the set : " + objSet.size());

}

}

Chương 6: Tập đối tượng

Biên soạn : Bùi Công Giao --------------------------------------- 107 --------------------------------------

Kết quả xuất ra:

Contents of the set :

[Bill, Gene, Claire, Patrick, Daniel]

Size of the set : 5

Contents of the set after adding 2 elements :

[Bill, Gene, Claire, Patrick, Yang Sun, Daniel, Rubio]

Size of the set : 7

6.4.5 TreeSet

Kiểu bộ dùng một cây (tree) để lưu dữ liệu. Trong cây các đối tượng được sắp xếp theo thứ tự tăng dần vì vậy truy xuất đối tượng nhanh hơn.

Chương trình TreeDemo sau đây minh hoạ TreeSet:

package chuong6;

import java.util.*;

class TreeDemo {

public static void main(String[] args) {

TreeSet objTree = new TreeSet();

// thêm các phần tử vào cây

objTree.add(“beta”);

objTree.add(“gama”);

objTree.add(“tera”);

objTree.add(“alpha”);

objTree.add(“penta”);

System.out.println(“Automatically sorted contents of the Tree : \n” + objTree);

}

}

Kết quả xuất ra:

Automatically sorted contents of the Tree :

[alpha, beta, gama, penta, tera]

Chương 6: Tập đối tượng

Biên soạn : Bùi Công Giao --------------------------------------- 108 --------------------------------------

6.5 Tạo kiểu tập hợp

6.5.1 Mở rộng một lớp tập hợp có sẳn

Để minh hoạ chúng ta mở rộng lớp ArrayList để tạo một lớp tập hợp MyIntCollection. Ngoài các đặc tính ArrayList, MyIntCollection có thể lưu giữ giá trị int nhỏ nhất và lớn nhất trong tập hợp của nó.

Lớp MyIntCollection minh hoạ điều này.

import java.util.ArrayList;

public class MyIntCollection extends ArrayList<Integer> {

/* Thừa hưởng tất cả thuộc tính và phương thức của ArrayList và có thêm 2 thuộc tính để lưu giữ giá trị int nhỏ nhất và lớn nhất trong tập hợp và thuộc tính tổng số */

private int smallestInt;

private int largestInt;

private int total;

// Thay thế phương thức xây dựng

public MyIntCollection() {

super();

// khởi tạo total.

total = 0;

}

// Ghi đè phương thức add() từ ArrayList.

public boolean add(int i) {

// Nếu lần đầu thêm phần tử thì phải gán i cho smallest và largest

if (this.isEmpty()) {

smallestInt = i;

largestInt = i;

} else {

// các lần thêm sau phải xác định lại smallest và largest

if (i < smallestInt) smallestInt = i;

if (i > largestInt) largestInt = i;

}

// tăng total cho việc thêm phần tử

total+=i;

Chương 6: Tập đối tượng

Biên soạn : Bùi Công Giao --------------------------------------- 109 --------------------------------------

/* Chèn giá trị i vào tập hợp, ta không cần biết chèn như thế nào do ta gọi phương thức add của ArrayList */

return super.add(i);

}

public int getSmallestInt() {

return smallestInt;

}

public int getLargestInt() {

return largestInt;

}

public double getAverage() {

// Ép kiểu int thành double để tránh việc thu hẹp giá trị khi chia

return ((double) total) / ((double) this.size());

}

}

6.5.2 Bao bọc tập hợp chuẩn

Chúng ta thiết kế một lớp bao bọc một thể hiện của ArrarList. Lớp MyIntCollection2 minh hoạ điều này.

import java.util.ArrayList;

// Không còn mở rộng lớp ArrayList

public class MyIntCollection2 {

// bao bọc một ArrayList trong lớp này.

ArrayList<Integer> numbers;

// định nghĩa vài thuộc tính như MyIntCollection

private int smallestInt;

private int largestInt;

private int total;

public MyIntCollection2() {

// Khởi tạo một ArrayList để nhúng vào lớp.

numbers = new ArrayList<Integer>();

// gán giá trị khởi đầu cho total.

total = 0;

}

Chương 6: Tập đối tượng

Biên soạn : Bùi Công Giao --------------------------------------- 110 --------------------------------------

/* vì không thừa kế phương thức size() nữa, ta tạo một phương thức size() mới */

public int size() {

// UỶ NHIỆM!

return numbers.size();

}

/* vì không thừa kế phương thức add() nữa, ta không thể ghi đè nó, ta tạo một phương thức add() mới */

public boolean add(int i) {

// UỶ NHIỆM cho tập hợp được bao bọc

if (numbers.isEmpty()) {

smallestInt = i;

largestInt = i;

} else {

if (i < smallestInt) smallestInt = i;

if (i > largestInt) largestInt = i;

}

total+=i;

// UỶ NHIỆM cho tập hợp được bao bọc để thêm phần tử

return numbers.add(i);

}

public int getSmallestInt() {

return smallestInt;

}

public int getLargestInt() {

return largestInt;

}

public double getAverage() {

return ((double) total)/this.size());

}

}

6.6 Phương thức trả về kiểu tập hợp

Nếu chúng ta định nghĩa một phương thức có kiểu trã về là một tập hợp, ta có thể nhận về một tập hợp có kích thước tuỳ ý tại nơi gọi phương thức.

Trong đoạn lệnh sau định nghĩa lớp Course, một phương thức getRegisteredStudents trã về toàn bộ đối tượng Student có đăng ký một khoá học xác định:

Chương 6: Tập đối tượng

Biên soạn : Bùi Công Giao --------------------------------------- 111 --------------------------------------

public class Course {

private ArrayList<Student> enrolledStudents;

/* … phương thức trả về một tham chiếu tới toàn bộ tập hợp chứa toàn bộ Students có đăng ký Course */

public boolean enroll( Student s) {

// nếu s thoả điều kiện đăng ký môn học này

enrolledStudents.add( s);

}

public ArrayList<Student> getRegisteredStudents() {

return enrolledStudents;

}

}

Dưới đây là đoạn lệnh sử dụng phương thức trên:

// Khởi tạo một khoá học và vài sinh viên

Course c = new Course();

Student s1 = new Student();

Student s2 = new Student();

Student s3 = new Student();

// đăng ký sinh viên cho khoá học này.

c.enroll(s1);

c.enroll(s2);

c.enroll(s3);

/* yêu cầu khoá học cho danh sách sinh viên đã đăng ký học và in bảng điểm của từng sinh viên này */

for (Student s : c.getRegisteredStudents())

s.printGradeReport();

TỔNG KẾT CHƯƠNG 6

Trong chương này các vấn đề sau được trình bày:

Tập hợp là các kiểu đối tượng đặc biệt dùng để tham chiếu tới các đối tượng khác.

Hầu hết các ngôn ngữ lập trình hướng đối tượng đều hỗ trợ 3 kiểu tập hợp:

o Danh sách có thứ tự

o Bộ

Chương 6: Tập đối tượng

Biên soạn : Bùi Công Giao --------------------------------------- 112 --------------------------------------

o Tự điển

Mảng là kiểu tập hợp đơn giản mà có vài hạn chế, nhưng Java cũng có các kiểu tập hợp nhiều tính năng hơn như ArrayList, HashMap, TreeMap,…

Có thể tạo các kiểu tập hợp riêng biệt bằng cách mở rộng các lớp tập hợp có sẳn hoặc tạo một lớp có thuộc tính là tập hợp đã định nghĩa trước.

Phương thức có thể trả về là một tập hợp.

BÀI TẬP

1. Trừu tượng hoá một vấn đề như sau: một quyển sách là tập hợp các chương, chương là tập hợp các trang.

Phác hoạ các lớp Book, Chapter, và Page :

Tạo bất kỳ thuộc tính nào mà ta nghĩ là cần thiết, hãy tận dụng tập hợp như là thuộc tính của lớp

Tạo các phương thức cho lớp Chapter cho việc thêm trang và xác định một chương có bao nhiêu trang.

Tạo các phương thức cho lớp Book cho việc thêm chương và xác định quyển sách có bao nhiêu chương, và số trang cho quyển sách (gợi ý sử dụng sự uỷ nhiệm-delegation), và xuất ra bảng nội dung của sách.

2. Viết một chương trình minh hoạ các lớp vừa tạo.

3. Kiểu tập hợp nào — danh sách có thứ tự, danh sách có thứ tự được sắp xếp, bộ, và tự điển—có thể dùng để trừu tượng hoá các vấn đề sau ? Hãy giải thích.

Danh mục thiết bị máy tính

Các lá bài trên tay người chơi

Các cuộc gọi tới bộ phận hỗ trợ kỹ thuật được lưu lại

4. Hệ thống sinh viên đăng ký môn học cần loại tập hợp gì?

5. Hệ thống cần theo dõi toa thuốc bệnh nhân cần loại tập hợp gì?

6. Loại tập hợp nào cần thiết cho bài tập 3 chương 1

7. Thay đổi lớp MyIntCollection để thêm phương thức printSortedContents nhằm in nội dung của tập hợp trong một thứ tự được sắp xếp.

Chương 7: Xử lý ngoại lệ

Biên soạn : Bùi Công Giao --------------------------------------- 113 --------------------------------------

Chương 7. Xử lý ngoại lệ

Nội dung chương này nhằm giới thiệu:

Ngoại lệ (exception) là gì ?

Mô tả cơ chế xử lý ngoại lệ và bắt ngoại lệ như thế nào qua cách sử dụng các khối try, catch và finally

Việc bắt ngoại lệ diễn ra như thế nào

Hiểu được phân cấp ngoại lệ trong Java

Các vấn đề lưu ý thêm khi xử lý ngoại lệ

7.1 Giới thiệu ngoại lệ

Vấn đề có thể phát sinh bất ngờ khi máy ảo Java thực hiện chương trình Java, ví dụ:

Chương trình không thể mở một tập tin dữ liệu do không có quyền thích hợp

Chương trình gặp khó khăn khi thiết lập kết nối đến một hệ thống quản lý CSDL vì người sử dụng đã cung cấp một mật khẩu không hợp lệ.

Người sử dụng cung cấp số liệu không phù hợp thông qua một ứng dụng của giao diện người dùng, ví dụ nhập một giá trị không phải số nơi một giá trị số được mong đợi.

Vấn đề có thể là một lỗi logic mà các trình biên dịch đã không thể phát hiện.

Ta xét ví dụ sau :

package chuong7;

public class Problem {

public static void main(String[] args) {

/* Khai báo hai tham chiếu tới đối tượng sinh viên và khởi tạo chúng là giá trị null */

Student s1 = null;

Student s2 = null;

// bỏ qua chi tiết …

/* tạo đối tượng và cho s1 tham chiếu tới nhưng quên làm điều này cho s2 */

s1 = new Student();

// … dòng lệnh này OK …

s1.setName(“Fred”);

Chương 7: Xử lý ngoại lệ

Biên soạn : Bùi Công Giao --------------------------------------- 114 --------------------------------------

/* … nhưng dòng này gây lỗi trong thời gian chạy vì ta gọi phương thức của đối tượng không tồn tại. */

s2.setName(“Mary”);

}

}

Đoạn lệnh trên sẽ biên dịch mà không có lỗi, nhưng nếu chúng ta thực hiện chương trình này, máy ảo Java sẽ báo cáo các lỗi chạy thời gian sau đây:

Exception in thread “main” java.lang.NullPointerException

at Problem.main(Problem.java:22)

Chúng ta hàm ý lỗi thời gian chạy (có thể phục hồi) trong Java là trường hợp ngoại lệ, và máy ảo Java báo cáo rằng một lỗi thời gian chạy đã phát sinh hành động ném ra một ngoại lệ (throwing an exception). Với ví dụ trên máy ảo Java ném NullPointerException trên dòng 22 của lớp Problem:

s2.setName(“Mary”); // dòng 22

Một NullPointerException phát sinh bất cứ khi nào chúng ta cố gắng để gọi một phương thức trên một tham khảo đối tượng (như s2, trong ví dụ trên) có giá trị null hay bất cứ khi nào chúng ta cố gắng để “nói chuyện với” một đối tượng không tồn tại.

Máy ảo Java

Hình 7.1 Khi máy ảo Java ném một ngoại lệ, như phát ra một pháo để thông báo cho ứng dụng một vấn đề phát sinh.

Khi máy ảo Java ném một ngoại lệ, đó như thể máy ảo bắn ra một pháo hiệu để thông báo cho một ứng dụng biết có một cái gì đó đã sai, để cung cấp cho ứng dụng một cơ hội để phục hồi và chạy tiếp (xem hình 7.1). Thông qua kỹ thuật xử lý các trường hợp

Mã bytecode được nạp vào máy ảo

Chương 7: Xử lý ngoại lệ

Biên soạn : Bùi Công Giao --------------------------------------- 115 --------------------------------------

ngoại lệ, chúng ta có thể thiết kế các ứng dụng để nó có thể dự đoán và phục hội từ trường hợp ngoại lệ trong thời gian chạy.

7.2 Cơ chế xử lý ngoại lệ

7.2.1 Khối try

Ta dùng khối lệnh try để bao các lệnh có thể tạo lỗi. Điều này giúp việc bắt lỗi thuận tiện và tập trung hơn.

Chương trình trên được sửa chữa như sau:

package chuong7;

public class Problem2 {

public static void main(String[] args) {

Student s1 = null;

Student s2 = null;

s1 = new Student();

// … thêm lệnh try để bao bọc khối lệnh gây lỗi

try {

s1.setName("Fred");

s2.setName("Mary");

}

// …

Trước khi biên dịch, khối try phải được theo sau ít nhất một khối catch hay khối finally.

7.2.2 Khối catch

Khối catch có cấu trúc như sau:

catch (ExceptionType variableName) { ...

// mã phục hồi cho một ngoại lệ kiểu ExceptionType ...

}

Một hay nhiều khối catch có thể theo sau một khối try như:

try {

// mã có thể gây ngoại lệ ...

} catch (ExceptionType1 variableName1) {

// mã phục hồi cho một ngoại lệ kiểu ExceptionType1 ...

} catch (ExceptionType2 variableName2) {

// mã phục hồi cho một ngoại lệ kiểu ExceptionType2 ...

Chương 7: Xử lý ngoại lệ

Biên soạn : Bùi Công Giao --------------------------------------- 116 --------------------------------------

}

// …

Có nhiều loại ngoại lệ khác nhau được xây dựng trong các ngôn ngữ Java, mỗi một loại được định nghĩa bởi một lớp đã được định nghĩa trước và tất cả chúng bắt nguồn từ một lớp gốc là Exception. Tùy thuộc vào loại trường hợp ngoại lệ chúng ta muốn bắt, chúng ta có thể import gói thư viện; tuy nhiên NullPointerException đã có trong gói java.lang.

Chương trình trên thêm khối lệnh catch như sau:

package chuong7;

public class Problem3 {

public static void main(String[] args) {

Student s1 = null;

Student s2 = null;

//…tạo đối tượng và cho s1 tham chiếu tới nhưng quên làm điều này cho s2

s1 = new Student();

// …thêm lệnh try để bao bọc khối lệnh gây lỗi

try {

s1.setName("Fred");

s2.setName("Mary");

} /* kết thúc khối try

bắt đầu các khối catch (3 khối) */

catch (ArithmeticException e) {

// mã phục hồi cho một ngoại lệ kiểu ArithmeticException ...

} catch (NullPointerException e2) {

// mã phục hồi cho một ngoại lệ kiểu NullPointerException ...

System.out.println("Quên khởi tạo tất cả sinh viên!");

} catch (ArrayIndexOutOfBoundsException e3) {

/* mã phục hồi cho một ngoại lệ kiểu ArrayIndexOutOfBoundsException */

}

}

}

Lưu ý rằng đoạn mã trong khối lệnh try này không ném ArithmeticExceptions hay ArrayIndexOutOfBoundsExceptions; chúng được thêm vào cho bao gồm cho mục đích minh hoạ.

Chương 7: Xử lý ngoại lệ

Biên soạn : Bùi Công Giao --------------------------------------- 117 --------------------------------------

Đối với việc xử lý ngoại lệ, có hai con đường thực hiện lệnh. Nếu là các lệnh trong try không có lỗi thì tất cả các khối catch được bỏ qua, và thực hiện chương trình vẫn tiếp tục ngay sau khối catch cuối cùng như minh họa trong hình 7.2.

Hình 7.2 Nếu không có ngoại lệ được ném ra trong khối try, tất cả các khối catch được bỏ qua.

Nếu một ngoại lệ được ném ra bởi máy ảo Java trong khi thực hiện khối lệnh try, việc thực hiện khối try phải chấm dứt ngay lập tức ngay dòng ngoại lệ xảy ra. Máy ảo Java sau đó xem xét các các lệnh catch theo thứ tự từ trên xuống dưới để tìm kiếm một kiểu ngoại lệ giống như loại ngoại lệ đã được ném.

Nếu tìm thấy, máy ảo Java thực thi khối catch này; trong trường hợp có nhiều lệnh catch so khớp , chỉ catch đầu tiên được thực hiện. Sau đó, việc thực hiện tiếp tục sau lệnh catch cuối cùng. Điều này được minh họa trong hình 7.3.

Nếu không có lệnh catch nào được tìm thấy, ngoại lệ được cho là còn tự do, và trong trường hợp của ví dụ trước thông báo lỗi:

Exception in thread "main" java.lang.NullPointerException

at Problem.main(Problem.java: ...)

Chương 7: Xử lý ngoại lệ

Biên soạn : Bùi Công Giao --------------------------------------- 118 --------------------------------------

Hình 7.3 Nếu một ngoại lệ phát sinh, khồi catch đầu tiên chặn bắt, nếu có, được thực thi, và phần còn lại được bỏ qua.

Xét chương trình Problem4 sau:

package chuong7;

public class Problem4 {

public static void main(String[] args) {

Student s1 = null;

Student s2 = null;

s1 = new Student();

//...

System.out.println("We're about to enter the try block ...");

try {

System.out.println("We're about to call s1.setName(...)");

s1.setName("Fred");

System.out.println("We're about to call s2.setName(...)");

s2.setName("Mary");

System.out.println("We've reached the end of the try block ...");

} catch (ArithmeticException e) {

System.out.println("Executing the first catch block ...");

} catch (NullPointerException e2) {

Chương 7: Xử lý ngoại lệ

Biên soạn : Bùi Công Giao --------------------------------------- 119 --------------------------------------

System.out.println("Executing the second catch block ...");

} catch (ArrayIndexOutOfBoundsException e3) {

System.out.println("Executing the third catch block ...");

}

System.out.println("We're past the last catch block ...");

}

}

Chương trình xuất ra màn hình:

We're about to enter the try block ...

We're about to call s1.setName(...)

We're about to call s2.setName(...)

Executing the second catch block ...

We're past the last catch block ...

7.2.3 Khối finally

Một khối try có thể có một khối finally liên kết với nó. Khối finally phải sau khối catch (cuối cùng) liên kết với khối try đó. Dù có hay không khối catch nào được thực hiện, khối finally luôn được thực thi.

Xét chương trình Problem5 sau:

package chuong7;

public class Problem5 {

public static void main(String[] args) {

Student s1 = null;

Student s2 = null;

s1 = new Student();

System.out.println("We're about to enter the try block ...");

try {

System.out.println("We're about to call s1.setName(...)");

s1.setName("Fred");

System.out.println("We're about to call s2.setName(...)");

s2.setName("Mary");

System.out.println("We've reached the end of the try block ...");

} catch (ArithmeticException e) {

System.out.println("Executing the first catch block ...");

Chương 7: Xử lý ngoại lệ

Biên soạn : Bùi Công Giao --------------------------------------- 120 --------------------------------------

} catch (NullPointerException e2) {

System.out.println("Executing the second catch block ...");

} catch (ArrayIndexOutOfBoundsException e3) {

System.out.println("Executing the third catch block ...");

} finally {

System.out.println("Executing the finally block ...");

}

System.out.println("We're past the last catch block ...");

}

}

Chương trình xuất ra màn hình:

We're about to enter the try block ...

We're about to call s1.setName(...)

We're about to call s2.setName(...)

Executing the second catch block ...

Executing the finally block ...

We're past the last catch block ...

Một biến thể cuối cùng của chương trình Problemx là nó ném ra NullPointerException, nhưng không có lệnh catch nào thích hợp:

package chuong7;

public class Problem6 {

public static void main(String[] args) {

Student s1 = null;

Student s2 = null;

s1 = new Student();

System.out.println("We're about to enter the try block ...");

try {

System.out.println("We're about to call s1.setName(...)");

s1.setName("Fred");

System.out.println("We're about to call s2.setName(...)");

s2.setName("Mary");

System.out.println("We've reached the end of the try block ...");

Chương 7: Xử lý ngoại lệ

Biên soạn : Bùi Công Giao --------------------------------------- 121 --------------------------------------

} // không bắt NullPointerException.

catch (ArithmeticException e) {

System.out.println("Executing first catch block ...");

} catch (ArrayIndexOutOfBoundsException e2) {

System.out.println("Executing second catch block ...");

} finally {

System.out.println("Executing finally block ...");

}

System.out.println("We're past the last catch block ...");

}

}

Chương trình xuất ra màn hình:

We're about to enter the try block ...

We're about to call s1.setName(...)

We're about to call s2.setName(...)

Executing the finally block ...

Nhận xét rằng chương trình không thực hiện câu lệnh:

System.out.println("We're past the last catch block ...");

Bởi vì ngoại lệ không bị bắt gây ra khối lệnh mà xảy ra ngoại lệ - trong trường hợp này là phương thức main – kết thúc bất bình thường.

Như vậy dù trường hợp nào xảy ra trong khối lệnh try/catch, khối finally nếu có luôn thực hiện.

Ta có thể bỏ qua khối catch nếu có khối finally:

// Các khối catch không cần thiết nếu có khối finally.

try { ... }

finally { ... }

7.3 Bắt các ngoại lệ

Nếu phương thức có một ngoại lệ phát sinh nhưng không nắm bắt được ngoại lệ thì nơi gọi (client code) phương thức đó có cơ hội để bắt ngoại lệ đó.

Xem xét 3 lớp sau:

public class MainProgram {

public static void main(String[] args) {

Student s = new Student();

Chương 7: Xử lý ngoại lệ

Biên soạn : Bùi Công Giao --------------------------------------- 122 --------------------------------------

s.methodX();

}

}

public class Student {

// bỏ qua chi tiết.

public void methodX() {

Professor p = new Professor();

p.methodY();

}

}

public class Professor {

// bỏ qua chi tiết.

public void methodY() {

// bỏ qua chi tiết.

}

}

Khi máy ảo Java thực hiện MainProgram, phương thức main gọi s.methodX(), mà nó sẽ gọi p.methodY(); điều này tạo một chồng (stack) lúc chạy chương trình như hình 7.4.

Chồng gọi hoạt động theo cơ chế LIFO. Vì vậy, khi methodY kết thúc thực hiện, quyền kiểm soát thực hiện trả lại cho methodX, methodY được đẩy ra như hình 7.5.

Bây giờ chúng ta hãy giả định rằng NullPointerException được ném trong khi thực hiện methodY. Nếu có khối lệnh try/catch thích hợp bắt ngoại lệ này trong methodY thì NullPointerException được xử lý như sau:

public class Professor {

// bỏ qua chi tiết…

public void methodY() {

try { ... }

catch (NullPointerException e) { ... }

}

}

Chương 7: Xử lý ngoại lệ

Biên soạn : Bùi Công Giao --------------------------------------- 123 --------------------------------------

Hình 7.4 Máy ảo Java theo dõi việc thứ tự các phương thức được gọi bằng cách

tạo chồng gọi (call stack).

Hình 7.5 Khi một phương thức chấm dứt, nó bị lấy ra khỏi chồng.

Sau đó cả Student và MainProgram không nhận biết những ngoại lệ được ném từ Proffessor. Từ góc nhìn của chồng gọi, nhận biết của các ngoại lệ được chứa trong phạm vi mức hiện hành của chồng, như minh họa trong hình 7.6

Hình 7.6 Khối lệnh try/catch trong trong phương thức methodY nhận biết ngoại lệ trong mức hiện hành của chồng gọi.

Giả sử methodY không bắt ngoại lệ NullPointerExceptions:

public class Professor {

// bỏ qua chi tiết.

public void methodY() {

// Ngoại lệ NullPointerException được ném ở đây nhưng không bị bắt

//…

}

}

Chương 7: Xử lý ngoại lệ

Biên soạn : Bùi Công Giao --------------------------------------- 124 --------------------------------------

Nếu NullPointerException được ném trong khi thực hiện phiên bản này của methodY, nó sẽ đi xuống một mức của chồng gọi tới methodX của lớp sinh viên, là nơi gọi p.methodY(). Nếu mã methodX của lớp Student có khối try/catch thích hợp như sau:

public class Student {

// bỏ qua chi tiết.

public void methodX() {

Professor p = new Professor();

// Xử lý ngoại lệ ở đây.

try {

p.methodY();

} catch (NullPointerException e) { ... }

}

}

Thì xử lý ngoại lệ trong methodX sẽ xử lý, và MainProgram không biết rằng có một ngoại lệ được ném ra. Điều này được minh họa trong hình 7.7

Hình 7.7 Ngoại lệ thoát khỏi p.methodY() và chuyển xuống s.methodX() xử lý

public class Professor {

// bỏ qua chi tiết.

public void methodY() {

// Ngoại lệ NullPointerException được ném ở đây nhưng không bị bắt

//…

}

}

//-----------------------------

public class Student {

// bỏ qua chi tiết.

public void methodX() {

Professor p = new Professor();

// Không có xử lý ngoại lệ ở đây

p.methodY();

}

}

Chương 7: Xử lý ngoại lệ

Biên soạn : Bùi Công Giao --------------------------------------- 125 --------------------------------------

//-----------------------------

public class MainProgram {

public static void main(String[] args) {

Student s = new Student();

// xử lý ngoại lệ ở đây.

try {

s.methodX();

} catch (NullPointerException e) { ... }

}

}

Trong trường hợp này, ngoại lệ NullPointerException được ném trong methodY sẽ thông qua chồng gọi chuyển tới phương thức main, nơi có xử lý ngoại lệ như hình 7.8. Trong trường hợp này, người sử dụng ứng dụng này là không biết rằng có một ngoại lệ đã xảy ra.

Hình 7.8 NullPointerException thông qua chồng gọi được chuyển tới phương thức main.

Một minh hoạ cuối cùng, chúng ta hãy giả định rằng ngay cả phương thức main bỏ qua xử lý ngoại lệ. Nếu một NullPointerException đã phát sinh trong methodY, chúng ta nhìn thấy dấu vết chồng (stack trace) xuất hiện trong cửa sổ lệnh:

java PSExample

Exception in thread "main" java.lang.NullPointerException

at Professor.methodY(Professor.java: ...)

at Student.methodX(Student.java: ...)

at MainProgram.main(MainProgram.java: ...)

Từ môi trường của chồng gọi, tình huống này được thể hiện trong hình 7.9

Bất cứ khi nào dấu vết chồng phát sinh từ một ngoại lệ, nơi đầu tiên chúng ta nên xem xét để chẩn đoán và sửa chữa vấn đề là dòng mã được báo cáo đầu tiên trong dấu vết chồng. Nếu kiểm tra dòng này, ta không thể hiểu tại sao ngoại lệ phát sinh, ta nhìn

Chương 7: Xử lý ngoại lệ

Biên soạn : Bùi Công Giao --------------------------------------- 126 --------------------------------------

sang mục thứ hai trong dấu vết chồng, sau đó thứ ba, và như vậy, cho đến khi chúng ta đã nhìn đủ xa trong lịch sử cuộc gọi để xác định lý do tại sao lỗi phát sinh.

Hình 7.9 Nếu ứng dụng bỏ qua xử lý ngoại lệ, máy ảo Java chấm dứt ứng dụng và báo cáo ngoại lệ tới cửa cửa sổ dòng lệnh cho người sử dụng để quan sát.

7.4 Phân cấp lớp ngoại lệ

Như đã đề cập phần trước lớp Exception tồng quát, bao gồm trong gói java.lang, là siêu lớp của tất cả các loại ngoại lệ trong Java. Như minh họa trong hình 7.10 (được lấy từ tài liệu Java trực tuyến của Sun), có nhiều lớp con trực tiếp của các lớp học ngoại lệ.

Một khối catch bắt ngoại lệ cho một loại X sẽ bắt kiểu cụ thể của ngoại lệ hay bất kỳ kiểu con nào của nó, vì đó là bản chất của thừa kế. Do lý do này, điều quan trọng là liệt kê khối lệnh catch từ chi tiết nhất tới tổng quát nhất sau khối lệnh try, có nghĩa là, từ lớp con mức thấp nhất đến siêu lớp cao nhất. Hãy sử dụng một ví dụ cụ thể để minh họa tại sao điều này là như vậy.

Chúng ta thực hiện truy xuất CSDL. Các ngoại lệ được định nghĩa trong gói java.sql. Ta xem xét ba kiểu ngoại lệ tồng quát sau:

DataTruncation là loại ngoại lệ chi tiết nhất mà chúng ta sẽ quan tâm trong ví dụ này. Một ngoại lệ DataTruncation xảy ra nếu dữ liệu được được ghi vào CSDL và bị cắt ngắn, ví dụ như khi một giá trị String quá dài và bị ghi vào một trường CSDL khi nó chỉ chỉ có thể chứa 255 ký tự.

DataTruncation là kiểu con của ngoại lệ tổng quát hơn là SQLWarning. SQLWarning được ném ra khi có một vấn đề truy cập CSDL (không nghiêm trọng) xảy ra. Nó chỉ cảnh báo ứng dụng về vấn đề này.

Ngoại lệ SQLWarning đến phiên nó là một trường hợp đặc biệt/lớp con của lớp ngoại lệ tổng quát hơn SQLException. SQLException được ném khi có bất cứ điều gì trong quá trình giao tiếp CSDL, từ cố gắng đăng nhập bằng cách sử dụng một mật khẩu không hợp lệ, hay cố gắng để truy cập vào một bảng không tồn tại, hay cố gắng để gửi một truy vấn SQL không chuẩn.

Chương 7: Xử lý ngoại lệ

Biên soạn : Bùi Công Giao --------------------------------------- 127 --------------------------------------

Cuối cùng SQLException là lớp con trực tiếp của lớp Exception tổng quát.

Hình 7.10 Lớp java.lang.Exception có nhiều lớp con

Đường đi thừa kế này được minh hoạ trong hình 7.11

Hình 7.11 Cây thừa kế của kiểu ngoại lệ java.sql.DataTruncation

Đoạn mã sau đây trình bày một cách để chúng ta viết try /catch để xử lý DataTruncation (và các loại khác liên quan đến CSDL). Lưu ý rằng chúng ta cần phải bao gồm chỉ thị

import java.sql.*;

Chương 7: Xử lý ngoại lệ

Biên soạn : Bùi Công Giao --------------------------------------- 128 --------------------------------------

//…

try {

// viết dữ liệu vào CSDL…

} catch (DataTruncation e1) {

// bắt kiểu ngoại lệ cụ thể nhất…

} catch (SQLWarning e2) {

//rồi ít cụ thể hơn ...

} catch (SQLException e3) {

// tổng quát hơn...

}

Bảng sau đây liệt kê một số ngoại lệ thường gặp:

Ngoại lệ Lớp cha của thứ tự phân cấp ngoại lệ

RuntimeException Lớp cơ sở cho nhiều ngoại lệ java.lang

ArthmeticException Trạng thái lỗi về số, ví dụ như ‘chia cho 0’

IllegalAccessException Lớp không thể truy cập

IllegalArgumentException Phương thức nhận một đối số không hợp lệ

ArrayIndexOutOfBoundsExeption Kích thước của mảng lớn hơn 0 hay lớn hơn kích thước thật sự của mảng

NullPointerException Khi muốn truy cập đối tượng null

SecurityException Việc thiết lập cơ chế bảo mật không được hoạt động

ClassNotFoundException Không thể nạp lớp yêu cầu

NumberFormatException Việc chuyển đối không thành công từ chuỗi sang số thực

AWTException Ngoại lệ về AWT

IOException Lớp cha của các ngoại lệ I/O

FileNotFoundException Không thể định vị tập tin

EOFException Kết thúc một tập tin

NoSuchMethodException Phương thức yêu cầu không tồn tại

InterruptedException Khi một luồng bị ngắt

SQLException Khi giao tiếp CSDL có lỗi

Bảng 7.1 Danh sách một số ngoại lệ

Chương 7: Xử lý ngoại lệ

Biên soạn : Bùi Công Giao --------------------------------------- 129 --------------------------------------

7.5 Các điểm cần lưu ý thêm về ngoại lệ

7.5.1 Bắt ngoại lệ tổng quát

Một số người lập trình sử dụng hướng tiếp cận lười biếng để bắt loại ngoại lệ chung nhất và sau đó không làm gì để khôi phục lại, chỉ để làm im lặng máy ảo Java, ví dụ như:

try {

// …

} catch (Exception e) { } // không làm gì để phục hồi !!!

Đây không phải là cách làm tốt vì có thể các lỗi do ngoại lệ sinh ra sẽ tiềm ẩn các tác hại mà ta không lường hết khi tiếp tục ứng dụng.

Ta có thể bắt ngoại lệ khi có cách xử lý ngoại lệ riêng, ví dụ:

public class Example {

public static void main(String[] args) {

try {

// …

} catch (Exception e) {

/* gọi phương thức tĩnh của lớp MyExceptionHandler do

người viết ứng dụng tạo */

MyExceptionHandler.handleException(e);

}

}

}

7.5.2 Trình biên dịch Java yêu cầu phải có xử lý ngoại lệ

Đôi khi Java bắt buộc chúng ta phải bao đoạn lệnh có thể gây ra ngoại lệ, ví dụ

public class FileIOExample {

public static void main(String[] args) {

// mã giả để mở và đọc tập tin;

while (chưa tới cuối tập tin) {

đọc dòng kế tiếp từ tập tin;

// ...

}

}

}

Chương 7: Xử lý ngoại lệ

Biên soạn : Bùi Công Giao --------------------------------------- 130 --------------------------------------

Trình biên dịch sẽ báo các lỗi sau:

Unreported exception java.io.FileNotFoundException; must be caught or

declared to be thrown

Trong trường hợp này, ta có hai cách khắc phục:

1. Bao khối truy cập tập tin trong try và có catch để bắt ngoại lệ và xử lý:

import java.io.FileNotFoundException;

public class FileIOExample {

public static void main(String[] args) {

try {

// mã giả để mở và đọc tập tin;

while (chưa tới cuối tập tin) {

đọc dòng kế tiếp từ tập tin;

// ...

}

} catch (FileNotFoundException e) { ...}

}

}

2. Ta thêm lệnh throws tới tiêu đề của phương thức mà ngoại lệ có thể xảy ra như:

import java.io.FileNotFoundException;

public class FileIOExample {

/* thêm throws tới phương thức main, ta không phải quan tâm

về bắt FileNotFoundExceptions */

public static void main(String[] args) throws FileNotFoundException {

// mã giả để mở và đọc tập tin;

while (chưa tới cuối tập tin) {

đọc dòng kế tiếp từ tập tin;

// ...

}

}

}

Chương 7: Xử lý ngoại lệ

Biên soạn : Bùi Công Giao --------------------------------------- 131 --------------------------------------

7.5.3 Tận dụng xử lý ngoại lệ để làm rõ lỗi phát sinh

Ta có thể sử dụng phương thức getMessage() trong khối catch để có thông báo mô tả lỗi như sau:

try {

// mở tập tin Foo.dat không tồn tại...

} catch (FileNotFoundException e) {

System.out.println("Error opening file " + e.getMessage());

}

Kết quả xuất ra:

Error opening file Foo.dat (The system cannot find the file specified)

• Phương thức printStackTrace() có thể sử dụng để xuất ra dấu vết chồng trong cửa sổ dòng lệnh (nhớ rằng dấu vết chồng chỉ xảy ra tự động nếu ta không xử lý ngoại lệ):

try {

// mở tập tin Foo.dat không tồn tại...

} catch (FileNotFoundException e) {

e.printStackTrace();

}

7.5.4 try/catch lồng nhau

Một khối try có thể được lồng vào bên trong khối try hoặc khối catch. Thông thường ta có nhu cầu lồng khối try trong một khối catch, vì mã phục hồi mà chúng ta viết trong một khối catch có thể có một nguy cơ ném một ngoại lệ bổ sung của riêng nó.

Ví dụ như khi một FileNotFoundException phát sinh, chúng ta muốn mở một tập tin mặc định để thay thế. Nhưng, do mở một tập tin mặc định có thể cũng có khả năng ném một FileNotFoundException của riêng nó, ta cần phải bao mã phục hồi trong khối try riêng của nó như sau:

try {

// mở tập tin Foo.dat không tồn tại...

} catch (FileNotFoundException e) {

/* Nếu ta không thể mở tập tin do người dùng chỉ ra, ta sẽ mở tập tin mặc định, nhưng điều gì xảy ra nếu tập tin mặc định cũng không có */

try {

// mở tập tin mặc định…

} catch (FileNotFoundException e2) {

Chương 7: Xử lý ngoại lệ

Biên soạn : Bùi Công Giao --------------------------------------- 132 --------------------------------------

// cố gắng để phục hồi ...

}

}

7.5.5 Kiểu ngoại lệ do người dùng định nghĩa

Ta có thể định nghĩa các loại ngoại lệ riêng để báo hiệu các kiểu lỗi đặc thù của ứng dụng của chúng ta, và để chương trình ném chúng khi có vấn đề phát sinh.

Để tạo kiểu ngoại lệ riêng, ta tạo lớp thừa kế từ các lớp ngoại lệ đã có sẵn của Java. Thông thường ta mở rộng lớp Exception trực tiếp:

public class MissingValueException extends Exception { ... }

Xét một minh hoạ tương đối hoàn chỉnh về ngoại lệ do người dùng định nghĩa:

public class MissingValueException extends Exception {

private Student student;

/* ... phương thức xây dựng có hai tham số, tham số đầu tiên gán cho thuộc tính student, và tham số thứ hai chuyển cho ngoại lệ ... */

public MissingValueException(Student s, String message) {

super(message);

student = s;

}

// ... thêm phương thức get cho thuộc tính Student.

public Student getStudent() {

return student;

}

}

Và dùng ngoại lệ này trong định nghĩa lớp Student:

public class Student {

private String name;

private String ssn;

// bỏ qua chi tiết.

public String getSsn() { ... }

public void setSsn() { ... }

public String getName() { ... }

public void setName(String n) throws MissingValueException {

// Ta muốn báo cáo một lỗi nếu n là chuỗi rỗng.

if (n.equals(""))

Chương 7: Xử lý ngoại lệ

Biên soạn : Bùi Công Giao --------------------------------------- 133 --------------------------------------

throw new MissingValueException(this, "A student's name cannot be blank");

else

name = n;

}

// ...

}

Khi tạo một ứng dụng như sau:

public class Example {

public static void main(String[] args) {

// mã giả.

String name = đọc giá trị từ GUI;

Student s = new Student();

s.setName(name);

// …

Trình biên dịch phát ra lỗi tại lệnh gọi phương thức setName của đối tượng s do ta đã bao gồm lệnh throws MissingValueException cho phương thức này

Unreported exception MissingValueException; must be caught or declared to be thrown

s.setName(name);

Trình biên dịch Java phải bắt kiểu ngoại lệ này trong đoạn mã gọi! Nghĩa là ta phải đặt setName trong khối try, và theo sau là khối catch thích hợp:

public class Example {

public static void main(String[] args) {

// mã giả.

String name = đọc giá trị từ GUI;

Student s = new Student();

try {

s.setName(name);

} catch (MissingValueException e) {

System.out.println(e.getMessage());

System.out.println("ID of affected student: " +

e.getStudent().getSsn());

Chương 7: Xử lý ngoại lệ

Biên soạn : Bùi Công Giao --------------------------------------- 134 --------------------------------------

}

// …

Trong khối catch, ta lợi dụng vừa phương thức thừa kế getMessage và phương thức getStudent của lớp MissingvalueException để xuất ra kết quả như sau:

A student's name cannot be blank

ID of affected student: 123-45-6789

7.5.6 Ném nhiều kiểu ngoại lệ

Ta có thể ném nhiều kiểu ngoại lệ từ một phương thức. Xét ví dụ sau: giả sử ta đã khai báo một ngoại lệ InvalidCharacterException cùng với MissingValueException.

public class Student {

// bỏ qua chi tiết.

public void setName(String s) throws MissingValueException, InvalidCharacterException {

if (s.equals(""))

throw new MissingValueException(this, "A student's name cannot be blank");

// mã giả.

else if (s có một ký tự không hợp lệ)

throw new InvalidCharacterException(this, s +" contains a non-alphabetic character");

else name = s;

}

// …

}

7.6 Case Study

Viết một chương trình NumberOfDigits để đếm số ký số được nhập vào như tham số từ cửa sổ dòng lệnh. Nếu một ký tự được nhập vào thì xuất ra câu thông báo lỗi.

Một chương trình minh hoạ cho bài toán trên như sau

package chuong7;

class NumberOfDigits {

public static void main(String args[]) {

try {

Chương 7: Xử lý ngoại lệ

Biên soạn : Bùi Công Giao --------------------------------------- 135 --------------------------------------

int num = Integer.parseInt(args[0]);

System.out.println(args[0].length());

} catch (NumberFormatException e) {

System.out.println("\nEnter only numbers!");

}

}

}

Khi chạy chương trình sẽ có dạng như hình 7.12:

 

Hình 7.12 Kết quả demo của NumberOfDigits.java

TỔNG KẾT CHƯƠNG 7

Trong chương này các vấn đề sau được trình bày:

Bất cứ khi nào một lỗi xuất hiện trong khi thi hành chương trình, nghĩa là một ngoại lệ đã xuất hiện.

Mỗi ngoại lệ phát sinh ra phải bị bắt giữ, nếu không ứng dụng sẽ bị ngắt.

Java sử dụng các khối try và catch để xử lý các ngoại lệ. Các câu lệnh trong khối try chặn ngoại lệ còn khối catch xử lý ngoại lệ.

Các khối chứa nhiều catch có thể được sử dụng để xử lý các kiểu ngoại lệ khác nhau.

Khối finally khai báo các câu lệnh luôn được thực hiện dù ngoại lệ có xảy ra hay không.

Từ khoá throws liệt kê các ngoại lệ mà phương thức chặn.

Từ khoá throw chỉ ra một ngoại lệ vừa xuất hiện.

Cơ chế xử lý ngoại lệ và bắt ngoại lệ qua chồng gọi.

Ngoại lệ do người dùng định nghĩa.

Ném nhiều kiểu ngoại lệ cho một phương thức như thế nào.

Chương 7: Xử lý ngoại lệ

Biên soạn : Bùi Công Giao --------------------------------------- 136 --------------------------------------

BÀI TẬP

1. Viết chương trình ArraySize để tạo mảng 5 số. Viết lệnh truy xuất phần tử thứ 6; điều này gây ra một ngoại lệ. Bắt ngoại lệ thích hợp và in thông báo lỗi.

2. Sau khi học xong chương 9 hãy viết một applet Square. Một ngoại lệ sẽ phát sinh khi người dùng nhập vào giá trị không phải là số. Hãy bắt ngoại lệ thích hợp và in thông báo lỗi.

Chương 8: Xử lý vào/ra

Biên soạn : Bùi Công Giao --------------------------------------- 137 --------------------------------------

Chương 8. Xử lý vào/ra

Nội dung chương này nhằm giới thiệu:

Đề cập đến các khái niệm về luồng và các kiểu luồng

Việc quét dữ liệu vào qua scanner và đặt qui cách dữ liệu vào và xuất ra bằng formatting.

Mô tả vào ra trong chế độ dòng lệnh

Làm việc với CSDL qua JDBC

8.1 Luồng vào/ra

8.1.1 Giới thiệu luồng vào/ra

Theo thuật ngữ chung, luồng (stream) là một dòng lưu chuyển, trong thuật ngữ về kỹ thuật luồng là một lộ trình mà dữ liệu được truyền trong một chương trình.

Còn trong Java luồng là những dàn ống (pipelines) để gửi và nhận thông tin. Một ứng dụng về các luồng mà ta đã quen thuộc đó là luồng nhập System.in. Khi một luồng dữ liệu được gửi hoặc nhận, ta hàm ý như đang ghi hoặc đọc luồng đó và các luồng khác bị phong toả. Nếu có một lỗi xảy ra khi đọc hay ghi luồng, một IOException được kích hoạt. Do vậy, các câu lệnh luồng phải bao gồm khối try/catch. Hình 8.1 và 8.2 minh hoạ việc đọc và ghi dữ liệu qua luồng nhập, xuất tương ứng:

Hình 8.1 Đọc dữ liệu vào chương trình.

Luồng xử lý tất cả dữ liệu từ cơ bản đến đối tượng. Nguồn dữ liệu (data source) và nơi đến của dữ liệu của hai hình trên có thề là bất cứ cái gì giữ, phát ra, hay xử lý dữ liệu. Có thể là tập tin, chương trình khác, thiết bị ngoại vi, mảng, hay network socket.

Lớp java.lang.System định nghĩa các luồng nhập và xuất chuẩn. chúng là các lớp chính của các luồng byte mà Java cung cấp. Ta cũng đã sử dụng các luồng xuất để xuất dữ liệu và hiển thị kết quả trên màn hình. Luồng vào ra bao gồm:

Lớp System.out: Luồng xuất chuẩn dùng để hiển thị kết quả trên màn hình.

Lớp System.in: Luồng nhập chuẩn thường đến từ bàn phím và được dùng để đọc các ký tự dữ liệu.

Lớp System.err: Đây là luồng lỗi chuẩn.

Chương 8: Xử lý vào/ra

Biên soạn : Bùi Công Giao --------------------------------------- 138 --------------------------------------

Các lớp InputStream và OutputStream cung cấp nhiều khả năng vào ra khác nhau. Cả hai lớp này có các lớp con để thực hiện vào ra thông qua các vùng đệm bộ nhớ, các tập tin và ống dẫn. Các lớp con của lớp InputStream thực hiện đầu vào, trong khi các lớp con của lớp OutputStream thực hiện kết xuất.

Hình 8.2 Viết dữ liệu từ chương trình.

Trong phần kế tiếp ta sử dụng luồng cơ bản nhất , luồng byte để minh hoạ các thao tác thông thường. Ta sử dụng tập tin xanadu.txt có nội dung như sau:

In Xanadu did Kubla Khan

A stately pleasure-dome decree:

Where Alph, the sacred river, ran

Through caverns measureless to man

Down to a sunless sea.

8.1.2 Luồng byte

Chương trình sử dụng luồng byte để thực hiện vào ra trên các byte. Tất cả lớp luồng byte thừa kế từ lớp InputStream và OutputStream.

Trước hết ta xét luồng byte vào ra trên tập tin, các luồng byte khác được sử dụng tương tự như vậy nhưng chỉ khác cách mà chúng được xây dựng.

Chương trình CopyBytes dưới đây dùng luồng byte để chép xanadu.txt theo từng byte.

package chuong8;

import java.io.FileInputStream;

import java.io.FileOutputStream;

import java.io.IOException;

public class CopyBytes {

public static void main(String[] args) throws IOException {

FileInputStream in = null;

FileOutputStream out = null;

try {

in = new FileInputStream("xanadu.txt");

out = new FileOutputStream("outagain.txt");

Chương 8: Xử lý vào/ra

Biên soạn : Bùi Công Giao --------------------------------------- 139 --------------------------------------

int c;

// đọc đến khi cuối tập tin

while ((c = in.read()) != -1)

out.write(c);

} finally {

if (in != null)

in.close();

if (out != null)

out.close();

}

}

}

Hình 8.3 minh hoạ chương trình CopyBytes.

Luồng vào

I n X a n a d u d i d

I n X a n a d u d

Luồng ra

Hình 8.3 Luồng byte vào ra đơn giản

Nhận xét rằng read() trả về giá trị int. Nếu luồng vào là chuỗi các byte tại sao read() không trả về byte? Sử dụng int như là kiểu trả về cho phép read() dùng -1 để báo hiệu đã đến cuối luồng.

Chú ý:

o Luôn đóng luồng khi không cần dùng nữa.

read(b)

Số nguyên d

write(b)

Chương 8: Xử lý vào/ra

Biên soạn : Bùi Công Giao --------------------------------------- 140 --------------------------------------

o Khi xử lý vào ra với tập tin chứa dữ liệu ký tự ta nên dùng luồng ký tự. Luồng byte chỉ dùng cho việc vào ra cơ bản nhất. Nhưng tại sao ta dùng luồng byte cho minh hoạ đầu tiên. Lý do rằng các kiểu luồng khác được xây dựng trên luồng byte.

8.1.3 Luồng ký tự

Java lưu trữ ký tự theo mã Unicode. Luồng vào ra ký tự động dịch bảng mã này tới/từ bộ ký tự địa phương (local character set).

Tất cả lớp luồng ký tự được dẫn xuất từ Reader và Writer. Như luồng byte, ta có luồng ký tự chuyên dùng cho vào ra tập tin: FileReader and FileWriter. Chương trình CopyCharacters minh hoạ các lớp này.

package chuong8;

import java.io.FileReader;

import java.io.FileWriter;

import java.io.IOException;

public class CopyCharacters {

public static void main(String[] args) throws IOException {

FileReader inputStream = null;

FileWriter outputStream = null;

try {

inputStream = new FileReader("xanadu.txt");

outputStream = new FileWriter("characteroutput.txt");

int c;

while ((c = inputStream.read()) != -1)

outputStream.write(c);

} finally {

if (inputStream != null)

inputStream.close();

if (outputStream != null)

outputStream.close();

}

}

}

Nhận xét rằng trong CopyCharacters biến c chứa giá trị ký tự 16 bit; còn CopyBytes biến c chứa giá trị ký tự 8 bit.

Chương 8: Xử lý vào/ra

Biên soạn : Bùi Công Giao --------------------------------------- 141 --------------------------------------

Luồng ký tự thường bao lấy luồng byte. Luồng ký tự dùng luồng byte để thực hiện vào ra ở cấp độ vật lý, còn công việc còn lại của nó là xử lý việc dịch giữa ký tự và byte. Ví dụ FileReader dùng FileInputStream, trong khi đó FileWriter dùng FileOutputStream.

8.1.4 Luồng bộ đệm

Hầu hết các ví dụ mà ta đã xét đến giờ đều dùng vào ra không bộ đệm (unbuffered I/O). Nghĩa rằng mỗi lần đọc hoặc viết đều được xử lý trực tiếp bởi hệ điều hành. Điều này không kinh tế vì mỗi yêu cầu như vậy thường tạo nên truy xuất đĩa, hoặc vài hành động tương đối tốn kém chi phí.

Để khắc phục, Java giới thiệu luồng vào ra dùng bộ đệm. Luồng vào dùng bộ đệm đọc dữ liệu từ một vùng bộ nhớ, bộ đệm, việc lấp đầy bộ đệm chỉ thực hiện khi bộ đệm rỗng (đã được đọc hết). Tương tự luồng xuất dùng bộ đệm ghi dữ liệu vào bộ đệm. Việc ghi tới nơi chứa thực sự chỉ thực hiện khi bộ đệm đầy.

Khi xử lý dòng dữ liệu có dấu kết thúc dòng, thông thường "\r\n", hoặc "\r", hoặc "\n" thì ta dùng vào ra hướng dòng (line-oriented I/O). Để làm điều này, ta dùng BufferedReader và PrintWriter.

Chương trình có thể chuyển luồng không bộ đệm thành luồng có bộ đệm bằng cách dùng bao bọc như trong CopyLines.

package chuong8;

import java.io.FileReader;

import java.io.FileWriter;

import java.io.BufferedReader;

import java.io.PrintWriter;

import java.io.IOException;

public class CopyLines {

public static void main(String[] args) throws IOException {

BufferedReader inputStream = null;

PrintWriter outputStream = null;

try {

inputStream = new BufferedReader(new FileReader("xanadu.txt"));

outputStream = new PrintWriter(new FileWriter("characteroutput.txt"));

String l;

while ((l = inputStream.readLine()) != null)

outputStream.println(l);

} finally {

Chương 8: Xử lý vào/ra

Biên soạn : Bùi Công Giao --------------------------------------- 142 --------------------------------------

if (inputStream != null)

inputStream.close();

if (outputStream != null)

outputStream.close();

}

}

}

readLine trả về một dòng văn bản, println chèn dấu kết thúc dòng phù hợp với hệ điều hành hiện thời. Có thể dấu kết thúc dòng chèn vào khác với dấu kết thúc dòng được dùng cho tập tin nhập vào.

Có bốn luồng vào ra dùng bộ đệm bao bọc luồng không bộ đệm: BufferedInputStream và BufferedOutputStream tạo luồng byte có bộ đệm, BufferedReader và BufferedWriter tạo luồng ký tự có bộ đệm.

Đôi khi ta cần ghi bộ đệm ra nơi chứa ngay lập tức mà không cần chờ nó đầy. Khi đó việc xuất bộ đệm (flush the buffer) được thực hiện bằng phương thức flush.

Có hai luồng vào ra dùng bộ đệm trực tiếp (không cần bao bọc) để xử lý trên mảng:

Lớp ByteArrayInputStream : tạo luồng đầu vào từ bộ nhớ đệm. Nó là mảng các byte.

Lớp ByteArrayOutputStream : tạo ra luồng xuất trên một mảng các byte. Lớp này cung cấp các phương thức toByteArrray() và toString(). Chúng được dùng để chuyển đổi luồng thành một mảng byte hay đối tượng chuỗi. Lớp này cung cấp hai phương thức xây dựng. Một chấp nhận một đối số số nguyên dùng để ấn định mảng byte kết xuất theo một kích cỡ ban đầu. và thứ hai không chấp nhận đối số nào, và thiết lập đệm kết xuất với kích thước mặc định. Lớp này cung cấp vài phương thức bổ sung, không được khai báo trong OutputStream:

reset() : thiết lập lại kết xuất vùng đệm nhằm cho phép tiến trình ghi khởi động lại tại đầu vùng đệm.

size() : trả về số byte hiện tại đã được ghi tới vùng đệm.

writeto() :ghi nội dung của vùng đệm kết xuất ra luồng xuất đã chỉ định. Để thực hiện, nó chấp nhận một đối tượng của lớp OutputStream làm đối số.

Chương trình ByteExam minh hoạ cho cách sử dụng hai lớp trên:

package chuong8;

import java.lang.System;

import java.io.*;

Chương 8: Xử lý vào/ra

Biên soạn : Bùi Công Giao --------------------------------------- 143 --------------------------------------

public class ByteExam {

public static void main(String args[]) throws IOException {

ByteArrayOutputStream os =new ByteArrayOutputStream();

String s ="Welcome to Byte Array Input Output classes";

// Viết từng ký tự của chuỗi s vào os

for(int i=0; i<s.length( );i++)

os.write(s.charAt(i)) ;

System.out.println("Output Stream is:" + os);

System.out.println("Size of output stream is:"+ os.size());

ByteArrayInputStream in;

in = new ByteArraylnputStream(os.toByteArray());

// số byte còn lại có thể được đọc từ luồng nhập ByteArraylnputStream  

int ib = in.available();

System.out.println("Input Stream has :" + ib + "available bytes");

byte ibuf[] = new byte[ib];

int byrd = in.read(ibuf, 0, ib);

System.out.println("Number of Bytes read are :" + byrd);

System.out.println("They are: " + new String(ibuf));

}

}

Kết quả chương trình như sau:

Output Stream is:Welcome to Byte Array Input Outputclasses

Size of output stream is:41

Input Stream has : 41 available bytes

Number of Bytes read are :41

They are: Welcome to Byte Array Input Output classes

8.2 Scanning và Formatting

8.2.1 Scanning

Đối tượng kiểu Scanner dùng để ngắt các dữ liệu vào có qui cách thành các từ (token) và dịch từng từ theo kiểu dữ liệu của nó.

Chương 8: Xử lý vào/ra

Biên soạn : Bùi Công Giao --------------------------------------- 144 --------------------------------------

Thông thường một Scanner dùng khoảng trắng để phân biệt các từ (ký tự khoảng trắng bao gồm khoảng trắng, tab, dấu kết thúc dòng). Để xem scanning làm việc như thế nào, ta hãy xem chương trình ScanXan đọc từng ký tự trong xanadu.txt và in chúng theo từng dòng.

import java.io.*;

import java.util.Scanner;

public class ScanXan {

public static void main(String[] args) throws IOException {

Scanner s = null;

try {

s = new Scanner(new BufferedReader(new FileReader("xanadu.txt")));

while (s.hasNext())

System.out.println(s.next());

} finally {

if (s != null)

s.close();

}

}

}

Nhận xét rằng mặc dầu scanner không là luồng dữ liệu, ta cũng cần đóng nó. Kết quả của chương trình:

In

Xanadu

did

Kubla

Khan

A

stately

pleasure-dome

...

ScanXan xem tất cả từ nhập vào có kiểu String. Ngoài ra Scanner cũng hỗ trợ các kiểu dữ liệu cơ bản (trừ char), cũng như BigInteger và BigDecimal. Giá trị kiểu số dùng rất

Chương 8: Xử lý vào/ra

Biên soạn : Bùi Công Giao --------------------------------------- 145 --------------------------------------

nhiều dấu phân cách. Thông dụng như kiểu bản địa của Mỹ (US locale), Scanner đọc chính xác chuỗi "32,767" như là số nguyên.

Chương trình ScanSum đọc một danh sách các giá trị double và tính tổng chúng:

package chuong8;

import java.io.FileReader;

import java.io.BufferedReader;

import java.io.IOException;

import java.util.Scanner;

import java.util.Locale;

public class ScanSum {

public static void main(String[] args) throws IOException {

Scanner s = null;

double sum = 0;

try {

s = new Scanner( new BufferedReader(new

FileReader("usnumbers.txt")));

s.useLocale(Locale.US);

while (s.hasNext())

if (s.hasNextDouble())

sum += s.nextDouble();

else

s.next();

} finally {

s.close();

}

System.out.println(sum);

}

}

Sau đây là nội dung usnumbers.txt

8.5

32,767

3.14159

1,000,000.1

Chương 8: Xử lý vào/ra

Biên soạn : Bùi Công Giao --------------------------------------- 146 --------------------------------------

Chuỗi kết quả là

1032778.74159

Tuỳ theo qui ước của locale, dấu . có thể là dấu khác. Do System.out là một đối tượng PrintStream và lớp đó không cung cấp cách thức để ghi đè locale mặc định. Ta chỉ có thể ghi đè locale cho toàn bộ chương trình bằng formatting.

8.2.2 Formatting

Đối tượng luồng thực hiện việc formatting có kiểu là PrintWriter, lớp luồng ký tự, và PrintStream, lớp luồng byte.

Có hai mức formatting:

1. print và println đặt qui cách từng giá trị theo cách tiêu chuẩn.

print hay println xuất ra từng giá trị sau khi chuyển kiểu bằng phương thức toString. Xem lớp Root sau:

public class Root {

public static void main(String[] args) {

int i = 2;

double r = Math.sqrt(i);

System.out.print("Căn bậc hai của ");

System.out.print(i);

System.out.print(" là ");

System.out.print(r);

System.out.println(".");

i = 5;

r = Math.sqrt(i);

System.out.println("Căn bậc hai của " + i + " là " + r + ".");

}

}

Đây là kết quả xuất ra:

Căn bậc hai của 2 là 1.4142135623730951.

Căn bậc hai của 5 là 2.23606797749979.

Chương 8: Xử lý vào/ra

Biên soạn : Bùi Công Giao --------------------------------------- 147 --------------------------------------

2. format đặt qui cách hầu như bất kỳ giá trị số dựa trên chuỗi có qui cách với nhiều lựa chọn. Phương thức này trong lớp java.util.Formatter có cú pháp sau:

format (String format, Object... args)

Phương thức này hầu như giống hệt với printf () của PrintStream. Các tham số args sẽ được hiển thị trong kết quả theo các chỉ thị đặc tả trong chuỗi định dạng trong tham số đầu tiên.

Xét chương trình Root2 thay thế cho Root. Root2 đặt quy cách hai giá trị:

public class Root2 {

public static void main(String[] args) {

int i = 2;

double r = Math.sqrt(i);

System.out.format("Căn bậc hai của %d là %f.%n", i, r);

}

}

Kết quả xuất ra:

Căn bậc hai của 2 là 1.414214

Tất cả đặc tả qui cách bắt đầu với % và kết thúc bằng 1 hay 2 ký tự chuyển đổi. Các ký tự chuyển đổi thường gặp:

d đặt qui cách giá trị nguyên như loại thập phân.

f đặt qui cách giá trị dấu chấm động (float) như loại thập phân.

n xuất dấu kết thúc dòng.

x đặt qui cách giá trị nguyên như loại thập lục phân.

s đặt qui cách bất kỳ giá trị nào như là một chuỗi.

tB đặt qui cách số nguyên là tên tháng theo kiểu bản địa.

Chú ý:

Ngoại trừ %% và %n tất cả đặc tả qui cách phải có tham số đi kèm; Nếu không một ngoại lệ phát sinh.

Trong ngôn ngữ Java, \n phát ra ký tự linefeed (\u000A). Không nên dùng \n để xuống dòng. Ta nên dùng %n để xuống dòng cho phù hợp với môi trường thực hiện.

Đặc tả qui cách có thể có thêm nhiều thành phần để tuỳ biến kết quả xuất ra. Lớp Format sau đây minh hoạ thêm các tuỳ biến.

public class Format {

Chương 8: Xử lý vào/ra

Biên soạn : Bùi Công Giao --------------------------------------- 148 --------------------------------------

public static void main(String[] args) {

System.out.format("%f, %1$+020.10f %n", Math.PI);

}

}

Kết quả :

3.141593, +00000003.1415926536

Hình sau đây mô tả các lựa chọn thêm.

% 1$ +0 20 .10 f

Bắt đầu đặc tả qui

cách

Chỉ mục tham

số

Cờ Chiều rộng

Độ chính xác

Chuyển đổi

Hình 8.4 Các thành phần đặc tả qui cách.

Ý nghĩa các thành phần này như sau:

Độ chính xác: với dấu chấm động giá trị này xác định số các ký số sau phần thập phân. Với chuyển đổi chuỗi (%s) giá trị này xác định chiều rộng, kết quả bị cắt phải nếu không đủ chổ.

Chiều rộng: chiều rộng tối thiểu, khoảng trắng được thêm vào bên trái nếu cần thiết.

Cờ: trong ví dụ trên + xác định số luôn có dấu, và 0 là ký tự chèn vào. Cờ khác bao gồm - (chèn vào bên phải) và , (qui cách số dấu phân cách phần nghìn).

Chỉ mục tham số cho phép so khớp tường minh một tham số xác định. Đặc tả < hàm ý dùng lại tham số trước, ví dụ như:

System.out.format("%f, %<+020.10f %n", Math.PI);

8.3 Vào ra từ chế độ dòng lệnh

Một chương trình thường được chạy từ dòng lệnh và tương tác với người sử dụng trong môi trường dòng lệnh. Các nền tảng Java hỗ trợ loại hình tương tác theo hai cách: thông qua các luồng chuẩn và thông qua Console.

8.3.1 Vào ra qua luồng chuẩn

Để xử lý đầu vào ta phải dùng System.in, đây là một luồng byte. Với System.in ta có thể đọc trong một byte đơn lẻ, hoặc một mảng kiểu byte, nó trả là một số nguyên. Sau đó ta chuyển từng byte từ đầu vào bàn phím thành kiểu ký tự. Ví dụ:

try {

Chương 8: Xử lý vào/ra

Biên soạn : Bùi Công Giao --------------------------------------- 149 --------------------------------------

int tmp = System.in.read ();

char c = (char)tmp; }

Để đọc một chuỗi ký tự thì đoạn mã trên nên đi vào bên trong một vòng lặp:

String strInput = "";

while (true) {

try {

int tmp = System.in.read ();

if (tmp == -1)

break;

char c = (char) tmp;

strInput = strInput + c;

} catch (IOException e) { }

}

System.out.println ("Xuất ra = " + strInput);

Giá trị trả về -1 báo hiệu kết thúc việc nhập. Vì giá trị kiểu byte chiếm 8 bit đầu tiên của một giá trị int 32-bit nên giá trị thực sự sẽ không bao giờ có dấu.

Để sử dụng nhập chuẩn này như là luồng ký tự, ta phải bao System.in trong InputStreamReader.

InputStreamReader cin = new InputStreamReader(System.in);

Như đã thảo luận phần trước, các lớp bao bộ đệm cung cấp việc truyền luồng byte hiệu quả.

Trong ví dụ BufferedReaderApp, ta bao System.in với InputStreamReader và sau đó bao thể hiện này với BufferedReader.

Ta dùng phương thức readLine() trong BufferedReader để có một dòng nhập vào thay vì vòng lặp để nhận từng ký tự. Kỹ thuật này cũng loại bỏ việc chuyển từ int tới char.

package chuong8;

import java.io.*;

public class BufferedReaderApp {

public static void main (String arg[]) {

// System.in đã mở theo mặc định.

// Bao một luồng reader mới để có khả năng 16 bit.

InputStreamReader reader = new InputStreamReader (System.in); // Bao reader với một reader có bộ đệm.

Chương 8: Xử lý vào/ra

Biên soạn : Bùi Công Giao --------------------------------------- 150 --------------------------------------

BufferedReader buf_in = new BufferedReader (reader); // Bao một luồng writer mới để có khả năng 16 bit.

OutputStreamWriter writer = new OutputStreamWriter (System.out); PrintWriter print_writer = new PrintWriter (writer, true); String str = "q";

try {

/* Đọc nguyên dòng. Kiểm tra chuỗi nhập là "quit" để thoát ra vòng lặp */

do {

// Đọc văn bản từ bàn phím

str = buf_in.readLine (); // Xuất ngược văn bản ra console.

print_writer.println ("echo " + str);

} while (!str.toLowerCase ().equals ("q") );

} catch (IOException e) {

System.out.println ("IO exception = " + e );

}

}

}

Một ví dụ kết quả chương trình:

My input from the keyboard

echo My input from the keyboard

More of my input

echo More of my input

q

echo q

Để có được giá trị số từ bàn phím đòi hỏi phải đọc các giá trị như chuỗi và sau đó chuyển đổi chúng thành các kiểu cơ bản. Ví dụ đoạn mã sau đọc một số nguyên như là chuỗi và sau đó chuyển thành giá trị số nguyên.

InputStreamReader reader = new InputStreamReader (System.in);

BufferedReader buf_reader = new BufferedReader (reader);

Chương 8: Xử lý vào/ra

Biên soạn : Bùi Công Giao --------------------------------------- 151 --------------------------------------

try {

String s = buf_reader.readLine (); // đọc số như chuỗi

// Cắt khoảng trắng đằng sau trước khi chuyển kiểu.

tmp = Integer.parseInt (s.trim ());

System.out.println (" echo = " + tmp);

} catch (IOException ioe) {

System.out.println ("IO exception = " + ioe);

}

Chương trình PrintWriterApp dưới đây xuất chuỗi, các kiểu dữ liệu cơ bản, và bao các luồng để có khả năng nhiều hơn. Đầu tiên chúng ta bao đối tượng System.out, luồng 8 bit, với một OutputStreamWriter để có được luồng 16-bit.

Tuy nhiên lớp OutputStreamWriter chỉ có vài phương thức, vì vậy ta bao đối tượng OutputStreamWriter bằng PrintWriter, mà nó cung cấp đầy đủ các phương thức print() và println() cho việc in chuỗi và các kiểu dữ liệu cơ bản.

package chuong8;

import java.io.*;

public class PrintWriterApp {

public static void main (String arg[]) {

OutputStreamWriter writer = new OutputStreamWriter (System.out);

// Tham số thứ hai là true để autoflush.

PrintWriter print_writer = new PrintWriter (writer,true);

print_writer.println ("Text output with PrintWriter.");

print_writer.println ("Primitives converted to strings:");

boolean a_boolean = false;

byte a_byte = 114;

short a_short = 1211;

int an_int = 1234567;

long a_long = 987654321;

float a_float = 983.6f;

double a_double = -4.297e-15;

print_writer.println (a_boolean);

print_writer.println (a_byte);

print_writer.println (a_short);

Chương 8: Xử lý vào/ra

Biên soạn : Bùi Công Giao --------------------------------------- 152 --------------------------------------

print_writer.println (an_int);

print_writer.println (a_long);

print_writer.println (a_float);

print_writer.println (a_double);

// PrintWriter không ném ra IOExceptions mà đưa ra checkError ()

if( print_writer.checkError ())

System.out.println ("An output error occurred!" );

}

}

Kết quả xuất ra:

Text output with PrintWriter.

Primitives converted to strings:

false

114

1211

1234567

987654321

983.6

-4.297E-15

Lưu ý rằng vài lớp luồng bao bọc (wrapper) bao gồm một bộ đệm thu thập dữ liệu cho đến khi nó đầy và sau đó toàn dữ liệu trong bộ đệm được gửi đến đích của nó trong một lần. Điều này hiệu quả hơn gửi mỗi một byte mỗi lần vì chi phí liên quan đến việc chuyển giao.

Để bảo đảm rằng dữ liệu được đẩy ra khỏi bộ đệm ngay lập tức ta dùng phương thức flush. Với cả hai PrintStream và PrintWriter, ta có tùy chọn autoflush.

Chương trình FormatWriteApp minh hoạ dùng Formatter để gửi các giá trị số đã tạo qui cách tới console.

package chuong8;

import java.io.*;

import java.util.*;

Chương 8: Xử lý vào/ra

Biên soạn : Bùi Công Giao --------------------------------------- 153 --------------------------------------

public class FormatWriteApp {

public static void main (String arg[]) {

// Gửi kết xuất đặt qui cách tới luồng System.out.

Formatter formatter = new Formatter ((OutputStream)System.out); formatter.format ("Text output with Formatter. %n");

formatter.format ("Primitives converted to strings: %n"); boolean a_boolean = false;

byte a_byte = 114;

short a_short = 1211;

int an_int = 1234567;

long a_long = 987654321;

float a_float = 983.6f;

double a_double = -4.297e-15; formatter.format ("boolean = %9b %n", a_boolean);

formatter.format ("byte = %9d %n", a_byte);

formatter.format ("short = %9d %n", a_short);

formatter.format ("int = %9d %n", an_int);

formatter.format ("long = %9d %n", a_long);

formatter.format ("float = %9.3f %n", a_float);

formatter.format ("double = %9.2e %n", a_double); formatter.flush ();

formatter.close ();

}

}

Kết xuất chương trình:

Text output with Formatter.

Primitives converted to strings:

boolean = false

byte = 114

short = 1211

int = 1234567

long = 987654321

Chương 8: Xử lý vào/ra

Biên soạn : Bùi Công Giao --------------------------------------- 154 --------------------------------------

float = 983.600

double = -4.30e-15

8.3.2 Vào ra qua Console

Console là một thay thế cao cấp hơn cho luồng chuẩn. Console có hầu hết các tính năng cung cấp bởi luồng chuẩn và đặc biệt hữu ích cho việc nhập mật khẩu an toàn. Nó cũng cung cấp luồng vào và ra là luồng ký tự thực sự, thông qua phương thức reader và writer.

Trước khi một chương trình có thể sử dụng Console, nó phải cố gắng tạo đối tượng điều khiển bằng cách gọi System.console(). Nếu đối tượng điều khiển có sẵn, phương thức này trả về một tham chiếu. Nếu System.console trả về null, thì các hoạt động Console không được phép, hoặc vì hệ điều hành không hỗ trợ chúng hay bởi vì chương trình chạy trong môi trường không tương tác.

Đối tượng Console hỗ trợ nhập mật khẩu an toàn thông qua các phương thức readPassword. Phương thức này giúp đảm bảo nhập mật khẩu trong hai cách. Đầu tiên, nó ngăn chặn hiển thị, do đó, mật khẩu là không thể nhìn thấy trên màn hình của người dùng. Thứ hai, readPassword trả về một mảng ký tự, không phải là một chuỗi, vì vậy các mật khẩu có thể được ghi đè, loại bỏ nó khỏi bộ nhớ ngay sau khi nó không còn cần thiết.

Chương trình Password sau thay đổi mật khẩu.

package chuong8;

import java.io.Console;

import java.util.Arrays;

import java.io.IOException;

public class Password {

public static void main (String args[]) throws IOException {

Console c = System.console();

if (c == null) {

System.err.println("No console.");

System.exit(1);

}

String login = c.readLine("Enter your login: ");

char [ ] oldPassword = c.readPassword("Enter your old password: ");

if (verify(login, oldPassword)) {

boolean noMatch;

Chương 8: Xử lý vào/ra

Biên soạn : Bùi Công Giao --------------------------------------- 155 --------------------------------------

do {

char [ ] newPassword1 = c.readPassword("Enter your new password: ");

char [ ] newPassword2 = c.readPassword("Enter new password again: ");

noMatch = ! Arrays.equals(newPassword1, newPassword2);

if (noMatch)

c.format("Passwords don't match. Try again.%n");

else {

change(login, newPassword1);

c.format("Password for %s changed.%n", login);

}

Arrays.fill(newPassword1, ' ');

Arrays.fill(newPassword2, ' ');

} while (noMatch);

}

Arrays.fill(oldPassword, ' ');

}

// giả lập phương thức kiểm tra.

static boolean verify(String login, char[] password) {

return true;

}

// giả lập phương thức thay đổi.

static void change(String login, char[ ] password) { }

}

Chương trình này thực hiện:

Cố gắng để lấy đối tượng Console. Nếu đối tượng không có, hủy bỏ.

Gọi Console.readLine để nhắc và đọc tên đăng nhập của người dùng.

Gọi Console.readPassword để nhắc và đọc mật khẩu hiện hành.

Kiểm tra người dùng có quyền thay đổi mật khẩu.

Lặp lại các bước sau cho đến khi người dùng nhập mật khẩu hai lần:

a. Console.readPassword gọi hai lần để nhắc cho và đọc một mật khẩu mới.

b. Nếu người dùng nhập vào cùng một mật khẩu cả hai lần, gọi change để thay đổi nó. Ghi đè lên cả hai mật khẩu với các khoảng trống.

c. Ghi đè lên các mật khẩu cũ với khoảng trống.

Chương 8: Xử lý vào/ra

Biên soạn : Bùi Công Giao --------------------------------------- 156 --------------------------------------

Dùng Scanner cho nhập từ Console

Đoạn lệnh sau minh hoạ việc đọc giá trị nguyên từ bàn phím:

Scanner scanner = new Scanner (System.in);

int i = scanner.nextInt ();

Đối với mỗi kiểu cơ bản có phương thức nextXxx() trả về giá trị của kiểu tương ứng. Nếu chuỗi không thể chuyển được như kiểu đó, thì xử lý ngoại lệ InputMismatchException xuất hiện.

Chương trình ScanConsoleApp minh hoạ việc đọc int, float, và double từ bàn phím.

package chuong8;

import java.io.*;

import java.util.*;

public class ScanConsoleApp {

public static void main (String arg[]) {

// Tạo mộ scanner để đọc từ bàn phím

Scanner scanner = new Scanner (System.in);

try {

System.out.printf ("Input int (e.g. %4d): ",3501);

int int_val = scanner.nextInt ();

System.out.println (" You entered " + int_val +"\n");

System.out.printf ("Input float (e.g. %5.2f): ", 2.43);

float float_val = scanner.nextFloat ();

System.out.println (" You entered " + float_val +"\n");

System.out.printf ("Input double (e.g. %6.3e): ",4.943e15);

double double_val = scanner.nextDouble ();

System.out.println (" You entered " + double_val +"\n");

} catch (InputMismatchException e) {

System.out.println ("Mismatch exception:" + e );

}

}

}

Kết quả chương trình:

Input int (e.g. 3501): 23431

Chương 8: Xử lý vào/ra

Biên soạn : Bùi Công Giao --------------------------------------- 157 --------------------------------------

You entered 23431 Input float (e.g. 2.43): 1.2343

You entered 1.2343 Input double (e.g. 4.943e+15): -2.34e4

You entered -23400.0

Có một số phương thức khác hữu ích trong lớp Scanner như skip() để nhảy qua đầu vào, findInLine () để tìm kiếm các chuỗi con. useDelimiter() để chỉ ra một dấu phân cách từ khác (different token separator). Ví dụ ta muốn dấu phân cách từ là dấu phẩy theo sau là khoảng trắng, dùng lệnh:

s.useDelimiter(",\\s*");

8.4 Làm việc với CSDL

8.4.1 JDBC

8.4.1.1 Giới thiệu JDBC

JDBC là một API có chứa một tập hợp các lớp, các giao diện Java và các thông báo lỗi ngoại lệ nằm trong cùng một đặc tả mà theo đó cả các công ty sản xuất JDBC driver cũng như các nhà phát triển JDBC đều phải tuân thủ chặt chẽ khi phát triển ứng dụng.

SUN chuẩn bị và duy trì đặc tả JDBC. Bởi JDBC chỉ là một đặc tả (đề xuất cách viết và sử dụng các JDBC driver), nên các công ty sản xuất phần mềm bên thứ ba sẽ phát triển các JDBC driver tuân thủ chặt chẽ đặc tả này. Các nhà phát triển JDBC sau đó sẽ sử dụng các driver này để truy cập vào các nguồn dữ liệu.

JDBC tồn tại là để giúp các nhà phát triển Java tạo nên các ứng dụng truy xuất CSDL mà không cần phải học và sử dụng các API độc quyền do các công ty sản xuất phần mềm khác nhau (bên thứ ba) cung cấp.

8.4.1.2 Kiến trúc JDBC

Trong Java có 2 lớp chủ yếu chịu trách nhiệm về thiết lập kết nối đến một CSDL:

1. DriverManager - Nạp các JDBC driver vào trong bộ nhớ. Có thể sử dụng nó để mở các kết nối tới một nguồn dữ liệu.

2. Connection - Giúp kết nối đến một nguồn dữ liệu. Được dùng để tạo ra các đối tượng Statement, PreparedStatement và CallableStatement.

Statement - Biểu diễn một lệnh SQL tĩnh. Có thể sử dụng nó để thu về đối tượng ResultSet.

PreparedStatement - Một giải pháp thay thế hoạt động tốt hơn đối tượng Statement, thực thi một câu lệnh SQL đã được biên dịch trước.

Chương 8: Xử lý vào/ra

Biên soạn : Bùi Công Giao --------------------------------------- 158 --------------------------------------

CallableStatement – biểu diễn một thủ tục được lưu trữ. Có thể được sử dụng để thực thi các thủ tục được lưu trữ trong một RDBMS có hỗ trợ chúng.

ResultSet - biểu diễn một tập kết quả trong CSDL tạo ra bởi việc sử dụng một câu lệnh SQL là SELECT.

SQLException - một lớp xử lý lỗi ngoại lệ chứa các lỗi truy cập CSDL.

Có nhiều loại JDBC driver, nhưng ở đây ta chỉ quan tâm đến loại mà SUN khuyến cáo sử dụng và phát triển. Đây là loại được viết thuần túy bằng Java và là loại hiệu quả nhất. Chúng cho phép kết nối trực tiếp vào CSDL, cung cấp kết quả tối ưu và cho phép lập trình viên thực hiện các chức năng tùy thuộc vào CSDL. Điều này đã tạo ra tính cơ động cao nhất là khi ta cần thay đổi CSDL bên dưới một ứng dụng. Loại driver này thường được dùng cho các ứng dụng phân tán cao.

8.4.2 MySQL và Java

MySQL là hệ quản trị CSDL mã nguồn mở phổ biến nhất thế giới và được các nhà phát triển rất ưa chuộng trong quá trình phát triển ứng dụng. MySQL miễn phí hoàn toàn nên ta có thể tải về MySQL từ trang chủ www.mysql.com.

8.4.2.1 Cài đặt Connector/J - JDBC Driver của MySQL

Sau khi cài MySQL lên máy, bước kế tiếp ta cài đặt Connector/J - JDBC Driver của MySQL. JDBC Driver của MySQL có thể lấy về ở địa chỉ http://dev.mysql.com/downloads/connector/j. File tải về sẽ là một file .zip hoặc .gz. Sau khi giải nén ta sẽ có một file có tên tương tự như sau: mysql-connector-java-

5.1.13.rar. Hãy chép file này vào thư mục %JAVA_HOME%/jre/lib/ext trên hệ thống của ta. Ví dụ:

C:\Program Files\Java\jdk1.6.0_21\jre\lib\ext

Ta cũng cần đưa đường dẫn đến file JAR này vào biến môi trường MYSQL_DRIVER của hệ thống.

MYSQL_DRIVER : C:\Program Files\Java\jdk1.6.0_21\jre\lib\ext\mysql-connector-java-5.1.13-bin.jar

Chương 8: Xử lý vào/ra

Biên soạn : Bùi Công Giao --------------------------------------- 159 --------------------------------------

8.4.2.2 Kiểm tra Connector/J

Chúng ta tạo ra một chương trình Java nhỏ để kiểm tra xem chúng ta đã cài đặt đúng JDBC driver của MySQL chưa. Nếu chương trình chạy thành công thì nghĩa là trình điều khiển JDBC đã sẵn sàng cho các tác vụ phức tạp hơn. Ta tạo ra một file Connect.java với đoạn mã sau:

package chuong8;

import java.sql.*;

public class Connect {

public static void main (String[] args) {

Connection conn = null;

try {

String userName = “root”;

String password = “localhost”;

String url = “jdbc:mysql://127.0.0.1:3306/mysql”;

// Đăng kí/Nạp driver

Class.forName (“com.mysql.jdbc.Driver”).newInstance ();

// Thiết lập kết nối đến CSDL

conn = DriverManager.getConnection (url, userName, password);

System.out.println (“Da ket noi CSDL”);

// Gửi câu lệnh SQL và xử lý kết quả thu về…

} catch (Exception e) {

System.err.println (“Khong ket noi duoc”);

} finally {

if (conn != null) {

try {

conn.close ();

System.out.println (“Dong ket noi”);

} catch (Exception e) { /* bo qua loi luc dong CSDL */ }

}

}

}

Chương 8: Xử lý vào/ra

Biên soạn : Bùi Công Giao --------------------------------------- 160 --------------------------------------

}

Trước hết biên dịch file này ra mã bytecode nhưng để chạy nó, ta cần chú ý đảm bảo MySQL đang chạy. Nếu ta đã cài đặt MySQL để nó chỉ chạy khi ta gọi thì ta hãy bật MySQL với câu lệnh mysqld-nt --console hay nếu ta đã cài chương trình quản trị CSDL này dưới hình thức một dịch vụ thì ta có thể gõ net start mysql trong shell Run trong Windows.

Đoạn mã trên sử dụng cặp tên người sử dụng và mật khẩu là root/localhost với CSDL là mysql. Nếu kết nối thành công, ta sẽ thấy một màn hình như dưới đây xuất hiện.

Da ket noi CSDL

Dong ket noi

8.4.2.3 Thực hiện các câu truy vấn

Để thực hiện câu lệnh SQL trong một ứng dụng có sử dụng JDBC, ta hãy tạo ra một đối tượng Statement từ đối tượng Connection của ta. Đối tượng này chứa một kết nối đơn đến CSDL. Các đối tượng Statement hỗ trợ phương thức executeUpdate() để đưa vào các câu truy vấn thực hiện chức năng thay đổi CSDL và không trả lại tập kết quả, và phương thức executeQuery() để tạo ra các câu truy vấn cho phép trả lại tập kết quả. Để minh họa kết quả xử lý dữ liệu chúng ta sử dụng một bảng, animal, bảng này hứa 1 cột id chứa số nguyên và 2 cột chứa các chuỗi, name và category. Câu lệnh MySQL để tạo bảng này trông như sau:

CREATE TABLE animal(

id INT UNSIGNED NOT NULL AUTO_INCREMENT, PRIMARY KEY (id),

name CHAR(40), category CHAR(40)

)

Cột id có thuộc tính AUTO_INCREMENT tức là giá trị của nó tự động tăng thêm mà không cần chúng ta trực tiếp can thiệp, vì MySQL tự động gán các giá trị nối tiếp nhau 1, 2, 3, … mỗi khi có 1 bản ghi được bổ sung vào bảng.

Thực hiện câu truy vấn không trả lại tập kết quả

Đầu tiên chúng ta tạo ra một đối tượng Statement từ đối tượng Connection, và sau đó sử dụng chúng để tạo ra và cung cấp giá trị cho bảng animal. DROP TABLE, CREATE TABLE, UPDATE, DELETE và INSERT đều là các câu lệnh thực hiện việc thay đổi CSDL, cho nên phương thức executeUpdate() là phương thức thích hợp để thực thi chúng. Phương thức này trả lại một số nguyên chỉ số lượng hàng trong CSDL

Chương 8: Xử lý vào/ra

Biên soạn : Bùi Công Giao --------------------------------------- 161 --------------------------------------

đã bị tác động sau khi thực hiện câu truy vấn. Trong ví dụ dưới đây, số nguyên này đã được gán vào biến count:

Statement s = conn.createStatement ();

int count;

s.executeUpdate (“DROP TABLE IF EXISTS animal”);

s.executeUpdate ( “CREATE TABLE animal (“ + “id INT UNSIGNED NOT NULL AUTO_INCREMENT,” + “PRIMARY KEY (id),” + “name CHAR(40), category CHAR(40))”);

count = s.executeUpdate (“INSERT INTO animal (name, category) VALUES (‘snake’, ‘reptile’), (‘frog’, ‘amphibian’), (‘tuna’, ‘fish’), (‘racoon’, ‘mammal’)”);

s.close();

System.out.println (count + “ dong da duoc tao ra”);

Ở đây, biến count được sử dụng để báo lại số lượng hàng mà câu lệnh INSERT đã bổ sung vào bảng animal.

Một đối tượng Statement có thể sử dụng để tạo ra nhiều câu truy vấn. Khi đã thực hiện xong các thao tác trên CSDL, ta hãy gọi phương thức close() để xóa đối tượng và giải phóng tất cả các tài nguyên liên kết đến nó.

Nếu như ta muốn biết câu lệnh SQL có trả lại tập kết quả hay không (ví dụ như khi người sử dụng nhập câu lệnh vào trường nhập liệu của form), bạn có thể dùng phương thức execute() của đối tượng Statement. Phương thức này trả lại true nếu câu lệnh có trả lại tập kết quả. Trong trường hợp đó, đối tượng ResultSet có thể được thu hồi thông qua phương thức getResultSet() và số lượng hàng được tác động có thể biết được thông qua phương thức getUpdateCount():

Statement unknownSQL = con.createStatement();

If (unknownSQL.execute(sqlString)) {

ResultSet rs = unknownSQL.getResultSet();

// hiển thị kết quả

} else

System.out.println(“Cac dong da cap nhat: “ + unknownSQL.getUpdateCount());

Thực thi các câu truy vấn có trả lại một tập kết quả

Các câu lệnh truy vấn có trả lại tập kết quả là các câu lệnh giúp chúng ta lấy ra dữ liệu từ CSDL dưới một dạng thức nào đó. Ví dụ câu lệnh SELECT lấy thông tin từ một CSDL về thì khi đó ta dùng phương thức executeQuery(). Sau khi gọi phương thức

Chương 8: Xử lý vào/ra

Biên soạn : Bùi Công Giao --------------------------------------- 162 --------------------------------------

này, ta hãy tạo ra một đối tượng ResultSet và sử dụng nó để lặp lại các thao tác dữ liệu trên các hàng mà câu truy vấn trả về. Ví dụ sau cho thấy cách sử dụng phương thức này để lấy thông tin từ bảng animal về:

Statement s = conn.createStatement ();

s.executeQuery (“SELECT id, name, category FROM animal”);

ResultSet rs = s.getResultSet ();

count = 0;

while (rs.next ()) {

int idVal = rs.getInt (“id”);

String nameVal = rs.getString (“name”);

String catVal = rs.getString (“category”);

System.out.println ( “id = “ + idVal + “, name = “ + nameVal + “, category = “ + catVal);

++count;

}

Phương thức executeQuery() không trả lại số nguyên đếm số hàng mà nó tác động, do vậy nếu ta muốn biết một tập kết quả chứa bao nhiêu hàng thì ta cần tự mình đếm lấy khi ta thực thi thao tác lấy dữ liệu trên từng hàng. Để lấy được các giá trị của cột trên từng hàng, thì ta hãy gọi các phương thức getXXX(),trong đó XXX đại diện cho kiểu giá trị của cột. Chẳng hạn, các phương thức getInt() và getString() được sử dụng trong ví dụ trên trả lại các giá trị chuỗi và số nguyên. Như đã trình bày, các phương thức này có thể được gọi bằng cách sử dụng tên của một cột nằm trong tập kết quả. Ta cũng có thể lấy các giá trị về từ vị trí của chúng. Đối với tập kết quả lấy về từ câu truy vấn SELECT trong ví dụ trên, id, name, và category nằm ở các vị trí cột 1, 2 và 3 và do vậy có thể lấy về theo cách sau:

int idVal = rs.getInt (1);

String nameVal = rs.getString (2);

String catVal = rs.getString (3);

Các đối tượng ResultSet, cũng như các đối tượng Statement, nên được đóng lại khi ta đã dùng xong chúng. Để kiểm tra xem liệu giá trị của một cột nào đó có là NULL hay không, ta hãy gọi phương thức wasNull() của đối tượng chứa tập kết quả sau khi lấy giá trị đó về. Ví dụ, ta có thể kiểm tra giá trí NULL nằm trong cột name như sau:

String nameVal = rs.getString (“name”);

Chương 8: Xử lý vào/ra

Biên soạn : Bùi Công Giao --------------------------------------- 163 --------------------------------------

if (rs.wasNull ()) nameVal = “(khong ten)”;

Sử dụng ký tự giữ chỗ

Thỉnh thoảng ta cần xây dựng một câu truy vấn với các giá trị có chứa các kí tự cần được xử lý đặc biệt. Ví dụ, trong các câu truy vấn, các giá trị dạng chuỗi được viết bên trong các dấu trích dẫn đơn, nhưng bất cứ kí tự trích dẫn đơn nào nằm trong chính chuỗi đó đều cần phải được biến thành dấu trích dẫn kép hoặc phải được chuyển vị (escaped) bằng kí tự ‘\’ để tránh tạo ra các câu SQL hoạt động sai mục đích. Trong trường hợp này, cách dễ dàng hơn cả là ta hãy để JDBC xử lý vấn đề chuyển vị này cho ta, chứ không nên tự mình làm lấy. Để làm được điều đó, ta hãy tạo ra một kiểu lệnh khác (một lệnh PreparedStatement), tham chiếu đến các giá trị dữ liệu nằm trong chuỗi truy vấn dưới hình thức các kí tự giữ chỗ (placeholders). Sau đó báo cho JDBC biết để liên kết các giá trị dữ liệu đó vào các kí tự giữ chỗ này và nó sẽ xử lý một cách tự động bất cứ một kí tự đặc biệt nào. Giả sử ta có hai biến có tên là nameVal và catVal dùng để chứa các giá trị sẽ chèn vào trong bảng animal. Để làm được điều đó mà không cần quan tâm đến việc liệu các biến đó có chứa các kí tự đặc biệt hay không, ta hãy tạo ra một câu truy vấn kiểu như thế này:

PreparedStatement s;

s = conn.prepareStatement ( “INSERT INTO animal (name, category)

VALUES(?,?)”);

s.setString (1, nameVal);

s.setString (2, catVal);

int count = s.executeUpdate ();

s.close ();

System.out.println (count + “ dong da duoc chen”);

Các kí tự ‘?’ nằm trong chuỗi truy vấn đóng vai trò là các kí tự giữ chỗ--các kí tự đánh dấu đặc biệt nhằm xác định nơi các giá trị dữ liệu cần được đặt vào đó. Phương thức setString() nhận thông tin về vị trí của kí tự giữ chỗ và một giá trị dạng chuỗi rồi gắn kết giá trị đó vào kí tự giữ chỗ thích hợp, thực thi bất cứ thao tác chuyển vị nào cần thiết. Phương thức mà ta sử dụng để gắn kết một giá trị tùy thuộc vào kiểu dữ liệu. Ví dụ, setString() gắn giá trị dạng chuỗi và setInt() gắn các giá trị dạng số nguyên.

Xử lý lỗi

Nếu ta muốn bẫy lỗi, hãy thực thi các thao tác JDBC bên trong khối try và sử dụng một bộ xử lý lỗi ngoại lệ để hiển thị thông tin về nguyên nhân gây ra vấn đề có thể xuất hiện. JDBC cung cấp các phương thức getMessage() và getErrorCode() mà khi lỗi xuất hiện ta có thể sử dụng để biết được thông báo lỗi và mã lỗi dạng số. Ví dụ sau cố

Chương 8: Xử lý vào/ra

Biên soạn : Bùi Công Giao --------------------------------------- 164 --------------------------------------

tình dùng một câu truy vấn mắc lỗi. Khi thực thi, phương thức executeQuery() sẽ không thể xử lý được và phát sinh một lỗi ngoại lệ. Lỗi này sẽ được thu về thông qua khối catch, trong khối này ta có thể chứa mã lệnh để xử lý lỗi hay chỉ đơn giản là hiển thị thông báo lỗi và mã lỗi:

try {

Statement s = conn.createStatement ();

s.executeQuery(“XYZ”); // tạo ra câu truy vấn không hợp lệ

s.close();

} catch( SQLException e) {

System.err.println (“Thong bao loi: “ + e.getMessage());

System.err.println (“Ma loi: “ + e.getErrorCode());

}

TỔNG KẾT CHƯƠNG 8

Trong chương này các vấn đề sau được trình bày:

Thế nào là luồng dữ liệu

Có ba luồng dữ liệu: byte, ký tự và có bộ đệm.

Đối tượng scanner quét luồng dữ liệu vào và chuyển thành các dữ liệu với kiểu xác định

Đặt qui cách dữ liệu xuất ra bằng formatting.

Mô tả vào ra trong chế độ dòng lệnh qua hai cơ chế: qua luồng chuẩn và đối tượng Console

Thế nào là JDBC

Làm việc với CSDL MySQL

BÀI TẬP

1. Điền các từ thích hợp vào các câu sau:

a. ---------- là các dàn ống (pipelines) để gửi và nhận thông tin trong các chương trình Java.

b. ----------- là luồng lỗi chuẩn.

c. Phương thức ------------- đọc các byte dữ liệu từ một luồng.

d. -------------- để lưu giữ dữ liệu tạm thời trước khi nhập/xuất tới đích (destination).

e. Phương thức ------------ xả sạch luồng.

f. Nhập/xuất mảng byte sử dụng các lớp ------------ và ---------------------

2. Viết một chương trình nhận một dãy ký tự riêng lẻ, cách nhau bởi khoảng trắng từ

Chương 8: Xử lý vào/ra

Biên soạn : Bùi Công Giao --------------------------------------- 165 --------------------------------------

cửa sổ dòng lệnh rồi nối chúng với nhau thành một từ, ví dụ như sau:

java Glue B A N A N A

thì chương trình xuất ra:

BANANA

với không có khoảng trắng

3. Viết một chương trình nhận một câu từ dòng lệnh và xuất ra kết quả thống kê câu này, ví dụ như sau:

java SentenceStatistics this is my sample sentence

thì chương trình xuất ra:

number of words: 5

longest word(s): sentence

length of longest word(s): 8

shortest word(s): is my

length of shortest word(s): 2

cho đơn giản không dùng bất kỳ dấu ngắt (punctuation) trong câu.

4. Viết chương trình xuất ra dữ liệu từ bảng animal.

Chương 9: Lập trình giao diện

Biên soạn : Bùi Công Giao --------------------------------------- 166 --------------------------------------

Chương 9. Lập trình giao diện

Nội dung chương này nhằm giới thiệu:

Lập trình giao diện với các đối tượng cơ bản và với các đối tượng multimedia.

Lập trình giao diện với HTML&Applet

Lập trình giao diện với SWING

9.1 Giao diện với các đối tượng cơ bản

Trong mục này, chúng ta sẽ tìm hiểu và sử dụng các đối tượng cơ bản của lập trình giao diện trong Java:

Các đối tượng khung chứa (container) cơ bản: Frame, Panel, Dialog.

Các đối tượng thành phần (component) cơ bản: Button, Label, TextField, TextArea

Các sự kiện cơ bản của các đối tượng.

Muốn sử dụng các đối tượng này, cần thêm lệnh sử dụng thư viện AWT của Java:

import java.awt.*;

9.1.1 Các đối tượng container cơ bản

Các đối tượng container được dùng để chứa các đối tượng thành phần khác. Các lớp đối tượng này có một số phương thức chung như sau:

add(Object): thêm một đối tượng (kiểu component) vào container.

remove(Object): loại bỏ một đối tượng ra khỏi container.

removeAll(): loại bỏ tất cả các đối tượng mà container đang chứa.

getComponent(int): trả về đối tượng thành phần có chỉ số là tham số đầu vào.

Container quản lý các đối tượng chứa trong nó dưới dạng mảng. Chỉ số của các thành phần là số thứ tự khi thành phần đó được thêm vào container.

getComponents(): trả về mảng tất cả các đối tượng mà container đang chứa.

countComponents(): trả về số lượng các đối tượng mà container đang chứa.

9.1.1.1 Frame

Frame là một đối tượng có thể dùng một cách độc lập, hoặc được gắn vào một đối tượng khác như một đối tượng conponent bình thường. Thông thường, Frame được dùng như một cửa sổ của một chương trình độc lập. Các phương thức cơ bản của lớp Frame:

Frame(): khởi tạo không tham số.

Frame(String): khởi tạo với tham số là dòng tiêu đề của frame.

Chương 9: Lập trình giao diện

Biên soạn : Bùi Công Giao --------------------------------------- 167 --------------------------------------

setSize(int, int): định kích cỡ của frame, tham số tương ứng là chiều rộng và chiều cao của frame.

setVisible(boolean): cho phép frame xuất hiện hay ẩn đi trên màn hình.

setTitle(String)/getTitle(): truy nhập thuộc tính dòng tiêu đề của frame.

setResizable(boolean): thiết lập thuộc tính cho phép thay đổi kích cỡ frame.

setIconImage(Image): thiết lập ảnh icon ở góc trên (biểu tượng) của frame.

Chương trình FrameDemo minh hoạ việc sử dụng một đối tượng của lớp Frame.

package chuong9;

import java.awt.*;

public class FrameDemo {

public static void main(String[] args) {

// Khai báo và khởi tạo frame có tiêu đề

Frame myFrame = new Frame(“This is my Frame!”);

myFrame.setSize(300,150); // Định kích cỡ frame

myFrame.setVisible(true); // Hiển thị frame

}

}

Hình 9.1 Kết quả demo Frame

9.1.1.2 Panel

Panel cũng là một dang khung chứa, nhưng khá đơn giản. Panel chỉ dùng để nhóm các đối tượng giao diện với nhau. Thông thường, panel được dùng trong một cửa sổ của Frame hoặc một ứng dụng khác. Các phương thức cơ bản của lớp Panel, ngoài các phương thức chung của container:

Panel(): Khởi tạo không tham số.

Chương trình PanelDemo minh hoạ việc sử dụng một Panel trong một Frame.

package chuong9;

import java.awt.*;

public class PanelDemo {

public static void main(String[] args) {

Chương 9: Lập trình giao diện

Biên soạn : Bùi Công Giao --------------------------------------- 168 --------------------------------------

// Khai báo và khởi tạo frame có tiêu đề

Frame myFrame = new Frame(“Frame has a panel!”);

myFrame.setSize(300,150); // Định kích cỡ frame

Panel myPanel = new Panel();// Khai báo panel

myFrame.add(myPanel); // Thêm panel vào frame

myFrame.setVisible(true); // Hiển thị frame

}

}

Hình 9.2 Kết quả demo Panel

9.1.1.3 Dialog

Dialog là một đối tượng cửa sổ con của một cửa sổ chương trình chính. Do vậy, Dialog chỉ được sử dụng kèm với một Frame. Có hai dạng Dialog:

Modal: khi hiện của sổ dialog, các cửa sổ khác của chương trình sẽ bị khoá lại, không thao tác được, chỉ thao tác được trên cửa sổ dialog.

Non-modal: không khoá các cửa sổ khác. Khi dialog xuất hiện, người dùng vẫn có thể chuyển sang thao tác trên các cửa sổ khác, nếu cần.

Các phương thức cơ bản của lớp Dialog:

Dialog(Frame, boolean): Khởi tạo dialog, tham số thứ nhất là frame chứa dialog, tham số thứ hai xác định dialog có là modal hay không.

Dialog(Frame, String, boolean): Khởi tạo dialog, thêm tham số thứ hai là dòng tiêu đề của dialog.

setVisible(boolean): Thiết lập trạng thái hiển thị hoặc ẩn dialog trên màn hình.

setSize(int, int): Định kích cỡ cho dialog, các tham số tương ứng là chiều rộng và chiều cao của dialog.

setTitle(String)/getTitle(): Truy nhập thuộc tính dòng tiêu đề của dialog.

setResizable(boolean): Thiết lập thuộc tính cho phép thay đổi kích cỡ của dialog.

setLayout(Layout): Thiết lập chế độ hiển thị các đối tượng chứa trong dialog.

Chương trình DialogDemo minh hoạ việc thêm một dialog (đang rỗng, chưa có đối tượng thành phần nào) vào một frame.

Chương 9: Lập trình giao diện

Biên soạn : Bùi Công Giao --------------------------------------- 169 --------------------------------------

package chuong9;

import java.awt.*;

public class DialogDemo {

public static void main(String[] args) {

// Khai báo và khởi tạo frame có tiêu đề

Frame myFrame = new Frame(“Frame has a dialog!”);

myFrame.setSize(300,150); // Định kích cỡ frame

// Khai báo và khởi tạo dialog

Dialog myDialog = new Dialog(myFrame, “An empty dialog!”, true);

myDialog.setSize(300,150); // Định kích cỡ dialog

myDialog.setVisible(true); // Hiển thị dialog

}

}

Hình 9.3 Kết quả demo Dialog

9.1.2 Các đối tượng component cơ bản

Các đối tượng component được dùng để làm thành phần của các đối tượng khung chứa, chúng không thể dùng độc lập, mà luôn phải gắn vào trong một đối tượng khung chứa container.

9.1.2.1 Label

Label (nhãn) là một đối tượng để hiển thị văn bản tĩnh, những văn bản mà người dùng không thể thay đổi trực tiếp được. Các phương thức cơ bản của Label:

Label(): Khởi tạo một nhãn rỗng.

Label(String): Khởi tạo một nhãn với nội dung văn bản là tham số đầu vào.

Label(String, int): Khởi tạo một nhãn có nội dung sẵn, tham số thứ hai xác định cách căn lề của nhãn so với khung chứa, bao gồm {Label.CENTER, Label.LEFT, Label.RIGHT}.

setText(String)/getText(): Truy nhập nội dung văn bản của nhãn.

setAlignment(int)/getAlignment(): Truy nhập thuộc tính căn lề của nhãn.

setFont(Font): Định dạng phông chữ của nhãn.

Chương trình LabelDemo minh hoạ việc sử dụng nhãn trong một frame.

Chương 9: Lập trình giao diện

Biên soạn : Bùi Công Giao --------------------------------------- 170 --------------------------------------

package chuong9;

import java.awt.*;

public class LabelDemo {

public static void main(String[] args) {

// Khai báo và khởi tạo frame có tiêu đề

Frame myFrame = new Frame(“Frame has a label!”);

myFrame.setSize(300,150); // Định kích cỡ frame

// Khai báo và khởi tạo label

Label myLabel = new Label();

myLabel.setText(“This is a label!”);//Gán nội dung văn bản

myLabel.setAlignment(Label.CENTER);// Căn lề giữa

myFrame.add(myLabel); // Gắn label vào frame

myFrame.setVisible(true); // Hiển thị frame

}

}

Hình 9.4 Kết quả demo Label

9.1.2.2 TextField và TextArea

Đây là hai đối tượng dùng để biểu diễn văn bản và người dùng có thể thay đổi nội dung văn bản chứa trong chúng. Điểm khác biệt là TextField chỉ cho phép một dòng văn bản, trong khi TextArea cho phép chứa nhiều dòng văn bản. Các phương thức chung của hai lớp này:

setText(String)/getText(): Truy nhập thuộc tính nội dung văn bản chứa trong ô.

getSelectedText(): Trả về chuỗi văn bản được bôi đen (đánh dấu chọn) trong ô.

getSelectedStart(): Trả về vị trí kí tự đầu trong vùng được đánh dấu chọn (tính từ 0).

getSelectedEnd(): Trả về vị trí kí tự cuối trong vùng được đánh dấu chọn (tính từ 0).

selectAll(): Đánh dấu chọn toàn văn bản.

setEditable(boolean): Xác định vùng văn bản có thể edit được hay không.

Các phương thức khác của lớp TextField:

TextField(): Khởi tạo một ô văn bản rỗng.

Chương 9: Lập trình giao diện

Biên soạn : Bùi Công Giao --------------------------------------- 171 --------------------------------------

TextField(int): Khởi tạo một ô văn bản rỗng, độ rộng xác định bởi tham số vào.

TextField(String): Khởi tạo một ô văn bản có nội dung xác định bởi tham số đầu vào.

TextField(String, int): Khởi tạo vởi nội dung có sẵn, độ rộng xác định.

setEchoChar(char)/getEchoChar(): Truy nhập thuộc tính là kí tự thay thế văn bản trong ô.

Thuộc tính này được dùng khi ta cần che dấu thông tin văn bản, ví dụ, ô gõ mật khẩu của chương trình.

getColums(): Trả về độ rộng của ô văn bản.

Các phương thức khác của lớp TextArea:

TextArea(): Khởi tạo một vùng văn bản rỗng.

TextArea(int, int): Khởi tạo một vùng văn bản rỗng, kích cỡ (số dòng, số cột) xác

định bởi tham số vào.

TextArea(String): Khởi tạo một vùng văn bản có nội dung xác định bởi tham số đầu vào.

TextArea(String, int, int): Khởi tạo vùng văn bản với nội dung có sẵn, độ rộng xác định.

appendText(String): Thêm một đoạn văn bản vào cuối đoạn văn bản trong vùng.

insertText(String, int): Chèn một đoạn văn bản vào vị trí xác định (tham số thứ hai) của vùng văn bản.

replaceText(String, int, int): Thay thế một đoạn văn bản trong vùng, đánh dấu bằng vị trí bắt đầu và vị trí kết thúc (tham số thứ hai và thứ ba), bằng một đoạn văn bản mới (tham số thứ nhất).

getRows()/getColums(): Trả về số dòng/cột của vùng văn bản.

Chương trình TextDemo minh hoạ việc đặt các đối tượng ô văn bản và vùng văn bản vào một frame.

package chuong9;

import java.awt.*;

public class TextDemo {

public static void main(String[] args) {

// Khai báo và khởi tạo frame có tiêu đề

Frame myFrame = new Frame(“Frame has some texts!”);

myFrame.setLayout(new GridLayout(0,2));

Chương 9: Lập trình giao diện

Biên soạn : Bùi Công Giao --------------------------------------- 172 --------------------------------------

myFrame.setSize(300,150); // Định kích cỡ frame

// Khai báo và khởi tạo textField

TextField myTextField = new TextField(“A text field!”);

myFrame.add(myTextField); // Gắn vào frame

// Khai báo và khởi tạo textArea

TextArea myTextArea = new TextArea(5, 40);

String str=“The TextField’s columns is: ”+myTextField.getColumns();

str += “\nThe TextArea’s size is: ” + myTextArea.getRows() + “*” + myTextArea.getColumns();

myTextArea.setText(str); // Thiết lập nội dung

myFrame.add(myTextArea); // Gắn vào frame

myFrame.setVisible(true); // Hiển thị frame

}

}

Hình 9.5 Kết quả demo Text

9.1.2.3 Button

Button là đối tượng nút lệnh, dùng để thực hiện một nhiệm vụ xác định. Các phương thức cơ bản của nút nhấn:

Button(String): Khởi tạo nút nhấn với tên xác định trên nút.

setLabel(String)/getLabel(): Truy nhập tên của nút nhấn.

Chương trình ButtonDemo minh hoạ việc tạo một nút nhấn trong một frame.

package chuong9;

import java.awt.*;

public class ButtonDemo {

public static void main(String[] args) {

// Khai báo và khởi tạo frame có tiêu đề

Frame myFrame = new Frame(“Frame has a button!”);

myFrame.setSize(300,150); // Định kích cỡ frame

Chương 9: Lập trình giao diện

Biên soạn : Bùi Công Giao --------------------------------------- 173 --------------------------------------

// Khai báo và khởi tạo button

Button myButton = new Button(“Click!”);

myFrame.add(myButton); // Gắn vào frame

myFrame.setVisible(true); // Hiển thị frame

}

}

Hình 9.6 Kết quả demo Button

Tuy nhiên, khi click vào nút nhấn này, không xảy ra điều già cả. Lí do là chúng ta chưa cài đặt việc xử lý sự kiện cho nút nhấn. Phần sau sẽ trình bày việc xử lý sự kiện cho các đối tượng.

9.1.3 Xử lý sự kiện

Khi mỗi đối tượng component được tạo nó có khả năng phát ra sự kiện khi người dùng giao tiếp với nó. Người lập trình phải xác định việc phản ứng như thế nào với một số sự kiện cần quan tâm sinh ra từ đối tượng này. Kỹ thuật này gọi là xử lý sự kiện. Hai bước để xử lý sự kiện :

1. Tạo một đối tượng listenner có thể nghe và phản ứng với các kiểu sự kiện đặc biệt từ đối tượng component. Ví dụ để phản ứng tới việc click một Button mà phát ra ActionEvents, ta phải tạo một ActionListener mà có khả năng lắng ghe và phản ứng tới ActionEvents.

2. Ta phải đăng ký đối tượng listenner với đối tượng component này.

Khi xử lý sự kiện, để xác định sự kiện phát sinh từ component nào, ta dùng phương thức getSource():

<Kiểu component> <Đối tượng sự kiện>.getSource();

Các kiểu sự kiện cơ bản

ActionEvent: xuất hiện khi một nút bị click vào, một danh sách (list) được chọn, một menu được chọn.

ComponentEvent: xuất hiện khi một component bị thay đổi kích cỡ, vị trí, trạng thái.

FocusEvent: xuất hiện khi một component có hoặc mất focus.

ItemEvent: xuất hiện khi một menu item được chọn hoặc bỏ, khi checkbox hoặc list item được click vào.

Chương 9: Lập trình giao diện

Biên soạn : Bùi Công Giao --------------------------------------- 174 --------------------------------------

WindowEvent: xuất hiện khi một của sổ được mở ra, kích hoạt, đóng lại hoặc thoát ra.

TextEvent: xuất hiện khi giá trị văn bản của các đối tượng TextField và TextArea bị thay đổi.

MouseEvent: xuất hiện khi chuột được click, di chuyển qua, nhấn xuống và thả ra.

KeyEvent: xuất hiện khi có đầu vào từ bàn phím.

Các giao tiếp được cài đặt để xử lý các sự kiện trên:

ActionListener.

ComponentListener

FocusListener

ItemListener

WindowListener

TextListener

MouseListener và MouseMotionListener

KeyListener

Cách tạo và đăng ký Listeners

Trước hết ta tìm hiểu cách khai báo lớp trong có tên như sau:

public class OuterClass {

// Khai báo thuộc tính của lớp ngoài, bỏ qua chi tiết

// Khai báo lớp trong trong thân của lớp ngoài.

class InnerClass {

// Khai báo thuộc tính và phương thức của lớp trong, bỏ qua chi tiết.

} // Kết thúc lớp trong

public void someMethodOfOuterClass() {

/* Ta có thể tạo một đối tượng kiểu InnerClass bên trong của bất kỳ phương thức OuterClass. Nếu InnerClass được tham chiếu từ bất kỳ nơi nào khác trong ứng dụng thì trình biên dịch sẽ báo lỗi "symbol not found" */

InnerClass x = new InnerClass();

}

}

Chú ý : khi biên dịch có hai tập tin được tạo ra: OuterClass.class và OuterClass$InnerClass.class.

Chương 9: Lập trình giao diện

Biên soạn : Bùi Công Giao --------------------------------------- 175 --------------------------------------

Trong việc tạo và đăng ký Listener ta dùng kỹ thuật lớp trong không tên (anonymous inner class). Lớp trong không tên về bản chất giống như lớp trong có tên nhưng ngoại trừ việc không có tên. Để tạo lớp lớp Listener điển hình như ActionListener ta phải thực hiện một giao diện ActionListener.

Giao diện này có một phương thức actionPerformed có tiêu đề như sau:

void actionPerformed(ActionEvent e)

Phương thức này sẽ phản ứng tới việc nghe một ActionEvent.

Ta tạo một lớp trong không tên dựa theo cú pháp lớp trong có tên:

Đầu tiên tạo một đối tượng kiểu ActionListener

ActionListener l = new ActionListener();

Tuy nhiên vì ActionListener là một giao diện nên không thể tạo đối tượng kiểu ActionListener trực tiếp. Cái mà ta cần là tạo một đối tượng của lớp X nào đó mà thực hiện giao diện ActionListener:

ActionListener l = new X();

với X được khai báo như sau:

class X implements ActionListener {

// Phải định nghĩa phương thức actionPerformed của ActionListener

public void actionPerformed(ActionEvent e) {

// chi tiết bỏ qua

}

}

Giả sử ta có thể chen toàn bộ khai báo của lớp X giữa ) và ; của khai báo

ActionListener l = … như sau:

ActionListener l = new ActionListener()

class X implements ActionListener { ... };

Nhận xét dấu ; sau } của câu lệnh phức tạp này. Bình thường không có dấu ; sau } nhưng đây là trường hợp đặc biệt để ; kết thúc câu lệnh đơn bắt đầu với

“ActionListener l = ...”.

Giả sử lớp X không có tên, chỉ có phần thân

ActionListener l = new ActionListener() {

public void actionPerformed(ActionEvent e) {

// bỏ qua chi tiết …

}

}; // Đừng quên dấu ;

Chương 9: Lập trình giao diện

Biên soạn : Bùi Công Giao --------------------------------------- 176 --------------------------------------

Lớp WindowListener

Khi chạy một ứng dụng GUI, ta click vào ở góc trên bên phải của cửa sổ, cửa sổ đóng lại nhưng chương trình vẫn còn chạy, và vẫn chiếm tài nguyên hệ thống. Để kết thúc chương trình hoàn toàn ta cần thực hiện:

Tạo một WindowListener và đăng ký cho cửa sổ chương trình.

Tạo lệnh cho phương thức windowClosing của WindowListener để đóng chương trình System.exit(0).

exit(int status) là phương thức tĩnh của lớp System để kết thúc tường minh máy ảo Java. Mã 0 trả về báo hiệu không có lỗi. Các chương trình chạy ở chế độ dòng lệnh đơn luồng thì không cần có lệnh System.exit(0).

Chú ý:

WindowListener có 7 phương thức, thông thường ta chỉ quan tâm đến windowClosing, nên thường sẽ có đoạn lệnh sau:

WindowListener w = new WindowListener() {

public void windowClosing(WindowEvent e) {

//….

System.exit(0);

}

// các phương thức sau được định nghĩa nhưng không làm gì

public void windowOpened(WindowEvent e) { }

public void windowClosed(WindowEvent e) { }

public void windowIconified(WindowEvent e) { }

public void windowDeiconified(WindowEvent e) { }

public void windowActivated(WindowEvent e) { }

public void windowDeactivated(WindowEvent e) { }

};

Để tránh rườm rà, Java đã có các lớp Adapter có tác dụng tương đương đoạn lệnh trên:

WindowListener w = new WindowAdapter() {

public void windowClosing(WindowEvent e) {

//….

System.exit(0);

}

};

Chương 9: Lập trình giao diện

Biên soạn : Bùi Công Giao --------------------------------------- 177 --------------------------------------

Tương tự ta có các đoạn lệnh tương đương:

MouseListener m = new MouseListener() {

public void mouseClicked (MouseEvent e) {

// bỏ qua chi tiết

}

// các phương thức sau được định nghĩa nhưng không làm gì

public void mouseEntered(MouseEvent e) { }

public void mouseExited(MouseEvent e) { }

public void mousePressed(MouseEvent e) { }

public void mouseReleased(MouseEvent e) { }

};

tương đương:

MouseListener m = new MouseAdapter() {

public void mouseClicked (MouseEvent e) {

// bỏ qua chi tiết

}

};

Nhận xét rằng mỗi giao diện XxxListener có một lớp XxxAdapter tương ứng chỉ trừ ActionListener không có ActionAdapter vì ActionListener chỉ có một phương thức là actionPerformed.

Chương trình EventDemo cài đặt một ứng dụng hoàn chỉnh, bao gồm:

Hai nhãn tiêu đề cho hai ô văn bản.

Hai ô văn bản, để nhập số liệu vào.

Bốn nút nhấn tương ứng để thực hiện các thao tác nhân, chia, cộng, trừ các số liệu nhập từ hai ô văn bản.

Thêm một nút nhấn, khi click vào sẽ thoát khỏi chương trình (chương trình kết thúc).

package chuong9;

import java.awt.*;

import java.awt.event.*;

public class EventDemo extends Frame implements ActionListener {

Label lbl1, lbl2, lblKq;

TextField txt1, txt2;

Chương 9: Lập trình giao diện

Biên soạn : Bùi Công Giao --------------------------------------- 178 --------------------------------------

Button btnCong, btnTru, btnNhan, btnChia, btnThoat;

public EventDemo() {

super(“Event demo!”);

this.setLayout(new GridLayout(6,2)); //Chế độ hiển thị 6 dòng, 2 cột

lbl1 = new Label(“So thu nhat:”); // Nhãn số thứ nhất

this.add(lbl1);

txt1 = new TextField(); // Ô văn bản số thứ nhất

this.add(txt1);

lbl2 = new Label(“So thu hai:”); // Nhãn số thứ hai

this.add(lbl2);

txt2 = new TextField(); // Ô văn bản số thứ hai

this.add(txt2);

lblKq = new Label(); // Nhãn kết quả

this.add(lblKq);

this.add(new Label());

// Các nút nhấn

btnCong = new Button(“Cong”); // Nút cộng

btnCong.addActionListener(this); // Bắt sự kiện

this.add(btnCong);

btnTru = new Button(“Tru”); // Nút trừ

btnTru.addActionListener(this);

this.add(btnTru);

btnNhan = new Button(“Nhan”); // Nút nhân

btnNhan.addActionListener(this);

this.add(btnNhan);

btnChia = new Button(“Chia”); // Nút chia

btnChia.addActionListener(this);

this.add(btnChia);

btnThoat = new Button(“Thoat”); // Nút thoát

btnThoat.addActionListener(this);

this.add(btnThoat);

// Phương thức bắt sự kiện click vào nút đóng frame

this.addWindowListener( new WindowAdapter() {

public void windowClosing(WindowEvent e) {

System.exit(0);

}

});

Chương 9: Lập trình giao diện

Biên soạn : Bùi Công Giao --------------------------------------- 179 --------------------------------------

}

// Phương thức xử lý sự kiện nút được nhấn

public void actionPerformed(ActionEvent ae) {

try {

float x = Float.parseFloat(txt1.getText());

float y = Float.parseFloat(txt2.getText());

float kq = 0;

if (ae.getSource() == btnCong) // Cộng hai số

kq = x + y;

if (ae.getSource() == btnTru) // Trừ hai số

kq = x - y;

if (ae.getSource() == btnNhan) // Nhan hai số

kq = x*y;

if ((ae.getSource() == btnChia) && (y != 0)) // Chia hai số

kq = x/y;

lblKq.setText(“Ket qua la: ” + String.valueOf(kq));

} catch (Exception ex) {

lblKq.setText(“Nhap sai”);

return;

}

if (ae.getSource() == btnThoat) // Thoát khỏi chương trình

System.exit(0);

// Thay đổi nội dung kết quả

}

public static void main(String[] args) {

// Khai báo đối tượng demo

EventDemo myFrame = new EventDemo();

myFrame.setSize(300,150); // Định kích cỡ frame

myFrame.setVisible(true); // Hiển thị frame

}

}

Hình 9.7 Kết quả demo sự kiện

Chương 9: Lập trình giao diện

Biên soạn : Bùi Công Giao --------------------------------------- 180 --------------------------------------

9.2 Giao diện với các đối tượng Multimedia

Nội dung phần này sẽ tập trung trình bày các đối tượng multimedia, bao gồm:

Ô đánh dấu (Checkbox) và Nút chọn (Radio button)

Lựa chọn (Choice)

Danh sách (List)

9.2.1 Ô đánh dấu và nút chọn

Checkbox và Radio button là các đối tượng dùng để đánh dấu, hoặc chọn thông tin. Sự khác biệt giữa chúng là checkbox cho phép chọn đồng thời nhiều ô cùng lúc, trong khi đó, trong mỗi nhóm radio button, chỉ cho phép chọn một thông tin.

Phương thức chung của hai lớp này:

setState(boolean)/getState(): truy nhập đến trạng thái của nút.

Các phương thức xây dựng Checkbox:

Checkbox(): khởi tạo một ô đánh dấu rỗng.

Checkbox(String): khởi tạo ô đánh dấu có nhãn xác định.

Checkbox(String, boolean): khởi tạo ô đánh dấu có nhãn, có trạng thái xác định.

Các phương thức xây dựng Radio button tương tự như Checkbox, ngoại trừ việc phải chỉ ra nhóm của các radio button:

Checkbox(String, boolean, CheckboxGroup);

Checkbox(String, CheckboxGroup, boolean);

Xử lý sự kiện thay đổi trạng thái nút chọn:

Kiểu sự kiện: ItemEvent

Cài đặt giao tiếp: ItemListener

Phương thức xủa lý: itemStateChange(ItemEvent)

Chương trình RadioDemo minh hoạ việc dùng một nhóm radio button gồm ba nút, tương ứng với ba màu (RED, BLUE, GREEN). Khi click vào nút nào, thì màu phần tử sẽ hiện lên.

package chuong9;

import java.awt.*;

import java.awt.event.*;

public class RadioDemo extends Frame implements ItemListener {

Checkbox cbxRed, cbxBlue, cbxGreen;

public RadioDemo() {

super(“Radio demo!”);

Chương 9: Lập trình giao diện

Biên soạn : Bùi Công Giao --------------------------------------- 181 --------------------------------------

// Chế độ hiển thị 3 dòng, 1 cột

this.setLayout(new GridLayout(3,1));

CheckboxGroup cbxg = new CheckboxGroup(); // Nhóm radio

cbxRed = new Checkbox(“Red”, cbxg, true); // Nút red

cbxRed.addItemListener(this); // Bắt sự kiện

this.add(cbxRed);

cbxBlue = new Checkbox(“Blue”, cbxg, false);// Nút blue

cbxBlue.addItemListener(this); // Bắt sự kiện

this.add(cbxBlue);

cbxGreen = new Checkbox(“Green”, cbxg, false);// Nút green

cbxGreen.addItemListener(this); // Bắt sự kiện

this.add(cbxGreen);

// Phương thức bắt sự kiện click vào nút đóng frame

this.addWindowListener( new WindowAdapter() {

public void windowClosing(WindowEvent e) {

System.exit(0);

}

});

}

// Phương thức xử lý sự kiện thay đổi trạng thái nút

public void itemStateChanged(ItemEvent ie) {

if(ie.getStateChange() == ItemEvent.SELECTED) {

String item = (String)ie.getItem();

if (item.equals("Red")) { // Đổi màu red

cbxRed.setBackground(Color.red);

cbxBlue.setBackground(Color.white);

cbxGreen.setBackground(Color.white);

}

if (item.equals("Blue")) { // Đổi màu blue

cbxRed.setBackground(Color.white);

cbxBlue.setBackground(Color.blue);

cbxGreen.setBackground(Color.white);

}

if (item.equals("Green")) { // Đổi màu green

cbxRed.setBackground(Color.white);

Chương 9: Lập trình giao diện

Biên soạn : Bùi Công Giao --------------------------------------- 182 --------------------------------------

cbxBlue.setBackground(Color.white);

cbxGreen.setBackground(Color.green);

}

this.repaint(); // Vẽ lại màu nền

}

}

public static void main(String[] args) {

// Khai báo đối tượng demo

RadioDemo myFrame = new RadioDemo();

myFrame.setSize(300,150); // Định kích cỡ frame

myFrame.setVisible(true); // Hiển thị frame

}

}

Hình 9.8 Kết quả demo Radio Button

9.2.2 Lựa chọn

Choice là đối tượng menu sổ xuống, hiển thị một danh sách các item và cho phép người dùng chọn một trong số các item đó (tương tự đối tượng dropdown list của window). Các phương thức cơ bản của lớp Choice:

Choice(): khởi tạo đối tượng choice.

addItem(String): thêm một item vào danh sách lựa chọn.

remove(int): xoá item ở vị trí thứ i trong danh sách (bắt đầu là vị trí 0).

removeAll(): xoá toàn bộ item trong danh sách chọn.

select(int)/select(String): chọn một item theo số thứ tự hoặc theo tên.

getSelectedIndex(): trả về chỉ số của item được chọn.

getSelectedItem(): trả về tên của item được chọn.

getItem(int): trả về tên của item tương ứng với số thứ tự đưa vào.

Xử lý sự kiện thay đổi trạng thái nút chọn:

Kiểu sự kiện: ItemEvent

Cài đặt giao tiếp: ItemListener

Phương thức xử lý: itemStateChange(ItemEvent)

Chương trình ChoiceDemo thay đổi màu nền theo phần tử trong lựa chọn được chọn.

Chương 9: Lập trình giao diện

Biên soạn : Bùi Công Giao --------------------------------------- 183 --------------------------------------

package chuong9;

import java.awt.*;

import java.awt.event.*;

public class ChoiceDemo extends Frame implements ItemListener {

Choice myChoice;

public ChoiceDemo() {

super(“Choice demo!”);

myChoice = new Choice(); // Khởi tạo

myChoice.addItem(“Red”); // Thêm item red

myChoice.addItem(“Blue”); // Thêm item blue

myChoice.addItem(“Green”); // Thêm item green

myChoice.addItemListener(this); // Bắt sự kiện

this.add(myChoice); // Gắn vào frame

// Phương thức bắt sự kiện click vào nút đóng frame

this.addWindowListener( new WindowAdapter() {

public void windowClosing(WindowEvent e) {

System.exit(0);

}

});

}

// Phương thức xử lý sự kiện thay đổi trạng thái item

public void itemStateChanged(ItemEvent ie) {

if (ie.getStateChange() == ItemEvent.SELECTED) {

String item = (String)ie.getItem();

if(item.equals(“Red”)) // Đổi màu red

this.setBackground(Color.red);

if(item.equals(“Blue”)) // Đổi màu blue

this.setBackground(Color.blue);

if(item.equals(“Green”)) // Đổi màu green

this.setBackground(Color.green);

this.repaint(); // Vẽ lại màu nền

}

}

public static void main(String[] args) {

Chương 9: Lập trình giao diện

Biên soạn : Bùi Công Giao --------------------------------------- 184 --------------------------------------

// Khai báo đối tượng demo

ChoiceDemo myFrame = new ChoiceDemo();

myFrame.setSize(300,150); // Định kích cỡ frame

myFrame.setVisible(true); // Hiển thị frame

}

}

Hình 9.9 Kết quả demo Choice Button

9.2.3 Danh sách

List là một danh sách hoạt động tương tự đối tượng choice. Tuy nhiên, list cho phép người dùng có thể chọn một hoặc nhiều item cùng một lúc. Các phương thức cơ bản của lớp List:

List(): khởi tạo một danh sách rỗng, mỗi lần chỉ được chọn một item.

List(int): tương tự, nhưng có qui định số dòng được nhìn thấy.

List(int, boolean): khởi tạo một danh sách có số dòng được nhìn thấy xác định, chế độ cho phép chọn một hay nhiều item xác định bởi tham số thứ hai.

add(String): thêm một item vào danh sách.

add(String, int): chèn một item vào vị trí xác định trong danh sách. Nếu chỉ số chèn vượt ra khỏi phạm vi danh sách, item sẽ được thêm vào cuối.

replaceItem(String, int): thay thế một item ở vị trí xác định (tham số thứ hai) trong danh sách bằng một item mới (tham số thứ nhất).

remove(int): xoá item ở vị trí xác định trong danh sách.

removeAll(): xoá toàn bộ item hiện có của danh sách.

getSeletedIndex(): trả về index của item được chọn (danh sách đơn chọn).

getSelectedItem(): trả về item được chọn (danh sách đơn chọn).

getSelectedIndexs(): trả về chỉ số các item được chọn (danh sách đa chọn).

getSelectedItems(): trả về các item được chọn (danh sách đa chọn).

Xử lý sự kiện khi thay đổi item được chọn:

Kiểu sự kiện: ItemEvent

Cài đặt giao tiếp: ItemListener

Phương thức xử lý: itemStateChange(ItemEvent);

Chương 9: Lập trình giao diện

Biên soạn : Bùi Công Giao --------------------------------------- 185 --------------------------------------

Chương trình ListDemo minh hoạ việc sử dụng đối tượng list với khả năng đa chọn. Mỗi khi thay đổi item được chọn, một thông báo các màu được chọn sẽ hiện ra.

package chuong9;

import java.awt.*;

import java.awt.event.*;

public class ListDemo extends Frame implements ItemListener {

List myList;

Label lbl;

public ListDemo() {

super(“List demo!”);

// Khởi tạo list đa chọn, chỉ nhìn được một dòng

myList = new List(1, true);

myList.setSize(300,150);

// Thêm các item là các loại màu sắc

myList.add(“White”);

myList.add(“Red”);

myList.add(“Orange”);

myList.add(“Green”);

myList.add(“Yellow”);

myList.add(“Blue”);

myList.add(“Black”);

myList.addItemListener(this); // Bắt sự kiện

this.setLayout(new GridLayout(2, 1));

this.add(myList); // Gắn vào frame

lbl = new Label(); // Khởi tạo nhãn

this.add(lbl); // Gắn vào frame

// Phương thức bắt sự kiện click vào nút đóng frame

this.addWindowListener(new WindowAdapter() {

public void windowClosing(WindowEvent e){

System.exit(0);

}

});

}

// Phương thức xử lý sự kiện thay đổi trạng thái item

public void itemStateChanged(ItemEvent ie) {

Chương 9: Lập trình giao diện

Biên soạn : Bùi Công Giao --------------------------------------- 186 --------------------------------------

if ((ie.getStateChange() == ItemEvent.SELECTED) ||

(ie.getStateChange() == ItemEvent.DESELECTED)) {

String kq = “Cac mau duoc chon:”;

String[] items = myList.getSelectedItems();

for(int i=0; i<items.length; i++)

kq += items[i] + “, ”;

lbl.setText(kq);

}

}

public static void main(String[] args) {

// Khai báo đối tượng demo

ListDemo myFrame = new ListDemo();

myFrame.setSize(300,150); // Định kích cỡ frame

myFrame.setVisible(true); // Hiển thị frame

}

}

Hình 9.10 Kết quả demo Listbox

9.3 Các kỹ thuật trình bày

Nội dung phần này sẽ tập trung trình bày các kỹ thuật trình bày các đối tượng giao diện trên frame theo các ý đồ thiết kế khác nhau bằng cách dùng bộ quản lý trình bày. Bao gồm các kỹ thuật sau:

Trình bày theo dòng (Flow layout)

Trình bày theo bảng (Grid layout)

Trình bày theo Border (Border layout)

Trình bày theo GridBag (GridBag layout)

Trình bày tự do (Null layout)

9.3.1 Trình bày Flow Layout

Cách trình bày Flow Layout sẽ xếp các đối tượng trên một hướng theo dòng. Nếu đối tượng mới thêm không đủ chỗ (chiều rộng) thì nó sẽ tự động thêm vào đầu dòng mới. Các phương thức:

Chương 9: Lập trình giao diện

Biên soạn : Bùi Công Giao --------------------------------------- 187 --------------------------------------

FlowLayout(): khởi tạo đối tượng trình bày.

FlowLayout(int): khởi tạo đối tượng trình bày với cách căn lề xác định.

FlowLayout(int, int, int): khởi tạo với ba tham số: Thứ nhất là cách căn lề, thứ hai là khoảng cách giữa hai dòng (chiều cao), thứ ba là khoảng cách giữa hai đối tượng (chiều ngang).

Tham số căn lề có thể nhận một trong ba giá trị:

FlowLayout.LEFT: căn lề trái, là giá trị mặc định.

FlowLayout.CENTER: căn lề giữa.

FlowLayout.RIGHT: căn lề phải.

Chương trình FlowLayoutDemo minh hoạ cách trình bày flow layout: Tạo ra một dãy 10 nút nhấn và gắn vào một frame theo kiểu flow layout.

package chuong9;

import java.awt.*;

public class FlowLayoutDemo {

public static void main(String[] args) {

// Khai báo và khởi tạo frame có tiêu đề

Frame myFrame = new Frame(“Frame has somes buttons!”);

myFrame.setSize(300,150); // Định kích cỡ frame

myFrame.setLayout(new FlowLayout( ));// Thiết lập cách trình bày

// Khai báo và khởi tạo button

for(int i=0; i<10; i++)

myFrame.add(new Button(“Click”+i));// Gắn vào frame

myFrame.setVisible(true); // Hiển thị frame

}

}

Hình 9.11 Kết quả demo Flow layout

9.3.2 Trình bày Grid Layout

Cách trình bày Grid Layout sẽ sắp xếp các đối tượng theo dạng bảng, được xác định số hàng và số cột. Các phương thức xây dựng:

Chương 9: Lập trình giao diện

Biên soạn : Bùi Công Giao --------------------------------------- 188 --------------------------------------

GridLayout(int, int): khởi tạo một đối tượng trình bày. Hai tham số đầu vào lần lượt là số hàng và số cột của grid trình bày.

GridLayout(int, int, int, int): khởi tạo một đối tượng trình bày, hai tham số đầu xác định số hàng và số cột trình bày. Hai tham số sau xác định khoảng cách giữa các dòng và các cột của bảng.

Lưu ý: khi số lượng đối tượng được chèn nhiều hơn vào frame, ta muốn chương trình tự tính số hàng, hoặc tự tính số cột hiển thị, thì ta để tham số tương ứng là 0.

Ví dụ:

setLayout(new GridLayout(3,0));

sẽ cố định số hàng trình bày là 3, số cột là tuỳ thuộc vào số đối tượng trong frame.

setLayout(new GridLayout(0,2));

sẽ cố định số cột là 2, số dòng là mềm dẻo theo số các đối tượng trong frame.

Chương trình GridLayoutDemo minh hoạ cách trình bày grid layout: tạo ra một dãy 10 nút nhấn và gắn vào một frame theo kiểu grid layout.

package chuong9;

import java.awt.*;

public class GridLayoutDemo {

public static void main(String[] args) {

// Khai báo và khởi tạo frame có tiêu đề

Frame myFrame = new Frame(“Frame has somes buttons!”);

myFrame.setSize(300,150); // Định kích cỡ frame

myFrame.setLayout(new GridLayout(0,2));// Thiết lập cách trình bày

// Khai báo và khởi tạo button

for(int i=0; i<10; i++)

myFrame.add(new Button(“Click”+i));// Gắn vào frame

myFrame.setVisible(true); // Hiển thị frame

}

}

Hình 9.12 Kết quả demo Grid layout

Chương 9: Lập trình giao diện

Biên soạn : Bùi Công Giao --------------------------------------- 189 --------------------------------------

9.3.3 Trình bày Border Layout

Cách hiển thị Border Layout sẽ chia frame thành 5 vùng cố định và tự động kéo dãn các vùng sao cho chiếm hết bề mặt của frame:

West: vùng phía tây, tức là phía lề bên trái.

East: vùng phía đông, tức là phía lề bên phải.

North: vùng phía bắc, tức là phía lề trên.

South: vùng phía nam, tức là phía lề dưới.

Center: vùng trung tâm, ở chính giữa frame.

Phương thức cơ bản của lớp BorderLayout:

BorderLayout(): Khởi tạo một đối tượng trình bày theo cách border.

Khi một frame được trình bày theo cách border, ta có thể dùng phương thức sau để gắn các đối tượng vào các vùng của frame:

<Đối tượng frame>.add(<Vùng border>, <Đối tượng component>);

Ví dụ:

myFrame.add(“Center”, new Button(“Click”));

sẽ gán vào vùng trung tâm của myFrame một nút nhấn có tên là “Click”.

Lưu ý:

Cách trình bày border luôn chia frame thành 5 vùng xác định.

Nếu gắn nhiều đối tượng vào cùng một vùng, chỉ có đối tượng gắn sau là nhìn thấy được.

Nếu muốn trong một vùng chứa được nhiều đối tượng, ta có thể gắn vào mỗi vùng một

Panel. Sau đó trong panel, ta chọn cách trình bày riêng cho panel và gắn các đối tượng vào panel.

Chương trình BorderLayoutDemo minh hoạ cách trình bày border: ta sẽ gắn vào năm vùng của frame năm nút nhấn khác nhau.

package chuong9;

import java.awt.*;

public class BorderLayoutDemo {

public static void main(String[] args) {

// Khai báo và khởi tạo frame có tiêu đề

Frame myFrame = new Frame(“Frame has somes buttons!”);

myFrame.setSize(300,150); // Định kích cỡ frame

myFrame.setLayout(new BorderLayout()); // Định cách trình bày

Chương 9: Lập trình giao diện

Biên soạn : Bùi Công Giao --------------------------------------- 190 --------------------------------------

// Khai báo và khởi tạo button

myFrame.add(“West”, new Button(“West”)); // Gắn vào vùng west

myFrame.add(“East”, new Button(“East”)); // Gắn vào vùng east

myFrame.add(“North”, new Button(“North”)); // Gắn vào vùng north

myFrame.add(“South”, new Button(“South”)); // Gắn vào vùng south

// Gắn vào vùng center

myFrame.add(“Center”, new Button(“Center”));

myFrame.setVisible(true); // Hiển thị frame

}

}

Hình 9.13 Kết quả demo Border layout

9.3.4 Trình bày GridBag Layout

Cách trình bày GridBag Layout cũng trình bày các đối tượng tương tự như Grid Layout: các đối tượng sẽ được định vị theo vị trí các ô của một khung lưới. Tuy nhiên, GridBag cho phép ta định kích thước của đối tượng sẽ chiếm bao nhiêu ô và sẽ được đặt ở vị trí nào trong khung lưới. Các phương thức cơ bản:

GridBagLayout(): khởi tạo một đối tượng trình bày theo cách gridbag.

setConstraints(Component, GridBagConstraints): Đặt vị trí và kích thước của đối tượng component theo các ràng buộc trong gridbagConstraints.

GridBagConstraints

Đây là lớp chứa các ràng buộc cho các đối tượng được trình bày theo cách GridBag. Các phương thức và thuộc tính cơ bản của lớp GridBagConstraints:

GridBagConstraints(): khởi tạo một đối tượng ràng buộc của GridBag.

gridx/gridy: vị trí của cell mà ta muốn đặt đối tượng vào (theo chiều X và chiều Y).

gridwidth/gridheight: kích thước (vùng trình bày) của đối tượng (Theo chiều rộng và chiều cao).

fill: Xác định cách đặt đối tượng, theo 4 cách:

- GridBagConstraints.NONE: đối tượng không thay đổi kích thước theo các ô nó chiếm.

- GridBagConstraints.VERTICAL: đối tượng có chiều cao kín vùng nó chiếm

Chương 9: Lập trình giao diện

Biên soạn : Bùi Công Giao --------------------------------------- 191 --------------------------------------

- GridBagConstraints.HORIZONAL: đối tượng có chiều rộng kín vùng nó chiếm

- GridBagConstraints.BOTH: đối tượng có chiều cao và chiều rộng phủ kín vùng nó chiếm.

ipadx/ipady: định đơn vị tăng giảm kích thước của đối tượng khi khung chứa bị thay đổi kích thước (theo chiều X và chiều Y).

insets: xác định khoảng cách giữa các cell theo bốn hướng: trên, dưới, trái, phải.

anchor: xác định vị trí neo đối tượng khi kích thước khung chứa thay đổi.

Bao gồm: NORTH, NORTHEAST, NORTHWEST, EAST, SOUTH, SOUTHEAST, SOUTHWEST.

weightx/weighty: định khoảng cách lớn ra tương đối giữa các đối tượng với nhau.

Chương trình GridBagLayoutDemo minh hoạ cách trình bày GridBag: Tạo ra ba nút nhấn trong frame, mỗi nút có một số ràng buộc khác nhau về kích thước và vị trí.

package chuong9;

import java.awt.*;

public class GridBagLayoutDemo {

public static void main(String[] args) {

// Khai báo và khởi tạo frame có tiêu đề

Frame myFrame = new Frame(“Frame has somes buttons!”);

myFrame.setSize(300,150); // Định kích cỡ frame

GridBagLayout layout = new GridBagLayout();

myFrame.setLayout(layout); // Định cách trình bày

// Khai báo đối tượng ràng buộc

GridBagConstraints cts = new GridBagConstraints();

cts.fill = GridBagConstraints.BOTH;

// Button1: vị trí (1,1), kích thước (1,1)

Button btn1 = new Button(“Click1”);

cts.gridx = 1;

cts.gridy = 1;

cts.gridheight = 1;

cts.gridwidth = 1;

layout.setConstraints(btn1, cts); // Định ràng buộc

myFrame.add(btn1); // Gắn vào frame

// Button2: vị trí (2,2), kích thước (1,1)

Chương 9: Lập trình giao diện

Biên soạn : Bùi Công Giao --------------------------------------- 192 --------------------------------------

Button btn2 = new Button(“Click2”);

cts.gridx = 2;

cts.gridy = 2;

cts.gridheight = 1;

cts.gridwidth = 1;

layout.setConstraints(btn2, cts); // Định ràng buộc

myFrame.add(btn2); // Gắn vào frame

// Button3: vị trí (3,3), kích thước (1,1)

Button btn3 = new Button(“Click3”);

cts.gridx = 3;

cts.gridy = 3;

cts.gridheight = 1;

cts.gridwidth = 1;

layout.setConstraints(btn3, cts); // Định ràng buộc

myFrame.add(btn3); // Gắn vào frame

myFrame.setVisible(true); // Hiển thị frame

}

}

Hình 9.14 Kết quả demo Gridbag layout

9.3.5 Trình bày Null Layout

Cách trình bày Null Layout sẽ trình bày các đối tượng không theo một quy tắc nào. Tất cả đều do người dùng tự định vị và thiết lập kích thước cho mỗi đối tượng.

Định vị đối tượng bằng phương thức setLocation():

<Đối tượng>.setLocation(Point);

Định kích thước đối tượng bằng phương thức setSize():

<Đối tượng>.setSize(int, int);

Ngoài ra, có thể vừa định vị, vừa định kích thước cho đối tượng thông qua phương thức: <Đối tượng>.setBounds(int, int, int, int);

Trong đó, hai tham số dầu định vị đối tượng, hai tham số sau định kích thước đối tượng.

Chương 9: Lập trình giao diện

Biên soạn : Bùi Công Giao --------------------------------------- 193 --------------------------------------

Chương trình NullLayoutDemo minh hoạ cách trình bày tự do Null layout: tạo ra hai nút nhấn và gắn vào frame theo hai cách khác nhau.

package chuong9;

import java.awt.*;

public class NullLayoutDemo {

public static void main(String[] args) {

// Khai báo và khởi tạo frame có tiêu đề

Frame myFrame = new Frame(“Frame has somes buttons!”);

myFrame.setSize(300,150); // Định kích cỡ frame

myFrame.setLayout(null); // Định cách trình bày

// Button1: vị trí (10,30), kích thước (100,40)

Button btn1 = new Button(“Click1”);

btn1.setSize(100, 40);

btn1.setLocation(new Point(10, 30));

myFrame.add(btn1); // Gắn vào frame

// Button2: vị trí (70,120), kích thước (50,20)

Button btn2 = new Button(“Click2”);

btn2.setBounds(70, 120, 50, 20);

myFrame.add(btn2); // Gắn vào frame

myFrame.setVisible(true); // Hiển thị frame

}

}

Hình 9.15 Kết quả demo Null layout

9.4 Applet

Applet là một chương trình Java có thể chạy trong các trình duyệt web có hỗ trợ Java. Tất cả các applet đều là các lớp con của lớp Applet. Để tạo applet, ta cần import hai gói sau:

import java.applet.*;

import java.awt.*;

Chương 9: Lập trình giao diện

Biên soạn : Bùi Công Giao --------------------------------------- 194 --------------------------------------

9.4.1 Cấu trúc của một Applet

Cấu trúc tổng quát của một applet như sau:

public class <Tên lớp applet> extends Applet {

… // Các thuộc tính

public void init(){…}

public void start(){…}

public void stop(){…}

public void destroy(){…}

… // Các phương thức khác

}

Các phương thức cơ bản của một applet:

init(): Khởi tạo các tham số, nếu có, của applet.

start(): Applet bắt đầu hoạt động.

stop(): Chấm dứt hoạt động của applet.

destroy(): Thực hiện các thao tác dọn dẹp trước khi thoát khỏi applet.

Lưu ý:

Không phải tất cả các applet đều phải cài đặt đầy đủ 4 phương thức cơ bản trên.

Applet còn có thể cài đặt một số phương thức tuỳ chọn (không bắt buộc) sau:

paint(Graphics): phương thức vẽ các đối tượng giao diện bên trong applet. Các thao tác vẽ này được thực hiện bởi đối tượng đồ hoạ Graphics (là tham số đầu vào).

repaint(): dùng để vẽ lại các đối tượng trong applet. Phương thức này sẽ tự động gọi phương thức update().

update(Graphics): phương thức này được gọi sau khi thực hiện phương thức paint nhằm tăng hiệu quả vẽ. Phương này sẽ tự động gọi phương thức paint().

Chương trình SimpleApplet cài đặt một applet đơn giản, mỗi phương thức sẽ in ra thông báo rằng applet đang ở trong thời điểm tương ứng.

package chuong9;

import java.awt.*;

import java.applet.*;

public class SimpleApplet extends Applet {

private StringBuffer buffer; // Chuỗi thông báo

public void init() { // Khởi tạo

buffer = new StringBuffer();

addBuffer(“initializing…”);

Chương 9: Lập trình giao diện

Biên soạn : Bùi Công Giao --------------------------------------- 195 --------------------------------------

}

public void start() { // Kích hoạt

addBuffer(“starting…”);

}

public void stop() { // Dừng

addBuffer(“stopping…”);

}

public void destroy() { // Thoát

addBuffer(“unloading…”);

}

private void addBuffer(String newBuffer) {

buffer.append(newBuffer); // Thêm thông báo

repaint();

}

public void paint(Graphics g) {

g.drawString(buffer.toString(), 5, 15); // Hiện thông báo

}

}

Hình 9.16 Kết quả demo Applet

9.4.2 Sử dụng Applet

Applet không thể chạy như một ứng dụng Java độc lập (nó không có hàm main), mà nó chỉ chạy được khi được nhúng trong một trang HTML (đuôi .htm, .html) và chạy bằng một trình duyệt web thông thường.

Các bước xây dựng và sử dụng một applet bao gồm:

Cài đặt chương trình có dạng một applet như mục trên

Biên dịch mã nguồn thành lớp .class

Nhúng mã .class của applet vào trang html.

Chương 9: Lập trình giao diện

Biên soạn : Bùi Công Giao --------------------------------------- 196 --------------------------------------

Để nhúng một applet vào một trang html, ta dùng thẻ (tag) <Applet> như sau:

<APPLET CODE = “Tên_file_applet.class” WIDTH = “Chiều_rộng” HEIGHT = “Chiều_cao”>

</APPLET>

Trong đó:

Tên applet là tên file mã nguồn đã biên dịch thành file chạy có đuôi .class của Java. Chiều rộng và chiều cao là kích thước của vùng trên trang html mà applet sẽ được đặt vào. Ví dụ, trong trang myHtml.htm có chứa nội dung như sau:

<HTML>

<HEAD>

<TITLE> A simple applet </TITLE>

</HEAD>

<BODY>

This is the output of applet:

<APPLET CODE = “SimpleApplet.class” WIDTH=200 HEIGHT=200>

</APPLET>

</BODY>

</HTML>

sẽ nhúng applet đã được định nghĩa trong chương trình SimpleApplet vào một vùng có kích thước 200*20 trong trang myHtml. Bây giờ, ta có thể kiểm nghiệm chương trình applet của mình bằng cách mở trang myHtml trên các trình duyệt thông thường.

Chương trình AppletDemo cài đặt một applet có chức năng tương tự như chương trình EventDemo, thực hiện các thao tác tính toán cơ bản trên hai số. Ngoại trừ việc đây là một applet, nên có thể chạy trên một trang HTML.

package chuong9;

import java.awt.*;

import java.awt.event.*;

import java.applet.*;

public class AppletDemo extends Applet implements ActionListener {

Label lbl1, lbl2, lblKq;

TextField txt1, txt2;

Button btnCong, btnTru, btnNhan, btnChia, btnThoat;

public void init() {

this.setLayout(new GridLayout(6,2)); //Chế độ hiển thị 6 dòng, 2 cột

lbl1 = new Label(“So thu nhat:”); // Nhãn số thứ nhất

Chương 9: Lập trình giao diện

Biên soạn : Bùi Công Giao --------------------------------------- 197 --------------------------------------

this.add(lbl1);

txt1 = new TextField(); // Ô văn bản số thứ nhất

this.add(txt1);

lbl2 = new Label(“So thu hai:”); // Nhãn số thứ hai

this.add(lbl2);

txt2 = new TextField(); // Ô văn bản số thứ hai

this.add(txt2);

lblKq = new Label(); // Nhãn kết quả

this.add(lblKq);

this.add(new Label());

// Các nút nhấn

btnCong = new Button(“Cong”); // Nút cộng

btnCong.addActionListener(this); // Bắt sự kiện

this.add(btnCong);

btnTru = new Button(“Tru”); // Nút trừ

btnTru.addActionListener(this);

this.add(btnTru);

btnNhan = new Button(“Nhan”); // Nút nhân

btnNhan.addActionListener(this);

this.add(btnNhan);

btnChia = new Button(“Chia”); // Nút chia

btnChia.addActionListener(this);

this.add(btnChia);

btnThoat = new Button(“Thoat”); // Nút thoát

btnThoat.addActionListener(this);

this.add(btnThoat);

}

// Phương thức xử lý sự kiện nút được nhấn

public void actionPerformed(ActionEvent ae) {

float x = Float.parseFloat(txt1.getText());

float y = Float.parseFloat(txt2.getText());

float kq = 0;

if(ae.getSource() == btnCong) // Cộng hai số

kq = x + y;

if(ae.getSource() == btnTru) // Trừ hai số

Chương 9: Lập trình giao diện

Biên soạn : Bùi Công Giao --------------------------------------- 198 --------------------------------------

kq = x - y;

if(ae.getSource() == btnNhan) // Nhân hai số

kq = x*y;

if (ae.getSource() == btnChia) // Chia hai số

if (y != 0)

kq = x / y;

else {

lblKq.setText("So chia bang 0");

return;

}

if(ae.getSource() == btnThoat) // Thoát khỏi chương trình

System.exit(0);

// Thay đổi nội dung kết quả

lblKq.setText(“Ket qua la: ” + String.valueOf(kq));

repaint(); // Vẽ lại các đối tượng

}

}

Hình 9.17 Kết quả demo Applet bảng tính

Lưu ý: sự khác nhau giữa một application và một applet:

Application là một ứng dụng Java độc lập, nó có thể chạy độc lập trên máy ảo Java. Trong khi đó, applet chỉ chạy được khi nhúng trong một trang html, chạy nhờ vào các trình duyệt web có hỗ trợ Java.

Application chạy dựa vào hàm main(). Trong khi đó, applet không có hàm main().

Để hiển thị các thông báo, application dùng System.out.println(). Trong khi đó, applet dùng phương thức drawString() của lớp Graphics.

Chương 9: Lập trình giao diện

Biên soạn : Bùi Công Giao --------------------------------------- 199 --------------------------------------

9.4.3 Truyền tham số cho Applet

Trong nhiều trường hợp, applet phải phụ thuộc vào các tham số ở bên ngoài truyền vào. Khi đó ta có thể dùng thẻ PARAM của html để truyền tham số cho applet. Cú pháp:

<APPLET CODE = “Tên_file_applet.class” WIDTH = “Chiều_rộng” HEIGHT = “Chiều_cao”>

<PARAM NAME=”Tên_biến” VALUE=”Giá_trị”>

… // Các tham số khác

</APPLET>

Khi đó, trong mã nguồn của applet, ta dùng phương thức getParameter() để đọc giá trị các tham số được truyền vào:

getParameter(Tên_biến);

Chương trình ParamDemo minh hoạ việc truyền tham số cho một applet: Applet mô phỏng giao diện tìm kiếm: một nhãn hướng dẫn, một ô văn bản và một nút nhấn. Tuỳ vào ngôn ngữ mà nhãn và nút nhấn có giá trị text khác nhau. Biến ngôn ngữ là một tham số truyền từ trình duyệt vào. (Đây là mô phỏng giao diện, cơ chế tìm kiếm không hoạt động).

package chuong9;

import java.awt.*;

import java.applet.*;

public class ParamDemo extends Applet {

Label lbl;

TextField txt;

Button btn;

public void init() {

this.setLayout(new GridLayout(2,2)); //Chế độ hiển thị 2 dòng, 2 cột

String langue = getParameter(“langue”);// Loại ngôn ngữ

if (langue.equals(“vn”)) { // Tiếng Việt

lbl = new Label(“Nhap tu khoa”); // Nhãn số thứ nhất

btn = new Button(“Tim kiem”); // Nút cộng

} else if (langue.equals(“fr”)) { // Tiếng Pháp

lbl = new Label(“Tapez des mots”);

btn = new Button(“Chercher”);

} else { // Tiếng Anh, mặc định

lbl = new Label(“Enter keys”);

Chương 9: Lập trình giao diện

Biên soạn : Bùi Công Giao --------------------------------------- 200 --------------------------------------

btn = new Button(“Search”);

}

txt = new TextField();

this.add(lbl);

this.add(txt);

this.add(btn);

}

}

Khi đó, applet phải được nhúng vào trang html với đoạn mã như sau:

<APPLET CODE = “ParamDemo.class” WIDTH = 400 HEIGHT = 200>

<PARAM NAME=”langue” VALUE=”vn”>

</APPLET> 

Hình 9.18 Kết quả demo Applet có tham số

Ta có thể thay thế value của param bằng các giá trị “vn”, “fr” và “en” để thấy được các chế độ ngôn ngữ khác nhau được hiển thị trong applet.

9.5 SWING

Swing là thư viện lập trình mở rộng của Java. Nó mở rộng các đối tượng giao diện đồ hoạ cơ bản của Java. Swing còn được gọi là thư viện JFC. Khi muốn sử dụng các đối tượng đồ hoạ của thư viện này, ta phải khai báo chỉ thị:

import javax.swing.*;

Chương 9: Lập trình giao diện

Biên soạn : Bùi Công Giao --------------------------------------- 201 --------------------------------------

9.5.1 Mở rộng các đối tượng component

JFC mở rộng các đối tượng cơ bản của Java thành các lớp tương ứng, thêm ký tự J ở đầu mỗi tên lớp:

Button → JButton

Label → JLabel

TextField → JTextField

TextArea → JTextArea

Checkbox → JCheckbox

List → JList

Các lớp mở rộng này có đầy đủ các phương thức của các đối tượng lớp cơ bản của thư viện java.awt. Ngoài ra, chúng được bổ sung một số phương thức tạo hiệu ứng giao diện.

JButton

Chương trình JButtonDemo minh hoạ việc sử dụng đối tượng JButton. Đối tượng JButton được mở rộng thêm một số tính năng sau:

JButton(String, Icon): Khởi tạo một nút nhấn với một tên nhãn và một ảnh nền. Ảnh nền có kiểu icon (tham số thứ hai).

setMnemonic(char): Định phím tắt cho nút lệnh. Khi người dùng nhấn “Ctrl+phím tắt” thì nút lệnh cũng thực thi tương tự như kkhi ta click chuột vào nút lệnh.

setBorder(new MatteBorder(int, int, int, int, Icon)): Thiết lập khung nền cho nút với các tham số: Khoảng cách từ chữ đến biên (độ rộng biên) theo các chiều trên dưới, trái phải, cuối cùng là ảnh nền cho nút.

setBorder(new LineBorder(int)): Thiết lập viền cho nút dạng hình chữ nhật, tham số xác định màu cho viền của nút. Ngoài ra, tham số của phương thức này còn có thể là các lớp SoftBevelBorder, EtchedBorder và TitleBorder.

setToolTipText(String): Thiết lập dòng tooltip cho đối tượng. Dòng này sẽ hiển ra khi ta di chuột lên đối tượng trên cửa sổ.

package chuong9;

import javax.swing.*;

import javax.swing.border.MatteBorder;

public class JButtonDemo extends JFrame {

public static void main(String[] args) {

// Khai báo và khởi tạo frame có tiêu đề

JFrame myFrame = new JFrame(“Frame has somes buttons!”);

myFrame.setSize(300,150); // Định kích cỡ frame

Chương 9: Lập trình giao diện

Biên soạn : Bùi Công Giao --------------------------------------- 202 --------------------------------------

// Giả sử ta có file ảnh myImage trong cùng thư mục

ImageIcon myIcon = new ImageIcon(“myImage.gif”);

// Button1: có nền là ảnh

JButton btn1 = new JButton(“Back Image”, myIcon);

// Gán tooltip cho nút

btn1.setToolTipText(“Button’s background is an image”);

myFrame.getContentPane().add(btn1); // Gắn vào frame

// Button2: có biên là ảnh

JButton btn2 = new JButton(“Border Image”);

// Gán tooltip cho nút

btn2.setToolTipText(“Button’s border is an image”);

btn2.setBorder(new MatteBorder(10,10,10,10, myIcon));

myFrame.getContentPane().add(btn2); // Gắn vào frame

myFrame.setVisible(true); // Hiển thị frame

}

}

Hình 9.19 Kết quả demo JButton

Trong chương trình này, có dòng lệnh gắn các đối tượng vào frame bằng cách getContentPane(). Đây là phương thức mở rộng cho các đối tượng khung chứa container. Sự mở rộng này sẽ được trình bày chi tiết trong phần tiếp theo.

9.5.2 Mở rộng các đối tượng container

Tương tự như các đối tượng component, các đối tượng container cũng được mở rộng trong JFC thành các lớp có tên tương ứng và thêm kí tự J ở đầu:

Frame →JFrame

Panel → JPanel

Dialog → JDialog

Menu → JMenu

Chương trình JFrameDemo minh hoạ việc sử dụng các đối tượng mở rộng của khung chứa Frame thành JFrame. Khung chứa JFrame có nhiều tầng trình diễn khác nhau,

Chương 9: Lập trình giao diện

Biên soạn : Bùi Công Giao --------------------------------------- 203 --------------------------------------

các tầng là trong suốt và chồng khít lên nhau, khiến cho ta vẫn có cảm giác các đối tượng được trình bày trên cùng một mặt phẳng như khung chứa Frame của thư viện chuẩn AWT.

Một số tầng hay sử dụng của lớp JFrame (theo thứ tự từ trong ra ngoài):

ContentPane: là tầng thường dùng nhất, tầng này dùng để chứa các đối tượng component cơ bản như button, label, text, list…

MenubarPane: tầng dành để chứa các loại menu của frame như Menubar, PopupMenu.

GlassPane: tầng ngoài cùng, thường dùng để chứa các tooltip của các đối tượng trong tầng Content. Khi ta set tooltipText cho một đối tượng, tooltip đó sẽ tự động được add vào tầng Glass.

Để truy nhập vào một tầng bất kỳ, ta dùng phương thức có tên:

get + <Tên của tầng>();

Ví dụ:

JFrame myFrame = new JFrame(“My JFrame”);

myFrame.getContentPane().add(“Center”, new JButton(“Test”));

sẽ gắn một nút nhấn có nhãn Test vào tầng Content của khung chứa myFrame.

Chương trình JFrameDemo minh hoạ việc gắn các đối tượng vào các tầng khác nhau:

Gắn một nút nhấn vào tầng ContentPane.

Gắn một thanh Menubar có chứa một menu File vào tầng MenubarPane.

package chuong9;

import javax.swing.*;

import java.awt.event.*;

public class JFrameDemo extends JFrame implements ActionListener {

private JMenuBar myBar;

private JMenu myMenu;

public JFrameDemo() {

super("JFrame demo");

JButton btn = new JButton("My Button");

this.getContentPane().add("Center", btn); //Gắn nút nhấn vào tầng ContentPane

myBar = new JMenuBar();

myMenu = new JMenu("File");

Chương 9: Lập trình giao diện

Biên soạn : Bùi Công Giao --------------------------------------- 204 --------------------------------------

myBar.add(myMenu); // Gắn menubar vào tầng MenubarPane

this.setJMenuBar(myBar);

setItemMenu("Open", "open");

setItemMenu("New", "new");

myMenu.add(new JSeparator());

setItemMenu("Save", "save");

setItemMenu("Save as", "saveas");

myMenu.add(new JSeparator());

myMenu.add(new JSeparator());

setItemMenu("Exit", "exit");

// Phương thức bắt sự kiện click vào nút đóng frame

this.addWindowListener(new WindowAdapter() {

public void windowClosing(WindowEvent e) {

System.exit(0);

}

});

}

private void setItemMenu(String label, String command) {

JMenuItem item = new JMenuItem(label);

item.addActionListener(this);

item.setActionCommand(command);

myMenu.add(item);

}

// Phương thức xử lí sự kiện

public void actionPerformed(ActionEvent ae) {

JMenuItem item = (JMenuItem) ae.getSource();

String cmd = item.getActionCommand();

if (cmd.equals("exit")) {

System.exit(0);

} else {

JOptionPane.showMessageDialog(this, cmd + " was selected.");

Chương 9: Lập trình giao diện

Biên soạn : Bùi Công Giao --------------------------------------- 205 --------------------------------------

}

}

public static void main(String[] args) {

JFrameDemo myFrame = new JFrameDemo();

myFrame.setSize(300, 150); // Định kích cỡ frame

myFrame.setVisible(true); // Hiển thị frame

}

}

Java cung cấp trình đơn trong thư viện AWT và các tính năng nâng cao trong SWING. Sau đây ta xem xét trình đơn một cách chi tiết trong SWING.

Hình 9.20 Kết quả demo gắn các đối tượng vào các tầng

JMenu

Trình đơn được dùng trên các thanh công cụ của các cửa sổ hoặc là popup menu xuất hiện khi ta click chuột phải vào một đối tượng. Các thành phần căn bản của trình đơn: 

Menubar: thanh trình đơn

Menu: trình đơn đổ xuống

PopupMenu: trình đơn xuất hiện khi click chuột phải.

MenuItem: các mục chọn của trình đơn.

JMenubar

JMenubar là thanh công cụ dùng để chứa các trình đơn JMenu. Các phương thức cơ bản của lớp JMenubar:

JMenubar(): khởi tạo một thanh công cụ cho trình đơn

add(JMenu): thêm một trình đơn Menu lên menubar.

Để đặt một menubar lên một JFrame, ta gọi phương thức của JFrame:

<Đối tượng JFrame>.setJMenuBar(<Đối tượng menubar>);

Chương 9: Lập trình giao diện

Biên soạn : Bùi Công Giao --------------------------------------- 206 --------------------------------------

JMenu

Trình đơn JMenu là đối tượng sẽ sổ xuổng khi click chuột lên đối tượng hiển thị của JMenu. JMenu còn được gọi là menu con của một thanh trình đơn. Các phương thức cơ bản của lớp JMenu:

JMenu(String): khởi tạo một JMenu, với tên xác định.

add(MenuItem): thêm một MenuItem vào JMenu

add(JMenu): thêm một menu con vào JMenu đã có, dùng khi muốn tạo menu có nhiều mức.

addSeparator(): thêm một đường phân vùng vào menu (để nhóm các item với nhau).

Xử lý sự kiện của lớp JMenu:

Kiểu sự kiện: ActionEvent

Giao tiếp cài đặt: ActionListener

Phương thức xử lý: actionPerformed(ActionEvent);

MenuItem

JMenuItem là đối tượng item trong các trình đơn JMenu. Mỗi item, khi được click vào sẽ có tác dụng như một nút lệnh. Các phương thức cơ bản của lớp MenuItem:

MenuItem(String): khởi tạo một item.

CheckboxJMenuItem(String): khởi tạo một item có mục chọn như checkbox.

getState(): trả về trạng thái của item. Chỉ dùng cho item có mục chọn.

enable(): cho phép item hoạt động (là chế độ mặc định).

disable(): không cho phép item hoạt động (làm mờ item đi).

Xử lý sự kiện của lớp MenuItem:

Kiểu sự kiện: ActionEvent

Giao tiếp cài đặt: ActionListener

Phương thức xử lý: actionPerformed(ActionEvent);

Chương trình JMenuDemo minh hoạ việc sử dụng các loại menu:

Tạo một JFrame

Tạo một JPanel và gắn vào JFrame

Tạo một ActionListener cho JPanel

Tạo một JMenubar của JFrame

Trên JMenubar, tạo một JMenu “File” và “Edit”. Khi click vào sẽ sổ xuống một menu với các item: New, Open, Save, Save As, Exit, và Cut, Copy, Paste tương ứng

Tạo một JMenuPopup trên JPanel

Chương 9: Lập trình giao diện

Biên soạn : Bùi Công Giao --------------------------------------- 207 --------------------------------------

Khi click chuột phải vào JPanel, sẽ sổ ra một JMenuPopup gồm các item: Open và Colors.

Khi chọn Colors một JMenu chứa một nhóm Button xuất hiện. Khi chọn màu thì JPanel sẽ đổi màu

Khi click chuột vào item nào trên các menu, một thông báo sẽ hiển thị tên của item vừa được chọn.

Chương trình kết thúc khi click vào item Exit hoặc đóng cửa sổ.

package chuong9;

import java.awt.*;

import java.awt.event.*;

import javax.swing.*;

public class JMenuDemo {

public static void main(String[] args) {

JFrame frame = new JFrame("Menu Demo");

final JPanel panel = new JPanel();

frame.add(panel);

ActionListener listener = new MenuItemActionListener(panel);

JMenu file = new JMenu("File");

file.setMnemonic('F');

file.add(menuItem("New", listener, "new", 'N', KeyEvent.VK_N));

file.add(menuItem("Open...", listener, "open", 'O', KeyEvent.VK_O));

file.add(menuItem("Save", listener, "save", 'S', KeyEvent.VK_S));

file.add(menuItem("Save As...", listener, "saveas", 'A', KeyEvent.VK_A));

file.addSeparator();

file.add(menuItem("Exit", listener, "exit", 'E', KeyEvent.VK_E));

JMenu edit = new JMenu("Edit");

edit.setMnemonic('E');

edit.add(menuItem("Cut", listener, "cut", 0, KeyEvent.VK_X));

edit.add(menuItem("Copy", listener, "copy", 'C', KeyEvent.VK_C));

edit.add(menuItem("Paste", listener, "paste", 0, KeyEvent.VK_V));

JMenuBar menubar = new JMenuBar();

menubar.add(file);

Chương 9: Lập trình giao diện

Biên soạn : Bùi Công Giao --------------------------------------- 208 --------------------------------------

menubar.add(edit);

frame.setJMenuBar(menubar);

// tạo poppup

final JPopupMenu popup = new JPopupMenu();

popup.add(menuItem("Open...", listener, "open", 0, 0));

popup.addSeparator(); // Thêm một đường phân cách giữa các item

JMenu colors = new JMenu("Colors"); // Tạo một submenu

popup.add(colors); // Thêm nó tới popup menu

// tạo nhóm radio

ButtonGroup colorgroup = new ButtonGroup();

colors.add(radioItem("Red", listener, "color(red)", colorgroup));

colors.add(radioItem("Green", listener, "color(green)", colorgroup));

colors.add(radioItem("Blue", listener, "color(blue)", colorgroup));

// Kết thúc ứng dụng khi click đóng cửa sổ

frame.addWindowListener(new WindowAdapter() {

public void windowClosing(WindowEvent e) {

System.exit(0);

}

});

panel.addMouseListener(new MouseAdapter() {

public void mousePressed(MouseEvent e) {

// Nếu click chuột phải

if (e.getButton()==3)

popup.show((Component)e.getSource(), e.getX(), e.getY());

}

});

frame.setSize(450, 300);

frame.setVisible(true);

}

public static JMenuItem menuItem(String label, ActionListener listener,

String command, int mnemonic, int acceleratorKey) {

JMenuItem item = new JMenuItem(label);

item.addActionListener(listener);

Chương 9: Lập trình giao diện

Biên soạn : Bùi Công Giao --------------------------------------- 209 --------------------------------------

item.setActionCommand(command);

if (mnemonic != 0)

item.setMnemonic((char) mnemonic);

if (acceleratorKey != 0)

item.setAccelerator(KeyStroke.getKeyStroke(acceleratorKey,

java.awt.Event.CTRL_MASK));

return item;

}

public static JMenuItem radioItem(String label, ActionListener listener,

String command, ButtonGroup mutExGroup) {

JMenuItem item = new JRadioButtonMenuItem(label);

item.addActionListener(listener);

item.setActionCommand(command);

mutExGroup.add(item);

return item;

}

public static class MenuItemActionListener implements ActionListener {

Component parent;

public MenuItemActionListener(Component parent) {

this.parent = parent;

}

public void actionPerformed(ActionEvent e) {

JMenuItem item = (JMenuItem) e.getSource();

String cmd = item.getActionCommand();

if (cmd.equals("exit"))

System.exit(0);

else

if (cmd.equals("color(red)"))

this.parent.setBackground(Color.red);

else

if (cmd.equals("color(blue)"))

this.parent.setBackground(Color.blue);

else

if (cmd.equals("color(green)"))

this.parent.setBackground(Color.green);

else

Chương 9: Lập trình giao diện

Biên soạn : Bùi Công Giao --------------------------------------- 210 --------------------------------------

JOptionPane.showMessageDialog(parent, cmd + " was selected.");

}

}

}

Hình 9.21 Kết quả demo JMenu

Lưu ý:

Vì các đối tượng mở rộng của thư viện JFC được bổ sung khá nhiều tính năng, đặc biệt là các tính năng đồ hoạ, do đó, các đối tượng này có nhược điểm là rất cồng kềnh. Vì lý do nặng tải, cho nên hiện nay, các đối tượng của thư viện JFC vẫn ít được phổ biến trong các ứng dụng applet.

9.6 Case Study

Trong phần này, ta sẽ minh hoạ cách sử dụng các đối tượng đồ hoạ của thư viện chuẩn AWT để viết một chương trình mô phỏng một máy tính Calculator như sau:

Tạo một frame làm khung chương trình, tiêu đề là “Java Calculator”

Phía trên là một Label (hoặc ô văn bản đều được, nhưng nếu dùng ô văn bản thì không cho edit) để hiện các số được nhập vào và kết quả tính toán.

Phía dưới là các nút nhấn tương ứng với các chữ số và phép toán. Nhưng để nhóm các nút nhấn cho đồng bộ và layout đẹp mắt, ta nhóm chúng vào một Panel.

Khi đó, frame sẽ chứa trực tiếp hai đối tượng: label và frame. Ta sử dụng layout kiểu null, và xác định vị trí chính xác cho label và panel.

Đối với Panel, ta cũng dùng GridLayout. Vì có 10 nút nhấn số và các nút nhấn toán tử: nút cộng, nút nhân, nút chia, nút trừ, nút căn bậc hai, nút phẩy thập phân, nút bằng, nút luỹ thừa, nút nghịch đảo, nút reset. Nên sẽ tạo thành 4 dòng, 5 cột: mỗi dòng gồm có 3 nút số và hai nút chức năng:

o Dòng 1: các nút 7, 8, 9, cộng, reset (C).

o Dòng 2: các nút 4, 5, 6, trừ, luỹ thừa.

Chương 9: Lập trình giao diện

Biên soạn : Bùi Công Giao --------------------------------------- 211 --------------------------------------

o Dòng 3: các nút 1, 2, 3, nhân, nghịch đảo.

o Dòng 4: các nút 0, thập phân, nút bằng, nút chia, nút căn bậc hai.

Với các nút số và nút thập phân, khi click vào nút thì kí tự tương ứng được hiện lên trên label.

Với các nút chức năng, khi click vào thì thực hiện phép toán và hiện kết quả ra màn hình, nếu có.

Khi click vào nút bằng (kết quả) thì hiện kết quả trên label.

Chương trình CalculatorDemo cài đặt chi tiết chương trình này.

package chuong9;

import java.awt.*;

import java.awt.event.*;

import java.lang.Math;

public class CalculatorDemo extends Frame implements ActionListener {

private boolean operatorState; // Trạng thái của phép toán

private int operator; // Toán tử thực hiện

private float oldIterator; // Số hạng trước

private Label lbl;

private Panel pnl;

private Button btn0, btn1, btn2, btn3, btn4, btn5, btn6, btn7, btn8, btn9, btnPoint, btnReset, btnAdd, btnSub, btnMul, btnDiv, btnPow, btnSqrt, btnRev, btnResult;

public CalculatorDemo() {

super(“Java Calculator”);

this.setSize(250, 250);

this.setResizable(false); // Không cho thay đổi size

this.setLayout(null); // Thiết lập layout

lbl = new Label("0"); // Nhãn kết quả

lbl.setAlignment(2);

lbl.setSize(240, 30);

lbl.setLocation(5, 25);

this.add(lbl);

Panel pnl = new Panel(); // Panel chứa các nút

pnl.setSize(240, 190);

pnl.setLocation(5, 55);

Chương 9: Lập trình giao diện

Biên soạn : Bùi Công Giao --------------------------------------- 212 --------------------------------------

pnl.setLayout(new GridLayout(4, 5)); // Thiết lập layout

this.add(pnl);

btn7 = new Button(“7”); // Nút số 7

btn7.addActionListener(this); // Bắt sự kiện click chuột

pnl.add(btn7); // Gắn vào panel

btn8 = new Button(“8”); // Nút số 8

btn8.addActionListener(this);

pnl.add(btn8);

btn9 = new Button(“9”); // Nút số 9

btn9.addActionListener(this);

pnl.add(btn9);

btnAdd = new Button(“+”); // Nút phép toán cộng

btnAdd.addActionListener(this);

pnl.add(btnAdd);

btnReset = new Button(“C”); // Nút reset

btnReset.addActionListener(this);

pnl.add(btnReset);

btn4 = new Button(“4”); // Nút số 4

btn4.addActionListener(this);

pnl.add(btn4);

btn5 = new Button(“5”); // Nút số 5

btn5.addActionListener(this);

pnl.add(btn5);

btn6 = new Button(“6”); // Nút số 6

btn6.addActionListener(this);

pnl.add(btn6);

btnSub = new Button(“-”); // Nút phép toán trừ

btnSub.addActionListener(this);

pnl.add(btnSub);

btnPow = new Button(“x^y”); // Nút phép toán luỹ thừa

btnPow.addActionListener(this);

pnl.add(btnPow);

btn1 = new Button(“1”); // Nút số 1

btn1.addActionListener(this);

pnl.add(btn1);

btn2 = new Button(“2”); // Nút số 2

Chương 9: Lập trình giao diện

Biên soạn : Bùi Công Giao --------------------------------------- 213 --------------------------------------

btn2.addActionListener(this);

pnl.add(btn2);

btn3 = new Button(“3”); // Nút số 3

btn3.addActionListener(this);

pnl.add(btn3);

btnMul = new Button(“*”); // Nút phép toán nhân

btnMul.addActionListener(this);

pnl.add(btnMul);

btnRev = new Button(“1/x”); // Nút phép toán nghịch đảo

btnRev.addActionListener(this);

pnl.add(btnRev);

btn0 = new Button(“0”); // Nút số 0

btn0.addActionListener(this);

pnl.add(btn0);

btnPoint = new Button(“.”); // Nút dấu thập phân

btnPoint.addActionListener(this);

pnl.add(btnPoint);

btnResult = new Button(“=”); // Nút kết quả

btnResult.addActionListener(this);

pnl.add(btnResult);

btnDiv = new Button(“/”); // Nút phép toán chia

btnDiv.addActionListener(this);

pnl.add(btnDiv);

btnSqrt = new Button(“Sqrt”); // Nút phép toán căn bậc hai

btnSqrt.addActionListener(this);

pnl.add(btnSqrt);

operatorState = true;

operator = -1;

oldIterator = 0;

this.addWindowListener(new WindowAdapter() {

public void windowClosing(WindowEvent e) {

System.exit(0);

}

});

}

Chương 9: Lập trình giao diện

Biên soạn : Bùi Công Giao --------------------------------------- 214 --------------------------------------

// Phương thức xử lý sự kiện

public void actionPerformed(ActionEvent ae) {

float result;

float newIterator = Float.parseFloat(lbl.getText());

if(ae.getSource() == btnResult) {

switch(operator) {

case 0: result = oldIterator + newIterator;

lbl.setText(String.valueOf(result));

break;

case 1: result = oldIterator - newIterator;

lbl.setText(String.valueOf(result));

break;

case 2: result = oldIterator * newIterator;

lbl.setText(String.valueOf(result));

break;

case 3: if(newIterator != 0) {

result = oldIterator/newIterator;

lbl.setText(String.valueOf(result));

}

break;

case 4:result = (float)Math.pow(oldIterator, newIterator);

lbl.setText(String.valueOf(result));

break;

}

operator = -1;

operatorState = true;

return;

}

if(ae.getSource() == btnRev) {

newIterator = Float.parseFloat(lbl.getText());

if(newIterator != 0) {

result = (float)1/newIterator;

lbl.setText(String.valueOf(result));

}

operator = -1;

operatorState = true;

return;

Chương 9: Lập trình giao diện

Biên soạn : Bùi Công Giao --------------------------------------- 215 --------------------------------------

}

if(ae.getSource() == btnSqrt) {

newIterator = Float.parseFloat(lbl.getText());

if(newIterator >= 0) {

result = (float)Math.sqrt(newIterator);

lbl.setText(String.valueOf(result));

}

operator = -1;

operatorState = true;

return;

}

if(ae.getSource() == btnPoint) {

lbl.setText(lbl.getText() + “.”);

return;

}

if(ae.getSource() == btnAdd) {

operator = 0;

operatorState = true;

oldIterator = Float.parseFloat(lbl.getText());

return;

}

if(ae.getSource() == btnSub) {

operator = 1;

operatorState = true;

oldIterator = Float.parseFloat(lbl.getText());

return;

}

if(ae.getSource() == btnMul) {

operator = 2;

operatorState = true;

oldIterator = Float.parseFloat(lbl.getText());

return;

}

if(ae.getSource() == btnDiv) {

operator = 3;

operatorState = true;

oldIterator = Float.parseFloat(lbl.getText());

Chương 9: Lập trình giao diện

Biên soạn : Bùi Công Giao --------------------------------------- 216 --------------------------------------

return;

}

if(ae.getSource() == btnPow) {

operator = 4;

operatorState = true;

oldIterator = Float.parseFloat(lbl.getText());

return;

}

if(ae.getSource() == btnReset) {

operator = -1;

operatorState = true;

oldIterator = 0;

lbl.setText("0");

return;

}

// Trường hợp click vào nút số

if(operatorState) { // Bắt đầu số mới

lbl.setText(ae.getActionCommand());

operatorState = false;

}

else // Gõ tiếp số cũ

lbl.setText(lbl.getText() + ae.getActionCommand());

}

public static void main(String[] args) {

// Khai báo và khởi tạo frame

CalculatorDemo myFrame = new CalculatorDemo();

myFrame.setVisible(true); // Hiển thị frame

}

}

Chương trình sẽ cho kết quả như sau:

Hình 9.22 Kết quả demo Case Study

Chương 9: Lập trình giao diện

Biên soạn : Bùi Công Giao --------------------------------------- 217 --------------------------------------

TỔNG KẾT CHƯƠNG 9

Nội dung chương này đã trình bày phương pháp lập trình giao diện với các đối tượng trong thư viện chuẩn AWT của Java:

Các đối tượng có chức năng làm vật chứa cho các đối tượng giao diện: Frame, Panel, Dialog.

Các đối tượng là thành phần giao diện: Label, Button, TextField, TextArea, Checkbox, List, Menu.

Phương pháp nắm bắt và xử lý các sự kiện đặc thù của từng đối tượng giao diện.

Các phương pháp trình bày trên các vật chứa: FlowLayout, GridLayout, BorderLayout, GridBagLayout, NullLayout.

Giới thiệu một phương pháp lập trình giao diện Java được nhúng trong các trang web, đó là lập trình applet:

Cách tạo ra một applet với các phương thức cơ bản.

Cách nhúng một applet vào một trang web.

Cách kiểm nghiệm một applet sau khi nhúng vào trang web bằng các trình duyệt.

Ngoài ra, chương này cũng giới thiệu cách sử dụng thư viện các đối tượng đồ hoạ mở rộng JFC của Java. Các đối tượng của thư viện JFC có chức năng hoàn toàn tương tự các đối tượng tương ứng trong thư viện chuẩn AWT. Ngoài ra, chúng còn được bổ sung thêm một số khả năng đồ hoạ cao cấp.

BÀI TẬP

1. Viết chương trình thay đổi màu nền của frame theo lựa chọn của người dùng:

Tạo ra các nút nhấn có tên theo các màu: Blue, Cyan, Gray, Green, Magenta, Orange, Pink, Red, White, Yellow.

Khi click chuột vào nút nào, màu nền của frame sẽ đổi theo màu đó.

2. Viết chương trình thay đổi màu nền trong bài 1 bằng ô văn bản. Tạo ra một ô văn bản duy nhất, khi người dùng gõ vào một trong số các màu trong bài 1 và gõ enter, màu nền của frame sẽ đổi theo màu đó. Nếu người dùng gõ sai màu, không làm gì cả.

3. Viết chương trình thay đổi màu nền trong bài 1 bằng nút chọn radio. Tạo một nhóm các nút radio tương ứng với các loại màu. Khi màu nào được chọn, màu nền của frame sẽ thay đổi theo màu đó.

4. Viết chương trình thay đổi màu nền trong bài 1 bằng danh sách chọn list. Tạo một List có các item tương ứng với các loại màu. Khi màu nào được chọn, màu nền của frame sẽ thay đổi theo màu đó.

Chương 9: Lập trình giao diện

Biên soạn : Bùi Công Giao --------------------------------------- 218 --------------------------------------

5. Viết chương trình thay đổi màu nền trong bài 1 bằng menu. Tạo một menubar, trên đó có gắn một menu tên là color, khi click chuột vào menu color, sẽ sổ xuống các màu tương ứng trong bài 1. Khi màu nào được chọn, màu nền của frame sẽ thay đổi theo màu đó.

6. Viết chương trình thay đổi màu nền trong bài 1 bằng menu popup. Tạo một menu popup trong frame, khi click chuột phải lên frame, sẽ hiện ra menu gồm các màu tương ứng trong bài 1. Khi màu nào được chọn, màu nền của frame sẽ thay đổi theo màu đó.

7. Viết lại các chương trình trong các bài tập 1 đến 6 dưới dạng applet và nhúng chúng vào trang myHtml.htl để chạy. (Trang này phải cùng thư mục với các lớp vừa cài đặt và biên dịch).

Chương 10: Luồng xử lý

Biên soạn : Bùi Công Giao --------------------------------------- 219 --------------------------------------

Chương 10. Luồng xử lý

Nội dung chương này nhằm giới thiệu:

Định nghĩa một luồng

Hiểu được vòng đời của luồng

Tạo và quản lý luồng

Giải thích thiết lập luồng ưu tiên như thế nào

Mô tả đa luồng

Mô tả một luồng ngầm

Giải thích được sự cần thiết của sự đồng bộ

Giải thích vai trò của các phương thức wait(), notify() và notifyAll().

Mô tả một điều kiện bế tắc (deadlock condition)

Mô tả cơ chế thu rác

10.1 Giới thiệu luồng

Một luồng (thread) là đơn vị nhỏ nhất của đoạn mã có thể thi hành được mà thực hiện một công việc riêng biệt.

Java hỗ trợ đa luồng, mà có khả năng làm việc với nhiều luồng. Một ứng dụng có thể bao hàm nhiều luồng. Mỗi luồng được đăng ký một công việc riêng biệt, mà chúng được thực thi đồng thời với các luồng khác.

Đa luồng giữ thời gian nhàn rỗi của hệ thống thành nhỏ nhất. Điều này cho phép ta viết các chương trình có hiệu quả cao với sự tận dụng CPU là tối đa. Mỗi phần của chương trình được gọi một luồng, mỗi luồng định nghĩa một đường dẫn khác nhau của sự thực hiện. Đây là một thiết kế chuyên dùng của sự đa nhiệm.

Trong sự đa nhiệm, nhiều chương chương trình chạy đồng thời, mỗi chương trình có ít nhất một luồng trong nó. Một vi xử lý thực thi tất cả các chương trình. Cho dù nó có thể xuất hiện mà các chương trình đã được thực thi đồng thời, trên thực tế CPU nhảy qua lại giữa các tiến trình.

Một luồng đi qua các giai đoạn khác nhau trong vòng đời của nó. Ví dụ, một luồng được sinh ra, bắt đầu, chạy, và sau đó chết. Hình 10.1 cho thấy vòng đời hoàn chỉnh của một luồng.

New: một luồng mới bắt đầu chu kỳ sống của nó trong trạng thái mới.

Runnable: sau khi một luồng vừa được sinh ra là bắt đầu, luồng sẽ trở thành Runnable. Một luồng ở trạng thái này được coi là đang thực hiện nhiệm vụ của mình.

Chương 10: Luồng xử lý

Biên soạn : Bùi Công Giao --------------------------------------- 220 --------------------------------------

Waiting: đôi khi một luồng chuyển sang trạng thái chờ đợi trong khi một luồng khác thực hiện nhiệm vụ. Luồng chờ đợi chuyển về trạng thái thực hiện khi chỉ khi một luồng khác phát tín hiệu tới nó để tiếp tục thực hiện.

Timed waiting: một luồng Runnable có thể nhập trạng thái chờ đợi hẹn giờ trong một khoảng thời gian nhất định. Một luồng trong quá trình chuyển đổi trạnng thái chờ này về trạng thái Runnable khi mà khoảng thời gian hết hạn hoặc khi các sự kiện nó đợi xảy ra.

Terminated: một luồng Runnable đi vào trạng thái chấm dứt khi nó hoàn thành nhiệm vụ của mình.

Hình 10.1 Vòng đời của luồng

10.2 Tạo và quản lý luồng

10.2.1 Tạo luồng

Khi các chương trình Java được thực thi, luồng chính luôn được thực hiện và

Các luồng con sẽ được tạo ra từ nó.

Nó là luồng cuối cùng kết thúc việc thực hiện. Ngay khi luồng chính ngừng thực thi, chương trình bị chấm dứt.

Cho dù luồng chính được tạo ra một cách tự động với chương trình thực thi, nó có thể được điều khiển thông qua một luồng đối tượng.

Chương 10: Luồng xử lý

Biên soạn : Bùi Công Giao --------------------------------------- 221 --------------------------------------

Các luồng có thể được tạo ra bằng hai cách:

Trình bày lớp như là một lớp con của lớp luồng, nơi mà phương thức run() của lớp luồng cần được ghi đè, ví dụ:

class Mydemo extends Thread {

public void run() {

//thực thi

}

}

Trình bày một lớp mà lớp này thực hiện lớp Runnable. Rồi thì định nghĩa phương thức run().

class Mydemo implements Runnable {

public void run() {

//thực thi

}

}

Chương trình MyThread sẽ hiển thị thông tin về luồng chính

package chuong10;

import java.io.*;

public class MyThread extends Thread {

public static void main(String args[]){

Thread t = Thread.currentThread();

System.out.println("The current Thread is :" + t);

t.setName("MyJavaThread");

System.out.println("The thread is now named: " + t);

try {

for (int i = 0; i <3;i++) {

System.out.println(i);

Thread.sleep(1500);

}

} catch (InterruptedException e) {

System.out.println("Main thread interupted");

}

}

}

Chương 10: Luồng xử lý

Biên soạn : Bùi Công Giao --------------------------------------- 222 --------------------------------------

Kết quả chương trình trên

The current Thread is :Thread[main,5,main]

The thread is now named: Thread[MyJavaThread,5,main]

0

1

2

Trong kết quả xuất ra ở trên

Lớp luồng có vài phương thức xây dựng, hai trong số các phương thức xây dựng được đề cập đến dưới đây:

public Thread(String threadname)

Cấu trúc một luồng với tên là “threadname”

public Thread()

Cấu trúc một luồng với tên Thread và đi kèm một số; lấy ví dụ, Thread-1, Thread-2, …

Chương trình bắt đầu thực thi luồng với việc gọi phương thức start(), mà phương thức này phụ thuộc vào lớp luồng. Phương thức này, lần lượt, viện dẫn phương thức run(), nơi mà phương thức định nghĩa tác vụ được thực thi. Phương thức này có thể viết đè lên lớp con của lớp luồng, hoặc với một đối tượng Runnable.

Chương trình ChildThread minh hoạ việc tạo luồng con:

package chuong10;

class ChildThread extends Thread {

public static void main(String args[]) {

ChildThread Objex = new ChildThread();

Objex.create();

System.out.println("This is the main thread");

}

public void create() {

Thread Objth = new Thread(this);

[main, 5 , main] 

Nhóm luồng mà nó phụ thuộc

Quyền ưu tiên được đặt bởi JVM

Tên của luồng

Chương 10: Luồng xử lý

Biên soạn : Bùi Công Giao --------------------------------------- 223 --------------------------------------

Objth.start();

}

public void run() {

for (int i = 0; i< 3; i++) {

try {

System.out.println("This is the child thread");

Thread.sleep(500);

} catch (InterruptedException e) {

}

}

}

}

Kết quả chương trình trên

This is the child thread

This is the main thread

This is the child thread

This is the child thread

10.2.2 Phương thức của lớp luồng

Phương thức Mục đích

enumerate(Thread [ ] t) Chép tất cả các luồng hiện hành vào mảng từ nhóm luồng và các nhóm con của nó.

getName() Trả về tên của luồng

isAlive() Trả về true, nếu luồng vẫn còn tồn tại (sống)

getPriority() Trả về quyền ưu tiên của luồng

setName(String name) Đặt tên luồng.

join() Đợi cho đến khi luồng chết.

isDaemon() Kiểm tra có phải là luồng ngầm hay không.

setDaemon(boolean on) Thiết lập là luồng ngầm hay luồng người dùng.

sleep() Hoãn luồng một khoảng thời gian.

start() Gọi phương thức run() để bắt đầu một luồng.

activeCount() Số luồng đang hoạt động

yield() Tạm thời dừng luồng đang thực hiện và cho phép luồng

Chương 10: Luồng xử lý

Biên soạn : Bùi Công Giao --------------------------------------- 224 --------------------------------------

khác thực hiện.

Bảng 10.1 Các phương thức của một lớp luồng

Các nguyên nhân làm luồng không chạy:

Luồng không có độ ưu tiên cao

Luồng đi vào trạng thái ngũ khi dùng phương thức sleep()

Luồng đi vào trạng thái chờ khi dùng phương thức wait()

Luồng nhường quyền chiếm hữu CPU khi dùng yield()

Nó bị ngăn chặn khi thực hiện việc nhập xuất

Chương trình ThreadTest sau minh hoạ cách sử dụng vài phương thức cùa luồng

package chuong10;

public class ThreadTest implements Runnable {

Thread t;

public ThreadTest() {

System.out.println(Thread.currentThread().getName());

t = new Thread(this);

t.setName("My Thread");

t.start();

}

public void run() {

System.out.println(Thread.currentThread().getName());

System.out.println(Thread.activeCount());

System.out.println(t.isAlive());

}

public static void main(String args[]) {

new ThreadTest();

}

}

Kết quả chương trình trên

main

My Thread

2

true

Chương 10: Luồng xử lý

Biên soạn : Bùi Công Giao --------------------------------------- 225 --------------------------------------

10.2.3 Quyền ưu tiên của luồng

Khi có nhiều luồng cùng hoạt động thì mỗi luồng nhận một phần nhỏ thời gian CPU, được gọi là định lượng. Luồng có thể thực thi tác vụ của chính nó trong suốt khoảng thời gian định lượng đấy. Sau khoảng thời gian này, luồng không tiếp tục thực hiện, ngay cả nếu nó không được hoàn thành công việc của nó. Luồng kế tiếp của luồng có quyền ưu tiên bằng nhau này sẽ sở hửu CPU.

Mỗi luồng trong chương trình Java được đăng ký cho một quyền ưu tiên. JVM không bao giờ thay đổi quyền ưu tiên của luồng. Mỗi luồng có một giá trị ưu tiên nằm trong khoảng từ Thread.MIN_PRIORITY : 1 tới Thread.MAX_PRIORITY : 10. Mặc định luồng có số ưu tiên là Thread.NORM_PRIORITY : 5.

Phương thức yield() thích hợp cho các hệ thống không chia nhỏ thời gian (non-time-sliced), nơi mà các luồng hiện thời hoàn thành việc thực hiện trước khi các luồng có quyền ưu tiên ngang nhau kế tiếp tiếp quản. Ở đây, ta sẽ gọi phương thức yield() tại những khoản thời gian riêng biệt để có thể tất cả các luồng có quyền ưu tiên ngang nhau chia sẻ thời gian thực thi CPU.

Chương trình PriorityDemo minh hoạ quyền ưu tiên của luồng:

package chuong10;

public class PriorityDemo {

Priority t1, t2, t3;

public PriorityDemo() {

t1 = new Priority();

t1.start();

t2 = new Priority();

t2.start();

t3 = new Priority();

t3.start();

}

public static void main(String args[]){

new PriorityDemo();

}

}

class Priority extends Thread implements Runnable {

int sleep;

int prio = 3;

public Priority() {

sleep += 100;

Chương 10: Luồng xử lý

Biên soạn : Bùi Công Giao --------------------------------------- 226 --------------------------------------

setPriority(Thread.activeCount());

}

public void run() {

try {

Thread.sleep(sleep);

System.out.println("Name "+ getName()+" Priority = "+ getPriority());

} catch(InterruptedException e) {

System.out.println(e.getMessage());

}

}

}

Kết quả chương trình như sau:

Name Thread-2 Priority = 3

Name Thread-0 Priority = 1

Name Thread-1 Priority = 2

10.2.4 Luồng ngầm

Có hai kiểu luồng trong một chương trình Java:

Các luồng người sử dụng

Luồng ngầm (daemon thread)

Người sử dụng tạo ra các luồng người sử dụng, trong khi các luồng chạy phía sau (background) cung cấp các dịch vụ cho các luồng khác gọi là luồng ngầm. Khi máy ảo Java khám phá rằng chỉ có luồng ngầm đang chạy thì nó sẽ thoát. Máy ảo Java có ít nhất một luồng ngầm được biết đến như là luồng thu rác (thu lượm những dữ liệu vô nghĩa). Luồng thu rác thực thi chỉ khi hệ thống không có tác vụ nào. Nó là một luồng có quyền ưu tiên thấp. Lớp luồng có hai phương thức để làm việc với các luồng ngầm:

public void setDaemon(boolean on)

public boolean isDaemon()

Chương trình TestDaemon sau minh hoạ cho luồng ngầm

package chuong10;

class TestDaemon implements Runnable {

Thread Objth1, Objth2;

public TestDaemon() {

Chương 10: Luồng xử lý

Biên soạn : Bùi Công Giao --------------------------------------- 227 --------------------------------------

Objth1 = new Thread(this);

Objth1.start();

Objth2 = new Thread(this);

Objth2.setDaemon(true);

}

public void run() {

System.out.println(Thread.activeCount());

System.out.println(Objth1.isDaemon());

System.out.println(Objth2.isDaemon());

}

public static void main(String args[]) {

new TestDaemon();

}

}

Kết quả chương trình như sau:

2

false

true

10.2.5 Đa luồng với Applet

Trong khi đa luồng là rất hữu dụng trong các chương trình ứng dụng độc lập, nó cũng đáng được quan tâm với các ứng dụng trên Web. Đa luồng được sử dụng trên web, trong các trò chơi đa phương tiện, các bức ảnh sinh động, hiển thị các dòng chữ chạy qua lại trên biểu ngữ, hiển thị đồng hồ thời gian như là một phần của trang Web… Các chức năng này tạo thành các trang web quyến rũ và bắt mắt.

Trong đa luồng với Applet, lớp java.applet.Applet là lớp con được tạo ra bởi người sử dụng định nghĩa applet.

Chương trình MyApplet chỉ ra điều này thực thi như thế nào:

package chuong10;

import java.awt.*;

import java.applet.*;

public class MyApplet extends Applet implements Runnable {

int i;

Thread t;

Chương 10: Luồng xử lý

Biên soạn : Bùi Công Giao --------------------------------------- 228 --------------------------------------

public void init() {

t = new Thread(this);

t.start();

}

public void paint(Graphics g) {

g.drawString(" i = "+i,30,30);

}

public void run(){

for(i = 1; i<22; i++) {

try {

repaint();

Thread.sleep(500);

} catch(InterruptedException e) {

System.out.println(e.getMessage());

}

}

}

}

Hình 10.2 Đa luồng với Applet

Trong chương trình này, chúng ta tạo ra một Applet được gọi là MyApplet, và thực thi giao diện Runnable để cung cấp khả năng đa luồng cho applet. Sau đó, chúng ta tạo ra một thể hiên cho lớp luồng, với thể hiện applet hiện thời như là một tham số để khởi tạo. Rồi thì ta cho luồng thực hiện bằng phương thức start(). Màn hình sẽ xuất ra số từ 1 đến 20 với thời gian trễ là 500 miligiây giữa mỗi số bằng phương thức sleep(). Kết quả chương trình như hình 10.2.

Chương 10: Luồng xử lý

Biên soạn : Bùi Công Giao --------------------------------------- 229 --------------------------------------

10.3 Nhóm luồng

10.3.1 Giới thiệu nhóm luồng

Nhóm luồng dùng để quản lý các luồng như là một đơn vị thống nhất. Nó rất có ích trong các trường hợp ta muốn ngăn lại hay chạy lại nhiều luồng có liên quan với nhau.

Ví dụ, hãy tưởng tượng một chương trình trong đó có một tập hợp các luồng được sử dụng để in một tài liệu, tập hợp khác được sử dụng để hiển thị các tài liệu trên màn hình, và tập hợp khác lưu vào một tập tin đĩa. Nếu in được hủy bỏ, bạn sẽ muốn có một cách dễ dàng để ngăn chặn tất cả các chủ đề liên quan đến in. Nhóm luồng cung cấp tiện lợi này.

Hai kiểu nhóm luồng thiết lập (khởi tạo) là:

public ThreadGroup(String str)

Ở đây, str là tên của nhóm luồng mới nhất được tạo ra.

public ThreadGroup(ThreadGroup tgroup, String str)

Ở đây, tgroup chỉ ra nhóm luồng luồng cha, str là tên của nhóm luồng đang được tạo ra.

Một số các phương thức trong nhóm luồng được cho như sau:

public synchronized int activeCount()

Trả về số lượng các luồng kích hoạt hiện hành trong nhóm luồng

public synchronized int activeGroupCount()

Trả về số lượng các nhóm hoạt động trong nhóm luồng

public final String getName()

Trả về tên của nhóm luồng

public final ThreadGroup getParent()

Trả về cha của nhóm luồng

Chương trình sau đây tạo ra hai nhóm luồng, mỗi nhóm có hai luồng minh họa cách sử dụng nhóm luồng:

package chuong10;

class NewThread extends Thread {

boolean suspendFlag;

NewThread(String threadname, ThreadGroup tgOb) {

super(tgOb, threadname);

System.out.println("New thread: " + this);

suspendFlag = false;

start();

}

Chương 10: Luồng xử lý

Biên soạn : Bùi Công Giao --------------------------------------- 230 --------------------------------------

public void run() {

try {

for (int i = 5; i > 0; i--) {

System.out.println(getName() + ": " + i);

Thread.sleep(1000);

synchronized (this) {

while (suspendFlag)

wait();

}

}

} catch (Exception e) {

System.out.println("Exception in " + getName());

}

System.out.println(getName() + " exiting.");

}

void mysuspend() {

suspendFlag = true;

}

synchronized void myresume() {

suspendFlag = false;

notify();

}

}

class ThreadGroupDemo {

public static void main(String args[]) {

ThreadGroup groupA = new ThreadGroup("Group A");

ThreadGroup groupB = new ThreadGroup("Group B");

NewThread ob1 = new NewThread("One", groupA);

NewThread ob2 = new NewThread("Two", groupA);

NewThread ob3 = new NewThread("Three", groupB);

NewThread ob4 = new NewThread("Four", groupB);

System.out.format("%nHere is output from list():");

groupA.list();

groupB.list();

System.out.println();

System.out.println("Suspending Group A");

Thread tga[] = new Thread[groupA.activeCount()];

Chương 10: Luồng xử lý

Biên soạn : Bùi Công Giao --------------------------------------- 231 --------------------------------------

// Chép tất cả các luồng trong nhóm luồng A vào mảng luồng.

groupA.enumerate(tga);

for (int i = 0; i < tga.length; i++)

((NewThread) tga[i]).mysuspend(); // treo từng luồng

try {

Thread.sleep(4000);

} catch (InterruptedException e) {

System.out.println("Main thread interrupted.");

}

System.out.println("Resuming Group A");

for (int i = 0; i < tga.length; i++)

// khởi động lại từng luồng trong mảng luồng

((NewThread) tga[i]).myresume();

// Chờ các luồng kết thúc

try {

System.out.println("Waiting for threads to finish.");

ob1.join();

ob2.join();

ob3.join();

ob4.join();

} catch (Exception e) {

System.out.println("Exception in Main thread");

}

System.out.println("Main thread exiting.");

}

}

Kết quả chương trình như sau:

New thread: Thread[One,5,Group A]

New thread: Thread[Two,5,Group A]

New thread: Thread[Three,5,Group B]

New thread: Thread[Four,5,Group B]

Here is output from list():Four: 5

Chương 10: Luồng xử lý

Biên soạn : Bùi Công Giao --------------------------------------- 232 --------------------------------------

Two: 5

Three: 5

One: 5

java.lang.ThreadGroup[name=Group A,maxpri=10]

Thread[One,5,Group A]

Thread[Two,5,Group A]

java.lang.ThreadGroup[name=Group B,maxpri=10]

Thread[Three,5,Group B]

Thread[Four,5,Group B]

Suspending Group A

Four: 4

Three: 4

Four: 3

Three: 3

Four: 2

Three: 2

Three: 1

Four: 1

Resuming Group A

Two: 4

Waiting for threads to finish.

One: 4

Four exiting.

Two: 3

Three exiting.

One: 3

Two: 2

One: 2

Two: 1

One: 1

Two exiting.

One exiting.

Main thread exiting.

Chương 10: Luồng xử lý

Biên soạn : Bùi Công Giao --------------------------------------- 233 --------------------------------------

10.3.2 Sự đồng bộ luồng

Trong khi đang làm việc với nhiều luồng, có thể nhiều hơn một luồng muốn thâm nhập cùng một biến tại cùng thời điểm. Lấy ví dụ, một luồng có thể cố gắng đọc dữ liệu, trong khi luồng khác cố gắng thay đổi dữ liệu. Trong trường hợp này, dữ liệu có thể bị sai lạc.

Trong những trường hợp này, ta cần cho phép một luồng hoàn thành trọn vẹn tác vụ của nó (thay đổi giá trị), và rồi thì cho phép các luồng kế tiếp thực thi. Khi hai hoặc nhiều hơn các luồng cần thâm nhập đến một tài nguyên được chia sẻ, ta cần chắc chắn rằng tài nguyên đó sẽ được sử dụng chỉ bởi một luồng tại một thời điểm. Tiến trình này được gọi là sự đồng bộ (synchronization). Java cung cấp cơ chế đồng bộ để báo cho hệ thống đặt một khóa lên một tài nguyên đặc biệt.

Mấu chốt của sự đồng bộ hóa là khái niệm monitor (sự giám sát). Một monitor xem như là một hộp nhỏ chỉ chứa một luồng tại một thời điểm. Tất cả các luồng khác cố gắng thâm nhập vào monitor sẽ bị trì hoãn, cho đến khi luồng bên trong monitor thoát ra. Các luồng khác được báo chờ đợi monitor. Nếu cần thiết luồng có monitor có thể thâm nhập trở lại cùng monitor đó.

10.3.3 Mã đồng bộ

Tất cả các đối tượng trong Java được liên kết với các monitor của riêng nó. Đối với một luồng để đi vào monitor của một đối tượng, ta sử dụng từ khóa synchronized trước một phương thức. Khi một luồng đang được thực thi trong phạm vi một phương thức đồng bộ, bất kỳ luồng khác hoặc phương thức đồng bộ khác cố gắng gọi nó trong cùng thể hiện sẽ phải đợi.

Chương trình Sync chứng minh sự làm việc của từ khóa synchronized (sự đồng bộ). Ở đây, lớp Target có một phương thức display() (hiển thị) mà phương thức này lấy một tham số kiểu int. Số này được hiển thị theo dạng “< số số>”. Phương thức Thread.sleep(1000) tạm dừng luồng hiện tại sau khi phương thức display() được gọi.

Phương thức xây dựng của lớp Source lấy một tham chiếu đến một đối tượng t của lớp Target, và một biến số nguyên. Ở đây, một luồng mới được tạo ra. Luồng này gọi phương thức run() của đối tượng t. Lớp chính Synch tạo đối tượng của lớp Target, và ba đối tượng của lớp Source. Đối tượng target được truyền cho mỗi đối tượng Source. Phương thức join() làm luồng gọi đợi cho đến khi việc luồng kết thúc.

package chuong10;

class Target {

synchronized void display(int num) {

System.out.print("< "+num);

try {

Chương 10: Luồng xử lý

Biên soạn : Bùi Công Giao --------------------------------------- 234 --------------------------------------

Thread.sleep(1000);

} catch(InterruptedException e) {

System.out.println("Interrupted");

}

System.out.println(“ “+ num+ ">");

}

}

class Source implements Runnable {

int number;

Target target;

Thread t;

public Source(Target targ,int n) {

target = targ;

number = n;

t = new Thread(this);

t.start();

}

public void run(){

target.display(number);

}

}

public class Sync {

public static void main(String args[]){

Target target = new Target();

int digit = 10;

Source s1 = new Source(target,digit++);

Source s2 = new Source(target,digit++);

Source s3 = new Source(target,digit++);

try {

s1.t.join();

s2.t.join();

s3.t.join();

} catch(InterruptedException e) {

System.out.println("Interrupted");

}

Chương 10: Luồng xử lý

Biên soạn : Bùi Công Giao --------------------------------------- 235 --------------------------------------

}

}

Kết quả hiển thị như sau

<10 10>

<12 12>

<11 11>

Trong chương trình trên, có một dãy số đăng nhập được hiển thị bởi display(). Điều này có nghĩa là việc thâm nhập bị hạn chế một luồng tại mỗi thời điểm. Nếu ta bỏ từ khóa synchronized đặt trước phương thức display() của lớp Target, tất cả luồng trên có thể cùng lúc gọi cùng phương thức, trên cùng đối tượng. Điều kiện này được biết đến như là tình trạng tranh đua (race condition). Trong trường hợp này, việc xuất ra ngoài sẽ như sau

<10<11<12 10>

12>

11>

10.3.4 Sử dụng khối đồng bộ

Tạo ra các phương thức đồng bộ trong phạm vi các lớp là một con đường dễ dàng và có hiệu quả của việc thực hiện sự đồng bộ. Tuy nhiên, điều này không làm việc trong tất cả các trường hợp.

Hãy xem một trường hợp ta muốn sự đồng bộ được thâm nhập vào các đối tượng của lớp mà không được thiết kế cho thâm nhập đa luồng. Tức là, lớp không sử dụng các phương thức đồng bộ. Hơn nữa, mã nguồn định nghĩa lớp không có cho chúng ta thêm synchronized trước phương thức đồng bộ.

Để đồng bộ sự truy nhập một phương thức của đối tượng từ nơi gọi ta sử dụng chung một khối đồng bộ (synchronized block) như sau:

synchronized(object) {

// các câu lệnh đồng bộ

}

Ở đây, object là một tham chiếu đến đối tượng được đồng bộ. Một khối đồng bộ bảo đảm rằng nó gọi đến các phương thức (mà là thành phần của đối tượng) chỉ sau khi luồng hiện hành đã được thâm nhập thành công vào monitor của đối tượng.

Chương 10: Luồng xử lý

Biên soạn : Bùi Công Giao --------------------------------------- 236 --------------------------------------

Chương trình SynchBlock chỉ ra câu lệnh đồng bộ sử dụng như thế nào:

package chuong10;

class Target SynchBlock {

void display(int num) {

System.out.print("<"+num);

try {

Thread.sleep(1000);

} catch(InterruptedException e) {

System.out.println("Interrupted");

}

System.out.println(“ “+ num+ ">");

}

}

class Source SynchBlock implements Runnable{

int number;

Target SynchBlock target;

Thread t;

public Source(Target SynchBlock targ,int n) {

target = targ;

number = n;

t = new Thread(this);

t.start();

}

// đồng bộ gọi phương thức display()

public void run(){

synchronized(target) {

target.display(number);

}

}

}

public class SynchBlock {

public static void main(String args[]){

Target SynchBlock target = new Target SynchBlock ();

int digit = 10;

Chương 10: Luồng xử lý

Biên soạn : Bùi Công Giao --------------------------------------- 237 --------------------------------------

Source SynchBlock s1 = new Source SynchBlock (target,digit++);

Source SynchBlock s2 = new Source SynchBlock (target,digit++);

Source SynchBlock s3 = new Source SynchBlock (target,digit++);

try {

s1.t.join();

s2.t.join();

s3.t.join();

} catch(InterruptedException e){

System.out.println("Interrupted");

}

}

}

Ở đây, từ khóa synchronized không hiệu chỉnh phương thức display(). Từ khóa này được sử dụng trong phương thức run() của lớp Target SynchBlock.

Kết quả xuất ra màn hình như sau:

<10 10>

<11 11>

<12 12>

Sự không thuận lợi của các phương thức đồng bộ

Người lập trình thường viết các chương trình trên các đơn thể luồng. Tất nhiên các trạng thái này chắc chắn không lợi ích cho đa luồng. Lấy ví dụ, lụồng không tận dụng việc thực thi của trình biên dịch. Trình biên dịch Java từ Sun không chứa nhiều phương thức đồng bộ.

Các phương thức đồng bộ không thực thi tốt như là các phương thức không đồng bộ. Các phương thức này chậm hơn từ ba đến bốn lần so với các phương thức tương ứng không đồng bộ. Trong trạng thái nơi mà việc thực thi là có giới hạn, các phương thức đồng bộ bị ngăn chặn.

10.3.5 Kỹ thuật đợi – thông báo

Java cung cấp một cơ chế liên lạc bên trong sử dụng các phương thức wait(), notify() và notifyAll(). Các phương thức này được thực hiện như là các các phương thức final trong lớp Object, để tất cả lớp có thể truy xuất chúng. Ba phương thức này có thể được gọi chỉ từ trong một phương thức đồng bộ.

Phương thức wait() báo cho luồng đang gọi thoát khỏi monitor để đi vào trạng thái sleep cho đến khi một số luồng khác vào cùng monitor và gọi phương thức notify().

Chương 10: Luồng xử lý

Biên soạn : Bùi Công Giao --------------------------------------- 238 --------------------------------------

Phương thức notify() đánh thức hay thông báo cho luồng đầu tiên mà đã gọi phương thức wait().

Phương thức notifyAll() đánh thức hay thông báo tất cả các luồng mà đã gọi phương thức wait().

Khi tất cả luồng đi vào trạng thái sleep, luồng có quyền ưu tiên cao nhất sẽ chạy đầu tiên.

Chương trình Dining biểu thị cho việc sử dụng các phương thức notify() và wait():

package chuong10;

class ChopStick {

boolean available;

ChopStick() {

available = true;

}

public synchronized void takeup(int philo_num, String side) {

while (!available) {

try {

System.out.println("Philosopher "+ philo_num+ " is waiting for the "+ side+ " chopstick");

wait();

} catch (InterruptedException e) {

}

}

System.out.println("Philosopher "+ philo_num+ " is taking up the "+ side+ " chopstick");

available = false;

}

public synchronized void putdown(int philo_num, String side) {

System.out.println("Philosopher " + philo_num + " is putting down the "+ side+ " chopstick");

available = true;

notify();

}

}

class Philosopher extends Thread {

ChopStick left, right;

Chương 10: Luồng xử lý

Biên soạn : Bùi Công Giao --------------------------------------- 239 --------------------------------------

int philo_num;

Philosopher(int num, ChopStick chop1, ChopStick chop2) {

philo_num = num;

left = chop1;

right = chop2;

}

public void eat() {

left.takeup(philo_num+ 1, "left");

right.takeup(philo_num+ 1, "right");

System.out.println("Philosopher " + (philo_num + 1) + " is eating");

}

public void think() {

left.putdown(philo_num+ 1, "left");

right.putdown(philo_num+ 1, "right");

System.out.println("Philosopher " + (philo_num + 1) + " is thinking");

}

public void run() {

while (true) {

eat();

try {

sleep(1000);

} catch (InterruptedException e) {

}

think();

try {

sleep(1000);

} catch (InterruptedException e) {

}

}

}

}

public class Dining {

static ChopStick[] chopsticks = new ChopStick[5];

static Philosopher[] philos = new Philosopher[5];

public static void main(String args[]) {

for (int count = 0; count <= 4; count++) {

Chương 10: Luồng xử lý

Biên soạn : Bùi Công Giao --------------------------------------- 240 --------------------------------------

chopsticks[count] = new ChopStick();

}

for (int count = 0; count <= 4; count++)

philos[count] = new Philosopher(count, chopsticks[count], chopsticks[(count + 1) % 5]);

for (int count = 0; count <= 4; count++) {

philos[count].start();

}

}

}

Kết quả xuất ra màn hình như sau:

Philosopher 1 is taking up the left chopstick

Philosopher 1 is taking up the right chopstick

Philosopher 1 is eating

Philosopher 3 is taking up the left chopstick

Philosopher 3 is taking up the right chopstick

Philosopher 3 is eating

Philosopher 5 is taking up the left chopstick

Philosopher 5 is waiting for the right chopstick

Philosopher 2 is waiting for the left chopstick

Philosopher 4 is waiting for the left chopstick

Philosopher 1 is putting down the left chopstick

Philosopher 1 is putting down the right chopstick

Philosopher 1 is thinking

Philosopher 3 is putting down the left chopstick

Philosopher 3 is putting down the right chopstick

Philosopher 3 is thinking

……………………….

10.3.6 Sự bế tắt

Một sự bế tắt (deadlock) xảy ra khi hai luồng có một phụ thuộc vòng trên một cặp đối tượng đồng bộ. Ví dụ, khi một luồng thâm nhập vào monitor trên đối tượng ObjA, và một luồng khác thâm nhập vào monitor trên đối tượng ObjB. Nếu luồng trong ObjA cố gắng gọi bất kỳ phương thức đồng bộ trên ObjB, một bế tắt xảy ra. Khó gỡ lỗi một bế

Chương 10: Luồng xử lý

Biên soạn : Bùi Công Giao --------------------------------------- 241 --------------------------------------

tắt bởi nó hiếm khi xảy ra.

Xét chương trình SimpleDeadLock sau:

package chuong10;

public class SimpleDeadLock extends Thread {

public static Object l1 = new Object();

public static Object l2 = new Object();

private int index;

public static void main(String[] a) {

Thread t1 = new Thread1();

Thread t2 = new Thread2();

t1.start();

t2.start();

}

private static class Thread1 extends Thread {

public void run() {

synchronized (l1) {

System.out.println("Thread 1: Holding lock 1...");

try {

Thread.sleep(10);

} catch (InterruptedException e) {

}

System.out.println("Thread 1: Waiting for lock 2...");

synchronized (l2) {

System.out.println("Thread 2: Holding lock 1 & 2...");

}

}

}

}

private static class Thread2 extends Thread {

public void run() {

synchronized (l2) {

System.out.println("Thread 2: Holding lock 2...");

Chương 10: Luồng xử lý

Biên soạn : Bùi Công Giao --------------------------------------- 242 --------------------------------------

try {

Thread.sleep(10);

} catch (InterruptedException e) {

}

System.out.println("Thread 2: Waiting for lock 1...");

synchronized (l1) {

System.out.println("Thread 2: Holding lock 2 & 1...");

}

}

}

}

}

Kết quả của chương trình này được hiển thị như sau:

Thread 1: Holding lock 1...

Thread 2: Holding lock 2...

Thread 2: Waiting for lock 1...

Thread 1: Waiting for lock 2..

Như kết quả xuất ra, sau khi có khóa 1, luồng t1 bị treo lại để chờ đối tượng l2 mà đối tượng này được giữ bởi luồng t2 và nó sẽ không bao giờ được nhã ra, bởi vì t2 cũng bị treo lại để chờ đối tượng l1 mà đối tượng này được giữ bởi t1. Vì vậy, không luồng nào có thể di chuyển tiếp. Tạ phải nhấn Ctrl-C để dừng chương trình.

10.4 Thu rác

Thu rác (garbage collection) cải tạo hoặc làm trống bộ nhớ đã dành cho các đối tượng mà các chúng không được sử dụng trong thời gian dài. Trong ngôn ngữ lập trình hướng đối tượng khác như C++, lập trình viên phải tự giải phóng bộ nhớ. Còn Java tự động tiến trình thu rác để giải quyết vấn đề này. Một đối tượng trở nên thích hợp cho sự thu rác nếu không có tham chiếu đến nó, hoặc nếu nó đã đăng ký rỗng.

Sự thu rác thực thi như là một luồng riêng biệt có quyền ưu tiên thấp. Ta có thể viện dẫn một phương thức gc() của một đối tượng kiểu Runtime để viện dẫn sự thu rác. Ta có thể chạy phương thức thu rác, nhưng không bảo đảm rằng nó sẽ thu rác ngay lập tức.

Tham chiếu không phải là mục tiêu thu rác; chỉ các đối tượng mới được thu rác.

Ví dụ:

Chương 10: Luồng xử lý

Biên soạn : Bùi Công Giao --------------------------------------- 243 --------------------------------------

Object a = new Object();

Object b = a;

a = null;

Ở đây, sẽ là sai khi nói rằng b là một đối tượng. Nó chỉ là một đối tượng tham chiếu. Trong đoạn mã trên mặc dù a được gán là null, nhưng đối tượng được tạo ra bởi

new Object() không thể được thu rác, bởi vì nó vẫn còn có một tham chiếu (b) đến nó.

Tuy nhiên, trong ví dụ cho dưới đây, giả định rằng không có tham chiếu đến đối tượng thì nó trở nên thích hợp cho thu rác.

Object a = new Object();

a = null;

Một ví dụ khác:

Object m = new Object();

Object m = null;

Đối tượng bắt đầu có thể được thu rác

Một ví dụ khác:

Object m = new Object();

m = new Object();

Bây giờ, đối tượng đầu tiên có thể được thu rác do m đang đang tham chiếu tới đối tượng khác.

Chương trình GCTest minh hoạ cho thu rác.

package chuong10;

public class GCTest {

final int CAPACITY = 50000;

void eatMemory() {

int[] intArray = new int[CAPACITY];

for (int i = 0; i < CAPACITY; i++)

intArray[i] = i;

}

public static void main(String[] args) {

GCTest gct = new GCTest();

// tạo đối tượng kiểu Runtime

Runtime r = Runtime.getRuntime();

Chương 10: Luồng xử lý

Biên soạn : Bùi Công Giao --------------------------------------- 244 --------------------------------------

// xác định kích thước bộ nhớ trống

System.out.println("free memory before creating array: " + r.freeMemory());

// cấp phát bộ nhớ

gct.eatMemory();

// xác định kích thước bộ nhớ trống sau khi cấp phát

System.out.println("free memory after creating array: " + r.freeMemory());

// thu rác rồi kiểm tra bộ nhớ trống

r.gc();

System.out.println("free memory after running gc(): " + r.freeMemory());

}

}

Kết quả xuất ra màn hình của chương trình trên như sau:

free memory before creating array: 16047416

free memory after creating array: 15847400

free memory after running gc(): 16103664

TỔNG KẾT CHƯƠNG 10

Một luồng là đơn vị nhỏ nhất của đoạn mã thực thi được mà một tác vụ riêng biệt.

Đa luồng giữ cho thời gian rỗi là nhỏ nhất. Điều này cho phép ta viết các chương trình có khả năng sử dụng tối đa CPU.

Luồng bắt đầu thực thi sau khi phương thức start() được gọi

Lập trình viên, máy ảo Java, hoặc hệ điều hành bảo đảm rằng CPU được chia sẻ giữa các luồng.

Có hai loại luồng trong một chương trình Java:

Luồng người dùng

Luồng ngầm.

Một nhóm luồng dùng để quản lý một nhóm các luồng.

Đồng bộ cho phép chỉ một luồng thâm nhập một tài nguyên được chia sẻ tại một

Chương 10: Luồng xử lý

Biên soạn : Bùi Công Giao --------------------------------------- 245 --------------------------------------

thời điểm.

Để tránh kiểm soát vòng, Java bao gồm một thiết kế tốt trong tiến trình kỹ thuật truyền thông sử dụng các phương thức wait(), notify() và notifyAll()

Một bế tắt xảy ra khi hai luồng có mọt phụ thuộc xoay vòng trên một phần của các đối tượng đồng bộ

Thu rác là một tiến trình nhờ đó bộ nhớ được định vị để các đối tượng mà không sử dụng trong thời gian dài, có thể cải tạo hoặc làm rãnh bộ nhớ.

BÀI TẬP

1. Kiểm tra lại sự hiểu biết

a. Một ứng dụng có thể chứa đựng nhiều luồng Đúng/Sai

b. Các luồng con được tạo ra từ luồng chính Đúng/Sai

c. Mỗi luồng trong một chương trình Java được đăng ký một quyền ưu tiên mà máy ảo Java có thể thay đổi. Đúng/Sai

d. Phương thức____________ có thể tạm thời ngừng việc thực thi luồng

e. Mặc định, một luồng có một quyền ưu tiên ________ một hằng số của _______

f. _________ luồng được dùng cho các luồng “nền”, cung cấp dụch vụ cho luồng khác.

g. Trong luồng đồng bộ, một __________ là một đối tượng mà được sử dụng như là một khóa riêng biệt lẫn nhau.

h. ___________ thường thực thi bởi một vòng lặp mà được sử dụng để lặp lại việc kiểm tra một số điều kiện.

2. Viết một chương trình mà hiển thị một sự đếm lùi từng giây cho đến không, như hình sau:

Ban đầu, số 300 sẽ được hiển thị. Giá trị sẽ được giảm dần cho đến 1 đến khi ngoài giá trị 0. Giá trị sẽ được trả lại 300 một lần nữa giảm đến trở thành 0.

Chương 10: Luồng xử lý

Biên soạn : Bùi Công Giao --------------------------------------- 246 --------------------------------------

3. Viết một chương trình hiển thị như hình dưới đây:

Tạo ba luồng và một luồng chính trong main. Thực thi mỗi luồng như một chương trình thực thi. Khi chương trình kết thúc, các câu lệnh thoát cho mỗi luồng sẽ được hiển thị. Sử dụng kỹ thuật nắm bắt lỗi.

 

Biên soạn : Bùi Công Giao --------------------------------------- 247 --------------------------------------

Tham khảo

[1] Jacquie Barker, Beginning Java Objects: From Concepts to Code, Second Edition, Apress, 2005

[2] Trần Đình Quế và Nguyễn Mạnh Hùng, Bài giảng Lập trình Hướng đối tượng, Học viện Công nghệ Bưu chính Viễn thông

[3] Giáo trình Aptech Java Simplified I

[4] Introduction to File Input and Output, http://www.seas.upenn.edu/~cis1xx/resources/java/fileIO/introToFileIO.html

[5] http://vi.wikipedia.org/wiki/Java(ngôn_ngữ_lập_trình)

[6] Nhập môn Java với JDBC và MySQL, http://www.aptech.vn/news/laptrinhvien/612/

[7] Menu Demo, http://www.java2s.com/Code/Java/Swing-JFC/MenuDemo.htm

[8] Link List Example in Java, http://www.roseindia.net/java/beginners/linked-list-demo.shtml

[9] ThreadGroup Sample in Java, http://www.java-samples.com/showtutorial.php?tutorialid=233

[10] Java Thread Deadlock Demo Program,

http://www.herongyang.com/Java-Tools/jstack-Java-Thread-Deadlock-Demo-Program.html

[11] Java Runtime class - Invoke the Java garbage collector, http://www.devdaily.com/java/edu/pj/pj010008

[12] Overloading, Overriding, Runtime Types and Object Orientation - Overloading Methods, http://www.janeg.ca/scjp/overload/overloadingMethods.html

[13] http://www.congdongjava.com

[14] Trịnh Thanh Bình, Trương Anh Hoàng, Nguyễn Việt Hà, Kiểm chứng giao thức tương tác giữa các thành phần trong chương trình đa luồng sử dụng lập trình hướng khía cạnh, Tạp chí thông tin, khoa học công nghệ của Bộ thông tin truyền thông, 11-2010, trang 37


Recommended