1. Trang chủ
  2. » Luận Văn - Báo Cáo

Giáo trình tài liệu lập trình java

241 11 0

Đang tải... (xem toàn văn)

Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống

THÔNG TIN TÀI LIỆU

Thông tin cơ bản

Định dạng
Số trang 241
Dung lượng 6,18 MB

Nội dung

Mục lục GIỚI THIỆU .5 Chương MỞ ĐẦU 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 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 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 .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 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 cấu trúc điều khiển 43 Chương 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 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 4.5 MẢNG 64 Chương 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 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 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 kiểu liệu 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 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 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 75 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 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 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 cố 175 11.1.2 Xử lý ngoại lệ 177 11.1.3 Ngoại 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 khối try/catch 180 11.2.3 Khối finally – việc dù phải làm 182 11.2.4 Thứ tự cho 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 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 224 13.5.2 So sánh lớn hơn/nhỏ 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 215 Giíi thiƯu Phần mềm ngày lớn phức tạp đòi hỏi cập nhật liên tục để đáp ứng yêu cầu 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 địi hỏi ngành cơng nghiệp phần mềm Lập trình hướng đối tượng đời bối cảnh để hỗ trợ sử dụng lại phát triển phần mềm qui mơ lớn Giáo trình cung cấp cho sinh viên kiến thức từ số kỹ thuật nâng cao 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 lập trình Giáo trình sử dụng ngơn ngữ lập trình Java để minh họa đồng thời giới thiệu số kiến thức ngôn ngữ Các nội dung phương pháp lập trình hướng đối tượng trình bày giáo trình bao gồm lớp đối tượng, đóng gói/che giấu thơng tin, kế thừa đa hình, xử lý ngoại lệ lập trình tổng qt Ngồi ra, giáo trình trình bày kiến thức Java bao gồm đặc trưng ngôn ngữ, thư viện cách thức tổ chức vào/ra liệu Thay cách trình bày theo tính hàn lâm 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 học cụ thể xếp theo trình tự kiến thức từ sở đến chuyên sâu Mỗi chủ đề giảng dạy với thời lượng 2~3 lý thuyết thực hành tương ứng Ch-¬ng Ch-¬ng 6, với nội dung kiến thức ngơn ngữ lập trình Java, cần thiết nội dung trọng tâm môn học Lập trình hướng đối tượng Các chương này, đó, nên để sinh viên tự học Chương Chương 10 không thiết phải dạy thành chủ đề độc lập mà tách rải rác nội dung kiến thức giới thiệu kèm theo khái niệm hướng đối tượng có liên quan, yêu cầu sinh viên tự đọc cần đến kiến thức trình thực hành Tuy giáo trình khơng trình bày sâu lập trình Java, kiến thức lập trình Java lại cần thiết sinh viên, với mục đích thực hành mơn học Do đó, ngồi mục đích thực hành nội dung liên quan đến lập trình hướng đối tượng, tập thực hành môn học nên có thêm đóng vai trị định hướng gợi ý giúp đỡ sinh viên tự học chủ đề túy Java mà giáo viên cho cần thiết, chẳng hạn học vào liệu đơn giản từ tuần môn học Các định hướng thể tập thực hành với đoạn chương trình mẫu, yêu cầu tìm hiểu tài liệu API số lớp tiện ích Một số tập cuối chương ví dụ dạng tập Các thuật ngữ hướng đối tượng nguyên gốc tiếng Anh chuyển sang tiếng Việt theo cách khác tùy tác giả Sinh viên cần biết thuật ngữ nguyên gốc tiếng Anh cách dịch khác để tiện cho việc sử dụng tài liệu tiếng Anh để liên hệ kiến thức tài liệu tiếng Việt Vì lí đó, giáo trình cung cấp bảng thuật ngữ Anh-Việt với cách dịch khác Phụ lục C, bên cạnh Phụ lục A cơng cụ lập trình JDK Phụ lục B tổ chức gói 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 Hồng, TS Cao Tuấn Dũng, TS Đặng Đức Hạnh, đồng nghiệp sinh viên Khoa Công nghệ thông tin, Trường Đại học Công nghệ đọc thảo giáo trình có góp ý q báu nội dung chun mơn cách thức trình bày Tuy vậy, giáo trình cịn nhiều khiếm khuyết, tác giả mong tiếp tục nhận góp ý để hồn thin tng lai Chơng mở đầu Lp trình cơng đoạn quan trọng chủ chốt khơng thể thiếu để tạo sản phẩm phần mềm Phần mềm trở nên đa dạng ngành công nghiệp phần mềm phát triển người ta thấy rõ tầm quan trọng phương pháp lập trình Phương pháp lập trình tốt khơng đảm bảo tạo phần mềm tốt mà hỗ trợ thiết kế phần mềm có tính mở hỗ trợ khả sử dụng lại mơ đun Nhờ dễ dàng bảo trì, nâng cấp phần mềm giảm chi phí phát triển phần mềm Trong thập kỷ 1970, 1980, phương pháp phát triển phần mềm chủ yếu lập trình có cấu trúc (structured programming) Cách tiếp cận cấu trúc việc thiết kế chương trình dựa chiến lược chia để trị: Để giải tốn lớn, tìm cách chia thành vài tốn nhỏ giải riêng bài; để giải bài, coi tốn tiếp tục chia thành tốn nhỏ hơn; cuối cùng, ta đến tốn giải mà không cần phải chia tiếp Cách tiếp cận gọi lập trình từ xuống (top-down programming) Lập trình từ xuống phương pháp tốt áp dụng thành công cho phát triển nhiều phần mềm Tuy nhiên, với đa dạng phức tạp phần mềm, phương pháp bộc lộ hạn chế Trước hết, đáp ứng việc tạo lệnh quy trình để giải tốn Dần dần, người ta nhận thiết kế cấu trúc liệu cho chương trình có tầm quan trọng không việc thiết kế hàm/thủ tục cấu trúc điều khiển Lập trình từ xuống không quan tâm đủ đến liệu mà chương trình cần xử lý Thứ hai, với lập trình từ xuống, khó tái sử dụng phần chương trình cho chương trình khác Bằng việc xuất phát từ toán cụ thể chia thành mảnh cho thuận, cách tiếp cận có xu hướng tạo thiết kế đặc thù cho tốn Chúng ta khó có khả lấy đoạn mã lớn từ chương trình cũ lắp vào dự án mà sửa đổi lớn Việc xây dựng chương trình chất lượng cao khó khăn tốn kém, nhà phát triển phần mềm luôn muốn tái sử dụng sản phẩm cũ Thứ ba, môi trường hoạt động thực tế ứng dụng thay đổi Dẫn đến việc yêu cầu phần mềm phải liên tục thay đổi theo để đáp ứng nhu cầu người dùng không muốn phần mềm bị đào thải Do đó, thiết kế linh hoạt mềm dẻo mà nhà phát triển phần mềm mong muốn Phương pháp tiếp cận từ lên (bottom-up) hỗ trợ tốt cho tính linh hoạt mềm dẻo Trong thực tế, thiết kế lập trình từ xuống thường kết hợp với thiết kế lập trình từ lên Trong tiếp cận từ lên, từ vấn đề mà ta biết cách giải có sẵn thành phần tái sử dụng xây dựng dần theo hướng lên trên, hướng đến giải pháp cho toán tổng Các thành phần tái sử dụng nên có tính mơ-đun hóa cao Mỗi mơ-đun thành phần hệ thống lớn hơn, tương tác với phần lại hệ thống theo cách đơn giản quy ước chặt chẽ Ý tưởng mơ-đun "lắp vào" hệ thống Chi tiết xảy bên mô-đun không cần xét đến hệ thống nói chung, miễn mơ-đun hồn thành tốt vai trị giao Đây gọi che giấu thơng tin (information hiding), nguyên lý quan trọng công nghệ phần mềm Một dạng thường thấy mơ-đun phần mềm chứa số liệu kèm theo số hàm/thủ tục để xử lý liệu Ví dụ, mơ-đun sổ địa chứa danh sách tên địa chỉ, kèm theo hàm/thủ tục để thêm mục tên mới, in nhãn địa chỉ…Với cách này, liệu bảo vệ xử lý theo cách biết trước định nghĩa chặt chẽ Ngồi ra, tạo thuận lợi cho chương trình sử dụng mơ-đun này, chương trình khơng phải quan tâm đến chi tiết biểu diễn liệu bên mô-đun Thông tin biểu diễn liệu che giấu Các mô-đun hỗ trợ dạng che giấu thông tin bắt đầu trở nên phổ biến ngơn ngữ lập trình đầu thập kỷ 1980 Từ đó, hình thức tiên tiến ý tưởng lan rộng ngành cơng nghệ phần mềm Cách tiếp cận gọi lập trình hướng đối tượng (object-oriented programming), thường gọi tắt OOP Câu chuyện tưởng tượng sau đây1 minh họa phần khác biệt lập trình thủ tục lập trình hướng đối tượng thực tế ngành cơng nghệ phàn mềm Có hai lập trình viên nhận đặc tả hệ thống yêu cầu xây dựng hệ thống đó, thi xem người hoàn thành sớm Dậu người chuyên dùng phương pháp lập trình thủ tục, cịn Tuất quen dùng lập trình hướng đối tượng Cả Dậu Tuất cho nhiệm vụ đơn giản Đặc tả sau: Nguồn: Head First Java, 2nd Edition Dậu tính tốn, "Chương trình phải làm gì? Ta cần đến thủ tục nào?" Anh tự trả lời, "xoay chơi nhạc." Và anh bắt tay vào viết thủ tục Chương trình khơng phải loạt thủ tục gì? Trong đó, Tuất nghĩ, "Trong chương trình có thứ đâu nhân tố chính?" Đầu tiên, nghĩ đến Hình vẽ Ngồi ra, anh cịn nghĩ đến đối tượng khác người dùng, âm thanh, kiện click chuột Nhưng anh có sẵn thư viện mã cho đối tượng đó, nên anh tập trung vào việc xây dựng Hình vẽ Dậu thạo với công việc kiểu rồi, anh bắt tay vào viết thủ tục quan trọng nhanh chóng hoàn thành hai thủ tục xoay (rotate) chơi nhạc (playSound): rotate(shapeNum) { // cho hình xoay 360o } playSound(shapeNum) { // dùng shapeNum để tra xem cần chơi file AIF // chơi file } Cịn Tuất ngồi viết ba lớp, lớp dành cho hình Dậu vừa nghĩ thắng sếp nói "Về mặt kĩ thuật Dậu xong trước, ta phải bổ sung chút xíu vào chương trình." Hai người quen với chuyện đặc tả thay đổi – chuyện thường ngày ngành Đặc tả bổ sung nội dung sau: Đối với Dậu, thủ tục rotate ổn, mã dùng bảng tra cứu để khớp giá trị shapeNum với hình đồ họa cụ thể Nhưng playSound phải sửa Rốt cục khơng phải sửa nghiêm trọng, Dậu thấy không thoải mái phải động vào sửa phần mã test xong từ trước Anh biết, dù quản lý dự án có nói nữa, đặc tả thay đổi suốt Cịn Tuất thản nhiên vừa nhâm nhi cà phê vừa viết lớp Điều anh thích OOP anh khơng phải sửa phần mã test bàn giao Anh nghĩ ích lợi OOP lẩm bẩm "Tính linh hoạt, khả mở rộng, " Dậu vừa kịp hoàn thành lát trước Tuất Nhưng nụ cười anh tắt nhìn thấy mặt sếp nghe thấy giọng sếp vẻ thất vọng "không rồi, amoeba thực không xoay kiểu " Thì hai lập trình viên viết đoạn xoay hình theo cách: (1) xác định hình chữ nhật bao hình; (2) xác định tâm hình chữ nhật xoay hình quanh điểm Nhưng hình trùng biến hình lại cần xoay quanh điểm đầu mút, kiểu kim đồng hồ "Mình tèo rồi." Dậu ngán ngẩm "Tuy là, ừm, thêm lệnh if/else vào thủ tục rotate, hard-code tâm xoay cho amoeba Làm không làm hỏng đoạn khác." Nhưng giọng nói đầu Dậu thào, "Nhầm to! Cậu có đặc tả khơng thay đổi lần không đấy?" Cuối Dậu chọn cách bổ sung tham số tâm xoay vào cho thủ tục rotate Rất nhiều đoạn mã bị ảnh hưởng Phải test lại, dịch lại đống mã Có đoạn trước chạy tốt khơng chạy rotate(shapeNum, xPt, yPt) { 10 Hình 13.9: Lỗi run-time sử dụng TreeSet cho Contact Tương tự tình so sánh bằng, TreeSet, hay Collections tự biết cách so sánh đối tượng thuộc lớp mà lập trình viên tự xây dựng Chương trình Hình 13.9 biên dịch khơng có lỗi add() khơng u cầu tham số kiểu Comparable, chạy gặp lỗi run-time lệnh gọi đến phương thức Tóm lại, phần tử cấu trúc danh bạ phải thuộc lớp đối tượng có cung cấp phương tiện so sánh Ta chọn hai cách sau để giải vấn đề đó: Các phần tử danh sách phải thuộc lớp có cài interface Comparable Ta sửa lớp Contact để bổ sung phần in đậm Hình 13.10, chương trình Hình 13.9, sau chạy khơng có lỗi Hình 13.10: Cài interface Comparable Sử dụng phương thức chồng có lấy tham số kiểu Comparator Ta viết thêm lớp ContactCompare theo interface Comparator dùng chương trình TestTreeSet dịng in đậm Hình 13.11 Theo đó, ContactCompare loại Comparator riêng dành cho việc so sánh đối tượng Contact Còn danh bạ đối tượng TreeSet tạo kèm với loại Comparator đặc biệt để 227 biết cách đối xử với phần tử danh bạ (cContact đối số gọi hàm khởi tạo TreeSet) Hình 13.11: Sử dụng Comparator Cả hai cách áp dụng cho phương thức sort() Collection tiện ích tổng quát tương tự thư viện Java 13.6 KÍ TỰ ĐẠI DIỆN TRONG KHAI BÁO THAM SỐ KIỂU Quan hệ thừa kế hai lớp khơng có ảnh hưởng đến quan hệ cấu trúc tổng quát dùng cho hai lớp Chẳng hạn, Dog Cat lớp Animal, ta đưa đối tượng Dog Cat vào ArrayList, tính chất đa hình Dog, Cat, Animal hoạt động bình thường (xem ví dụ Hình 13.12) Tuy nhiên, ArrayList, ArrayList lại khơng có quan hệ với ArrayList Vậy cho nên, dùng ArrayList làm đối số cho phương thức yêu cầu đối số kiểu ArrayList, ví dụ Hình 13.13, trình biên dịch báo lỗi sai kiểu liệu 228 Hình 13.12: Đa hình bên cấu trúc tổng quát Hình 13.13: Khơng có đa hình cấu trúc tổng quát Tóm lại, ta khai báo phương thức lấy đối số kiểu ArrayList, lấy đối số kiểu ArrayList lấy kiểu ArrayList hay ArrayList Ta khơng hài lịng với với việc thỏa hiệp, nghĩa dùng ArrayList thay ArrayList cho danh sách chứa tồn Dog Vì trình biên dịch khơng kiểm tra kiểu liệu để ngăn chặn tình chẳng hạn danh sách chó nghiệp vụ lính cứu hỏa lại có mèo 229 Hình 13.14: Nguy cho mèo vào danh sách chó Vậy làm để làm cho phương thức nhận đối số thuộc kiểu ArrayList, ArrayList,…nghĩa ArrayList dành cho kiểu lớp Animal? Giải pháp sử dụng kí tự đại diện (wildcard) Ta sửa phương thức makeASymphony() sau, chương trình Hình 13.13 chạy chạy ? extends Animal có nghĩa kiểu thuộc loại Animal Nhớ từ khóa extends có nghĩa "là lớp của" "cài đặt", tùy vào việc theo sau từ khóa extends tên lớp hay tên interface Vậy nên muốn makeASymphony() lấy đối số ArrayList loại cài interface Pet, ta khai báo sau: Nhưng ArrayList khai báo, trình biên dịch khơng cho ta thêm vào danh sách mà tham số phương thức chiếu tới Ta gọi phương thức phần tử danh sách, ta thêm phần tử vào danh sách Do đó, ta n tâm chương trình chạy Ví dụ, makeASymphony() với nội dung không gặp lỗi biên dịch, takeAnimals() với nội dung Hình 13.14 khơng biên dịch 230 Hai cú pháp sau tương đương: public void foo( ArrayList

Ngày đăng: 28/02/2021, 16:15