Giáo trình Lập trình hướng đối tượng với Java (Phần 1)
Lập trình là công đoạn quan trọng chủ chốt và không thể thiếu để tạo ra sản
phẩm phần mềm. Phần mềm càng trở nên đa dạng và ngành công nghiệp phần mềm
càng phát triển thì người ta càng thấy rõ tầm quan trọng của phương pháp lập trình.
Phương pháp lập trình tốt không chỉ đảm bảo tạo ra phần mềm tốt mà còn hỗ trợ
thiết kế phần mềm có tính mở và hỗ trợ khả năng sử dụng lại các mô đun. Nhờ đó
chúng ta có thể dễ dàng bảo trì, nâng cấp phần mềm cũng như giảm chi phí phát
triển phần mềm.
Trong những thập kỷ 1970, 1980, phương pháp phát triển phần mềm chủ yếu là
lập trình có cấu trúc (structured programming). Cách tiếp cận cấu trúc đối với việc
thiết kế chương trình dựa trên chiến lược chia để trị: Để giải một bài toán lớn, chúng
ta tìm cách chia nó thành vài bài toán nhỏ hơn và giải riêng từng bài; để giải mỗi bài,
hãy coi nó như một bài toán mới và có thể tiếp tục chia nó thành các bài toán nhỏ
hơn; cuối cùng, ta sẽ đi đến những bài toán có thể giải ngay được mà không cần phải
chia tiếp. Cách tiếp cận này được gọi là lập trình từ trên xuống (top-down
programming).
Lập trình từ trên xuống là một phương pháp tốt và đã được áp dụng thành công
cho phát triển rất nhiều phần mềm. Tuy nhiên, cùng với sự đa dạng và phức tạp của
phần mềm, phương pháp này bộc lộ những hạn chế. Trước hết, nó hầu như chỉ đáp
ứng việc tạo ra các lệnh hay là các quy trình để giải quyết một bài toán. Dần dần,
người ta nhận ra rằng thiết kế các cấu trúc dữ liệu cho một chương trình có tầm
quan trọng không kém việc thiết kế các hàm/thủ tục và các cấu trúc điều khiển. Lập
trình từ trên xuống không quan tâm đủ đến dữ liệu mà chương trình cần xử lý.
Thứ hai, với lập trình từ trên xuống, chúng ta khó có thể tái sử dụng các phần
của chương trình này cho các chương trình khác. Bằng việc xuất phát từ một bài toán
cụ thể và chia nó thành các mảnh sao cho thuận, cách tiếp cận này có xu hướng tạo
ra một thiết kế đặc thù cho chính bài toán đó. Chúng ta khó có khả năng lấy một
đoạn mã lớn từ một chương trình cũ lắp vào một dự án mới mà không phải sửa đổi
lớn. Việc xây dựng các chương trình chất lượng cao là khó khăn và tốn kém, do đó
những nhà phát triển phần mềm luôn luôn muốn tái sử dụng các sản phẩm cũ.
Thứ ba, môi trường hoạt động trong thực tế của các ứng dụng luôn thay đổi.
Dẫn đến việc yêu cầu phần mềm cũng phải liên tục thay đổi theo để đáp ứng nhu
cầu của người dùng nếu không muốn phần mềm bị đào thải. Do đó, một thiết kế
linh hoạt mềm dẻo là cái mà các nhà phát triển phần mềm mong muốn. Phương
pháp tiếp cận từ dưới lên (bottom-up) hỗ trợ tốt hơn cho tính linh hoạt mềm dẻo đó.
Trong thực tế, thiết kế và lập trình từ trên xuống thường được kết hợp với thiết
kế và lập trình từ dưới lên. Trong tiếp cận từ dưới lên, từ các vấn đề mà ta đã biết8
cách giải và có thể đã có sẵn các thành phần tái sử dụng được chúng ta xây dựng
dần theo hướng lên trên, hướng đến một giải pháp cho bài toán tổng.
Tóm tắt nội dung tài liệu: Giáo trình Lập trình hướng đối tượng với Java (Phần 1)
1 Mục lục GIỚI THIỆU .............................................................................5 Chương 1. MỞ ĐẦU ............................................................7 1.1. KHÁI NIỆM CƠ BẢN ................................................ 12 1.2. ĐỐI TƯỢNG VÀ LỚP................................................ 13 1.3. CÁC NGUYÊN TẮC TRỤ CỘT ................................ 15 Chương 2. NGÔN NGỮ LẬP TRÌNH JAVA ................... 20 2.1. ĐẶC TÍNH CỦA JAVA .............................................. 20 2.1.1. Máy ảo Java – Java Virtual Machine ............... 21 2.1.2. Các nền tảng Java ............................................. 23 2.1.3. Môi trường lập trình Java ................................ 23 2.1.4. Cấu trúc mã nguồn Java .................................. 24 2.1.5. Chương trình Java đầu tiên ............................. 25 2.2. BIẾN ............................................................................. 27 2.3. CÁC PHÉP TOÁN CƠ BẢN...................................... 28 2.3.1. Phép gán ............................................................ 28 2.3.2. Các phép toán số học........................................ 28 2.3.3. Các phép toán khác .......................................... 29 2.3.4. Độ ưu tiên của các phép toán .......................... 30 2.4. CÁC CẤU TRÚC ĐIỀU KHIỂN ................................ 30 2.4.1. Các cấu trúc rẽ nhánh....................................... 31 2.4.2. Các cấu trúc lặp ................................................ 37 2.4.3. Biểu thức điều kiện trong các cấu trúc điều khiển 43 Chương 3. LỚP VÀ ĐỐI TƯỢNG .................................... 48 3.1. TẠO VÀ SỬ DỤNG ĐỐI TƯỢNG ............................ 49 3.2. TƯƠNG TÁC GIỮA CÁC ĐỐI TƯỢNG ................. 51 Chương 4. BIẾN VÀ CÁC KIỂU DỮ LIỆU ...................... 57 4.1. BIẾN VÀ CÁC KIỂU DỮ LIỆU CƠ BẢN ................. 58 4.2. THAM CHIẾU ĐỐI TƯỢNG VÀ ĐỐI TƯỢNG ...... 59 4.3. PHÉP GÁN .................................................................. 62 4.4. CÁC PHÉP SO SÁNH ................................................ 63 2 4.5. MẢNG ......................................................................... 64 Chương 5. HÀNH VI CỦA ĐỐI TƯỢNG ....................... 70 5.1. PHƯƠNG THỨC VÀ TRẠNG THÁI ĐỐI TƯỢNG70 5.2. TRUYỀN THAM SỐ VÀ GIÁ TRỊ TRẢ VỀ .............. 71 5.3. CƠ CHẾ TRUYỀN BẰNG GIÁ TRỊ .......................... 73 5.4. ĐÓNG GÓI VÀ CÁC PHƯƠNG THỨC TRUY NHẬP 75 5.5. KHAI BÁO VÀ KHỞI TẠO BIẾN THỰC THỂ........ 79 5.6. BIẾN THỰC THỂ VÀ BIẾN ĐỊA PHƯƠNG ........... 80 Chương 6. SỬ DỤNG THƯ VIỆN JAVA ......................... 85 6.1. ArrayList ..................................................................... 85 6.2. SỬ DỤNG JAVA API ................................................. 87 6.3. MỘT SỐ LỚP THÔNG DỤNG TRONG API ........... 88 6.3.1. Math ................................................................... 88 6.3.2. Các lớp bọc ngoài kiểu dữ liệu cơ bản ............ 89 6.3.3. Các lớp biểu diễn xâu kí tự .............................. 90 6.4. TRÒ CHƠI BẮN TÀU ................................................ 91 Chương 7. THỪA KẾ VÀ ĐA HÌNH ............................. 103 7.1. QUAN HỆ THỪA KẾ .............................................. 103 7.2. THIẾT KẾ CÂY THỪA KẾ ...................................... 104 7.3. CÀI ĐÈ – PHƯƠNG THỨC NÀO ĐƯỢC GỌI? ... 107 7.4. CÁC QUAN HỆ IS-A VÀ HAS-A ........................... 108 7.5. KHI NÀO NÊN DÙNG QUAN HỆ THỪA KẾ?.... 110 7.6. LỢI ÍCH CỦA QUAN HỆ THỪA KẾ ..................... 110 7.7. ĐA HÌNH .................................................................. 111 7.8. GỌI PHIÊN BẢN PHƯƠNG THỨC CỦA LỚP CHA114 7.9. CÁC QUY TẮC CHO VIỆC CÀI ĐÈ ....................... 115 7.10. CHỒNG PHƯƠNG THỨC .................................... 116 7.11. CÁC MỨC TRUY NHẬP ....................................... 117 Chương 8. LỚP TRỪU TƯỢNG VÀ INTERFACE ........ 124 8.1. MỘT SỐ LỚP KHÔNG NÊN TẠO THỰC THỂ .... 124 8.2. LỚP TRỪU TƯỢNG VÀ LỚP CỤ THỂ ................. 126 3 8.3. PHƯƠNG THỨC TRỪU TƯỢNG .......................... 127 8.4. VÍ DỤ VỀ ĐA HÌNH ................................................ 127 8.5. LỚP Object ................................................................ 131 8.6. ĐỔI KIỂU – KHI ĐỐI TƯỢNG MẤT HÀNH VI CỦA MÌNH 132 8.7. ĐA THỪA KẾ VÀ VẤN ĐỀ HÌNH THOI.............. 135 8.8. INTERFACE .............................................................. 137 Chương 9. VÒNG ĐỜI CỦA ĐỐI TƯỢNG ................... 143 9.1. BỘ NHỚ STACK VÀ BỘ NHỚ HEAP ................... 143 9.2. KHỞI TẠO ĐỐI TƯỢNG ........................................ 145 9.3. HÀM KHỞI TẠO VÀ VẤN ĐỀ THỪA KẾ ............ 149 9.3.1. Gọi hàm khởi tạo của lớp cha ........................ 150 9.3.2. Truyền đối số cho hàm khởi tạo lớp cha ...... 152 9.4. HÀM KHỞI TẠO CHỒNG NHAU ........................ 153 9.5. TẠO BẢN SAO CỦA ĐỐI TƯỢNG ....................... 154 9.6. CUỘC ĐỜI CỦA ĐỐI TƯỢNG............................... 159 Chương 10. THÀNH VIÊN LỚP VÀ THÀNH VIÊN THỰC THỂ 164 10.1. BIẾN CỦA LỚP ...................................................... 164 10.2. PHƯƠNG THỨC CỦA LỚP ................................. 165 10.3. GIỚI HẠN CỦA PHƯƠNG THỨC LỚP ............. 167 10.4. KHỞI TẠO BIẾN LỚP ........................................... 169 10.5. MẪU THIẾT KẾ SINGLETON .............................. 170 10.6. THÀNH VIÊN BẤT BIẾN – final .......................... 171 Chương 11. NGOẠI LỆ ................................................... 174 11.1. NGOẠI LỆ LÀ GÌ? .................................................. 175 11.1.1. Tình huống sự cố .......................................... 175 11.1.2. Xử lý ngoại lệ ................................................ 177 11.1.3. Ngoại lệ là đối tượng .................................... 178 11.2. KHỐI try/catch ........................................................ 179 11.2.1. Bắt nhiều ngoại lệ ......................................... 179 11.2.2. Hoạt động của khối try/catch ...................... 180 11.2.3. Khối finally – những việc dù thế nào cũng phải làm 182 4 11.2.4. Thứ tự cho các khối catch ............................ 183 11.3. NÉM NGOẠI LỆ ..................................................... 184 11.4. NÉ NGOẠI LỆ ........................................................ 185 11.5. NGOẠI LỆ ĐƯỢC KIỂM TRA VÀ KHÔNG ĐƯỢC KIỂM TRA 189 11.6. ĐỊNH NGHĨA KIỂU NGOẠI LỆ MỚI ................. 190 11.7. NGOẠI LỆ VÀ CÁC PHƯƠNG THỨC CÀI ĐÈ . 191 Chương 12. CHUỖI HÓA ĐỐI TƯỢNG VÀ VÀO RA FILE 196 12.1. QUY TRÌNH GHI ĐỐI TƯỢNG............................ 197 12.2. CHUỖI HÓA ĐỐI TƯỢNG ................................... 199 12.3. KHÔI PHỤC ĐỐI TƯỢNG .................................... 202 12.4. GHI CHUỖI KÍ TỰ RA TỆP VĂN BẢN ............... 205 12.4.1. Lớp File .......................................................... 206 12.4.2. Bộ nhớ đệm ................................................... 207 12.5. ĐỌC TỆP VĂN BẢN .............................................. 207 12.6. CÁC DÒNG VÀO/RA TRONG Java API ............. 209 Chương 13. LẬP TRÌNH TỔNG QUÁT VÀ CÁC LỚP COLLECTION 215 13.1. LỚP TỔNG QUÁT ................................................. 217 13.2. PHƯƠNG THỨC TỔNG QUÁT ........................... 219 13.3. CÁC CẤU TRÚC DỮ LIỆU TỔNG QUÁT TRONG JAVA API 220 13.4. ITERATOR VÀ VÒNG LẶP FOR EACH ............. 222 13.5. SO SÁNH NỘI DUNG ĐỐI TƯỢNG ................... 224 13.5.1. So sánh bằng ................................................. 224 13.5.2. So sánh lớn hơn/nhỏ hơn ............................. 226 13.6. KÍ TỰ ĐẠI DIỆN TRONG KHAI BÁO THAM SỐ KIỂU 228 Phụ lục A. DỊCH CHƯƠNG TRÌNH BẰNG JDK .......... 233 Phụ lục B. PACKAGE – TỔ CHỨC GÓI CỦA JAVA .... 236 Phụ lục C. BẢNG THUẬT NGỮ ANH-VIỆT ................. 239 Tµi liÖu tham kh¶o ............................................................... 241 5 Giíi thiÖu Phần mềm ngày càng lớn và phức tạp và đòi hỏi được cập nhật liên tục để đáp ứng những yêu cầu mới của người dùng. Phương pháp lập trình thủ tục truyền thống dần trở nên không đáp ứng được những đòi hỏi đó của ngành công nghiệp phần mềm. Lập trình hướng đối tượng đã ra đời trong bối cảnh như vậy để hỗ trợ sử dụng lại và phát triển các phần mềm qui mô lớn. Giáo trình này cung cấp cho sinh viên các kiến thức từ cơ bản cho đến một số kỹ thuật nâng cao về phương pháp lập trình hướng đối tượng. Giáo trình dùng cho sinh viên ngành Công nghệ thông tin đã có kiến thức căn bản về lập trình. Giáo trình sử dụng ngôn ngữ lập trình Java để minh họa và đồng thời cũng giới thiệu một số kiến thức căn bản của ngôn ngữ này. Các nội dung chính về phương pháp lập trình hướng đối tượng được trình bày trong giáo trình bao gồm lớp và đối tượng, đóng gói/che giấu thông tin, kế thừa và đa hình, xử lý ngoại lệ và lập trình tổng quát. Ngoài ra, giáo trình cũng trình bày các kiến thức về Java bao gồm các đặc trưng cơ bản của ngôn ngữ, các thư viện cơ bản và cách thức tổ chức vào/ra dữ liệu. Thay vì cách trình bày theo tính hàn lâm về một chủ đề rộng, để thuận tiện cho giảng dạy, giáo trình chọn cách trình bày theo các bài học cụ thể được sắp xếp theo trình tự kiến thức từ cơ sở đến chuyên sâu. Mỗi chủ đề có thể được giảng dạy với thời lượng 2~3 giờ lý thuyết và giờ thực hành tương ứng. Ch-¬ng 2 và Ch-¬ng 6, với nội dung là các kiến thức cơ bản về ngôn ngữ lập trình Java, tuy cần thiết nhưng không phải nội dung trọng tâm của môn học Lập trình hướng đối tượng. Các chương này, do đó, nên để sinh viên tự học. Chương 9 và Chương 10 không nhất thiết phải được dạy thành những chủ đề độc lập mà có thể được tách rải rác các nội dung kiến thức và giới thiệu kèm theo các khái niệm hướng đối tượng có liên quan, hoặc yêu cầu sinh viên tự đọc khi cần đến các kiến thức này trong quá trình thực hành. Tuy cuốn giáo trình này không trình bày sâu về lập trình Java, nhưng kiến thức về lập trình Java lại là cần thiết đối với sinh viên, ngay cả với mục đích thực hành môn học. Do đó, ngoài mục đích thực hành các nội dung liên quan đến lập trình hướng đối tượng, các bài tập thực hành của môn học này nên có thêm đóng vai trò định hướng và gợi ý giúp đỡ sinh viên tự học các chủ đề thuần túy Java mà giáo viên cho là cần thiết, chẳng hạn như học về vào ra dữ liệu đơn giản ngay từ tuần đầu tiên của môn học. Các định hướng này có thể được thể hiện ở những bài tập thực hành với những đoạn chương trình mẫu, hoặc yêu cầu tìm hiểu tài liệu API về một số lớp tiện ích. Một số bài tập cuối chương là ví dụ của dạng bài tập này. 6 Các thuật ngữ hướng đối tượng nguyên gốc tiếng Anh đã được chuyển sang tiếng Việt theo những cách khác nhau tùy các tác giả. Sinh viên cần biết thuật ngữ nguyên gốc tiếng Anh cũng như các cách dịch khác nhau đó để tiện cho việc sử dụng tài liệu tiếng Anh cũng như để liên hệ kiến thức giữa các tài liệu tiếng Việt. Vì lí do đó, giáo trình này cung cấp bảng thuật ngữ Anh-Việt với các cách dịch khác nhau tại Phụ lục C, bên cạnh Phụ lục A về công cụ lập trình JDK và Phụ lục B về tổ chức gói của ngôn ngữ Java. Các tác giả chân thành cảm ơn PGS. TS. Nguyễn Đình Hóa, TS. Trương Anh Hoàng, TS. Cao Tuấn Dũng, TS. Đặng Đức Hạnh, cũng như các đồng nghiệp và sinh viên tại Khoa Công nghệ thông tin, Trường Đại học Công nghệ đã đọc bản thảo giáo trình và có các góp ý quí báu về nội dung chuyên môn cũng như cách thức trình bày. Tuy vậy, giáo trình vẫn còn nhiều khiếm khuyết, các tác giả mong tiếp tục nhận được góp ý để hoàn thiện trong tương lai. 7 Ch−¬ng 1. më ®Çu Lập trình là công đoạn quan trọng chủ chốt và không thể thiếu để tạo ra sản phẩm phần mềm. Phần mềm càng trở nên đa dạng và ngành công nghiệp phần mềm càng phát triển thì người ta càng thấy rõ tầm quan trọng của phương pháp lập trình. Phương pháp lập trình tốt không chỉ đảm bảo tạo ra phần mềm tốt mà còn hỗ trợ thiết kế phần mềm có tính mở và hỗ trợ khả năng sử dụng lại các mô đun. Nhờ đó chúng ta có thể dễ dàng bảo trì, nâng cấp phần mềm cũng như giảm chi phí phát triển phần mềm. Trong những thập kỷ 1970, 1980, phương pháp phát triển phần mềm chủ yếu là lập trình có cấu trúc (structured programming). Cách tiếp cận cấu trúc đối với việc thiết kế chương trình dựa trên chiến lược chia để trị: Để giải một bài toán lớn, chúng ta tìm cách chia nó thành vài bài toán nhỏ hơn và giải riêng từng bài; để giải mỗi bài, hãy coi nó như một bài toán mới và có thể tiếp tục chia nó thành các bài toán nhỏ hơn; cuối cùng, ta sẽ đi đến những bài toán có thể giải ngay được mà không cần phải chia tiếp. Cách tiếp cận này được gọi là lập trình từ trên xuống (top-down programming). Lập trình từ trên xuống là một phương pháp tốt và đã được áp dụng thành công cho phát triển rất nhiều phần mềm. Tuy nhiên, cùng với sự đa dạng và phức tạp của phần mềm, phương pháp này bộc lộ những hạn chế. Trước hết, nó hầu như chỉ đáp ứng việc tạo ra các lệnh hay là các quy trình để giải quyết một bài toán. Dần dần, người ta nhận ra rằng thiết kế các cấu trúc dữ liệu cho một chương trình có tầm quan trọng không kém việc thiết kế các hàm/thủ tục và các cấu trúc điều khiển. Lập trình từ trên xuống không quan tâm đủ đến dữ liệu mà chương trình cần xử lý. Thứ hai, với lập trình từ trên xuống, chúng ta khó có thể tái sử dụng các phần của chương trình này cho các chương trình khác. Bằng việc xuất phát từ một bài toán cụ thể và chia nó thành các mảnh sao cho thuận, cách tiếp cận này có xu hướng tạo ra một thiết kế đặc thù cho chính bài toán đó. Chúng ta khó có khả năng lấy một đoạn mã lớn từ một chương trình cũ lắp vào một dự án mới mà không phải sửa đổi lớn. Việc xây dựng các chương trình chất lượng cao là khó khăn và tốn kém, do đó những nhà phát triển phần mềm luôn luôn muốn tái sử dụng các sản phẩm cũ. Thứ ba, môi trường hoạt động trong thực tế của các ứng dụng luôn thay đổi. Dẫn đến việc yêu cầu phần mềm cũng phải liên tục thay đổi theo để đáp ứng nhu cầu của người dùng nếu không muốn phần mềm bị đào thải. Do đó, một thiết kế linh hoạt mềm dẻo là cái mà các nhà phát triển phần mềm mong muốn. Phương pháp tiếp cận từ dưới lên (bottom-up) hỗ trợ tốt hơn cho tính linh hoạt mềm dẻo đó. Trong thực tế, thiết kế và lập trình từ trên xuống thường được kết hợp với thiết kế và lập trình từ dưới lên. Trong tiếp cận từ dưới lên, từ các vấn đề mà ta đã biết 8 cách giải và có thể đã có sẵn các thành phần tái sử dụng được chúng ta xây dựng dần theo hướng lên trên, hướng đến một giải pháp cho bài toán tổng. Các thành phần tái sử dụng được nên có tính mô-đun hóa cao nhất có thể. Mỗi mô-đun là một thành phần của một hệ thống lớn hơn, nó tương tác với phần còn lại của hệ thống theo một cách đơn giản và được quy ước chặt chẽ. Ý tưởng ở đây là một mô-đun có thể được "lắp vào" một hệ thống. Chi tiết về những gì xảy ra bên trong mô-đun không cần được xét đến đối với hệ thống nói chung, miễn là mô-đun đó hoàn thành tốt vai trò được giao. Đây gọi ... viện. Thay cho một lô các lớp đặt cùng một chỗ, các lớp được đặt vào các gói khác nhau tùy theo chức năng, chẳng hạn GUI, cấu trúc dữ liệu, hay cơ sở dữ liệu. Thứ hai, cấu trúc gói cho ta một không gian tên, giúp tránh trùng tên. Nếu một loạt lập trình viên tạo các lớp có tên giống nhau nhưng đặt tại các gói khác nhau thì máy ảo Java vẫn có thể được các lớp đó. Thứ ba, tổ chức gói cho ta một mức bảo mật (mức gói), ta có thể hạn chế mã ta viết trong một gói để chỉ có các lớp nằm trong gói đó mới có thể truy nhập. Ta sẽ nói kĩ hơn về vấn đề này sau. Sử dụng API bằng cách nào? Ta cần biết hai điều: (1) trong thư viện có những lớp nào, (2) khi đã tìm thấy một lớp, làm thế nào để biết nó có thể làm được gì. Để trả lời cho hai câu hỏi đó, ta có thể tra cứu một cuốn sách về Java hoặc tài liệu API. 88 Hình 6.2: Tài liệu API phiên bản Java 6, trang về ArrayList. Tài liệu API là nguồn tài liệu tốt nhất để tìm chi tiết về từng lớp và các phương thức của nó. Tại đó, ta có thể tìm và duyệt theo gói, tìm và tra cứu theo tên lớp. Với mỗi lớp, ta có đầy đủ thông tin mô tả lớp, các lớp liên quan, danh sách các phương thức, và đặc tả chi tiết của từng phương thức. 6.3. MỘT SỐ LỚP THÔNG DỤNG TRONG API 6.3.1. Math Math là lớp cung cấp các hàm toán học thông dụng. • Math.random() : trả về một giá trị kiểu double trong khoảng [0.0,..,1.0). • Math.abs() : trả về một giá trị double là giá trị tuyệt đối của đối số kiểu double, tương tự đối với đối số và giá trị trả về kiểu int. • Math.round() : trả về một giá trị int hoặc long (tùy theo đối số là kiểu float hay double) là giá trị làm tròn của đối số tới giá trị nguyên gần nhất. Lưu ý rằng các hằng kiểu float được Java hiểu là thuộc kiểu double trừ khi thêm kí tự f vào cuối, ví dụ 1.2f. • Math.min() : trả về giá trị nhỏ hơn trong hai đối số. Đối số có thể là int, long, float, hoặc double. • Math.max(): trả về giá trị lớn hơn trong hai đối số. Đối số có thể là int, long, float, hoặc double. Ngoài ra, Math còn các phương thức khác như sqrt(), tan(), ceil(), floor(), và sin(). Ta nên tra cứu chi tiết tại tài liệu API. 89 6.3.2. Các lớp bọc ngoài kiểu dữ liệu cơ bản Đôi khi, ta muốn đối xử với một giá trị kiểu cơ bản như là một đối tượng. Ví dụ, ở các phiên bản Java trước 5.0, ta không thể chèn thẳng một giá trị kiểu cơ bản vào trong một cấu trúc kiểu ArrayList. Các lời gọi tương tự như list.add(2) sẽ bị trình biên dịch báo lỗi do phương thức add lấy đối số là tham chiếu đối tượng. Trong những trường hợp như vậy, ta có các lớp bọc ngoài mỗi kiểu cơ bản (wrapper class). Các lớp bọc ngoài này có tên gần trùng với tên kiểu cơ bản tương ứng: Boolean, Character, Byte, Short, Integer, Long, Float, Double. Mỗi đối tượng thuộc các lớp trên bao bọc một giá trị kiểu cơ bản tương ứng, kèm theo các phương thức để thao tác với giá trị đó. Ví dụ: Hình 6.3: Sử dụng lớp Integer. Các lớp bọc ngoài khác cũng có cách sử dụng và các phương thức tiện ích tương tự như Integer. chẳng hạn mỗi đối tượng Boolean có phương thức booleanValue() trả về giá trị boolean chứa trong nó. Tóm lại, nếu dùng phiên bản Java trước 5.0 hay từ 5.0 trở đi, ta sẽ sử dụng ArrayList cho các giá trị int theo kiểu như sau: Với các phiên bản Java từ 5.0 trở đi, trình biên dịch tự động làm hộ ta các công việc bọc và gỡ các đối tượng bọc ngoài thuộc kiểu tương ứng. Nói cách khác, 90 ArrayList thực sự là danh sách của các đối tượng Integer, nhưng ta có thể coi như ArrayList lấy vào và trả về các giá trị int. Trình biên dịch không chỉ tự động bọc và gỡ bọc trong các tình huống sử dụng các cấu trúc dữ liệu tương tự ArrayList. Việc này còn xảy ra ở hầu hết các tình huống khác: • Đối số của phương thức: dù một phương thức khai báo tham số kiểu cơ bản hay kiểu lớp bọc ngoài thì nó vẫn chấp nhận đối số ở cả dạng cơ bản cũng như kiểu lớp bọc ngoài. • Giá trị trả về: dù một phương thức khai báo kiểu trả về kiểu cơ bản hay bọc ngoài thì lệnh return trong phương thức dùng giá trị ở cả dạng cơ bản cũng như bọc ngoài đều được. • Biểu thức boolean: ở những vị trí yêu cầu một biểu thức boolean, ta có thể dùng biểu thức cho giá trị boolean (chẳng hạn 2 < a), hoặc một biến boolean, hoặc một tham chiếu kiểu Boolean đều được. • Phép toán số học: ta có thể dùng tham chiếu kiểu bọc ngoài làm toán hạng của các phép toán số học, kể cả phép ++. • Phép gán: ta có thể dùng một tham chiếu kiểu bọc ngoài để gán trị cho một biến kiểu cơ bản và ngược lại. Ví dụ: Double d = 10.0; 6.3.3. Các lớp biểu diễn xâu kí tự String và StringBuffer là hai lớp thông dụng để biểu diễn dữ liệu dạng xâu kí tự. String dành cho các chuỗi kí tự không thể sửa đổi nội dung. Tất cả các hằng xâu kí tự như "abc" đều được Java coi như các thực thể của lớp String. StringBuffer và StringBuilder cho phép sửa đổi nội dung chuỗi, sử dụng một trong hai lớp này sẽ hiệu quả hơn String nếu ta cần dùng nhiều thao tác sửa xâu. Từ Java 5.0, ta nên dùng StringBuilder thay vì String Buffer cho mục đích này, trừ khi ta cần chú ý tránh xung đột giữa các thao tác xử lý xâu tại các luồng khác nhau. String và StringBuffer/StringBuilder đều có các phương thức sau: • charAt (int index) trả về kí tự tại một vị trí • compareTo() so sánh giá trị với một đối tượng cùng loại. • các phương thức indexOf() tìm vị trí của một kí tự/xâu con theo chiều từ trái sang phải. • các phương thức lastIndexOf() tìm vị trí của một kí tự/xâu con theo chiều từ phải sang trái. • length() trả về độ dài của xâu. • substring(int start, int end) trả về đối tượng String là xâu con. Để nối xâu, ta dùng concat() cho String và append() cho StringBuffer/StringBuilder. Ngoài ra, String còn có thêm các tiện ích : 91 • valueOf() trả về biểu diễn kiểu String của một giá trị thuộc kiểu cơ bản, • split() để tách xâu thành các từ con theo một cú pháp cho trước, • replace(char old, char new) trả về một String mới là kết quả của việc thay thế hết các kí tự old bằng kí tự new • trim() trả về một String mới là kết quả của việc xóa các kí tự trắng ở đầu và cuối String hiện tại. StringBuffer và StringBuilder có các phương thức cung cấp các phương thức để chèn (insert), thay (replace), xóa một phần (delete), đảo xâu (reverse) tại đối tượng StringBuffer/StringBuilder hiện tại. Ta đã biết những cách đơn giản để lấy biểu diễn bằng xâu kí tự cho các giá trị số: int n = 302044; String s1 = "" + n; String s2 = Integer.toString(n); Đôi khi, ta cần biểu diễn các giá trị số một cách cầu kì hơn, chẳng hạn 302,044, hay quy định số chữ số nằm sau dấu phảy thập phân sẽ được in ra, biểu diễn dạng nhị phân, hệ cơ số 16... Phương thức format() của lớp String giúp chúng ta làm được việc này. Ví dụ: 6.4. TRÒ CHƠI BẮN TÀU Trong mục này, ta sẽ làm một chương trình ví dụ: trò chơi bắn tàu SinkAShip6. Đây sẽ là một ứng dụng hoàn chỉnh minh họa việc sử dụng Java API, và cũng là một ứng dụng đủ lớn để minh họa rõ hơn sự tương tác giữa các đối tượng trong chương trình. Trò chơi bắn tàu được mô tả như sau: Máy tính có một số con tàu kích thước 1 x 3 trên một vùng là lưới vuông 7 x 7, cho phép người chơi bắn mỗi lần một viên đạn, mỗi viên trúng ô nào sẽ làm cháy phần tàu nằm trong ô đó, nếu như ở đó có tàu. Người chơi không biết các con tàu đó ở đâu, nhưng có mục tiêu là bắn cháy hết tàu, nên phải đoán xem nên bắn vào đâu để tốn càng ít đạn càng tốt. 6 Chỉnh sửa từ ví dụ DotComBust của cuốn Head First Java, 2nd Edition. 92 Khi bắt đầu một ván chơi, chương trình sẽ đặt ngẫu nhiên ba con tàu vào một lưới ảo kích thước 7x7, sau đó mời người chơi bắn phát đầu tiên. Ta chưa học lập trình giao diện đồ họa, do đó chương trình của chúng ta sẽ sử dụng giao diện dòng lệnh. Mỗi lần, chương trình sẽ mời người chơi nhập tọa độ một phát bắn, người chơi nhập một tọa độ có dạng "A5" hay "B1". Chương trình xử lý phát bắn, kiểm tra xem có trúng hay không rồi in ra màn hình một thông báo thuộc một trong các loại: "hit" (trúng), "miss" (trượt), hoặc "You sunk a ship" (khi một tàu vừa bị bắn cháy hết). Khi cả ba con tàu đều bị cháy hết, ván chơi kết thúc, chương trình thông báo điểm của người chơi. Tọa độ trong trò chơi có dạng "A4", trong đó kí tự thứ nhất là một chữ cái trong đoạn từ A đến G đại diện cho tọa độ dòng, kí tự thứ hai là một chữ số trong đoạn từ 0 đến 6 đại diện cho tọa độ cột trong lưới vuông 7x7. Thiết kế mức cao cho hoạt động của chương trình: Bước tiếp theo là xác định ta cần đến các đối tượng nào. Ít nhất, ta sẽ cần đến ván chơi và các mô hình tàu, tương ứng với hai lớp SinkAShip và Ship. Khi viết một lớp, quy trình chung được gợi ý như sau: 93 • Xác định các nhiệm vụ và hoạt động của lớp • Liệt kê các biến thực thể và phương thức • Viết mã giả cho các phương thức để mô tả thuật toán/quy trình công việc của chúng. • Viết chương trình test cho các phương thức. • Cài đặt lớp • Test các phương thức • Tìm lỗi và cài lại nếu cần • Test với người dùng thực. Ta sẽ bỏ qua bước cuối cùng. Đầu tiên là lớp Ship, ta cần lưu hai thông tin chính: tọa độ các ô của tàu và tàu đã bị bắn cháy hết hay chưa. Dưới đây là thiết kế mà ta dễ dàng nghĩ đến. Nhưng thiết kế trên chưa tính đến trường hợp người chơi bắn hai phát vào cùng một ô, chưa phân biệt một phát đạn bắn vào ô chưa bị cháy với một phát đạn bắn vào ô đã cháy. Nếu người chơi bắn ba lần vào cùng một ô thì thuật toán trên sẽ cho là tàu đã bị bắn cháy, mặc dù thực tế vẫn còn hai ô chưa bị bắn. Ta có thể giải quyết vấn đề này bằng một mảng phụ chứa các giá trị boolean để đánh dấu các ô đã bị bắn, hoặc dùng giá trị int sẵn có tại mảng locationCells để mã hóa các trạng thái chưa bị bắn / đã bị bắn. Tuy nhiên, để có giải pháp vừa gọn gàng, vừa tận dụng thư viện Java, ta chọn cách dùng ArrayList để lưu danh sách các ô chưa bị bắn của con tàu. Mỗi khi ô nào bị bắn trúng, phần tử tương ứng sẽ bị xóa khỏi danh sách. Khi danh sách rỗng là khi tàu đã bị bắn cháy. Như vậy ta chỉ cần một đối tượng ArrayList là đủ dùng thay cho cả mảng int locationCells và biến đếm numOfHits. Ta có thiết kế như sau: 94 Cài đặt lớp Ship theo thiết kế trên: Lớp SinkAShip có các nhiệm vụ sau: • tạo ra ba con tàu, • cho mỗi con tàu một cái tên, • đặt ba con tàu vào lưới. Ở đây ta cần tính vị trí tàu một cách ngẫu nhiên, ta tạo một lớp GameHelper để cung cấp tiện ích này (sẽ nói đến Helper sau). • hỏi tọa độ bắn của người chơi, kiểm tra với cả ba con tàu rồi in kết quả. Lặp cho đến khi nào cả ba con tàu đều đã bị cháy. Như vậy, ta cần ba lớp: SinkAShip vận hành trò chơi, Ship đại diện cho tàu, và GameHelper cung cấp cho Sink các tiện ích trợ giúp như nhận input từ người chơi và sinh vị trí cho các con tàu. Ta cần một đối tượng SinkAShip, ba đối tượng Ship, và một đối tượng GameHelper. Ngoài ra còn có các đối tượng ArrayList chứa trong ba đối tượng Ship. 95 Vậy ai làm gì trong một ván SinkAShip? Các đối tượng trong chương trình bắn tàu hoạt động và tương tác với nhau theo từng giai đoạn như sau: 1. Phương thức main() của lớp SinkAShip tạo một đối tượng SinkAShip, đối tượng này sẽ vận hành trò chơi. 2. Đối tượng SinkAShip tạo một đối tượng GameHelper để nó làm 'trợ lí'. 3. Đối tượng SinkAShip tạo một ArrayList để chuẩn bị lưu trữ ba đối tượng Ship. 4. Đối tượng SinkAShip tạo ba đối tượng Ship và gắn vào ArrayList nói trên. 5. Đối tượng SinkAShip yêu cầu 'trợ lí' sinh tọa độ cho từng đối tượng Ship, chuyển dữ liệu tọa độ nhận được cho các đối tượng Ship. Các đối tượng Ship cập nhật danh sách tọa độ tại ArrayList của mình. 6. Đối tượng SinkAShip yêu cầu 'trợ lí' lấy tọa độ bắn của người chơi, 'trợ lí' hiển thị lời mời nhập tại giao diện dòng lệnh và nhận input của người chơi). Nhận được kết quả do 'trợ lí' cung cấp, đối tượng SinkAShip yêu cầu từng đối tượng Ship tự kiểm tra xem có bị bắn trúng hay không. Mỗi đối tượng Ship kiểm tra từng vị trí trong ArrayList của mình và trả về kết quả tương ứng 96 ("miss", "hit", ). Bước này lặp đi lặp lại cho đến khi tất cả các con tàu đều bị bắn cháy. Như đã nói ở Chương 1, chương trình hướng đối tượng là một nhóm các đối tượng tương tác với nhau. Các ví dụ trước trong cuốn sách này đều nhỏ nên khó thấy rõ sự tương tác giữa các đối tượng. Ví dụ trò chơi bắn tàu này đủ lớn để minh họa được khía cạnh đó. Với hoạt động như đã mô tả, lớp SinkAShip được thiết kế như sau: 97 Lớp SinkAShip được cài đặt như sau: 98 99 Cuối cùng là lớp GameHelper chứa các phương thức tiện ích cho SinkAShip sử dụng. Lớp này cung cấp hai phương thức. Phương thức getUserInput() nhận input của người chơi bằng cách hiển thị lời mời nhập tọa độ bắn và đọc chuỗi kí tự người dùng gõ vào từ dòng lệnh. Phương thức thứ hai, placeShip(), sinh tự động vị trí cho các con tàu. Trong mã nguồn, có một số lệnh System.out.print(ln) trong phương thức placeShip() đã được chuyển thành dòng chú thích. Đó là các lệnh hiển thị tọa độ của các con tàu. Nếu cho các lệnh này chạy, chúng sẽ cho phép ta biết tọa độ của tàu để chơi "ăn gian" hoặc để test chương trình. Do chỉ là một ví dụ minh họa, chương trình này tuy hoàn chỉnh nhưng được viết ở mức độ vắn tắt tối đa với giao diện tối thiểu. Bạn đọc có thể sửa để cải thiện phần giao diện đối với người dùng, chẳng hạn như hiển thị bản đồ vùng biển cùng với các thông tin về các tọa độ đã bắn trúng hoặc trượt để hỗ trợ người chơi, hoặc có thể sử dụng thư viện giao diện đồ họa của Java để tăng tính thẩm mỹ và tính thân thiện người dùng. 100 Hình 6.4: GameHelper, phần 1/2. 101 Hình 6.5: GameHelper, phần 2/2. 102 Bài tập 1. Viết lớp Dice mô hình hóa xúc xắc và việc tung xúc xắc. Mỗi đối tượng Dice có một biến int lưu trạng thái hiện tại là mặt ngửa của lần gieo gần nhất (một giá trị trong khoảng từ 1 đến 6), một phương thức public roll() giả lập việc gieo xúc xắc và trả về giá trị của mặt ngửa vừa gieo được. Hãy sử dụng thư viện Math cho việc sinh số ngẫu nhiên. 2. Viết lớp Card mô hình hóa các quân bài tú-lơ-khơ. Sử dụng ArrayList để xây dựng lớp CardSet mô hình hóa một xấp bài có quân không xác định. Cài phương thức shuffle() của lớp CardSet với nhiệm vụ tráo ngẫu nhiên các quân bài trong xấp bài. Viết lớp CardTestDrive để thử nghiệm hai lớp Card và CardSet nói trên. 3. Có thể dùng một đối tượng thuộc lớp Scanner để đọc dữ liệu từ một file text tương tự như đọc dữ liệu từ bàn phím. Ví dụ: try { Scanner input = new Scanner (new File("C:\\Tmp\\test.txt")); // đọc dữ liệu int n = input.nextInt(); } catch (java.io.FileNotFoundException e) { } a) Hãy viết một chương trình Java đọc dữ liệu từ một file text và in từng từ ra màn hình. b) Sửa chương trình tại phần a để bỏ qua các dấu .,:.khi đọc các từ trong văn bản. Gợi ý: Lệnh sau đây đặt chế độ cho đối tượng Scanner coi tất cả các kí tự không phải a..z hay A..Z như các kí tự phân tách giữa các từ khi thực hiện lệnh đọc từng từ input.useDelimiter(Pattern.compile("[^a-zA-Z]")); Lệnh sau đây bỏ qua tất cả các kí tự không phải a..z hay A..Z cho đến khi gặp một kí tự trong khoản a..z hay A..Z input.skip("[^a-zA-Z]*");
File đính kèm:
- giao_trinh_lap_trinh_huong_doi_tuong_voi_java_phan_1.pdf