Bài giảng Java (Mới nhất)

doc 146 trang ngocly 2590
Bạn đang xem 20 trang mẫu của tài liệu "Bài giảng Java (Mới nhất)", để tải tài liệu gốc về máy bạn click vào nút DOWNLOAD ở trên

Tài liệu đính kèm:

  • docbai_giang_java_moi_nhat.doc

Nội dung text: Bài giảng Java (Mới nhất)

  1. MỤC LỤC MỤC LỤC 1 Đề tài 0. Giới thiệu về Java 6 I. Lịch sử hình thành và phát triển ngôn ngữ lập trình Java 6 I.1. Giới thiệu về Java 6 I.2 Tóm tắt lịch sử hình thành của Java 6 II. Các đặc trưng của Java 7 II.1. Tính đơn giản 7 II.2. Tính hướng đối tượng 7 II.3. Tính phân tán 7 II.4. Tính mạnh mẽ 7 II.5. Tính an toàn 7 II.6. Tính trung lập 7 II.7. Tính di động 8 II.8. Tính thông dịch 9 II.9. Tính thực thi cao 9 II.10. Tính đa luồng 9 II.11. Tính động 9 III. Các loại ứng dụng của Java 9 IV. Công cụ và môi trường lập trình Java 9 V. Cài đặt Java 9 Đề tài 1. Ngôn ngữ mô hình hóa UML 12 I. Xây dựng hệ thống phần mềm theo hướng đối tượng 12 I.1 Các khái niệm căn bản của công nghệ hướng đối tượng 12 I.2 Quy trình chung phát triển hệ thống phần mềm 13 I.3 Những thách thức của ngành công nghiệp phần mềm hiện nay 13 II. Lịch sử phát triển ngôn ngữ mô hình hóa UML 13 II.1. Tóm tắt lịch sử UML 13 II.2. Khái niệm về UML 14 II.3 Đặc trưng của UML 14 III. Ngôn ngữ UML 15 III.1. Các thành phần của UML 15 III.2. Các hướng nhìn (view) 15 III.3 Ứng dụng UML trong quy trình làm phần mềm 22 IV. Quy trình Rational Unified Process (RUP) phát triển phần mềm dựa trên UML 23 IV.1. Giới thiệu về RUP 23 IV.2. Các nguyên tắc chính của RUP: 23 IV.3. Vòng đời của phần mềm theo quy trình RUP 24 IV.4. Các công cụ của RUP 25 Bài tập 26 Đề tài 2. Nhập môn Java 27 I. Viết và thực hiện một chương trình Java 27 I.1 Tìm hiểu mã nguồn một chương trình đơn giản 27 I.2. Thực hiện chương trình Java 27 I.3. Một số chú ý khi lập trình Java 28 1
  2. I.4. Cấu trúc một chương trình Java 28 II. Các kiểu dữ liệu trong Java 29 II.1 Các kiểu dữ liệu số nguyên 29 II.2 Các kiểu số thực 30 II.3 Kiểu ký tự (character) 30 II.4 Kiểu logic (boolean) 30 II.5 Kiểu chuỗi 30 II.6 Chuyển đổi giữa các kiểu số 30 III. Khai báo biến và hằng trong Java 31 III.1 Quy tắc đặt tên biến 31 III.2 Khai báo biến 31 III.3 Biến kiểu mảng 32 III.4 Hằng số (literal) 33 III.5 Phạm vi hoạt động của hằng và biến: 34 IV. Các toán tử và biểu thức 34 IV.1 Các toán tử và thứ tự ưu tiên 34 IV.2 Biểu thức 35 V. Các lệnh điều khiển rẽ nhánh 35 V.1 Lệnh if 35 V.2. Lệnh switch case 36 VI. Các lệnh lặp 37 VI.1. Vòng lặp for 37 VI.2. Vòng lặp while 38 VI.3. Vòng lặp do while 38 VI.4. Phép nhảy 39 VII. Vào dữ liệu từ bàn phím và xuất dữ liệu ra màn hình 40 VII.1. Lấy giá trị nhập vào từ bàn phím 40 VII.2 Kết xuất dữ liệu ra màn hình 41 Bài tập 42 Đề tài 3. Lập trình hướng đối tượng trong Java 44 I. Khái niệm lập trình hướng đối tượng (Object-Oriented Programming - OOP) 44 I.1. Khái niệm OOP 44 I.2 Cơ sở lý luận của OOP 44 I.3 Trừu tượng hóa 44 II. Tính đóng gói trong Java 46 II.1 Khái niệm tính đóng gói 46 II.2 Mối quan hệ giữa các class 46 II.3 Một số gợi ý khi thiết kế class 46 IV. Sử dụng các Class xây dựng sẵn trong thư viện 47 V. Xây dựng Class trong Java 48 V.1 Cấu trúc của class 48 V.2 Các thuộc tính thành phần: 49 V.3 Các phương thức thành phần 50 V.4 Gọi và truyền tham số cho phương thức 51 V.6 Các hàm và phương thức đặc biệt 51 V.7 Khai báo chồng các phương thức 52 V.8 Lớp lồng nhau – lớp nội 53 2
  3. VI. Tính kế thừa trong Java 54 VI.1 Sự kế thừa các thuộc tính và phương thức 54 VI.2 Sự kế thừa đối với các constructor 57 VII. Tính đa hình trong Java 58 VII.1 Sự ép kiểu và gán tham chiếu đối tượng 58 VII.2 Sự ràng buộc động –Dynamic Binding 58 VIII. Lớp Object 59 IX. Giao diện 60 IX.1 Cấu trúc của giao diện 60 IX.2 Các tính chất của giao diện 62 X. Package 62 X.1 Sử dụng các package trong thư viện Java 62 X.2 Đặt lớp vào package 63 Bài tập 63 Đề tài 4. Lớp và phương thức trừu tượng 64 I. Khái niệm lớp trừu tượng 64 II. Cài đặt lớp và phương thức trừu tượng trong Java 64 Bài tập 65 Đề tài 5. Lưu trữ và xử lý đối tượng 66 I. Lớp Vector và giao diện Enumeration 66 I.1 Lớp Vector 66 I.2 Giao diện Enumeration 67 II. Mảng trong Java và lớp ArrayList 69 II.1 Mảng trong Java 69 II.2. Các thuật toán cơ bản trên mảng 70 II.3 Class Arrays 71 III Danh sách trong java và giao diện Lists 73 Bài tập 74 Đề tài 6. Các luồng vào ra dữ liệu với file 75 I. Khái niệm luồng vào ra (I/O stream) 75 II. Lớp InputStream: 76 III. Lớp OutputStream 77 IV. Lớp FileInputStream 77 V. Lớp FileOutputStream 77 VI. Lớp File 77 VII. Nhập xuất lọc 78 VII.1 Lớp FilterInputStream: 79 VII.2 Lớp FilterOutputStream 79 VIII. Vào/ra có sử dụng bộ đệm 79 VIII.1 Lớp BufferedInputStream: 79 VIII.2 Lớp BufferedOutputStream 79 IX. Lớp RandomAccessFile 81 X. Đối tượng System.in 82 XI. Truy cập file ở chế độ tuần tự 82 XII. Truy cập file nhị phân 86 Bài tập 86 Đề tài 7. Xử lý ngoại lệ 88 3
  4. I. Các tình huống sử dụng ngoại lệ 88 II. Cơ sở quản lý ngoại lệ trong Java 88 III. Cấu trúc cây kế thừa các xử lý ngoại lệ 89 IV. Sử dụng ngoại lệ được kiểm soát 90 V. Xây dựng một ngoại lệ 91 VI. Bài tập 92 Đề tài 8. Xử lý các sự kiện trong Java 93 I. Khái niệm và cơ sở xử lý sự kiện 93 II. Truy cập thông tin sự kiện 98 III. Xử lý các sự kiện trên window 99 IV. Các lớp thích nghi 100 V. Xử lý các sự kiện chuột 102 Bài tập 103 Đề tài 9. Applet 104 I. Xây dựng một Applet đơn giản 104 II. Cấu trúc cơ bản và vòng đời của một Applet 104 III. An ninh và khả năng của Applet 106 IV. Ứng dụng Applet với của sổ Popup 106 V. Các thẻ HTML của Applet 107 VI. Các phương thức, lập trình đồ họa và bắt sự kiện của applet 108 Đề tài 10. Lập trình giao diện đồ họa GUI 110 I. Giới thiệu AWT 110 II. Vật chứa (Container) 111 II.1 JFrame 111 II.2 JPanel 111 II.3 JDialog 112 II.4 JScrollPane 113 III. Giới thiệu về các thành phần GUI cơ bản 113 III.1 Nút nhấn 113 III.2 Nhãn (Label) 114 III.3 Nút đánh dấu (checkbox) 115 III.4 Nút chọn (radio button) 117 III.5 Hộp thoại Combo 118 III.6 Danh sách (Lists) 119 III.7 Ô văn bản (text field) và vùng văn bản (text areas) 121 III.8 Thanh trượt (Scrollbar) 123 IV. Thành phần Menu 124 V. Bộ quản lý cách trình bày (Layout Manager) 127 V.1 Cách trình bày FlowLayout: 128 V.2 Cách trình bày GridLayout: 128 V.3 Cách trình bày BorderLayout 128 VI. Các hộp thoại 128 VI.1 Hộp thoại thông báo 128 VI.2 Hộp thoại chọn File 129 VI.3 Hộp thoại chọn màu 130 Bài tập 130 Đề tài 11. Threading 132 4
  5. I. Khái niệm thread 132 I.1 Khái niệm: 132 I.2. Lớp Thread 132 I.3 Các bước để tạo một thread 132 II. Các trạng thái của thread. 133 III. Các thuộc tính của thread 134 III.1 Độ ưu tiên của thread 134 III.2 Nhóm thread 135 III.3 Quản lý các ngoại lệ của thread 135 IV. Điều khiển các thread 136 IV.1 Interrupt một thread 136 IV.2 Dừng một thread 137 IV.3 Tạm dừng và phục hồi một thread 138 IV.4 Giải phóng thời gian cho CPU 138 IV.5 Đợi một thread kết thúc công việc 138 V. Đồng bộ thread 139 V.1 Tình trạng “đua tranh” 139 V.2 Khóa đối tượng 140 V.3 Đối tượng điều kiện 141 Bài tập 143 Phụ lục A. Các từ khóa của Java 144 Phụ lục B Một số hàm hay sử dụng 145 Tài liệu tham khảo 146 5
  6. Đề tài 0. Giới thiệu về Java I. Lịch sử hình thành và phát triển ngôn ngữ lập trình Java I.1. Giới thiệu về Java Java là một ngôn ngữ lập trình mạnh đang được sử dụng rất rộng rãi hiện nay trên toàn thế giới. Trên thực tế, Java được biết đến không chỉ là một ngôn ngữ lập trình mà là một platform – một môi trường và công nghệ phát triển – riêng biệt. Khi làm việc với Java, người lập trình được sở hữu một thư viện lớn, có tính mở với một lượng mã nguồn tái sử dụng khổng lồ luôn có trên internet. Ngoài ra, các chương trình viết bằng Java có môi trường thực thi riêng với các tính năng bảo mật, khả năng triển khai trên nhiều hệ điều hành khác nhau và nhiều tính năng ưu việt khác chúng ta sẽ xem xét trong phần sau. I.2 Tóm tắt lịch sử hình thành của Java Năm 1991, một nhóm kỹ sư của hãng SUN bao gồm Patrick Naughton, Sun Fellow và James Gosling có ý tưởng phát minh ra một ngôn ngữ lập trình nhỏ gọn có thể thực thi được trên các thiết bị dạng như bộ chuyển kênh của truyền hình cáp vì các thiết bị kiểu này có bộ nhớ nhỏ. Bên cạnh đó, do các hãng khác nhau sử dụng các chíp xử lý (CPUs) khác nhau nên một đặc tính quan trọng mà ngôn ngữ này phải có là độc lập với các dòng CPUs khác nhau – gọi là đặc tính di động. Nhóm đã mở một dự án có tên là Green để hiện thực hóa ý tưởng này. Để giải quyết vấn đề di động, nhóm đã sử dụng ý tưởng của kỹ sư Niklaus Wirth – người sáng lập ngôn ngữ Pascal – về việc sử dụng cơ chế thông dịch và máy ảo (virtual machine). Về nền tảng ngôn ngữ, do hãng SUN phát triển trên nền UNIX nên họ sử dụng ngôn ngữ lập trình C++ là chủ yếu. Do đó, ngôn ngữ mới thiên về hướng đối tượng (Object Oriented) của C++ hơn là hướng thủ tục như Pascal. Ban đầu nhóm đặt tên cho ngôn ngữ mới là “Oak” nhưng sau đó được chuyển thành Java do Oak cũng đã là tên một ngôn ngữ lập trình khác. Năm 1992, dự án Green cho ra đời sản phẩm đầu tiên có tên là “*7” nhưng đã không được chào đón như mong đợi. Sau đó nhóm đã phải mất cả năm 1993 và nửa đầu 1994 để đi tiếp thị công nghệ của mình. Từ năm 1994, sự phát triển của Internet đã tạo cơ hội để Java phát triển nhanh chóng. Nhóm đã phát triển một trình duyệt có tên là HotJava cho phép các chương trình Java nhúng được trong đó (applet). Đây chính là minh chứng rõ ràng về sức mạnh của Java đã nhanh chóng được cộng đồng người sử dụng internet biết đến và là tiền đề để Java phát triển rực rỡ như ngày hôm nay. Phiên bản đầu tiên 1.0 của Java ra đời vào năm 1996, sau đó là phiên bản 1.1 mặc dù khá mạnh nhưng cũng còn nhiều hạn chế. Năm 1998 đánh đấu bước chuyển mình mạnh mẽ của Java với sự ra đời của phiên bản 1.2 làm cho Java tiến gần tới mục tiêu “viết một lần, chạy khắp nơi” (Write once, Run Anywhere). Các nhân viên tiếp thị của Java gọi đây là phiên bản “Java 2 Standard Edition Software Development Kit Version 1.2” ý nói tới sự có mặt đồng thời của 2 phiên bản “Standard Edition” là Micro Edition và Enterprise Edition trong Java. Các phiên bản 1.3, 1.4 là sự phát triển mở rộng tiếp theo của phiên bản 1.2. Phiên bản 1.5 (chuyển sang gọi là phiên bản 5.0) đánh dấu sự tích hợp đầy đủ nhất các công nghệ Java. Bảng sau cho thấy sự phát triển thư viện Java qua các phiên bản: Phiên bản Số các Class và Interface 6
  7. 1.0 211 1.1 477 1.2 1524 1.3 1840 1.4 2723 5.0 3270 Hiện tại, Java đã phát triển tới phiên bản 1.6. II. Các đặc trưng của Java Java được biết đến với các đặc trưng sau: II.1. Tính đơn giản Java được phát triển dựa trên C++ nhưng lược bớt đi hoặc thay thế các khái niệm khó hiểu như header file, con trỏ, structures, union, operator overloading, virtual base class. Trong Java chỉ có thừa kế đơn mà không có tính đa thừa kế như của C++. Tuy nhiên tính đa thừa kế được biểu hiện thông qua việc sử dụng các Interface. II.2. Tính hướng đối tượng Như đã trình bày ở trên, Java được phát triển từ C++ nên nó là ngôn ngữ lập trình hướng đối tượng. II.3. Tính phân tán Java cho phép lập trình truy cập các đối tượng từ xa thông qua các giao thức HTTP, FTP bằng các phương thức như RMI hay Socket. Java hoàn toàn thích hợp cho các ứng dụng Internet. Các công nghệ JSP, Servlet cho phép xây dựng các website tương tác với các tính năng thực thi tối ưu II.4. Tính mạnh mẽ Việc loại bỏ con trỏ làm tăng độ tin cậy của chương trình. Lập trình viên không cần quan tâm đến thao tác cấp phát và giải phóng bộ nhớ. Với Java, bộ nhớ được giải phóng tự động. II.5. Tính an toàn Ngôn ngữ Java được thiết kế để tránh các sự cố: Nạp chồng stack lúc runtime. Ảnh hưởng tới bộ nhớ nằm ngoài phạm vi được cấp phát. Đọc và ghi file tự do II.6. Tính trung lập Các chương trình viết bằng Java không bị phụ thuộc vào hệ điều hành. Điều này có được là do mã nguồn chương trình không được biên dịch thành mã máy ngay mà thành mã Bytecode. 7
  8. Khi đem mã Bytecode này chạy trên hệ máy tính nào thì một trình thông dịch virtual machine (Java Vitual Machine-JVM) sẽ thông dịch chúng sang mã máy tương ứng để thực thi. Mã nguồn -> ByteCodes -> machine code. Từ mã nguồn -> Bytecodes: Trình biên dịch Java. Từ Bytecodes -> machine code: Trình thông dịch Virtual machine. IBM Trình thông Bytecode dịch Java Sparc Trình biên dịch (Java Interpreter) Macintosh Java Virtual Machine – JVM Máy ảo là một phần mềm dựa trên cơ sở máy tính ảo. Nó có tập hợp các lệnh logic để xác định các hoạt động của máy tính. Người ta có thể xem nó như một hệ điều hành thu nhỏ. JVM thiết lập các lớp trừu tượng cho phần cứng bên dưới, hệ điều hành, mã đã biên dịch. Trình biên dịch chuyển mã nguồn thành tập các lệnh của máy ảo mà không phụ thuộc vào phần cứng cụ thể. Trình thông dịch trên mỗi máy sẽ chuyển tập lệnh này thành chương trình thực thi. Máy ảo tạo ra một môi trường bên trong để thực thi các lệnh bằng cách: Nạp các file .class Quản lý bộ nhớ Dọn “rác”, thu hồi bộ nhớ cấp cho các biến không còn được sử dụng. Việc không nhất quán của phần cứng làm cho máy ảo phải sử dụng ngăn xếp để lưu trữ các thông tin sau: Các “Frame” chứa các trạng thái của các phương thức. Các toán hạng của mã bytecode. Các tham số truyền cho phương thức. Các biến cục bộ. II.7. Tính di động Không giống C++ và C, các kiểu dữ liệu nguyên thủy của Java được cấp phát một lượng bộ nhớ cố định. Chẳng hạn kiểu dữ liệu int của Java luôn là 4 byte (32 bit) trong khi kiểu int của C++ có thể hiểu là 2 byte hoặc 4 byte. Thiết kế này giúp cho trình biên dịch luôn có số bytecode như nhau trên mọi hệ máy và sau đó phát sinh mã máy theo khuôn dạng cố định. Trong các phiên bản đầu của Java, vấn đề giao diện đồ họa cho người sử dụng (GUI) chưa được xử lý triệt để và phụ thuộc vào hệ máy. Ngày nay, thư viện GUI của Java đã được viết lại hoàn toàn và có tính độc lập cao giúp cho chương trình Java có giao diện giống nhau trên mọi hệ máy. 8
  9. II.8. Tính thông dịch Trình thông dịch Java sẽ thông dịch mã bytecode sang mã máy nơi mà nó được cài đặt. Quá trình này cũng làm các chương trình Java chạy chậm hơn. Tuy nhiên đây lại là giải pháp cho tính di động. II.9. Tính thực thi cao Java sử dụng công nghệ Just-In-Time (JIT) giúp quá trình thông dịch thực hiện nhanh hơn. Với công nghệ này, những mã bytecode giống nhau sẽ chỉ cần thông dịch một lần. Ngày nay, công nghệ này liên tục được cải tiến và cho kết quả vượt trội so với các trình thông dịch truyền thống. Ví dụ như JIT có thể quản lý các đoạn mã được sử dụng thường xuyên trong chương trình, tối ưu chúng để nâng cao tốc độc thực hiện. II.10. Tính đa luồng Với chương trình Java, lập trình viên có thể cùng lúc quản lý nhiều tiến trình khác nhau. Điều này giúp cho việc cài đặt các thuật toán song song bằng Java trên các máy tính nhiều CPU được dễ dàng hơn, đặc biệt trong các ứng dụng thời gian thực. II.11. Tính động Các chương trình Java có thể được nâng cấp mà không ảnh hưởng tới người sử dụng. Các phương thức mới có thể được cài đặt thêm vào các lớp đối tượng hay các giao diện trong thư viện của chương trình đang chạy. III. Các loại ứng dụng của Java Ứng dụng console: Không có giao diện GUI. Ứng dụng đồ hoạ: Có giao diện GUI. Applet: Nhúng trong các trang Web. Servlet: Các class thực thi phía web server. JSP: Các file nhúng mã Java và HTML. Ứng dụng EJB, RMI, JMS: Xây dựng ứng dụng bởi nhiều thành phần ghép lại, giao tiếp từ xa. IV. Công cụ và môi trường lập trình Java Hiện nay có rất nhiều môi trường phát triển Java (Integrated Development Environment - IDE). Mỗi môi trường cung cấp cho lập trình viên những tiện ích lập trình ở mức độ khác nhau. Một số IDE thông dụng là: Netbeans (miễn phí tại Jcreator (thương mại). Jbuilder (thương mại). Eclipse (miễn phí V. Cài đặt Java Java phiên bản Java mới nhất có thể download tại địa chỉ Sau đó cài đặt như ứng dụng bình thường. Thư mục cài đặt mặc định của Java trên Windows là C:\Program Files\Java\jdk1.6.0_02 (nếu phiên bản cài là jdk1.6.0_02). Trong đó có chứa các thư mục với ý nghĩa sau: 9
  10. bin Chứa các công cụ và trình biên dịch Java demo Chứa các chương trình Java Demo docs Chứa các tài liệu mô tả thư viện của Java includes Chứa các file dùng để biên dịch các đoạn mã nguồn viết bằng ngôn ngữ khác (native). jre Chứa các file lưu thông tin môi trường lúc thực thi lib Chứa các file thư viện src Chứa mã nguồn java Trong thư mục \bin có chữa các công cụ chính của Java: Trình biên dịch, 'javac' Cú pháp:javac [options] sourcecodename.java Trình thông dịch, 'java' Cú pháp:java [options] classname Trình dịch ngược, 'javap' javap dịch ngược bytecode và in ra thông tin về các thuộc tính (các trường), các phương thức của một lớp. Cú pháp:javap [options] classname Công cụ sinh tài liệu, 'javadoc' Tiện ích này cho phép ta tạo ra tệp HTML dựa trên các lời giải thích trong mã chương trình (phần nằm trong cặp dấu /* */). Cú pháp:javadoc [options] sourcecodename.java Chương trình tìm lỗi - Debug, 'jdb‘ Cú pháp:jdb [options] sourcecodename.java hay jdb -host -password [options] sourcecodename.java Cài đặt đường dẫn mặc định. 1. Mở Control Panel 2. Chọn System 10
  11. 3. Chọn Tab Advanced 4. Chọn Environment Variables 5. Thêm đường dẫn C:\Program Files\Java\jdk1.6.0_02\bin vào biến môi trường Path - Chọn Variable Path - Chọn Edit 11
  12. Đề tài 1. Ngôn ngữ mô hình hóa UML I. Xây dựng hệ thống phần mềm theo hướng đối tượng I.1 Các khái niệm căn bản của công nghệ hướng đối tượng Hướng đối tượng là một công nghệ để sản sinh ra các mô hình phản ánh một cách tự nhiên các nghiệp vụ thực tế. Xét cho cùng thì mọi quy trình nghiệp vụ trong thực tế đều là sự tương tác của các đối tượng theo một trình tự nhằm đạt được một mục đích cụ thể. Nói cách khác, các đối tượng và mối quan hệ giữa chúng phản ánh quy trình nghiệp vụ. Ví dụ như nghiệp vụ tuyển nhân sự cho một công ty có thể được tóm tắt qua một chuỗi các tương tác như sau: 1. Công ty đưa ra thông báo tuyển nhân sự tới các ứng viên có nhu cầu 2. Ứng viên gửi hồ sơ dự tuyển tới công ty 3. Công ty duyệt hồ sơ và gửi giấy hẹn phỏng vấn tới ứng viên 4. Công ty phỏng vấn ứng viên 5. Công ty ký hợp đồng với ứng viên hoặc từ chối Trong ví dụ trên có sự tham gia của hai đối tượng “công ty” và “ứng viên”. Sự trao đổi thông tin giữa hai đối tượng này cho thấy mối quan hệ phụ thuộc giữa chúng (dependence). Một ví dụ khác là trong hệ thống quản lý nhân sự chúng ta có các đối tượng như: nhân viên, quản lý, cho thấy mối quan hệ dạng khác: các nhân viên làm công việc quản lý tất nhiên cũng là nhân viên của công ty nhưng có thêm các thuộc tính riêng biệt – một quan hệ kế thừa (inheritance). Trong một hệ thống bán hàng, mỗi đơn hàng bao gồm trong nó các thông tin về khách hàng, ngày giờ, và một danh mục các mặt hàng. Khi này ta nói rằng giữa đơn hàng và mặt hàng tồn tại một quan hệ bao gồm (aggregation) Mục tiêu của công nghệ hướng đối tượng chính là thể hiện được các đối tượng và mối quan hệ giữa chúng vào trong các hệ thống phần mềm. Vì vậy, các hệ thống phần mềm theo công nghệ hướng đối tượng phản ánh một cách tự nhiên và trung thực nghiệp vụ thực tế và có khả năng đáp ứng các thay đổi dễ dàng. Phát triển một hệ thống phần mềm theo hướng đối tượng dựa trên 5 khái niệm cơ bản: Lớp (class), đối tượng (object), thông điệp (mesage), thừa kế (inheritance) và đa hình (polymorphism). Lớp là sự trừu tượng hóa các đối tượng thực tế theo phạm vi nghiệp vụ. Lấy ví dụ về hệ thống quản lý sinh viên của trường ĐHBK Hà Nội cần quản lý rất nhiều đối tượng sinh viên khác nhau nhưng có thể chỉ được trừu tượng hóa thành một lớp đối tượng có tên SinhVien chẳng hạn. Sự trừu tượng hóa một lớp đối tượng cho ta kết quả một tập các thuộc tính (attributes) và các hành vi (operations) đặc trưng cho bất kỳ đối tượng nào thuộc lớp đó. Đối tượng thực tế thì có vô số thuộc tính và hành vi nhưng chỉ thuộc tính và hành vi trong phạm vi nghiệp vụ là được xét đến. Sự chi tiết phụ thuộc vào phạm vi nghiệp vụ. Sự trừu tượng hóa diễn ra ở nhiều cấp độ. Lấy ví dụ: lớp động vật có vú bao gồm lớp động vật 4 chân, lớp 4 chân lại bao gồm các lớp như lớp mèo, lớp trâu, Mỗi đối tượng là một biểu hiện thực tế của một lớp. object = name + attributes + operations Hệ thống hướng đối tượng nếu được mô hình đúng sẽ rất linh hoạt, dễ hiệu chỉnh, được cài đặt dễ dàng bằng các ngôn ngữ hướng đối tượng. Các hệ thống phần mềm hướng đối tượng cũng được cài đặt bởi các ngôn ngữ lập trình hướng đối tượng. 12
  13. Hướng đối tượng không chỉ là một lý thuyết mà đã được chứng minh bằng những ứng dụng rất thành công trong thực tế ở nhiều lĩnh vực khác nhau. Tuy nhiên, lĩnh vực này vẫn cần được chuẩn hóa hơn nữa. I.2 Quy trình chung phát triển hệ thống phần mềm Xây dựng một hệ thống phần mềm hướng đối tượng cũng tuân theo quy trình chung như mọi công nghệ khác: 1. Requirements : Thu thập yêu cầu 2. Analysis : Phân tích 3. Design: Thiết kế 4. Implementation: Cài đặt 5. Test: Kiểm thử 6. Deployment: Triển khai Theo nguyên tắc chung này, mỗi công ty lại ứng dụng những công nghệ khác nhau để phát triển ứng dụng như các mô hình water fall, mô hình hướng chức năng, I.3 Những thách thức của ngành công nghiệp phần mềm hiện nay Mặc dù đã ứng dụng các công nghệ tiến tiến và quy trình phát triển chuẩn hóa nhưng ngành công nghiệp phần mềm vẫn phải đối mặt với những thách thức: 1. Sự gia tăng về quy mô từ nhỏ đến lớn của ứng dụng 2. Sức ép về thời gian hoàn thành 3. Sự phân tán về không gian làm việc 4. Đa dạng về nội dung 5. Sự thay đổi các yêu cầu của người sử dụng Những thách thức này có thể nói là gắn liền với mọi công ty phần mềm đặc biệt là ở Việt Nam, nơi mà vai trò của quá trình khảo sát, phân tích còn bị xem nhẹ cũng như các công ty còn đang bỡ ngỡ với sự mở rộng về quy mô và thiếu nhân lực chuyên môn về phân tích thiết kế. II. Lịch sử phát triển ngôn ngữ mô hình hóa UML II.1. Tóm tắt lịch sử UML Những năm 1980 là thời kỳ bùng nổ số lượng các công ty phần mềm sử dụng ngôn ngữ lập trình hướng đối tượng(Object Oriented Programming - OOP) để xây dựng các ứng dụng. Điều này dẫn tới một đòi hỏi phải có một quy trình làm phần mềm tiếp cận theo hướng phân tích và thiết kế hướng đối tượng (Object Oriented Analyze and Design - OOAD). Nhiều nhà nghiên cứu phương pháp trong đó có Booch, Rumbaugh và Jacobson đã làm việc độc lập và đã đề xuất các quy trình thỏa mãn yêu cầu này. Mỗi một quy trình có một tập ký hiệu mô hình riêng để truyền đạt và diễn tả các kết quả phân tích và thiết kế. Vào đầu những năm 1990, các công ty khác nhau, thậm chí là các bộ phận khác nhau của cùng một công ty đã sử dụng các quy trình khác nhau. Thêm vào đó, các công ty này lại muốn sử dụng các công cụ phần mềm hỗ trợ các quy trình của họ. Với quá nhiều quy trình khác nhau, các công ty phần mềm đã rất khó khăn trong việc cung cấp các công cụ này. Điều này cho thấy việc cần thiết phải có một quy trình với tập ký hiệu thống nhất. Năm 1994, James Rumbaugh đã hợp tác cùng Grady Booch tại công ty phần mềm Rational Software Corporation để cùng xây dựng một quy trình thống nhất dựa trên kết quả của từng người. Sau đó Ivar Jacobson cũng sớm gia nhập nhóm này. Năm 1996, nhóm đã cho xuất 13
  14. bản phiên bản đầu tiên của UML tới cộng đồng phát triển phần mềm và yêu cầu phản hồi. Cũng cùng thời gian đó, một tổ chức có tên Object Management Group (OMG) đã mời nhóm đệ trình một ngôn ngữ mô hình. OMG là một tổ chức phi lợi nhuận chuyên xúc tiến việc sử dụng công nghệ hướng đối tượng trong ngành công nghiệp phần mềm thông qua việc đưa ra các hướng dẫn và đặc tả OOP. Các thành viên của OMG ban đầu là 3Com Corporation; American Airlines; Canon, Inc.; Data General; Hewlett-Packard; Philips Telecommunications N.V.; Sun Microsystems; và Unisys Corporation. Các tập đoàn lớn như HP, IBM, Microsoft, Oracle và Rational Software đã nhận thấy lợi ích của UML và đã nhận lời tài trợ cho các dự án về UML của OMG. Năm 1997, OMG tiếp tục xem xét lại UML và đến năm 2001 thì phiên bản UML 1.4 ra đời. Hiện nay OMG đã phát hành tới phiên bản UML 2.0 và đang nghiên cứu phiên bản 2.1. II.2. Khái niệm về UML UML – Unified Modeling Language là một ngôn ngữ dùng các sơ đồ và mô hình thống nhất để mô hình các hệ thống phần mềm. Mục đích của UML là: Trở thành ngôn ngữ mô hình mà tất cả mọi người làm mô hình có thể sử dụng Tập trung hướng tới giải quyết các vấn đề tồn tại trong phát triển phần mềm hiện nay. Đơn giản nhất có thể trong khi vẫn mô hình được phần lớn các ứng dụng. Nâng cao tính tái sử dụng các thành phần của một hệ thống phần mềm UML là một ngôn ngữ diễn tả, UML không phải là một quy trình. Thuật ngữ “Unified” ở đây có một ý nghĩa quan trọng, nó nói lên các nguyên tắc của UML: Thống nhất phương thức, ký hiệu, thuật ngữ Gắn kết giữa các chu trình phát triển Không phụ thuộc vào lĩnh vực ứng dụng Không phụ thuộc vào ngôn ngữ lập trình và môi trường thực hiện Không phụ thuộc vào quy trình phát triển Gắn kết chặt chẽ các khái niệm nội tại của hệ thống. II.3 Đặc trưng của UML Hiện nay, UML là một ngôn ngữ đã được OMG chuẩn hóa và được đặc tả rõ ràng. Tất cả các mô hình, sơ đồ của UML đều theo hướng đối tượng. Các đặc tính của UML bao gồm: Mô hình class (class diagrams) mô tả cấu trúc tĩnh của hệ thống và mối quan hệ giữa các đối tượng Mô hình tương tác (interaction diagrams), mô hình trạng thái (state diagrams), mô hình hoạt động (activity diagrams) mô tả các hành vi động của các đối tượng trong hệ thống cũng như các thông điệp giữa chúng. Mô hình Use-case và mô hình hoạt động mô tả các yêu cầu và các luồng công việc trong hệ thống. Các mô hình cấu trúc hỗn hợp (composite structure diagrams) mô tả sự hợp tác cũng như các đặc điểm về cài đặt. Mô hình triển khai (deployment diagrams) mô tả việc triển khai phần mềm trên một môi trường xác định. 14
  15. III. Ngôn ngữ UML III.1. Các thành phần của UML Xét trên khía cạnh ngôn ngữ diễn tả, UML có đầy đủ các mô hình và sơ đồ để thể hiện hầu hêt các khía cạnh của hệ thống phần mềm. Các thành phần của ngôn ngữ UML bao gồm: Các Views (các hướng nhìn): Các view thể hiện các cách nhìn khác nhau tới hệ thống. Một hệ thống không thể chỉ được mô tả bởi một sơ đồ. Nhiều hướng nhìn khác nhau cho ta nhiều sơ đồ mô tả đầy đủ về hệ thống. Các hướng nhìn cũng liên kết ngôn ngữ mô hình với các quy trình được chọn cho việc phát triển hệ thống. Các Diagrams (các sơ đồ): Các sơ đồ bao gồm các phần tử hình vẽ dùng để mô tả nôi dung của các View. UML 2.0 bao gồm 13 loại sơ đồ khác nhau. Các Model Elements (các phần tử mô hình): Các khái niệm được sử dụng trong các sơ đồ và các phần tử của sơ đồ diễn tả các khái niệm phổ biến của công nghệ hướng đối tượng như class, object, message (thông điệp) và mối quan hệ giữa chúng bao gồm quan hệ dependence, inheritance và aggregation. Một phần tử mô hình có thể được sử dụng trong nhiều sơ đồ nhưng chúng luôn có cùng ý nghĩa và ký hiệu giống nhau. Các General Mechanisms (các đặc tả chung mở rộng): Mô tả các thông tin chú thích, ngữ nghĩa của các phần tử mô hình, thuật ngữ. III.2. Các hướng nhìn (view) Use-case view: Đây là hướng nhìn về mặt chức năng của hệ thống được thực hiện bởi các tác nhân bên ngoài. 15
  16. Các nguyên tắc cho việc xây dựng use-case view là: Các use-case được hình thành trên cơ sở nhìn từ bên ngoài vào trong hệ thống (out-> in). Các use-case phải đầy đủ, chúng được tổng hợp theo phương pháp đi từ tối đa đến tối thiểu (ban đầu mở rộng tối đa các use-case có thể sau đó thu hẹp lại theo nhu cầu khách hàng). Các use-case cần có tính độc lập tương đối để dễ tổ chức nhóm làm việc và có tính tái sử dụng cao. Bên cạnh sơ đồ, người làm mô hình có thể sử dụng mô tả bằng văn bản chi tiết cho mỗi use-case (text detail). Mỗi mô tả cần đảm bảo các thông tin sau: 1. Tên use-case 2. Mục tiêu của use-case 3. Phạm vi của use-case 4. Các tác nhân chính 5. Tiền điều kiện 6. Dòng chính 7. Dòng phụ 16
  17. 8. Ngoại lệ 9. Hậu điều kiện Sau đây là một ví dụ về use-case text detail: Use-case: Đòi tiền bồi thường Phạm vi: Công ty Bảo hiểm PCM Actor chính: Người đòi bồi thường Dòng chính 1. Người đòi bồi thường gửi đơn yêu cầu với các dữ liệu bằng chứng về tai nạn 2. Công ty bảo hiểm xác nhận người viết đơn có quyền lợi bảo hiểm hợp lệ 3. Công ty bảo hiểm phân công cho một đại lý xác minh trường hợp này 4. Đại lý đối chiếu tất cả các chi tiết trong đơn theo chính sách bảo hiểm của công ty 5. Công ty bảo hiểm trả tiền bảo hiểm Dòng phụ 1a. Bằng chứng tai nạn không đầy đủ 1a1. Công ty bảo hiểm yêu cầu dữ liệu thiếu 1a2. Người đòi bồi thường gửi lại các dữ liệu thiếu 2a. Người đòi bồi thường không có chính sách bảo hiểm hợp lệ 2a1. Công ty bảo hiểm từ chối yêu cầu, nhắc nhở, ghi lại và kết thúc xử lý vụ việc 3a. Không có đại lý nào rảnh rỗi 3a1. (Công ty bảo hiểm sẽ làm gì trong trường hợp này???) 4a. Vụ tai nạn vi phạm chính sách bảo hiểm cơ bản 4a1. Công ty bảo hiểm từ chối yêu cầu, nhắc nhở, ghi lại và kết thúc xử lý vụ việc 4b. Vụ tai nạn chỉ vi phạm chính sách bảo hiểm nhỏ 4b1. Công ty bảo hiểm điều đình với người đòi bảo hiểm và đồng ý trả bảo hiểm Trong trường hợp các use-case quá phức tạp và chưa được mô tả rõ ràng, chúng có thể được tác ra thành các use-case nhỏ hơn theo hai dạng: Include: use-case mới được tách ra và được bao gồm trong use-case chính một cách vô điều kiện Extend: use-case mới xảy ra khi một điều kiện xảy ra trong use-case chính. 17
  18. Nói chung, use-case view giúp ta trả lời câu hỏi WHAT? về hệ thống. Logical views Đây là hướng nhìn cho biết các chức năng được thiết kế vào trong hệ thống như thế nào (HOW?) thông qua việc mô tả các cấu trúc tĩnh và các hành vi động của hệ thống. Sau đây là một số sơ đồ quan trọng thường được sử dụng. Các cấu trúc tĩnh (static structure): Cấu trúc tĩnh được mô hình bằng UML chính là các class diagrams. Đây là sự mô hình các khái niệm trong ứng dụng, các đặc điểm nội tại cũng như mối quan hệ giữa chúng. Các khái niệm Classes Mô hình hóa Attributes Operations Object Ví dụ về mô hình class: 18
  19. Các hành vi động: State machine: 19
  20. Đây là mô hình mô tả việc chuyển đổi trạng thái của đối tượng trong quá trình tham gia các hoạt động nghiệp vụ của hệ thống. Trạng thái của đối tượng bị thay đổi bởi một hành vi nào đó. Sơ đồ hành động (activity diagrams): Đây là sơ đồ mô tả luồng nghiệp vụ trong hệ thống. 20
  21. Sơ đồ tương tác (interaction diagram): Đây là sơ đồ mô tả sự tương tác giữa các đối tượng theo trình tự thời gian (còn gọi là sequence diagram). Các sơ đồ sequence có thể được mô tả ở mức hệ thống (Sequence System – chỉ có user tương tác với hệ thống) hoặc mức chi tiết (Sequence Detail – Các đối tượng và thông điệp giữa chúng đã được định hình): V C M V C M Các class trong hệ thống được chia thành ba loại tách biệt: M (Model) – V (View) – C (Control). Các class V chịu trách nhiệm hiển thị giao diện tương tác với người sử dụng, các class M lưu trữ dữ liệu được mô hình hóa từ thông tin đối tượng quản lý trong khi các class C là nơi điều khiển việc dữ liệu từ M được đưa tới giao diện V như thế nào hoặc xử lý các yêu cầu từ V sau đó cập nhật vào M. Sơ đồ triển khai (Deployment Diagram): 21
  22. III.3 Ứng dụng UML trong quy trình làm phần mềm Như đã trình bày ở trên, UML chỉ thuần túy là một ngôn ngữ mô hình. Các công ty phần mềm khác nhau tuy đều ứng dụng UML nhưng có thể sử dụng các quy trình phát triển khác nhau. 22
  23. Sơ đồ trên cho thấy một trình tự sử dụng các mô hình UML để xây dựng một ứng dụng. Xuất phát từ ý tưởng ban đầu thuần túy mang tính nghiệp vụ, các bước tiếp theo cần làm song song là xây dựng mô hình use-case và vẽ phác thảo giao diện chương trình. Tiếp theo xây dựng mô hình sequence system vì thông qua giao diện ta đã biết user tương tác với system như thế nào. Sau đó xây dựng mô hình class ở mức phác thảo, sơ đồ trạng thái và sơ đồ hành động. Đến đây ta đã biết user tương tác với cụ thể đối tượng nào của system và các thông điệp giữa các đối tượng từ mô hình class. Đây là cơ sở để xây dựng sequence detail và hoàn chỉnh mô hình class với các quan hệ đầy đủ. Theo quan điểm về phân tích thiết kế bằng UML thì việc lập trình rõ ràng không chỉ bắt đầu khi cài đặt các class và các sequence detail bằng một ngôn ngữ lập trình mà thực tế chúng ta đã lập trình ngay từ khi xây dựng mô hình use-case và viết text detail cho nó. Như vậy việc lập trình cài đặt chỉ được tiến hành sau khi cac mô hình đã hoàn tất. Trong quá trình xây dựng các mô hình cần liên tục tiếp xúc với khách hàng để đảm bảo tính chính xác của mô hình. Một khi các mô hình và tài liệu đặc tả đã hoàn chỉnh, việc coding thực sự chỉ chiếm khoảng 20% tổng số thời gian. Sau đây là một số công cụ giúp sử dụng UML trong phát triển hệ thống phần mềm: IV. Quy trình Rational Unified Process (RUP) phát triển phần mềm dựa trên UML IV.1. Giới thiệu về RUP Trong số các quy trình hiện nay, RUP được phát triển bởi công ty Rational Software được sử dụng khá phổ biến. Lịch sử: RUP là sự phối hợp giữa cách tiếp cận công nghệ hướng đối tượng của Rational trong những năm 1980s và 1990s với công nghệ xử lý đối tượng của Ival Jarcobson. Phiên bản mới nhất của RUP là version 7.0 phát hành 2005. Động cơ phát triển RUP là tìm ra nguyên nhân của các lỗi phần mềm và tìm cách khắc phục chúng thông qua một quy trình. IV.2. Các nguyên tắc chính của RUP: 1- Tuân thủ tiến trình (Adapt the process) 2- Cân bằng các ưu tiên của stakeholder (Balance stackeholder priorities). Stakeholder là những người đưa ra những ý kiến có ảnh hưởng lớn tới tư tưởng thiết kế hệ thống. 3- Cộng tác giữa các nhóm (Collaborate across teams) 23
  24. 4- Thể hiện kết quả theo quy trình lặp (Demonstrate value iteratively). Kết thúc mỗi quy trình cần theo dõi kết quả và kiểm tra chặt chẽ. 5- Nâng cao mức độ trừu tượng (Elevate the level of abstraction) 6- Tập trung theo dõi chất lượng liên tục (Focus continuously on quality) IV.3. Vòng đời của phần mềm theo quy trình RUP Trong một vòng đời của phần mềm, có 4 pha: Inception, Elaboration, Construction và Transition. Biểu đồ trên cho biết trình tự các pha và lượng tài nguyên sử dụng cho mỗi pha. Inception: Đây là giai đoạn chuẩn bị, tiếp xúc với khách hàng để nắm bắt ý tưởng và thu thập yêu cầu, chuẩn bị nhân lực, vật tư kỹ thuật. Elaboration: Sau khi đã thu thập được yêu cầu thì đây là pha thiết kế phác thảo sử dụng các sơ đồ UML để mô hình hóa các yêu cầu, các quy trình nghiệp vụ của ứng dụng, Construction: Pha xây dựng hệ thống yêu cầu sử dụng nhiều nhân lực, tài nguyên của công ty. Các công việc như thiết kế chi tiết, cài đặt, kiểm thử, đều được tiến hành trong pha này. Transition: Pha chuyển giao cho khách hàng. Một biểu đồ khác chi tiết hơn cho ta thấy các các giai đoạn phát triển được tiến hành khi nào và mức độ sử dụng tài nguyên của chúng trong các pha theo các nguyên tắc chung. 24
  25. Các giai đoạn công việc của RUP bao gồm: Mô hình hóa nghiệp vụ (business modeling): mô tả cấu trúc và quy trình nghiệp vụ. Xác định yêu cầu (requirement): mô tả nghiệp vụ bằng phương pháp “tình huống sử dụng” (use case base method) Phân tích và thiết kế (analysis & design): mô tả kiến trúc hệ thống thông qua các sơ đồ phân tích thiết kế. Lập trình (Implement): thực hiện các việc xây dựng chương trình bằng ngôn ngữ lập trình. Thử nghiệm (Test): mô tả các tình huống và kịch bản thử nghiệm, tiến hành thử nghiệm hệ thống phần mềm. Triển khai (Deployment): đưa hệ thống phần mềm vào sử dụng. Quản trị cấu hình và quản trị thay đổi (Configuration & Change Management): kiểm soát các thay đổi và duy trì sự hợp nhất của các thành phần dự án. Quản trị dự án: quản lý toàn bộ quá trình làm việc của dự án. Đảm bảo môi trường: đảm bảo các hạ tầng cần thiết để có thể phát triển được hệ thống. IV.4. Các công cụ của RUP Để áp dụng được quy trình phát triển hệ thống phần mềm của Rational thì yêu cầu không thể thiếu là hệ thống các phần mềm công cụ hỗ trợ. Hãng Rational đã xây dựng một hệ thống công cụ như vậy, mà tiêu biểu và thường dùng nhất là: Phần mềm Rational Requisite Pro: cho phép phân tích các yêu cầu, xây dựng kế hoạch thực hiện, xác định các tác nhân của hệ thống cùng những tình huống sử dụng. 25
  26. Phần mềm Rational Rose: cho phép xây dựng các mô hình phân tích, thiết kế, triển khai. Phần mềm Rational XDE: cho phép vừa xây dựng các mô hình vừa kết sinh mà nguồn chương trình song song với nhau. Phần mềm Rational Clear Case: quản trị dự án phân tích thiết kế, cho phép làm việc theo nhóm. Bài tập 1. Xây dựng mô hình use-case cho hệ thống quản lý việc mượn và trả sách tại thư viện. Hãy tiết text detail và xây dựng mô hình sequence system cho các use-case : “Mượn sách” và “Trả sách”. 2. Hãy mô tả một quy trình ứng dụng UML trong phát triển hệ thống phần mềm. 26
  27. Đề tài 2. Nhập môn Java I. Viết và thực hiện một chương trình Java I.1 Tìm hiểu mã nguồn một chương trình đơn giản 1. /* Day la chuong trinh vi du*/ 2. public class Vidu 3. { 4. public static void main(String[] args) 5. { 6. System.out.println("Hello, World!"); 7. } 8. } Dòng 1: Dòng chú thích trong một chương trình Java. Trong khi lập trình, ta cần chú ý viết các dòng chú thích để mô tả về mã nguồn. Điều này rất quan trọng khi chương trình lớn và gặp lỗi, các dòng chú thích giúp ta nhanh chóng nhận ra vai trò và ý nghĩa của từng đoạn code. Các chú thích trong chương trình Java được đặt trong cặp /* và */ khi cần chú thích nhiều dòng hoặc để sau cặp // khi chỉ chú thích một dòng. Tuy nhiên các cặp /* và */ hay được dùng hơn vì các đoạn chú thích này sẽ được tự động đưa vào tài liệu khi ta dùng công cụ javadoc để sinh tài liệu cho mã nguồn. Dòng 2: Khai báo một lớp có tên Vidu. Dòng 3. Cùng với dòng 8 tạo thành một cặp {} dùng để đánh dấu việc mở và đóng cho một khối lệnh, trong trường hợp này, tất cả các lệnh nằm trong cặp này đều thuộc phạm vi của lớp Vidu. Dòng 4: Khai báo hàm main. Trong Java, mọi chương trình nếu cần thực thi đều bắt đầu từ hàm main. Trình thông dịch sẽ tìm hàm main làm điểm khởi phát cho một ứng dụng Java. Dòng 5 và 7 cũng là đánh dấu mở và đóng cho một khối lệnh thuộc hàm main. Dòng 6: Câu lệnh in ra màn hình dòng chữ “Hello, World!”. Các lệnh trong Java luôn được kết thúc bởi dấu ;. I.2. Thực hiện chương trình Java. Để thực hiện chương trình Java trong ví dụ trên, ta thực hiện theo các bước sau: 1. Dùng một trình soạn thảo bất kỳ hoặc một IDE Java thì càng tốt để soạn chương trình. 2. Ghi file vừa soạn với tên Vidu.java. Ở đây, tên file phải bắt buộc là Vidu giống như tên lớp trong chương trình. Phần mở rộng cũng bắt buộc là .java để trình biên dịch Java biết đây là file chứa mã nguồn Java. Giả sử ghi vào thư mục c:\JavaSample. 3. Gọi cửa sổ Command Line của Windows: Từ Menu Start, chọn Run và gõ vào lệnh “cmd”. 4. Từ dấu nhắc gõ lệnh: javac c:\JavaSample\Vidu.java Sau lệnh này, nếu chương trình không có lỗi cú pháp, Java sẽ sinh ra một file mã bytecode có tên Vidu.class mặc định cũng nằm trong thư mục c:\JavaSample. 5. Từ dấu nhắc gõ lệnh: java c:\ JavaSample\Vidu.class 27
  28. Lệnh này sẽ thông dịch file mã bytecode Vidu.class và in ra màn hình dòng chữ “Hello, World!”. Ở đây, ta cần chú ý Windows không tự động hiểu các lệnh javac, java. Tuy nhiên, khi cài đặt Java ta đã đặt đường dẫn mặc định đến thư mục chứa các lệnh này là C:\Program Files\Java\jdk1.6.0_02\bin Trường hợp chưa đặt đường dẫn mặc định, ta phải gọi các lệnh này với đường dẫn đầy đủ của nó. I.3. Một số chú ý khi lập trình Java Java phân biệt chữ viết hoa và viết thường. Hàm main phải nằm trong file gọi thực hiện Tên khai báo phải trùng với tên tệp “Vidu.java” Hàm main trong Java luôn luôn là static Trong Java tất cả hàm và thủ tục đều phải được đặt trong một lớp cụ thể. Không được khai báo phương thức, hàm hay thủ tục bên ngoài lớp. Sử dụng phương thức (method) để thay thế cho hàm và thủ tục. I.4. Cấu trúc một chương trình Java /*Khai bao goi*/ package ; /*Khai bao thu vien*/ import ; /*Khai bao lop chua ham main*/ public class { /*Các thuoc tinh cua lop */ . /*Cac phuong thuc*/ . /*Hàm main*/ public static void main(String[] args) { // Các lệnh trong hàm main } } /*Cac lop lien quan khac*/ class A { . } class B { . } Các thành phần trong một chương trình Java gồm có: 1. Khai báo gói: 28
  29. Cú pháp: package . Khai báo này là không bắt buộc với một chương trình Java. Trong trường hợp ứng dụng gồm nhiều class và cần tổ chức các class vào các gói riêng thì ta cần khai báo gói. Trong chương trình trên, class Vidu sau khi biên dịch sẽ được đặt trong gói my.java. Tên của gói có chứa các dấu “.” chỉ sự bao gồm, ở đây gói “java” nằm trong gói “my” và class Vidu nằm trong gói “java”. Khi một chương trình Java khác muốn truy cập tới lớp Vidu, nó cần truy cập theo đường dẫn gói “my.java.Vidu”. Thực ra, ý nghĩa quan trọng của gói là việc tổ chức một cách logic các lớp vào trong các domain riêng để người phát triển ứng dụng dễ dàng truy cập. Trong hàng ngàn class được Java hỗ trợ trong các gói thư viện, chúng đều được nhóm lại theo chức năng và mục đích sử dụng với tên gói có tính chất gợi ý. Nếu không làm như vậy, chúng ta sẽ rất khó khăn để tìm ra một class để sử dụng. 2. Nhập thư viện. Cú pháp: import Nếu có khai báo này, khi sử dụng các class nằm trong gói theo ta không cần viết đầy đủ tên gói mà chỉ cần viết tên class. Java dùng ký từ “*” để ngụ ý việc nhập tất cả các class trong . Ví dụ: import my.java.Vidu; // Nhập duy nhất class Vidu trong gói my.java import my.java.*; // Nhập tất cả các class có trong gói my.java, tất nhiên là bao gồm cả class Vidu. Sau đây là một chương trình ví dụ sử dụng import: import java.util.Date; //Khai báo thư viện /*Chương trình in ra ngày tháng hiện hành*/ public class Application { public static void main(String[] args) { Date date = new Date(); //tạo biến đối tượng thuộc class Date System.out.println(“Hôm nay "+date); } } Ở đây, class Date đã được khai báo import nên không cần khai báo đầy đủ “java.util.Date” tại dòng số 5. 3. Khai báo class, thuộc tính, hàm thành phần: Các khai báo này sẽ được bàn chi tiết trong mục “Lập trình hướng đối tượng trong Java”. 4. Khai báo hàm main: Không phải tất cả các class trong Java đều chứa hàm main. Chỉ có class được gọi thực thi đầu tiên mới cần chứa hàm main. 5. Khai báo các lớp khác: Thông thường 1 file chương trình Java chúng ta chỉ khai báo 1 class. Tuy nhiên khi class đó quá phức tạp chúng ta có thể tách ra thành các class khác. Trong số 5 phần trên, tất cả các class đều có phần 3, các phần còn lại có thể có hoặc không tùy theo nhu cầu. II. Các kiểu dữ liệu trong Java II.1 Các kiểu dữ liệu số nguyên Java có 4 kiểu số nguyên: 29
  30. int 4 bytes Từ –2,147,483,648 đến 2,147,483, 647 short 2 bytes Từ –32,768 đến 32,767 long 8 bytes Từ –9,223,372,036,854,775,808 đến9,223,372,036,854,775,807 byte 1 byte Từ –128 đến 127 II.2 Các kiểu số thực Java có 2 kiểu số thực dấu chấm động: float 4 bytes Xấp xỉ ±3.40282347E+38F double 8 bytes Xấp xỉ ±1.79769313486231570E+308 Số dấu chấm động có thể nhận các giá trị: Vô cực âm/dương. Số âm/dương. Số 0. NaN (không là một số = 0.0/0.0f). II.3 Kiểu ký tự (character) Là kiểu dữ liệu về ký tự. Một biến char sẽ có một giá trị là một ký tự Unicode. II.4 Kiểu logic (boolean) Là kiểu dữ liệu chỉ có hai giá trị true và false dùng để xác định kết quả một điều kiện. Chú ý: kiểu boolean không được thể hiện là 0 và 1. II.5 Kiểu chuỗi Java xem chuỗi là một đối tượng. Biến đối tượng chuỗi thường được khai báo từ lớp String nằm trong gói java.lang.String. II.6 Chuyển đổi giữa các kiểu số char long byte short int double float 30
  31. III. Khai báo biến và hằng trong Java III.1 Quy tắc đặt tên biến Khi khai báo các biến trong chương trình Java, ta cần chú ý tuân thủ các điểm sau: Chỉ bắt đầu bằng một ký tự (chữ), một dấu gạch dưới (_) hay một dấu dollard ($). Không có khoảng trống giữa tên. Sau ký tự đầu, có thể dùng các ký tự (chữ), ký tự số, dấu dollard, dấu gạch dưới. Không trùng với các từ khoá. III.2 Khai báo biến Các biến trong Java rơi vào hai trường hợp: Toán học: Các biến có kiểu nguyên thủy của Java đều thuộc dạng này. Sau khi khai báo, biến được cấp phát một vùng nhớ cố định tùy theo kích thước của kiểu dữ liệu của biến đó. Địa chỉ: Các biến đối tượng được lưu ở dạng này. Biến chỉ lưu giá trị địa chỉ đầu của một vùng nhớ được cấp phát cho đối tượng. Ví dụ khai báo cho từng kiểu biến toán học: byte i; short j; int k; long x; float y; double z; char ch; boolean bQuit; Các biến địa chỉ thường sử dụng để lưu trữ các địa chỉ mảng, con trỏ, đối tượng. Ví dụ về khai báo biến kiểu địa chỉ: String strHello; //khai báo một chuỗi AudioClip music; // ví dụ lớp của AudioClip Khởi động giá trị cho biến Ví dụ về khởi động biến ngay lúc khai báo: byte i = 3; short j = 10; int k = 1; long x = 1234567L; float y = 1.25f; double z = 23.45d; char ch = ‘T’; boolean bQuit = true; String strHello = “Hello everybody”; Ở đây cần chú ý rằng khi khởi tạo giá trị cho các kiểu số nên xác định rõ kiểu dữ liệu của giá trị. Các giá trị mặc định byte 0 short 0 int 0 31
  32. long 0L; float 0.0f; double 0.0d; char null; boolean false; Các biến dẫn suất null III.3 Biến kiểu mảng Khi cần làm việc với một tập các giá trị có cùng kiểu dữ liệu, ta có thể sử dụng một biến mảng để lưu trữ chúng. Khai báo: Một biến mảng được khai báo theo hai cách: Sử dụng cặp ngoặc vuông đặt sau tên biến. Sử dụng cặp ngoặc vuông đặt sau kiểu dữ liệu. Ví dụ: int [] intArray; hoặc int intArray[]; đều cho ta một mảng số nguyên có tên la intArray. Thông thường ta dùng kiểu khai báo thứ nhất để có thể khai báo nhiều biến mảng cùng kiểu dữ liệu: int [] intArray1, intArray2, intArray3; Định vị mảng Sau khi khai báo, bản thân mảng chưa xác định hay chưa được định vị vì chưa được cấp phát vùng nhớ . Do đó, mảng cần được cấp phát vùng nhớ trước khi sử dụng. Dùng từ khoá new để định vị cho một mảng trong vùng nhớ, ví dụ: int IntArray[] = new int[100];//tạo mảng 100pt float floatArray[]; floatArray = new float[10]; //tạo mảng 10 pt Khởi tạo giá trị cho mảng Ta cũng có thể khởi tạo một mảng bằng cách liệt kê các phần tử của nó, ví dụ: int IntArray[] = {1, 2, 3, 4, 5}; char [] charArray = {‘a’, ‘b’, ‘c’}; IntArray[] = new int[] {1, 2, 3, 4, 5}; Truy cập các phần tử của mảng Các phần tử trong một mảng luôn được đánh số bắt đầu từ số 0. Kiểu dữ liệu của chỉ số là kiểu int. Tuy nhiên nếu dùng kiểu char thì Java sẽ tự động chuyển sang mã ASCII tương ứng. Ví dụ b[‘a’] tương ứng với b[97]. Phần tử đầu tiên là phần tử thứ 0, và phần tử cuối cùng của một mảng có n phần tử là n-1. Các phần tử của mảng được truy cập một cách trực tiếp bằng chỉ số của nó. Ví dụ: int IntArray[] = {1, 2, 3, 4, 5}; int a = IntArray[2];//a=3 int b = IntArray[0];//b=1 Ta có thể gán các giá trị của mảng thông qua truy nhập vào từng phần tử của mảng. Ví dụ: int intList[] = new int[10]; //tạo mảng 10pt intList[0] = 99; //Phần tử thứ 0 (đầu tiên) có giá trị là 99. for (sort i=1; i<10; i++) 32
  33. intList[i] = 1; //các phần tử còn lại bằng 1 Ví dụ: int x, y, k; x = intList[0]; //x=99 y = intList[5]; //y=1 k = intList[y+1]; //k=intList[6]=1 Mảng nhiều chiều: Khai báo mảng 2 chiều: [][] = { }; Ví dụ: int[][] b = {{1,2},{3,4}}; Đây là khai báo và khởi tạo giá trị cho một mạng 2 chiều kích thước 2 x 2. {1,2} là các phần tử của hàng 1; {3,4} là các phần tử trên hàng thứ 2. Hoặc ta có thể khai báo rõ số hàng và số cột của mảng: int b[][] = new int[ 3 ][ 4 ]; Thậm chí ta có thể khai báo một mảng 3 chiều hoặc hơn. int b[][][] = {{{1,2},{3,4}},{{5,6},{7,8}}}; Ta được b[0][0][0] =1; b[0][0][1]=2;b[1][1][1]=8;b[0][1][0]=3; III.4 Hằng số (literal) Là một giá trị không đổi được sử dụng trong chương trình. Hằng số được biểu diễn như chính nó chứ không là một giá trị của biến hay một kết quả của biểu thức. Giá trị của hằng số không thể bị thay đổi. Ví dụ: Pi = 3.1415. Tên của hằng số được đặt tên như tên biến. Cách khai báo hằng cũng tương tự như biến nhưng có dùng thêm từ khóa final: final = Ví dụ: public final String mauda ="vang"; Hằng số có thể là: Hằng số nguyên: Hằng có thể được biểu diễn dưới dạng thập phân, bát phân, thập lục phân. Ví dụ: biểu diễn số 15 dạng int: 15. dạng long: 15L. dạng bát phân: 017. dạng thập lục phân: 0xF. Hằng số thực: Tương tự như hằng số nguyên, để chỉ rõ hằng là float ta thêm vĩ ngữ “ f ” hay “F”, hằng là double ta thêm “d” hay “D”. Hằng Boolean: Một hằng số kiểu boolean có giá trị là true hoặc false. Trong Java, các giá trị 0 và 1 không được dùng thay thế cho false và true như trong C hoặc C++. Hằng ký tự: Là một ký tự đơn giản hay một chuỗi ESCAPE, hằng ký tự được đặt trong hai dấu ngoặc đơn ‘’. Các chuỗi ESCAPE: ‘\b’ : Xoá lùi. ‘\n’ : Xuống dòng. ‘\t’ : Tab ngang. 33
  34. ‘\f’ : đẩy trang. ‘\r’ : dấu enter. ‘\”’ : nháy kép. ‘\’’ : nháy đơn. ‘\\’ : sổ ngược. ‘uxxxx’: ký tự unicode. Ví dụ: System.out.println(“Xin chào bạn \n Nguyễn Văn A”); Sẽ in ra màn hình kết quả: Xin chào bạn Nguyễn Văn A Hằng chuỗi ký tự: Một hằng chuỗi ký tự có thể có 0 ký tự (hằng chuỗi rỗng) hay nhiều ký tự. Ví dụ: “A String”, “” //chuỗi rỗng, “dong 1 \t\n dong 2”. III.5 Phạm vi hoạt động của hằng và biến: Khối lệnh Block 1 chứa 2 khối lệnh con Block 2, Block 3. { Block 1 { Block 2 } { Block 3 } } Biến hay hằng sẽ chỉ có ý nghĩa trong phạm vi khối lệnh mà nó được khai báo. IV. Các toán tử và biểu thức IV.1 Các toán tử và thứ tự ưu tiên Các toán tử thường sử dụng: Toán tử Ý nghĩa Ví dụ = Gán x=10 != so sánh khác x!=5 > so sánh lớn hơn x>5 = lớn hơn hoặc bằng x>=10 <= nhỏ hơn hoặc bằng x<=10 + cộng y=x+1 - trừ y=x-1 * Nhân y=x*3 / Chia y=x/3 34
  35. % Lấy phần dư 10%3 = 1 ++ tăng giá trị lên 1 x++ giảm giá trị đi 1 x += cộng kết hợp phép gán x+=y tương đương x=x+y -= trừ kết hợp phép gán x-=y tương đương x=x-y *= nhân kết hợp phép gán x*=y tương đương x=x*y /= chia kết hợp phép gán x/=y tương đương x=x/y ^ phép XOR trên bit x ^ y | phép OR trên bit x | y & phép và trên bit ! Toán tử logic NOT && Toán tử logic AND || Toán tử logic OR = = So sánh bằng nhau Thứ tự ưu tiên: ( ), *, /, %, +, -,= =, !=, &, ^, |, &&, ||, =, %=, /=, *=, -=, += IV.2 Biểu thức Biểu thức là sự kết hợp các biến số, các giá trị bởi các toán tử hoặc có thể là một phép gán giá trị một biểu thức cho một biến số. Ví dụ: (x+3)/(y-2); Có 3 loại biểu thức chính là: Biểu thức số liên kết các biến số, các hằng bằng các phép toán số, kết quả là một giá trị số. Biểu thức gán dùng để gán giá trị cho một biến, một hằng. Biểu thức logic chỉ cho ra kết quả là các giá trị true hay false. Khi sử dụng câu lệnh gán kết quả của một biểu thức cho một biến, ta cần chú ý tới vấn đề đồng nhất kiểu dữ liệu giữa hai vế để tránh mất thông tin. Ví dụ: Double delta = 0.0d; //khai báo một biến số thực có tên delta delta = 1/ 100; // Gán cho delta kết quả của phép chia 1 cho 100. Trong tình huống này, ta không thu được delta = 0.01 như mong đợi mà là delta =0. Lý do là các số 1 và 100 đều được hiểu là các số nguyên và kết quả của phép chia được tự động làm tròn thành một giá trị nguyên trước khi gán cho delta. Để khắc phục tình trạng này, ta cần xác định rõ các số 1 và 100 là các số double. delta = 1d/100d; V. Các lệnh điều khiển rẽ nhánh V.1 Lệnh if Lệnh if { }: là một phép kiểm tra giá trị của một biểu thức boolean, nếu cho giá trị là true thì khối lệnh sẽ được thực hiện. Cấu trúc: if { ; 35
  36. } Nếu biểu thức boolean đúng, khối lệnh sẽ được thực hiện, còn nếu biểu thức đó sai thì khối lệnh sẽ bị bỏ qua. Ví dụ: public class dkIfThen { public static void main(String[] args) { int x=1; int y=x+1; if (x y"); } }} Dạng 2 của câu lệnh if: if { ; } else { ; } Nếu biểu thức boolean đúng thì được thực hiện, còn nếu biểu thức boolean sai thì được thực hiện. Ví dụ: public class dkIfThen { public static void main(String[] args) { int x=1; int y=x-1; if (x y"); } else { System.out.println("x { case : ; break; case : ; break; case : ; break; default: ; break; } Một số chú ý khi sử dụng lệnh switch-case: Các giá trị: , , phải là các hằng số. 36
  37. Nếu không sử dụng lệnh break mỗi khi kết thúc các khối lệnh thì sau khi thực hiện xong khối lệnh, các lệnh tiếp theo sẽ được thực hiện. VI. Các lệnh lặp VI.1. Vòng lặp for Khi muốn một khối lệnh được thực hiện với một số lần biết trước, ta có thể sử dụng một vòng lặp for. Cấu trúc lệnh: for([ ]; [ ]; [ ]) { ; } Bắt đầu với giá trị của biến đếm, được thực hiện. Sau mỗi lần thực hiện xong , biến đếm thay đổi giá trị một lượng bằng và được tính lại. Nếu biểu thức kiểm tra là true thì lại thực hiện khối lệnh, còn nếu là false, vòng lặp chấm dứt. Lưu đồ hoạt động của vòng lặp for như sau: Biểu thức biến điều khiển Biểu thức điều chỉnh Biểu thức false theo bước nhảy kiểm tra true Thực hiện khối lệnh Kết thúc vòng for Ví dụ: tính tổng 1 dãy số thực public class vdFor { public static void main(String[] args) { double accounts[]={1.2,1.4,1.6}; double sum=0; for (int i=0;i<accounts.length;i++){ sum+=accounts[i]; } System.out.println(sum); } 37
  38. }//kết quả là: 4.2 Các vòng for có thể được đặt lồng nhau nếu cần Ví dụ: for(int i=0; i { ; } Khối lệnh được thực hiện khi còn có giá trị true. Chú ý: trong khối lệnh phải có câu lệnh có tác dụng ảnh hưởng tới kết quả để vòng lặp có thể dừng. Lưu đồ thực hiện: Bt false boolean true Các câu lệnh Câu lệnh tiếp theo VI.3. Vòng lặp do while Vòng lặp này có ý nghĩa tương tự như vòng lặp while nhưng được thực hiện ngay ở vòng đầu tiên mà chưa cần kiểm tra kết quả . 38
  39. Cấu trúc lệnh: do { ; } while ; Vòng lặp này cho thực hiện rồi mới kiểm tra . Nếu có giá trị true thì tiếp tục thực hiện , nếu không sẽ dừng vòng lặp. Ví dụ tính tổng 10 số đầu tiên: public class vdDoWhile { public static void main(String[] args) { int x=1, sum=0; do{ sum+=x; x++; } while (x { lệnh 1; lệnh 2; if break; lệnh 3; } lệnh 4; Lệnh continue Cho phép chương trình bỏ qua vòng hiện tại và nhảy đến vòng tiếp theo. Ví dụ: while { lệnh 1; lệnh 2; if 39
  40. { continue; } lệnh 3; } lệnh 4; Khi có giá trị true, lệnh continue được thực hiện, chương trình sẽ không thực hiện tiếp mà quay lại kiểm tra . VII. Vào dữ liệu từ bàn phím và xuất dữ liệu ra màn hình VII.1. Lấy giá trị nhập vào từ bàn phím Để lấy giá trị mà người sử dụng nhập từ bàn phím, ta làm theo các bước sau: 1. Khai báo biến thuộc lớp Scanner”. Lớp Scanner chỉ được hỗ trợ từ phiên bản Java 1.5 và nằm trong gói java.util. // Khai báo một biến Scanner có tên là “nhap”. Java.util.Scanner nhap = new java.util.Scanner(System.in); 2. Lấy giá trị nhập vào: System.out.print("What is your name? "); // In ra màn hình một câu hỏi tên /* Khai báo và gán giá trị nhập từ bàn phím cho một biến kiểu String có tên name.*/ String name = nhap.nextLine(); Để đọc 1 từ trong chuỗi nhập vào: String firstName = nhap.next(); Nếu đọc vào một số nguyên: int Tuoi = nhap.nextInt(); Tương tự cho các kiểu dữ liệu khác. Ví dụ: import java.util.*; public class InputTest { public static void main(String[] args) { Scanner nhap = new Scanner(System.in); // Lay gia trị nhap ho ten System.out.print("What is your name? "); String name = nhap.nextLine(); // Lay gia tri nhap tiep theo System.out.print("How old are you? "); int age = nhap.nextInt(); // Hien thi ket qua nhap tren man hinh System.out.println("Hello, " + name + ". Next year, you'll be " + (age + 1)); } } 40
  41. VII.2 Kết xuất dữ liệu ra màn hình Trong các chương trình ví dụ trên, ta đã biết dùng hàm System.out.print để in dữ liệu ra màn hình. Tuy nhiên, trong một số trường hợp ta cần định dạng dữ liệu xuất ra chẳng hạn như hiển thị một số thực dạng thập phân với độ chính xác nhất định. System.out.printf(“%8.1f”, 10/3); System.out.printf(“Hello, %s. You have %f VND ”, name,money); Sau đây là bảng các ký tự định dạng: Ký tự định dạng Định dạng Ví dụ D Số nguyên thập phân 159 X Số nguyên hệ 16 9f O Số nguyên hệ 8 237 F Số thực float 15.9 E Số thực float theo ký pháp cơ số e 1.59e+01 A Số thực float dạng Hexa 0x1.fccdp3 S String Hello C Ký tự H B Boolean True H Hash code 42628b2 tx Date and time % Ký hiệu phần trăm % N Xuống dòng Sau đây là ví dụ sử dụng các ký tự định dạng khi xuất dữ liệu ra màn hình. public class TestFormat { public static void main(String[] argvs) { double sothuc = 10.09898765; /*In ra số thực chiếm 10 ký tự trên màn hình trong đó phần thập phân chiếm 3 ký tự*/ System.out.printf("Ket qua 3 so sau dau phay la %10.3f \n",sothuc); int songuyen =100; /*In ra số nguyên ở hệ cơ số 16*/ System.out.printf("He 16 cua 100 duoc viet la: %x \n",songuyen); /*In ra số nguyên ở hệ cơ số 8*/ System.out.printf("He 8 cua 100 duoc viet la: %o \n",songuyen); /*In ra ký tự đặc biệt “” */ System.out.print("\"Mot tram phan tram\" thi in ra the nao: 100%"); }} Lưu ý rằng khi sử dụng các ký tự định dạng ta cần dùng hàm System.out.printf() thay vì System.out.print() hay System.out.println() như thường lệ. 41
  42. Bài tập 1. Viết chương trình tính giá trị các biểu thức (Giá trị n, k, nhập từ bàn phím): A = 1 + 1/2! + 1/3! + + 1/n! B = 1/1! – 1/2! + 1/3! - 1/4! + + 1/(2k+1)! - . 2. Viết chương trình đếm số từ có trong một chuỗi ký tự nhập vào. Các từ cách nhau bởi dấu space,. và ; 3. Viết chương trình đếm tần suất xuất hiện của các từ trong một chuỗi nhập từ bàn phím. 4. Nhập vào một chuỗi họ tên của một người, hãy sửa lại các ký tự đầu các từ cho đúng quy định viết hoa và khoảng cách giữa các từ. Ví dụ: nguyen van nam -> Nguyen Van Nam 5. Tìm các lỗi trong các đoạn chương trình sau: a) For ( x = 100, x >= 1, x++ ) System.out.println( x ); b) Đoạn mã sau sẽ in ra các giá trị chẵn hay lẻ: switch ( value % 2 ) { case 0: System.out.println( "Even integer" ); case 1: System.out.println( "Odd integer" ); } c) Đoạn mã sau sẽ in ra các số nguyên lẻ từ 19 đến 1 ?: for ( x = 19; x >= 1; x += 2 ) System.out.println( x ); d) Đoạn mã sau sẽ in ra các số nguyên chẵn từ 2 đến 100 ?: counter = 2; do { System.out.println( counter ); counter += 2; } While ( counter < 100 ); 6. Đoạn chương trình sau làm gì? public class Printing { public static void main( String args[] ) { for ( int i = 1; i <= 10; i++ ) { for ( int j = 1; j <= 5; j++ ) System.out.print( '@' ); System.out.println(); } } } 7. Viết chương trình tìm số nhỏ nhất trong số các số được nhập vào từ bàn phím. Cho biết số nguyên đầu tiên nhập vào sẽ chính là số các con số được nhập. 8. Viết chương trình in ra màn hình các mẫu như sau: 42
  43. 9. Tính số PI theo công thức sau: In ra 10 giá trị chính xác hơn 3.141. 43
  44. Đề tài 3. Lập trình hướng đối tượng trong Java I. Khái niệm lập trình hướng đối tượng (Object-Oriented Programming - OOP) I.1. Khái niệm OOP Lập trình hướng đối tượng là sự cài đặt một chương trình theo hướng đối tượng bằng các ngôn ngữ lập trình, thường là ngôn ngữ OOP. Như vậy, nếu dùng một ngôn ngữ OOP mà chương trình không theo hướng đối tượng thì cũng không phải là lập trình OOP. Trong khi nếu dùng một ngôn ngữ không hướng đối tượng để viết một chương trình OOP (rất khó khăn) thì cũng có thể gọi là lập trình OOP. Thực tế thì ta không thể viết chương trình hướng đối tượng bằng các ngôn ngữ cấu trúc (như Pascal chẳng hạn) vì các ngôn ngữ này không hỗ trợ cú pháp cài đặt và kỹ thuật biên dịch các đặc tính của hướng đối tượng. Những ngôn ngữ OOP không chỉ bao gồm cú pháp và một trình biên dịch (compiler) mà còn có một môi trường phát triển toàn diện. Môi trường này bao gồm một thư viện được thiết kế tốt, thuận lợi cho việc sử dụng kế thừa các đối tượng – tính tái sử dụng. Đây là một điểm mạnh của OOP và phương pháp trước đây không có được. Đối với một ngôn ngữ lập trình hỗ trợ OOP thì việc triển khai kỹ thuật lập trình hướng đối tượng sẽ dễ dàng hơn. Hơn nữa, các dự án phần mềm phân tích và thiết kế theo UML bắt buộc phải sử dụng kỹ thuật OOP để cài đặt thì mới phát huy hiệu quả. I.2 Cơ sở lý luận của OOP Chúng ta thấy rằng thuật ngữ “hướng đối tượng” có nghĩa là lấy đối tượng làm trung tâm và tất cả nằm trong đối tượng. Quan sát thế giới thực, ta thấy mọi vật đều có vị trí riêng của nó, chúng sở hữu các tính chất và thuộc tính riêng, cách thức vận động riêng. Chúng ta gọi chúng là những đối tượng. Theo cách hiểu như vậy thì mọi nghiệp vụ thực tế suy cho cùng chỉ là việc quản lý các đối tượng, khai thác thông tin cũng như các mối quan hệ từ chúng hoặc thay đổi trạng thái của chúng. OOP là phương thức tư duy mới để giải quyết vấn đề bằng máy tính. Để đạt kết quả, lập trình viên phải nắm được sự tương ứng giữa các các đối tượng thực tế, mối quan hệ giữa chúng và sự hỗ trợ của ngôn ngữ để cài đặt chúng vào máy tính. Ngôn ngữ OOP cung cấp đầy đủ phương tiện để thực hiện điều này. Chúng ta sử dụng kỹ thuật hướng đối tượng để ánh xạ những thực thể chúng ta gặp phải trong đời sống thực thành những thực thể tương tự trong máy tính. Do đó, phát triển phần mềm theo kỹ thuật lập trình hướng đối tượng có khả năng giảm thiểu sự lẫn lộn thường xảy ra giữa hệ thống và lĩnh vực ứng dụng. Tuy nhiên, từ nghiệp vụ thực tế chúng ta không thể ngay lập tức đem vào cài đặt trong ngôn ngữ OOP mà phải qua một quy trình phân tích và thiết kế theo hướng đối tượng như chúng ta đã thấy qua việc nghiên cứu ngôn ngữ mô hình hóa UML – một ngôn ngữ giúp chúng ta trừu tượng hóa thế giới thực. I.3 Trừu tượng hóa Quản lý thông tin, các hành vi, các mối quan hệ của các đối tượng là nhiệm vụ mà lập trình OOP phải làm. Thông tin về đối tượng và mối quan hệ giữa chúng thực tế là vô cùng. Vậy làm thế nào để đưa chúng vào máy tính? Câu trả lời là chúng ta cần một quá trình trừu tượng hóa. 44
  45. Giả sử đối tượng quản lý là một sinh viên. Một hệ thống quản lý sinh viên có thể chỉ cần quan tâm tới: Họ và tên, ngày sinh, lớp học, địa chỉ nơi ở, điểm các môn học. Trong khi đó, các thông tin khác về sinh viên – cũng là một con người – như chiều cao, cân nặng, nhóm máu, chúng ta không cần quan tâm. Một quá trình suy luận như vậy là một quá trình trừu tượng hóa dữ liệu. Ở đây ta không quan tâm tới giá trị cụ thể của các thuộc tính này. Khi quan tâm tới giá trị của các thuộc tính, chúng ta có một câu hỏi: Cái gì làm cho dữ liệu này biến đối? Câu trả lời là chính hành vi của đối tượng làm cho thuộc tính của chúng bị thay đổi. Trong ví dụ trên, một anh sinh viên bất kỳ có thể có hành vi “xin đổi lớp học” hoặc “đổi địa chỉ nơi ở” làm cho giá trị các thuộc tính “lớp học”, “địa chỉ nơi ở” bị thay đổi. Quá trình xác định các hành vi của đối tượng phục vụ cho nghiệp vụ quản lý cũng là một quá trình trừu tượng hóa hành vi. Tóm lại, chúng ta có một mô hình trừu tượng hóa của một sinh viên: Sinh viên Họ và tên Ngày sinh Tên lớp Địa chỉ Điểm môn học Thay đổi lớp Thay đổi nơi ở Điểm quan trọng là sau khi trừu tượng hóa từ một lớp các sinh viên (tất cả các sinh viên trong phạm vi quản lý), mô hình này đại diện cho tất cả sinh viên và là khuôn mẫu để tạo ra bất kỳ sinh viên nào khác. Qua đây, ta cũng rút ra nhận xét rằng quá trình trừu tượng hóa tùy theo yêu cầu nghiệp vụ sẽ cho kết quả khác nhau. Cũng là một sinh viên nhưng nếu chẳng may anh ta bị ốm nằm viện, anh ta được quản lý như một bệnh nhân. Một hệ thống quản lý bệnh nhân tất nhiên không thể bỏ qua các thông tin về nhóm máu, cân nặng, huyết áp, và tất nhiên là cũng có các mô hình hành vi khác. Các lớp đối tượng trong chương trình - Classes Trừu tượng hóa Thế giới thực = Các đối tượng + Các mối quan hệ Tóm lại: Các thực thể tồn tại trong thế giới thực được mô hình hóa thành các lớp đối tượng. Các mối quan hệ giữa các thực thể trong thế giới thực được mô hình hóa bằng các mối quan hệ giữa các lớp đối tượng. 45
  46. II. Tính đóng gói trong Java II.1 Khái niệm tính đóng gói Tính đóng gói thể hiện bởi việc thuộc tính mô tả đối tượng và hành vi của đối tượng được gắn chặt với nhau. Thuộc tính thể hiện trạng thái đối tượng, hành vi làm thay đổi trạng thái đối tượng thông qua việc thay đổi giá trị các thuộc tính. Thuộc tính được cài đặt thông qua mã lệnh Java bằng các biến thành phần trong lớp. Hành vi được cài đặt thông qua mã lệnh Java bởi các phương thức. Thông tin và hành vi đối tượng được bảo vệ bằng các cấp độ truy cập: public, private. Cấp độ public cho phép sự truy cập từ bên ngoài lớp trong khi private chỉ cho phép nội bộ lớp truy cập. Đối tượng được sinh ra bởi class - một sự mô hình hóa một lớp đối tượng trong thực tế. object = attributes (các thuộc tính) + operations(các hành vi) + name (tên định danh) Như vậy tính đóng gói thể hiện việc chuyển mô hình đối tượng thực tế thành các lớp trong Java. II.2 Mối quan hệ giữa các class Các class trong chương trình có thể quan hệ với nhau theo 1 trong 3 dạng: Phụ thuộc (Dependence): Class A có quan hệ phụ thuộc với class B nếu phương thức của class A có sử dụng đối tượng thuộc class B. Bao gồm (Aggregation): Class A có quan hệ bao gồm với class B nếu đối tượng của class A chứa đối tượng của class B. Thừa kế (inheritance): Class B gọi là thừa kế class A nếu class B có các phương thức và thuộc tính của class A, ngoài ra class B còn định nghĩa các phương thức và thuộc tính khác của riêng nó. Ví dụ: Trong hệ thống bán hàng chúng ta có: class DanhMucMatHang class TaiKhoan class HoaDon class HoaDonThanhToanNhanh Mỗi HoaDon bao gồm 1 DanhMucMatHang: Quan hệ aggregation. Khi một HoaDon được tạo ra, nó cần truy cập đến class TaiKhoan để kiểm tra tình trạng thẻ tín dụng: Quan hệ dependence. Mỗi HoaDonThanhToanNhanh thừa kế các thuộc tính và phương thức của HoaDon và có thêm các thuộc tính, phương thức khác: Quan hệ inheritance. II.3 Một số gợi ý khi thiết kế class Khai báo dữ liệu private: Điều này tránh việc truy cập tùy ý từ bên ngoài lớp Khởi tạo cho dữ liệu: Các thuộc tính nên được khởi tạo bằng các phương thức constructor khi một đối tượng mới được tạo ra. Không sử dụng quá nhiều kiểu dữ liệu cơ bản trong 1 lớp Không phải thuộc tính nào cũng cần mutator và accesor: Mỗi thuộc tính bao giờ cũng có một phương thức thiết đặt giá trị cho nó gọi là mutator (tiền tố set) và một phương thức lấy ra giá trị của nó gọi là accesor (tiền tố get). Tách các class phức tạp 46
  47. Đặt tên phương thức và thuộc tính phản ánh theo tính chất và nghiệp vụ của nó. IV. Sử dụng các Class xây dựng sẵn trong thư viện Java hỗ trợ cho lập trình viên một thư viện phong phú các lớp đối tượng đã được xây dựng và thiết kế cẩn thận. Lập trình viên chỉ cần biết cách lấy chúng ra và sử dụng chúng theo kịch bản của ứng dụng. Các gói thư viện quan trọng của Java 2 bao gồm: (tham khảo chi tiết tại: ) Các gói thường dùng trong Java 2 SE Hỗ trợ các class cần thiết cho việc tạo ra các Applet và giao tiếp giữa Applet java.applet với môi trường ngữ cảnh của nó. Chứa các lớp dùng để tạo ra các giao diện người dùng và cho các thao tác vẽ java.awt các hình đồ họa và ảnh. java.awt.color Cung cấp các lớp cho không gian màu. Cung cấp các giao diện và các lớp cho việc giải quyết các vấn đề về xử lý java.awt.event các sự kiện trên các thành phần giao diện AWT. java.awt.font Hỗ trợ các giao diện và lớp liên quan đến font chữ. java.awt.image Cung cấp các lớp tạo và hiệu chỉnh hình ảnh. java.awt.print Cung cấp các lớp và giao diện cho mục đích in ấn. Chứa các lớp liên quan tới việc phát triển các thành phần (beans) dựa trên java.beans kiến trúc của Java. java.io Hỗ trợ cho các thao tác vào / ra dữ liệu trên hệ thống file. java.lang Cung cấp các lớp nền tảng để thiết kế ngôn ngữ lập trình Java. Hỗ trợ các lớp để thao tác và thuật toán với các số nguyên lớn BigInteger và java.math BigDecimal. java.net Cung cấp các lớp cho việc cài đặt các ứng dụng mạng. java.rmi Cung cấp các gói cho lập trình RMI – Remote Method Invocation. Cung cấp các lớp và giao diện cho việc xử lý các vấn đề an ninh và bảo mật java.security trong Java. Cung cấp các hàm API cho việc truy cập vào dữ liệu trong một nguồn dữ java.sql liệu – thường là các CSDL quan hệ. Cung cấp các lớp và giao diện cho việc quản lý text, dates, numbers và các java.text thông điệp. Chứa đựng các lớp tiện ích thuộc nhiều loại khác nhau như sinh số ngẫu java.util nhiên, ngày tháng, . javax.crypto Hỗ trợ các lớp và giao diện cho các thao tác mã hóa dữ liệu. javax.net Cung cấp các lớp cho lập trình mạng. javax.print Cung cấp các lớp cơ bản cho các dịch vụ in ấn qua mạng. 47
  48. javax.sql Cung cấp các hàm API cho việc truy cập dữ liệu phía server. Cung cấp một tập các thành phần được chấp nhận trên hầu hết các hệ thống javax.swing máy tính. javax.swing.event Hỗ trợ cho các sự kiện kích hoạt bởi các thành phần của Swing. javax.swing.table Cung cấp các lớp và giao diện làm việc với bảng. javax.swing.tree Cung cấp các lớp và giao diện làm việc với cây javax.swing.JTree. javax.xml.parsers Hỗ trợ các lớp cho việc xử lý các tài liệu XML. Sau đây là hướng dẫn ví dụ về sử dụng lớp Date có sẵn trong thư viện của Java: Khai báo biến đối tượng với toán tử new Date myDate = new Date(); Date() là phương thức contructor của class Date. Một đối tượng luôn được tạo ra từ một constructor của lớp đó. Khai báo: Date myDate; Xác định cho ta một biến đối tượng Date nhưng không cho ta một đối tượng Date thực sự vì trong thực tế, myDate chưa được cấp phát vùng nhớ. Câu lệnh: myDate = new Date(); Xác định rằng myDate là một đối tượng thực sự thuộc class Date, ta có thể áp dụng các phương thức và thuộc tính của class Date cho myDate. System.out.println(myDate.getMonth()); // In ra tháng hiện tại V. Xây dựng Class trong Java Cài đặt các class là công việc thường xuyên của lập trình viên Java. Các vấn đề của công việc thiết kế một lớp bằng ngôn ngữ Java sẽ được đề cập dưới đây. V.1 Cấu trúc của class [ ] class [extends ] [implements ]{ ; } Ta sẽ lần lượt xem xét từng thành phần: : public: Có thể truy cập lớp từ các lớp khác. abstract: Lớp trừu tượng, không được khởi tạo đối tượng của lớp này. Các lớp trừu tượng được thiết kế chỉ cho mục đích kế thừa. final: Không cho phép kế thừa. Nếu không được xác định, mặc định là public. : Tên của lớp, nếu là public thì tên lớp phải trùng với tên file chứa lớp. : Tên của lớp cha mà lớp hiện tại thừa kế. 48
  49. : Tên của giao diện được cài đặt tại lớp. Đây có thể là một danh sách các giao diện phân tách bởi dấu “,”. : đây là phần thân của lớp chứa các định nghĩa cho các thuộc tính và các phương thức thành phần. Ta sẽ lần lượt xem xét tới các thành phần này. V.2 Các thuộc tính thành phần: Khai báo thuộc tính chính là việc khai báo các biến. Khi khai báo trong lớp chúng thường được xác định phạm vi hoạt động là một trong các dạng: public: Biến có thể truy cập ở bất cứ lớp nào. private: Chỉ được truy cập trong chính lớp đó. protected: Chỉ được truy cập trong lớp đó và các lớp kế thừa. Mặc định thì phạm vi truy cập của biến là public, các biến thông thường có phạm vi private. Ví dụ: public int Tuoi; private String HoVaTen; Cách truy cập biến rất đơn giản, phụ thuộc vào biến thuộc dạng nào trong 2 dạng sau: 1. Biến có phạm vi đối tượng: Đây là biến tồn tại cùng với sự tồn tại của đối tượng. Muốn truy cập vào biến, trước hết phải khởi tạo một đối tượng thuộc lớp. SinhVien sv = new SinhVien(20,”Nguyen Van A”); Truy cập vào biến Tuoi như sau: sv.Tuoi, ví dụ gán Tuoi của sinh viên này bằng 21: Sv.Tuoi=21; 2. Biến có phạm vi lớp (biến tĩnh): Đây là biến có phạm vi tồn tại trong mọi đối tượng của lớp được tạo ra trong chương trình đang chạy. Giá trị của biến được dùng chung giữa các đối tượng. Khi khai báo một biến có phạm vi lớp, ta cần thêm từ khóa static như ví dụ sau: public static int MaSo; Khi truy cập, ta có thể không cần khởi tạo đối tượng mà trực tiếp thông qua tên lớp: SinhVien.MaSo = 10; Các biến có phạm vi lớp rất ít khi được sử dụng, trong khi các hằng static lại rất hay được dùng. Lý do là trong thực tế, các lớp đối tượng thường có các thuộc tính chung, cố định có ở mọi đối tượng. Hơn nữa, khi chúng đã không phụ thuộc vào một đối tượng cụ thể nào thì ta cũng không cần khởi tạo một đối tượng để truy cập. Do đó, ta sẽ cài đặt chúng như là các hằng static. Ví dụ: public static final String MauDa = “Vang”; // Mọi sinh viên đều có màu da là “Vang” hoặc khái báo hằng số PI: public static final double PI = 3.14159265358979323846; Là hằng số của lớp Math. Khi truy cập ta chỉ cần gọi: Math.PI Chú ý: Các biến rất ít khi được khai báo là public vì có thể thay đổi giá trị của nó bên ngoài lớp nên khó quản lý. Trong khi đó các hằng thường luôn được khai báo là public vì chúng được dùng chung và không thể bị thay đổi do từ khóa final (hằng). Biến this Biến this là biến đối tượng của lớp tồn tại ngầm trong mỗi lớp. Thường dùng biến this để truy cập đến các thuộc tính của lớp bị khai báo trùng trong phạm vi các phương thức của lớp. Ví dụ: 49
  50. public class TestThis { private int number = 10; public void PrintNumber() { int number =20; // khai báo trùng với biến của lớp System.out.println(number); //in bien number = 20 System.out.println(this.number);//in bien number =10 } } V.3 Các phương thức thành phần Phương thức thành phần là sự cài đặt các hành vi của đối tượng. Cú pháp khai báo một phương thức trong lớp như sau: [ ] ([ ]) [ ] { ; } : private: phương thức này chỉ được truy xuất bên trong lớp chứa nó. public: có thể truy xuất từ bất kỳ lớp bên ngoài nào. protected: chỉ các lớp là dẫn xuất của lớp chứa phương này mới truy xuất được nó. Nếu không khai rõ cách truy xuất, các phương thức sẽ có cách truy xuất mặc định là public. Các phương thức khai báo chồng ở lớp dẫn xuất phải có mức độ truy cập mạnh hơn hoặc giống với mức độ truy cập ở lớp cha. static: phương thức tác động không phụ thuộc vào các đối tượng cụ thể, nó có thể được gọi mà không cần khởi tạo đối tượng của lớp. abstract: phương thức đơn giản nhất, không cài đặt gì ở trong lớp khai báo nó, tức là nó không có phần thân. Phương thức này sẽ được phát triển trong các lớp là dẫn xuất của lớp chứa nó. Lớp có chứa phương thức abstract cũng phải được khai báo abstract. final: phương thức này được bảo vệ không cho các lớp dẫn xuất khai báo và cài đặt lại. native: là phương thức được viết bằng ngôn ngữ khác java. synchronyzed: đảm bảo dữ liệu không bị sai lạc khi cùng một lúc 2 phương thức truy cập cùng một dữ liệu. : integer, String, char, float, : là các kiểu dữ liệu mà phương thức trả về. void: phương thức không trả về giá trị. Khi xác định kiểu giá trị trả về, khi kết thúc các luồng xử lý trong phương thức nhất thiết phải có câu lệnh return để trả về một giá trị thuộc kiểu đó. public static int max(int num1, int num2) { if(num1>num2) 50
  51. return num1; else return num2; } Về chúng ta sẽ xem xét kỹ trong phần xử lý ngoại lệ. V.4 Gọi và truyền tham số cho phương thức Các tham số kiểu cơ bản được truyền theo kiểu tham trị. Các tham số có kiểu đối tượng được truyền theo kiểu tham chiếu. Các chú ý khi truyền tham số cho phương thức: Các phương thức không thể làm thay đổi giá trị của các tham số có kiểu nguyên thủy Phương thức có thể làm thay đổi trạng thái của tham số kiểu đối tượng Phương thức không thể làm cho tham số đối tượng tham chiếu tới một đối tượng mới. Gọi một phương thức: Có 2 cách gọi phương thức: Nếu phương thức trả về giá trị, việc gọi phương thức thường được xử lý như một giá trị. Ví dụ: int larger = max(3, 5); hoặc ta có thể in giá trị trả về của cuộc gọi phương thức: System.out.println(max(3, 5)); Nếu phương thức trả về void, việc gọi phương thức là câu lệnh. Ví dụ phương thức println() trả về void: System.out.println("Hello!"); V.6 Các hàm và phương thức đặc biệt Phương thức khởi tạo: Phương thức khởi tạo (constructor) dùng để khởi tạo một đối tượng của lớp và đặt trạng thái ban đầu cho đối tượng bằng cách xác định giá trị cho các thuộc tính của lớp. Mỗi lớp có thể có 1 hoặc nhiều phương thức khởi tạo. Phương thức khởi tạo có cùng tên với tên lớp và không có kiểu dữ liệu trả về. Khi không khai báo phương thức khởi tạo, đối tượng được tạo ra bằng phương thức khởi tạo mặc định với các giá trị mặc định của các thuộc tính. Constructor không được kế thừa, nó chỉ được định nghĩa cho chính lớp cha. Vấn đề sử dụng phương thức khởi tạo của lớp cha trong các lớp dẫn xuất sẽ bàn trong phần “Tính thừa kế” Phương thức hủy: Trái với phương thức khởi tạo, phương thức hủy được gọi khi đối tượng được giải phóng. Tuy nhiên, trong Java công việc này được làm tự động, lập trình viên không cần quan tâm. Trong trường hợp cần thiết ta có thể khai báo phương thức hủy theo cú pháp: protected void finalize() { // Body of Method } Hàm main() Đây là một hàm đặc biệt được cài đặt trong lớp được gọi thực thi đầu tiên trong chương trình. Vì nó được gọi khi chưa có đối tượng nào được tạo ra nên nó luôn được khai báo là static. Hơn nữa, việc gọi hàm main() đương nhiên là diễn ra bên ngoài lớp nên nó cũng cần có mức độ truy cập là public. Hàm main() thường không trả về giá trị nào nên kiểu giá trị trả về của nó là void. 51
  52. Hàm main() có một tham số là một mảng các chuỗi chứa nội dung các tham số dòng lệnh. V.7 Khai báo chồng các phương thức Các phương thức trong cùng một lớp có thể có cùng tên nhưng nhất định số lượng các tham số hoặc kiểu của chúng phải khác nhau. Điều này gọi là khai báo chồng phương thức. Từ phiên bản Java 1.5, kiểu giá trị trả về cũng được xem như một yếu tố để phân biệt các phương thức. Sau đây là chương trình ví dụ về xây dựng các class trong Java: abstract class People // Lớp trừu tượng { protected int Tuoi; protected String HoVaTen; public static final String MauDa=”Vang”; //hằng số // Phương thức khởi tạo public People(int t,String ht) { Tuoi=t; HoVaTen=ht; } // Phương thức hiển thị tên và tuổi của một người public String toString(){ return HoVaTen + "," + String.valueOf(Tuoi); };} Sau đó, ta có một lớp SinhVien kế thừa từ lớp People trên: public class SinhVien extends People { private String Lop; private double DiemTongKet; public static int MaSo; // biến lớp // Phương thức khởi tạo public SinhVien(int t,String ht,String l,double dtk) { super(t,ht); // Gọi phương thức khởi tạo của lớp cha cho các thuộc tính kế thừa // Các thuộc tính không kế thừa được gán tường minh Lop = l; DiemTongKet=dtk; } // Hàm main public static void main(String[] argvs) { // Truy cập vào biến lớp không cần khởi tạo đối tượng SinhVien.MaSo=10; // Khởi tạo một đối tượng sinh viên, MaSo của sinh viên này sẽ là 10 SinhVien k = new SinhVien(23,"Nguyen Thi Mai","Letio3",7.15); System.out.print(k.MaSo); 52
  53. SinhVien.MaSo=11; // Khởi tạo một đối tượng sinh viên, MaSo của sinh viên này sẽ là 11 SinhVien k1 = new SinhVien(20,"Pham Anh Thu","Letio3",8.15); System.out.print(k.toString()); People p = new People(20,"Pham Anh Hoa"); // Báo lỗi dòng này } } Trong chương trình trên, việc khởi tạo một đối tượng thuộc lớp People sẽ bị báo lỗi do lớp này là lớp trừu tượng. Chúng ta sẽ trở lại vấn đề này trong phần “Tính kế thừa”. V.8 Lớp lồng nhau – lớp nội Có thể định nghĩa một lớp bên trong một lớp khác. Lớp như vậy gọi là lớp lồng (Nested Class) và được cài đặt như sau : class EnclosingClass{ // Lớp bao bên ngoài . . . static class StaticNestedClass { // Lớp lồng tĩnh . . . } class InnerClass { // Lớp lồng phi tĩnh hay lớp nội bộ . . . } } Lớp lồng chỉ được biết bên trong phạm vi của lớp bao bên ngoài. Trình biên dịch Java sẽ báo lỗi nếu một đoạn mã bất kỳ của lớp bên ngoài truy cập trực tiếp lớp lồng. Một lớp lồng có quyền truy cập đến các thành viên của lớp bao bên ngoài, thậm chí nếu chúng được khai báo private. Tuy nhiên, lớp bao không thể truy xuất các thành phần của lớp lồng. Có hai kiểu lớp lồng : tĩnh và phi tĩnh. Lớp lồng tĩnh (static nested class) được bổ sung từ khoá static. Nó không thể tham chiếu trực tiếp đến biến hay phương thức đối tượng được định nghĩa trong lớp bao, mà chỉ dùng chúng thông qua đối tượng. Vì giới hạn này nên lớp lồng tĩnh ít được dùng. Hầu hết các lớp lồng là lớp nội bộ. Lớp lồng phi tĩnh (nonstatic nested class) không bổ sung từ khoá static, còn được gọi là lớp nội bộ (inner class). Nó có thể truy cập trực tiếp đến các biến và phương thức đối tượng. class Outer { int outer_x = 100; void test() { Inner inner = new Inner(); inner.display_x(); } class Inner { // có thể truy xuất trực tiếp biến đối tượng của lớp Outer int inner_y = 10; void display_x() { System.out.println(“display : outer_x = “ + outer_x); 53
  54. } } void display_y() { // không thể truy xuất biến đối tượng của lớp Inner System.out.println(“display : inner_y = “ + inner_y); // Error } } class InnerClassDemo { public static void main(String args[]) { Outer outer = new Outer(); outer.test(); } } Trong Java có sử dụng một kỹ thuật cài đặt lớp nội nặc danh, tức là không có tên, khi xử lý các sự kiện. Ví dụ: frame.addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent event) { System.exit(0); } public void windowIconified(WindowEvent e) {System.exit(0);} } ); Ở đây, ta có một lớp nặc danh kế thừa từ lớp WindowAdapter. Nó được khai báo chồng 2 phương thức của WindowAdapter. VI. Tính kế thừa trong Java Để theo dõi tính kế thừa trong Java được cài đặt như thế nào, trong phần này chúng ta sẽ xem xét một ví dụ về quản lý nhân sự tại một công ty. VI.1 Sự kế thừa các thuộc tính và phương thức Giả sử trong công ty, đội ngũ quản lý (managers) được đối xử khác với nhân viên (employees) bình thường. Lớp Employee được định nghĩa như sau: import java.util.Date; import java.util.GregorianCalendar; class Employee { // Phương thức khởi tạo public Employee(String n, double s, int year, int month, int day) { HoVaTen = n; Luong = s; GregorianCalendar calendar = new GregorianCalendar(year, month - 1, day); // GregorianCalendar coi 0 là Tháng 1 NgayBatDau = calendar.getTime(); } 54
  55. // Phương thức lấy họ tên public String getHoVaTen() { return HoVaTen; } // Phương thức lấy lương public double getLuong() { return Luong; } //phương thức lấy ngày bắt đầu làm việc public Date getNgayBatDau() { return NgayBatDau; } // Phương thức tăng lương public void raiseSalary(double PhanTram) { double PhanTang = Luong * PhanTram / 100; Luong += PhanTang; } // Các thuộc tính thành phần private String HoVaTen; private double Luong; private Date NgayBatDau; } Ta thấy rằng cả Manager và Employee đều có những điểm chung về mặt quản lý như họ cùng được trả lương. Tuy nhiên, với Employee chỉ được trả lương theo một hợp đồng có sẵn trong khi Manager ngoài lương ra còn được hưởng một khoản tiền thưởng. Đây chính là ngữ cảnh tốt để cài đặt tính kế thừa. Chúng ta sẽ thiết kế một lớp Manager nhưng kế thừa lại những gì đã viết ở lớp Employee. class Manager extends Employee { //Các thuộc tính và phương thức bổ sung } Từ khóa extends xác định rằng chúng ta đang tạo ra một lớp mới được dẫn xuất từ một lớp đang tồn tại. Lớp đang tồn tại còn được gọi là lớp cha, supperclass, hoặc lớp cơ sở (base class), lớp kế thừa còn được gọi là lớp dẫn xuất, lớp con. Các lớp dẫn xuất thường được cài đặt nhiều tính năng hơn lớp cha. Lớp Manager có thêm thuộc tính TienThuong và phương thức để thiết đặt giá trị cho nó: class Manager extends Employee { . . . public void setTienThuong(double b) { 55
  56. TienThuong = b; } private double TienThuong; } Việc sử dụng các thuộc tính và phương thức này không có gì đặc biệt. Nếu ta có một đối tượng Manager có tên boss, ta có thể gọi: boss.setTienThuong(10000); Tất nhiên là đối tượng của lớp Emplyee không thể gọi phương thức setTienThuong() vì nó không phải là phương thức được định nghĩa trong lớp Employee. Tuy nhiên, chúng ta có thể sử dụng các phương thức getHoVaTen(), getLuong(), getNgayBatDau() đối với các đối tượng của lớp Manager vì chúng được tự động kế thừa từ lớp Employee. Tương tự như thế, các thuộc tính HoVaTen, Luong và NgayBatDau cũng được kế thừa. Lớp Manager có 4 thuộc tính là HoVaTen, Luong, NgayBatDau và TienThuong. Đến đây ta có thể rút ra rằng khi thiết kế các lớp trong Java, ta đặt các thuộc tính và phương thức phổ biến vào các lớp cha, lớp con chỉ việc khai báo thêm các thuộc tính và phương thức đặc biệt. Tuy nhiên, một số phương thức của lớp cha có thể không còn phù hợp ở lớp con. Ở đây, phương thức getLuong() đối với lớp Manager phải là tổng của Luong và TienThuong. Do đó, ta cần định nghĩa một phương thức mới trong lớp Manager đè lên phương thức cũ: class Manager extends Employee { . . . public double getLuong() { . . . } . . . } Chúng ta có thể nghĩ việc cài đặt cho phương thức mới này rất đơn giản như: public double getLuong() { return Luong + TienThuong; // Không thực hiện } Tuy nhiên nó không thực hiện. Lý do là vì sự truy cập vào Luong ở đây là không hợp lệ. Thuộc tính Luong trong Employee được khai báo là private nên chỉ các phương thức trong lớp Employee mới được phép truy cập. Theo lý thuyết, việc xác định cho các thuộc tính lớp Employee cấp độ truy cập là protected có vẻ là hợp lý. Tuy nhiên điều này có thể cho phép tất cả các lớp dẫn xuất từ Employee thay đổi giá trị các thuộc tính này làm tính đóng gói của OOP bị phá vỡ. Đến đây chúng ta nghĩ rằng Luong phải được truy cập thông qua một cơ chế public khác là phương thức getLuong() được định nghĩa public trong Employee. public double getLuong() { double LuongCoBan = getLuong(); // vẫn không thực hiện return LuongCoBan + TienThuong; } 56
  57. Tuy nhiên, đoạn mã này cũng chưa thực hiện được. Lý do là phương thức getLuong() lúc này được coi là chính phương thức getLuong() của lớp Manager nên nó không có tác dụng. Để gọi được phương thức getLuong() của lớp Employee, ta dùng từ khóa super để chỉ lớp cha. public double getLuong() { double LuongCoBan = super.getLuong(); // OK return LuongCoBan + TienThuong; } Nếu không cho phép lớp con kế thừa một phương thức của lớp cha, ta dùng từ khóa final khi khai báo phương thức. VI.2 Sự kế thừa đối với các constructor Như đã đề cập, các phương thức khởi tạo không được tự động kế thừa cho lớp con. Tuy nhiên ta có thể sử dụng phương thức khởi tạo của lớp cha để khởi tạo giá trị cho các thuộc tính được kế thừa ở lớp con: public Manager(String n, double s, int year, int month, int day) { super(n, s, year, month, day); TienThuong = 0; } Ở đây, super(n, s, year, month, day); thay cho việc gọi constructor của Employee với 5 tham số n, s, year, month và day. Lưu ý là lệnh gọi super phải là lệnh đầu tiên trong phương thức constructor ở lớp con. Đến đây, ta đã có một lớp Manager được định nghĩa đúng đắn: class Manager extends Employee { // Phương thức khởi tạo public Manager(String n, double s, int year, int month, int day) { super(n, s, year, month, day); TienThuong = 0; } // Phương thức tính lương public double getLuong() { double LuongCoBan = super.getLuong(); // OK return LuongCoBan + TienThuong; } //Phương thức đặt tiền thưởng public void setTienThuong(double b) { TienThuong = b; } private double TienThuong; } 57
  58. VII. Tính đa hình trong Java Tính đa hình là một khả năng của OOP cho phép một phương thức thực thi theo ngữ cảnh lúc chương trình đang chạy. Cho dùng là cùng một tên gọi, thậm chí là cùng danh sách tham số, phương thức vẫn được gọi theo đúng đối tượng sở hữu nó. Trong khi chúng ta cài đặt tính chất thừa kế của OOP, cơ sở của tính đa hình cũng đã được cài đặt. Trong ví dụ trên, phương thức getLuong() được định nghĩa ở cả lớp cha và lớp con, chúng ta sẽ xem nó được gọi như thế nào. VII.1 Sự ép kiểu và gán tham chiếu đối tượng Trong quan hệ thừa kế, đôi khi có sự chuyển đổi vai trò của các lớp cha và con. Ta có thể gán tham chiếu một đối tượng của lớp con cho một đối tượng của lớp cha. Trường hợp ngược lại là không thể. Đây chính là thể hiện tính đa hình của đối tượng. Một đối tượng của lớp cha có thể được gán tham chiếu tới bất kỳ lớp con nào dẫn xuất từ nó nhưng không ép kiểu sang lớp con được. Ngược lại, đối tượng lớp con không thể được gán tham chiếu tới một đối tượng lớp cha nhưng có thể chuyển kiểu sang lớp cha. Ví dụ: Employee me = new Employee("Nguyen Anh Minh", 50000, 2000, 10, 1); Employee you = new Employee("Nguyen Anh Tai", 52000, 2000, 10, 1); Manager boss = new Manager ("Nguyen Tan Minh", 80000, 1987, 12, 15); boss.setTienThuong(5000); Manager boss1 = new Manager ("Nguyen Tan Phat", 81000, 1987, 12, 15); boss1.setTienThuong(15000); boss = me; // không gán tham chiêu được me=boss1; //OK. hoặc: you = (Employee)boss; // Chuyển kiểu OK boss1 = (Manager) me; // Cha không chuyển kiểu sang con được Thậm chí một mảng các đối tượng Employee có thể được gán cho một mảng các đối tượng Manager mà không cần chuyển kiểu: Manager[] managers = new Manager[10]; Employee[] staff = managers; // OK Tuy vậy, sự chuyển đổi này chỉ diễn ra trong thời gian chạy chương trình. Nếu khi lập trình ta viết: you = boss; // OK you.setTienThuong(2000);// Không được Lý do là việc chuyển kiểu chỉ xảy ra lúc chương trình chạy nên khi biên dịch “you” vẫn là một đối tượng Employee, nó không có phương thức setTienLuong(). VII.2 Sự ràng buộc động –Dynamic Binding Xét ví dụ sau: // Khai báo một đối tượng Manager Manager boss = new Manager("Phan Thanh Ha", 80000, 1987, 12, 15); boss.setTienThuong(5000); // Khai báo một mảng 3 đối tượng Employee Employee[] staff = new Employee[3]; // Gán boss cho đối tượng thứ 0 58
  59. staff[0] = boss; // Khởi tạo cho 2 đối tượng còn lại staff[1] = new Employee("Nguyen Hai Nam", 50000, 1989, 10, 1); staff[2] = new Employee("Pham Quyet Tan", 40000, 1990, 3, 15); // Dùng vòng lặp để in ra tên và lương từng người for (Employee e : staff) System.out.println(e.getHoVaTen() + " " + e.getLuong()); Kết quả in ra là: Phan Thanh Ha 85000.0 Nguyen Hai Nam 50000.0 Pham Quyet Tan 40000.0 Ở đây chúng ta thấy đối tượng thứ 1 và 2 in ra các giá trị vốn có của nó theo phương thức getLuong() của Employee. Tuy nhiên đối tượng thứ 0 đã gọi phương thức getLuong() của Manager. Mặt khác, nếu viết: staff[0].setTienThuong(2000); thì không được phép khi biên dịch. Ta gọi việc getLuong() của Manager được gọi trong tình huống này là sự ràng buộc muộn hay ràng buộc động (Dynamic Binding). Đặc trưng thể hiện tính đa hình trong Java. Để kết thúc phần này ta sẽ xem xét cơ chế của việc gọi phương thức của một đối tượng trong Java được thực hiện như thế nào: 1. Trình biên dịch kiểm tra kiểu của đối tượng và tên của phương thức, giả sử là x.f(param). Trong đó x được khai báo là đối tượng của lớp C. Trong lớp C có thể có nhiều phương thức có cùng tên f nhưng khác nhau ở tham số, ví dụ f(int) và f(String). Trình biên dịch sẽ liệt kê tất cả các phương thức tên f trong lớp C và phương thức tên f có mức độ truy cập public trong các lớp cha của C. 2. Tiếp theo, trình biên dịch sẽ xác định kiểu của tham số của phương thức được gọi. Nếu trong danh sách phương thức có tên f chỉ có 1 phương thức có kiểu tham số phù hợp thì phương thức này được gọi. Ví dụ câu lệnh là x.f(“Chao ban”) thì hàm f(String) được gọi chứ không phải f(int). Cơ chế này gọi là nạp chồng (overloading). Nếu trình biên dịch không thể tìm thấy phương thức có tham số phù hợp hoặc có nhiều hơn 1 phương thức phù hợp, nó sẽ đưa ra thông báo lỗi. Bây giờ, trình biên dịch đã biết rõ phương thức nào được gọi (tên và danh sách tham số). 3. Nếu phương thức là private, static và final hoặc là một constructor, trình biên dịch sẽ biết chính xác phương thức cần phải gọi đó là phương thức f của lớp C. Điều này gọi là ràng buộc tĩnh (static binding). Ngược lại, phương thức được gọi tùy theo kiểu hiện tại của đối tượng, giả sử kiểu hiện tại của x là D, một lớp dẫn xuất từ C (Ban đầu x là một đối tượng lớp C nhưng sau đó được gán tham chiếu tới một đối tượng của D). Nếu D có định nghĩa một phương thức f(String) thì phương thức này sẽ được gọi. Nếu lớp D không có, nó sẽ tìm lên các lớp cha của D (trong đó có lớp C). Cơ chế này gọi là ràng buộc động. VIII. Lớp Object Lớp Object là lớp cha của mọi lớp trong Java. Tuy nhiên chúng ta không bao giờ phải khai báo: class Employee extends Object Mọi lớp đều là hậu duệ của Object, do đó, đối tượng thuộc lớp Object có thể được tham chiếu tới bất kỳ đối tượng nào khác. Object obj = new Employee("AAA", 35000); Employee[] staff = new Employee[10]; 59
  60. obj = staff; // OK obj = new int[10]; // OK IX. Giao diện IX.1 Cấu trúc của giao diện Trong Java không hỗ trợ tính đa thừa kế. Tuy nhiên, Java hỗ trợ một kỹ thuật thay thế và rất linh hoạt, đó là các interface hay còn gọi là các giao diện. Giao diện không phải là một class nhưng nó chỉ ra các phương thức sẽ được định nghĩa trong các lớp cài đặt nó. Các lớp có sử dụng tới các phương thức này phải khai báo cài đặt giao diện bằng từ khóa implement. Cấu trúc của một giao diện rất đơn giản: public interface { //Danh sách các phương thức } Tại lớp có cài đặt giao diện: public class implements { //Phân thân lớp có chứa định nghĩa phương thức của giao diện } Ví dụ: Trong lớp Arrays của Java có phương thức sort dùng để sắp xếp các đối tượng trong một mảng. Tuy nhiên, muốn sắp xếp được thì trong định nghĩa lớp của các đối tượng này phải có cài đặt một phương thức so sánh compareTo(Object objKhac). Mở rộng hơn, tất cả các lớp muốn sử dụng phương thức sort của Array đều phải cài đặt phương thức này trong định nghĩa của nó. Điều này dẫn đến việc thiết kế một giao diện: public interface Comparable { int compareTo(Object objKhac); } Phương thức compareTo nhận một đối tượng khác làm tham số và là đối tượng đem ra so sánh, kết quả so sánh biểu hiện qua một số nguyên. Từ phiên bản Java 5.0 (1.5), các giao diện có thể được xác định cho một kiểu tổng quát: public interface Comparable { int compareTo(T objKhac); } Ví dụ các lớp muốn cài đặt giao diện Comparable phải cài đặt phương thức int compareTo(Employee objKhac); Tất cả các phương thức khai báo trong interface mặc định là public nên ta không cần khai báo public cho nó. Bây giờ chúng ta định nghĩa: x.compareTo(y) 0 nếu x > y 60
  61. Trong interface ta đang xem xét thì nó chỉ có một phương thức. Các interface thường có nhiều phương thức, các hằng số. Tuy nhiên trong interface ta không thể khai báo các biến thành phần và không thể cài đặt nội dung của các phương thức. Các interface có thể xem như các lớp trừu tượng không có thuộc tính thành phần tuy rằng chúng có nhiều điểm khác biệt. Trong phần sau đây chúng ta sẽ cài đặt một phương thức compareTo cho việc so sánh các đối tượng Employee. Khai báo cài đặt Comparable cho Employee: class Employee implements Comparable Giả sử các Employee được so sánh thông qua tiêu chí Luong, ta cài đặt compareTo như sau: public int compareTo(Object objKhac) { Employee other = (Employee) objKhac; // Chuyển kiểu if (Luong other.Luong) return 1; return 0; } Chú ý rằng các phương thức trong interface không khai báo public nhưng khi cài đặt chúng, nhất định ta phải có khai báo public. Đối với phiên bản Java 5.0, ta có thể cài đặt: class Employee implements Comparable { public int compareTo(Employee objKhac) { if (Luong objKhac.Luong) return 1; return 0; } . . . } Đối với lớp Manager thì sao. Rõ ràng là một đối tượng Manager vẫn có thể được so sánh với một đối tượng Employee về tiền lương. Tuy nhiên nếu ta cũng khai báo chồng một phương thức compareTo() thì không được do vi phạm quy tắc chuyển kiểu: class Manager extends Employee { public int compareTo(Employee objKhac) { Manager otherManager = (Manager) objKhac; // không được . . . } . . . } May mắn là ta không cần làm điều này vì tính kế thừa đã đảm bảo Manager cài đặt Comparable chứ không phải Comparable . Khi đưa một đối tượng Manager vào làm tham số so sánh, nó tự động được ép sang kiểu Employee. Việc so sánh vì thế diễn ra thoải mái giữa các đối tượng Employee và Manager với nhau. 61
  62. IX.2 Các tính chất của giao diện Khi sử dụng interface ta cần chú ý mấy điểm sau: Giao diện không phải là class nên không dùng từ khóa new để tạo một đối tượng kiểu giao diện: x = new Comparable(. . .); // Lỗi Có thể khai báo một biến interface Comparable x; // OK Biến interface phải tham chiếu tới một đối tượng của một lớp có cài đặt interface đó. x = new Employee(. . .); // OK Có thể dùng toán tử instanceof để kiểm tra xem một đối tượng có được cài đặt interface hay không: if (anObject instanceof Comparable) { . . . } Các interface cũng có thể được kế thừa Có thể khai báo các hằng số trong interface public interface Comparable { double greater = 1; int compareTo(Employee other); } Trong interface, khai báo “double greater =1” được tự động hiểu là: public static final double greater =1; Các lớp có cài đặt interface cũng tự động được thừa kế các hằng số này. Một lớp chỉ có thể kế thừa từ một lớp khác trong khi có thể cài đặt nhiều giao diện. X. Package X.1 Sử dụng các package trong thư viện Java Để sử dụng các class nằm trong một gói thư viện của Java mà không cần viết đường dẫn đầy đủ của gói, ta khai báo nhập thư viện: import java.util.*; Vị trí của dấu “*” có thể là tên một class cụ thể. Dấu “*” đại diện cho tất cả class. Trong một số trường hợp, sử dụng dấu “*” có thể gây ra lỗi. Ví dụ: import java.util.*; import java.sql.*; Trong cả hai gói này đều có lớp Date nên trong dòng lệnh: Date myDate = new Date(); Thì trình biên dịch báo lỗi vì không biết Date của gói nào. Khi này ta phải khai báo rõ ràng cho Date ở gói nào: import java.util.Date; import java.sql.*; Nếu muốn sử dụng cả hai Date trong chương trình ta phải khai báo trực tiếp: java.util.Date deadline = new java.util.Date(); java.sql.Date today = new java.sql.Date( ); Ta cũng có thể có một khai báo nhập thư viện với từ khóa static: import static java.lang.System.*; 62
  63. Khi đó các phương thức và thuộc tính tĩnh của lớp System được truy cập mà không cần có tên lớp: out.print(“Khong co ten lop System”); Ta thường dùng phương pháp này với các hàm của class Math: sqrt(pow(x, 2) + pow(y, 2)). X.2 Đặt lớp vào package Để đặt một lớp vào một package, ta phải có khai báo package ở dòng đầu tiên của chương trình: package com.horstmann.corejava; public class Employee { . . . } Nếu không có khai báo đó, lớp tự động thuộc vào một package mặc định không có tên. Các lớp trong gói sẽ tự động thuộc vào một đường dẫn đầy đủ theo tên của package. Trong ví dụ trên Employee.class sẽ nằm trong thư mục: com\horstmann\corejava tính từ thư mục hiện thời. Do đó, khi biên dịch hoặc chạy chương trình từ cửa sổ dòng lệnh CommandLine, ta phải có đường dẫn đầy đủ: Javac com\horstmann\corejava\Employee.java Bài tập 1. Thực hành cài đặt các ví dụ trong đề tài trên. 2. Xây dựng các class trong java để cài đặt các lớp đối tượng: Khách hàng, sản phẩm, hóa đơn, giỏ hàng trong một hệ thống quản lý bán hàng ở siêu thị. 3. Trong hệ thống đồ họa: a. Hãy xây dựng các class điểm, đoạn thẳng, hình vuông, đa giác, tứ giác, hình chữ nhật, tam giác, hình thoi. b. Hãy cho biết một thứ tự phân cấp kế thừa của các lớp này. c. Hãy cài đặt một phương thức toString() cho các lớp trên theo kỹ thuật ràng buộc động. Phương thức toString() in ra màn hình giá trị tất cả các thuộc tính của một đối tượng. 4. Xây dựng một lớp PhuongTrinhBac1 để giải phương trình bậc 1. 5. Xây dựng một lớp đối tượng phương trình bậc 2 sao cho ta có thể khởi tạo một phương trình bậc 2 và biết ngay các tình trạng của nó như có nghiệm hay không và giá trị nghiệm. 5. Xây dựng một giao diện GiaiPhuongTrinh dùng cho việc giải các phương trình bậc 1 và bậc 2 sau đó xây dựng lại các lớp PhuongTrinhBac1, PhuongTrinhBac2 có cài đặt giao diện này. 6. Xây dựng một lớp đối tượng để cài đặt cho lớp đối tượng lớp học bao gồm các thuộc tính như: Sĩ số, Tên lớp, Khóa học, danh sách lớp và các phương thức như: Thêm sinh viên, Xóa Sinh viên, Sắp xếp danh sách, Xem danh sách, thống kê học tập, thống kê giới tính. Trong đó các thành viên của lớp là các đối tượng Sinh viên có các thuộc tính như Họ và tên, Tuổi, Giới tính, Điểm tổng kết và các phương thức phù hợp (sinh viên tự xác định xem cần những phương thức gì). Các đối tượng sinh viên được so sánh theo tiêu chí điểm tổng kết. 63