You are on page 1of 146

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
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
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
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
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
Đề 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
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
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
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 http://www.netbeans.org).
 Jcreator (thương mại).
 Jbuilder (thương mại).
 Eclipse (miễn phí http://www.eclipse.org/).

V. Cài đặt Java


Java phiên bản Java mới nhất có thể download tại địa chỉ http://java.sun.com/j2se. 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
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
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
Đề 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
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
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
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
••••••••••••••••••••••••••
ud Primary Use Cases ••••••••••••••••••••••••••

• • • • • • • • • • • • • • • • HÖ
• •thèng
• • • b¸n
• • hµng
• • • qua• m¹ng
• • • Interrnet
••••••••••••••••••••••

• • • • • • • • • • • • •Chän
• • •hµng
•••••••••• ••••••••••••••••••••••••••
Xem chi tiÕt
• • • • • • • • • • • • • • • • • • • • • • s¶n
• • phÈm
•• ••••••••••••••••••••••••••
CËp nhËt giá
•Kh¸ch
• • • •hµng
••••••••••••••••••••• • • • hµng
•••••••••••••••••••••••
Göi th«ng
• • • • • • • • • • • • • • • • • • • • •tin• ph¶n
• • •håi
• ••••••••••••••••••••••••••

•••••••••••••••••••••••••• ••••••••••••••••••••••••••

• • • • • • • • • • • • • • • • • • • • • • §¨ng
• • • ký
• • • • • • • •LËp
• •®¬n
• •mua
•••••••••••••••
kh¸ch hµng hµng
Qu¶n lý
• • • • • • • • • • • • • danh
• • • môc
•••••••••• ••••••••••••••••••••••••••
hµng hãa
•Qu¶n
• • •trÞ• hÖ
••••••••••••••••••••• ••••••••••••••••••••••••••
thèng
Tr¶ lêi
• • • • • • • • • • • • • • • • • • • kh¸ch
• • • •hµng
••• ••••••••••••••••••••••••••
CËp nhËt
• • • • • • • • • • • • • •th«ng
• • •tin
••••••••• ••••••••••••••••••••••••••
mÆt hµng
•••••••••••••••••••••••••• ••••••••••••••••••••••••••

Nh©n Theo dâi


• • •viªn
• • kinh
• • • • • • • • • • • • • • • ®¬n
• • •hµng
••• ••••••••••••••••••••••••••
doanh
•••••••••••••••••••••••••• ••••••••••••••••••••••••••

•••••••••••••••••••••••••• ••••••••••••••••••••••••••
Các nguyên tắc cho việc xây dựng use-case view là:
• • • •Các • • • • • được
• • use-case • • • •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 • • • • •phải
• • use-case • • •đầ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
••••••

•••••••••••••••••••••••••• ••••••••••••••••••••••••••
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
uc Use Case Model

Use Case Include

«include»
Use Case Principal

 Extend: use-case mới xảy ra khi một điều kiện xảy ra trong use-case chính.

17
uc Use Case Model

Use Case Principal

«extend»
Use Case Extend

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
cd Jav a Model
•••••••••••••••••••••••••• •••••••••••••••••••••••••• •••••••••••••••••••••••••• •••••••••
LineItem StockItem
Order
•••••••••••••••••••••••••• •••••••••••••••••••••••••• •••
- • Author:
• • • • string
•••••••••••••••••• •••••••••
- quantity: int
- date: Date - catalogNumber: string
• • • • • • • • • • • • • • •- • deliveryInstructions:
• • • • • • • • • • String
• • • • • • • • • • • •property
••••• •••••••••
get •••
-item - • costPrice:
• • • • • •number
•••••••••••••••• •••••••••
- orderNumber: String + getItem() : StockItem - listPrice: number
«enumeration» - • title:
• • • • • • • • • • • • • • •+ • checkForOutstandingOrders()
• • • • • • • • • • • • • • :•void
•• • • • • •+• •getQuantity()
••••••• : int
••••• ••• • • •string
••••••••••••••••••• •••••••••
OrderStatus property set
property get property get
enum + setItem(StockItem) : void
• • • • • • • • • • • • •-status
• •+ • getDate()
• • • • • :•Date
•••• ••••••• ••••••••••••••••••• •• •
+ • getAuthor()
• • • • • •: •string
••••••••• •••••• •••••••••
- closed: int + getDeliveryInstructions() : String + setQuantity(int) : void
+ getCatalogNumber() : string
- delivered: int
• •- • •dispatched: • • • • •+ • getLineItem()
• • • • • • int • • • • • • • :•LineItem
•• ••••••• ••••••••••••••••••• •• + • getCostPrice()
• • • • • • • • •: number
•••••••• •••••• •••••••••
+ getOrderNumber() : String + getListPrice() : number
- new: int + getStatus() : OrderStatus + getTitle() : string
• •- • •packed:
• • • • int
• • • • • • •property
• • • •set
••••••• ••••••• ••••••••••••••••••• •• •••••••••••••••••• •••••• •••••••••
property set
+ setDate(Date) : void + setAuthor(string) : void
• • • • • • • • • • • • • • •+ • setDeliveryInstructions(String)
• • • • • • • • • • • • • • :•void
•• ••••••••••••••••••• •• •
+ • setCatalogNumber(string)
• • • • • • • • • • • • • :•void
•• •••••• •••••••••
+ setLineItem(LineItem) : void + setCostPrice(number) : void
• • • • • • • • • • • • • • •+ • setOrderNumber(String)
•••••••••• •• : void
••••• ••••••••••••••••••• •• •
+ • setListPrice(number)
• • • • • • • • • • •: • ••••
void •••••• •••••••••
+ setStatus(OrderStatus) : void + setTitle(string) : void
• • • • • • • • • • • • • •-account
•••••••••••• •••••••••••••••••••••••••• •••••••••••••••••••••••••• •••••••••
Account ShoppingBasket
•••••••••••••••••••••• • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • Transaction •••••••••••••••• ••••••••• •••••••••
- billingAddress: String - shoppingBasketNumber: String
- closed: boolean - date: Date
• • -• •deliveryAddress:
• • • • • • • • •String
••••••••• • • • • + •addLineItem()
• • • • • • •: •void
• • • • • • • • • • • • • -• •orderNumber:
• • • • • • String
••••••••••••• ••••••••• •••••••••
- emailAddress: String -basket + createNewBasket() : void
+ loadAccountHistory() : void
• • -• •name:
• • • •String
•••••••••••••• • • • • + •deleteItem()
• • • • • •: •void
• • • • • • • • • • • • • • +• •loadOpenOrders()
• • • • • • • • :•void
•••••••••• ••••••••• •••••••••
+ processOrder() : void
+ createNewAccount() : void property get
property get
• • +• •loadAccountDetails()
• • • • • • • • • • •: void
•••••• • • • • • • • • • • • • • • • • • • • • • • • • • • • +• •getAccount()
•• •••• •••••••••••••
: Account ••••••••• •••••••••
+ getLineItem() : LineItem
+ markAccountClosed() : void + getDate() : Date
property set
• • +• •retrieveAccountDetails()
• • • • • • • • • • • • :•void
•••• • • • • • + •setLineItem(LineItem) • • • • • • • • • • +• •getLineItem()
• • • • • • • • • • •: void • • • • • •: LineItem
••••••••••••• ••••••••• •••••••••
+ submitNewAccountDetails() : void + getOrderNumber() : String
+ validateUser(String, String) property set
• •••••••••••••••••••• • •••• •••••••••••••••••••••••••• ••••••••••••••••• ••••••••• •••••••••
property get -account -history
+ setAccount(Account) : void
+ getBasket() : ShoppingBasket + setDate(Date) : void
• •••••••••••••••••••• • • • • • • • • • • • • • • • • • • • • • • • • • • • +• •setLineItem(LineItem)
• • • • • • • • • •: void
••••••••• ••••••••• •••••••••
+ getBillingAddress() : String
+ getClosed() : boolean + setOrderNumber(String) : void
• • +• •getDeliveryAddress()
• • • • • • • • • • •: String
•••••• ••••• •••••••••••••••••••••••••• •••••••••••••••••••••••••• •••••••••
+ getEmailAddress() : String
• ••••••••••••••••••••
+ getName() : String ••••• •••••••••••••••••••••••••• •••••••••••••••••••••••••• •••••••••
+ getOrder() : Order
• • property
• • • • •set
•••••••••••••• ••••• •••••••••••••••••••••••••• •••••••••••••••••••••••••• •••••••••
+ setBasket(ShoppingBasket) : void
+ setBillingAddress(String) : void
• • +• •setClosed(boolean)
• • • • • • • • • •: •void
•••••• ••••• •••••••••••••••••••••••••• •••••••••••••••••••••••••• •••••••••
+ setDeliveryAddress(String) : void
• • +• •setEmailAddress(String)
••••••••••••• ••••
: void ••••• •••••••••••••••••••••••••• •••••••••••••••••••••••••• •••••••••
+ setName(String) : void
• • +• •setOrder(Order)
• • • • • • • • :•void
•••••••• ••••• •••••••••••••••••••••••••• •••••••••••••••••••••••••• •••••••••

•••••••••••••••••••••••••• •••••••••••••••••••••••••• •••••••••••••••••••••••••• •••••••••

Các hành vi động:


•••••••••••••••••••••••••• •••••••••••••••••••••••••• •••••••••••••••••••••••••• •••••••••

State machine:
•••••••••••••••••••••••••• •••••••••••••••••••••••••• •••••••••••••••••••••••••• •••••••••

•••••••••••••••••••••••••• •••••••••••••••••••••••••• •••••••••••••••••••••••••• •••••••••

•••••••••••••••••••••••••• •••••••••••••••••••••••••• •••••••••••••••••••••••••• •••••••••

•••••••••••••••••••••••••• •••••••••••••••••••••••••• •••••••••••••••••••••••••• •••••••••

•••••••••••••••••••••••••• •••••••••••••••••••••••••• •••••••••••••••••••••••••• •••••••••

•••••••••••••••••••••••••• •••••••••••••••••••••••••• •••••••••••••••••••••••••• •••••••••

•••••••••••••••••••••••••• •••••••••••••••••••••••••• •••••••••••••••••••••••••• •••••••••

•••••••••••••••••••••••••• •••••••••••••••••••••••••• •••••••••••••••••••••••••• •••••••••

•••••••••••••••••••••••••• •••••••••••••••••••••••••• •••••••••••••••••••••••••• •••••••••

•••••••••••••••••••••••••• •••••••••••••••••••••••••• •••••••••••••••••••••••••• •••••••••

•••••••••••••••••••••••••• •••••••••••••••••••••••••• •••••••••••••••••••••••••• •••••••••

•••••••••••••••••••••••••• •••••••••••••••••••••••••• •••••••••••••••••••••••••• •••••••••

•••••••••••••••••••••••••• •••••••••••••••••••••••••• •••••••••••••••••••••••••• •••••••••

•••••••••••••••••••••••••• •••••••••••••••••••••••••• •••••••••••••••••••••••••• •••••••••

•••••••••••••••••••••••••• •••••••••••••••••••••••••• •••••••••••••••••••••••••• •••••••••

•••••••••••••••••••••••••• •••••••••••••••••••••••••• •••••••••••••••••••••••••• •••••••••

•••••••••••••••••••••••••• •••••••••••••••••••••••••• •••••••••••••••••••••••••• •••••••••

•••••••••••••••••••••••••• •••••••••••••••••••••••••• •••••••••••••••••••••••••• •••••••••

•••••••••••••••••••••••••• •••••••••••••••••••••••••• •••••••••••••••••••••••••• •••••••••

•••••••••••••••••••••••••• •••••••••••••••••••••••••• •••••••••••••••••••••••••• •••••••••

•••••••••••••••••••••••••• •••••••••••••••••••••••••• •••••••••••••••••••••••••• •••••••••

19
Đâ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
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):
• • • sd
• Sequence
• • • • • • • • • • • • • • •V
••••••• • • • • • • •C
• • • • • • • • • • • • • •M••••• •••••

•••••••••••••••••••••••••• •••••••••••••••••••••••••• •••••


Khach mua DangKy AccountUtilities AccountTable
•••••••••••••••••••••••••• •••••••••••••••••••••••••• •••••
hang Page
• • • • • • • • • •Nhap
• •thong
• •tin• dang
• •ky• • • • • • • • • •••••••••••••••••••••••••• •••••
Click "Dang Ky"
•••••••••••••••••••••••••• •••••••••••••••••••••••••• •••••
XacNhanThongTinDangKy()

•••••••••••••••••••••••••• • • • • • • • • DangKyKhachHang()
•••••••••••••••••• •••••

•••••••••••••••••••••••••• •••••••••••••••••••••••••• •••••

•••••••••••••••••••••••••• •••••••••••••••••••••••••• •••••

••MuaHang
• • • sd ••••••••••••••••••• •• •• •• •• •• •• •• •• •
• •• ••V
•• • •• •• •• • •••••••••••••••••C
•••• •• •• •• •• •• •• •• •• •• •• •• • •• •• •• •• •
M
• • • ••••••••••••••••••••• •• •• •• •• •• •• •• •• •
• •• •• •• • •• •• •• • ••••••••••••••••••••• •• •• •• •• •• •• •• •• •• •• •• • •• •• •• •• •
Khach Hang TrangChu TrangDanhMucSanPham SanPhamUtilities SanPhamTable

• • • ••••••••••••••••••••• •• •• •• •• •• •• •• •• •
• •• •• •• • •• •• •• • ••••••••••••••••••••• •• •• •• •• •• •• •• •• •• •• •• • •• •• •• •• •
Chon xem san pham theo hang

• • • ••••••••••••••••••••• •• •• •• •• •• •• •• •• •
• •• •• •• • •• •• •• • ••••••••••••••••••••• •• •• •• •• •• •• •• •• •• •• •• • •• •• •• •• •
Redirect

• • • ••••••••••••••••••••• •• •• •• •• •• • • • • • • •• • •• •• •• •LayDanhSachSanPham
••••••••••••••••••••• •• •• •• •• •• • • • • • • • •• •• •• •• •

• • • ••••••••••••••••••••• •• •• •• •• •• • • • • • • •• • •• •• •• • ••••••••••••••••••••• •• •• •• •• •• • • • • • • • •• •• •• •• •

• • • ••••••••••••••••••••• •• •• •• •• •• • • • • • • •• • •• •• •• • ••••••••••••••••••••• •• •• •• •• •• • • • • • • • •• •• •• •• •
HienThi
• • • ••••••••••••••••••••• •• •• •• •• •• • • • • • • •• • •• •• •• • ••••••••••••••••••••• •• •• •• •• •• • • • • • • • •• •• •• •• •

• • • ••••••••••••••••••••• •• •• •• •• •• •• •• •• •
• •• •• •• • •• •• •• • ••••••••••••••••••••• •• •• •• •• •• •• •• •• •• •• •• • •• •• •• •• •

• • • ••••••Các•••••class
••••••trong
•••• ••hệ•• •thống
• •• •• ••được
•• •• ••chia
•• •• thành
•• • •• •ba
• ••loại
• •••tách
•••••biệt:
•••••M• •••(Model)
•••• •• •• –•• V
• •• •• •• ••– ••C•• • •• •• •• •• •
• ••(View)
(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.
•• •(Deployment
• •• •• •• •• •• •• •• •• •• •• •• • •• •• •• • ••••••••••••••••••••• •• •• •• •• •• •• •• •• •• •• •• • •• •• •• •• •
Sơ đồ triển khai Diagram):
•••••••••••••••••••••••••• •••••••••••••••••••••••••• •••••
•••••••••••••••••••••••••• ••••••••••••••••••••••••••
•••••••••••••••••••••••••• •••••••••••••••••••••••••• •••••
• • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • 21
•••••••
•••••••••••••••••••••••••• •••••••••••••••••••••••••• •••••
•••••••••••••••••••••••••• ••••••••••••••••••••••••••
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
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
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
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
 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
Đề 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
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 <tên gói>;
/*Khai bao thu vien*/
import <tên gói thư viện>;
/*Khai bao lop chua ham main*/
public class <tên lớp>
{
/*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
Cú pháp: package <tên gói>.
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 <tên thư viện>
Nếu có khai báo này, khi sử dụng các class nằm trong gói theo <tên thư viện> 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 <tên thư viện>. 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
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
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
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
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:
<kiểu phần tử>[][] <tên mảng> = {<danh sách phần tử>};
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:
<phạm vi> final <kiểu dữ liệu của hằng> <tên hằng> = <giá trị>
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
„\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
< so sánh nhỏ hơn x<20
>= 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
% 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 <biểu thức boolean>
{
<khối lệnh>;

35
}
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) {
System.out.println("x>y");
} }}

Dạng 2 của câu lệnh if:


if <biểu thức boolean>
{
<khối lệnh 1>;
}
else
{
<khối lệnh 2>;
}
Nếu biểu thức boolean đúng thì <khối lệnh 1> được thực hiện, còn nếu biểu thức boolean
sai thì <khối lệnh 2> đượ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) {
System.out.println("x>y");
} else {
System.out.println("x<y");
}}}

V.2. Lệnh switch …case


Lệnh switch ... case ...: Cho phép chọn nhiều trường hợp xảy ra của giá trị so sánh.
Cấu trúc:
switch <biểu thức>{
case <giá trị 1>: <khối lệnh 1>; break;
case <giá trị 2>: <khối lệnh 2>; break;
...
case <giá trị n>: <khối lệnh n>; break;
default: <khối lệnh default>; break;
}
Một số chú ý khi sử dụng lệnh switch-case:
Các giá trị: <giá trị 1>, <giá trị 2>,…<giá trị n> phải là các hằng số.

36
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([<khởi tạo>]; [<biểu thức kiểm tra>]; [<bước nhảy>])
{
<Khối lệnh>;
}
Bắt đầu với giá trị <khởi tạo> của biến đếm, <khối lệnh> được thực hiện. Sau mỗi lần
thực hiện xong <khối lệnh>, biến đếm thay đổi giá trị một lượng bằng <bước nhảy> và <biểu
thức kiểm tra> đượ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
}//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<10; i++) {
[các câu lệnh; ]
for(int j=0; j<5; j++) {
[các câu lệnh; ]
...
}
[các câu lệnh; ]
}

VI.2. Vòng lặp while


Khi một khối lệnh cần được thực hiện, thường là với một số lần không được biết trước, ta
có thể sử dụng vòng lặp while.
Cấu trúc lệnh:
while <bt boolean> {
<Khối lệnh>;
}
Khối lệnh được thực hiện khi <bt boolean> 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ả <bt boolean>
để 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 <khối lệnh> được thực hiện
ngay ở vòng đầu tiên mà chưa cần kiểm tra kết quả <biểu thức boolean>.

38
Cấu trúc lệnh:
do {
<khối lệnh>;
} while <biểu thức boolean>;
Vòng lặp này cho thực hiện <khối lệnh> rồi mới kiểm tra <biểu thức boolean>. Nếu <biểu
thức boolean> có giá trị true thì tiếp tục thực hiện <khối lệnh>, 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<11);
System.out.println(sum);
}
} //kết quả là: 55

VI.4. Phép nhảy


Trong một số trường hợp, việc thực hiện các vòng lặp đôi khi không theo hết vòng mà có
thể dừng hoặc thoát khỏi vòng lặp.
Ví dụ: Tìm một giá trị nào đó trong một dãy 1000 phần tử. Khi thực hiện vòng lặp duyệt
dãy, so sánh các phần tử của dãy với giá trị cần tìm từ phần tử đầu tiên cho đến phần tử cuối
cùng, nếu tìm thấy phải thôi duyệt dãy ngay.
Việc nhảy ra khỏi vòng lặp được thực hiện với từ khoá break hoặc kết thúc vòng hiện tại
với lệnh continue.
Lệnh break
Lệnh này kết thúc ngay vòng lặp trong cùng (nếu có nhiều vòng lồng nhau) chứa nó.
Ví dụ:
while <bt boolean 1>{
lệnh 1;
lệnh 2;
if <bt boolean 2>
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 <bt boolean 1>{
lệnh 1;
lệnh 2;
if <bt boolean 2>

39
{
……..
continue;
}
lệnh 3;
}
lệnh 4;
Khi <bt boolean 2> 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 <lệnh 3> mà quay lại kiểm tra <bt boolean 1>.

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
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%");
}}

41
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ệ.

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.

42
8. Viết chương trình in ra màn hình các mẫu như sau:

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
Đề 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
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
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
Đặ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: http://java.sun.com/j2se/1.4.2/docs/api/overview-summary.html )
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
java.applet
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
java.awt
vẽ 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
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


[<Cách truy xuất>] class <tên lớp> [extends <tên lớp cha>] [implements <tên giao
diện>]{
<các thành phần của lớp>;
}
Ta sẽ lần lượt xem xét từng thành phần:
<Cách truy xuất>:
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 <cách truy xuất> không được xác định, mặc định là public.
<tên lớp>: Tên của lớp, nếu <cách truy xuất > là public thì tên lớp phải trùng với tên file
chứa lớp.
<tên lớp cha>: Tên của lớp cha mà lớp hiện tại thừa kế.

48
<tên giao diện>: 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 “,”.
<các thành phần của lớp>: đâ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
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:
[<Cách truy xuất> <cách cập nhật>] <Kiểu giá trị trả về> <Tên phương thức>([<danh
sách biến hình thức>]) [<mệnh đề throws>]
{
<Nội dung của phương thức>;
}
<Cách truy xuất>:
 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.
< Cách cập nhật>
 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.
<Kiểu giá trị trả về>:
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
return num1;
else
return num2;
}
Về <mệnh đề throws> 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
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
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 go ̣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 tiñ h
...
}
class InnerClass { // Lớp lồ ng phi tiñ h 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 đoa ̣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 đinh ̣ nghiã 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 go ̣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
}
}
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
// 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
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
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
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
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
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 <tên giao diện>
{
//Danh sách các phương thức
}
Tại lớp có cài đặt giao diện:
public class <tên lớp> implements <tên giao diện>
{
//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<T>
{
int compareTo(T objKhac);
}
Ví dụ các lớp muốn cài đặt giao diện Comparable<Employee> 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
x.compareTo(y) =0 nếu x = y
x.compareTo(y) >0 nếu x > y

60
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;
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<Employee>
{
public int compareTo(Employee objKhac)
{
if (Luong < objKhac.Luong) return -1;
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<Employee> chứ không phải Comparable<Manager>. 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
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<Employee>
{
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
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
Đề tài 4. Lớp và phương thức trừu tượng
I. Khái niệm lớp trừu tượng
Những khái niệm trừu tượng trong thế giới thực được cài đặt trong chương trình như là
các lớp trừu tượng. Chúng thường làm cơ sở để giúp ta có được các định nghĩa cụ thể hơn: Ví dụ:
Các con trâu là động vật bốn chân được nuôi phục vụ cày bừa.
Các con hổ là động vật bốn chân sống trong các khu rừng.
Các con mèo là động vật bốn chân hay bắt chuột.
…….
Trong các định nghĩa trên, một khái niệm chung cần được làm rõ là “động vật bốn chân”.
Để cho các định nghĩa này thêm sáng tỏ, ta cần một định nghĩa về “động vật bốn chân”
Động vật bốn chân là động vật có bốn chân để di chuyển.
Đến đây ta lại bắt gặp một khái niệm trừu tượng hơn là “động vật” v.v…và có rất nhiều
khái niệm dùng đến khái niệm này như động vật ăn cỏ, động vật ăn thịt, động vật bò sát,….
Trong lập trình cũng vậy, những lớp đối tượng sinh ra chỉ để cho các lớp khác kế thừa gọi
là các lớp trừu tượng. Chúng thực sự không có một biểu hiện nào (hoặc nếu ta bắt buộc chúng có
biểu hiện thì biểu hiện đó cũng không có ý nghĩa trong phạm vi đang xét.)

II. Cài đặt lớp và phương thức trừu tượng trong Java
Khai báo: Lớp trừu tượng được khai báo như lớp thông thường nhưng có thêm từ khóa
abstract. Ví dụ:
abstract class People
{
private int NamSinh;
private String HoTen;
public People(int ns, String ht)
{
NamSinh= ns;
HoTen = ht;
}
public String getHoTen()
{
return HoTen;
}
abstract String toString();
}

Ta thấy rằng một lớp trừu tượng thực sự là một lớp với đầy đủ các thành phần, không
giống như interface.
Giả sử trong chương trình có các đối tượng SinhVien, GiaoVien. Các đối tượng này đều
có một phương thức toString để trả về một chuỗi bao gồm toàn bộ thông tin về chúng. Đây là tình
huống sử dụng phương thức trừu tượng một cách hiệu quả.
abstract String toString();
Chú ý là các phương thức trừu tượng không có phần thân.
Lớp SinhVien kế thừa từ People được cài đặt như sau:

64
public class SinhVien extends People
{
private String Lop;
private double DiemTongKet;
// Phương thức khởi tạo
public SinhVien(int ns,String ht,String l,double dtk)
{
//Dùng phương thức khởi tạo của lớp cha
super(ns,ht);
Lop = l;
DiemTongKet=dtk;
}
// Cài đặt phương thức toString
public String toString()
{
return String.valueOf(NamSinh) + String.valueOf(DiemTongKet) + HoTen;
}
}

Khi một phương thức được khai báo là abstract thì lớp chứa nó cũng phải là một lớp
abstract. Khi sử dụng phương thức abstract cho một lớp thì nhất thiết phải có một lớp khác kế
thừa và khai báo cài đặt chồng thì mới có tác dụng. Khi kế thừa một lớp có chứa phương thức
trừu tượng thì phương thức đó bắt buộc phải được cài đặt.

Bài tập
1. Trong hệ thống quản lý bệnh nhân ở bệnh viện có 2 loại bệnh nhân là nội trú và ngoại
trú trong đó bệnh nhân nội trú có tính tiền giường nằm. Xây dựng một lớp BenhNhan trừu tượng
có các thuộc tính như họ và tên, tiền điều trị, có phương thức tính tiền phải thanh toán bằng tiền
điều trị cộng với tiền giường nằm. Sau đó xây dựng hai lớp BenhNhanNoiTru và
BenhNhanNgoaiTru kế thừa lớp BenhNhan. (Gợi ý: Phương thức TinhTien ơ lớp BenhNhan khai
báo trừu tượng sau đó được khai báo chồng tại các lớp thừa kế).
3. Một nhà máy sử chữa phương tiện vận chuyển có các loại xe ô tô thuộc nhiều hàng
khác nhau như Honda, Toyota, Mishubishi, Mercedec, Ford, Kia. Mỗi hãng ô tô lại có nhiều kiểu
khác nhau như 2 chỗ, 4 chỗ, 7 chỗ, 12 chỗ và 24 chỗ. Ngoài ra, nhà máy còn nhận bảo dưỡng cho
các loại xe mô tô tay ga và xe số. Nhà máy có các chế độ khuyến mại và bảo hành khác nhau tùy
theo xe của từng hãng.
a. Hãy xây dựng một lớp PHUONG_TIEN trừu tượng có phương thức trừu tượng là
Xac_Dinh_Gia() trong đó việc tính giá sửa cho một phương tiện bằng giá của nhà máy trừ đi tiền
khuyến mại.
b. Hãy xây dựng một giao diện trong đó có phương thức so sánh phương tiện theo tiêu chí
giá sửa.
c. Xây dựng chương trình nhập vào dữ liệu cho 5 chiếc xe cả ô tô và mô tô và in ra màn
hình theo thứ tự tăng dần của giá sửa.

65
Đề tài 5. Lưu trữ và xử lý đối tượng
Trong phần này chúng ta sẽ đề cập tới các lớp tiện ích của Java trong gói java.util dùng
cho việc lưu trữ và xử lý các đối tượng.

I. Lớp Vector và giao diện Enumeration

I.1 Lớp Vector


Lập trình viên dùng lớp Vector để lưu trữ các đối tượng có kiểu giống nhau nhưng có thể
thay đổi động về kích thước tùy theo nhu cầu. Chúng ta sẽ xem xét việc sử dụng interface
Enumeration để cho phép duyệt lặp trên các cấu trúc như Vector.
Kích thước của Vector được tăng theo một lượng đặt trước hoặc được tự động tăng gấp
đôi khi ta cần tăng dung lượng của nó. Vì vậy thao tác chèn một phần tử mới vào một Vector sẽ
nhanh hơn khi kích thước của nó còn đủ dùng.
Vector lưu trữ tham chiếu của các đối tượng nên chương trình có thể dùng Vector để lưu
tham chiếu đến bất kỳ đối tượng nào. Để lưu các giá trị dữ liệu nguyên thủy, ta phải sử dụng các
lớp trong java.lang như Integer, Long hay Float thay vì dùng chính kiểu đó.
Sau đây là một ví dụ nhập vào 10 số nguyên, lưu vào một Vector rồi in ra để kiểm tra:

// Nhâp 10 doi tuong so nguyen


1. java.util.Vector v = new java.util.Vector(5,2);
2. for (int i=0;i<10;i++)
3. {
4. System.out.print("Nhap so nguyen thu " + String.valueOf(i) + ":");
5. java.lang.Integer p = new java.lang.Integer(nhap.nextInt());
6. v.addElement(p);
7. }
8. // In ra de kiem tra
9. for (int i=0;i<10;i++)
10. {
11. System.out.print("So nguyen thu " + String.valueOf(i) + " la:");
12. System.out.println(v.get(i));
13. }
14. System.out.println(v.size());
15. System.out.println(v.capacity());
Dòng 1: Khai báo một đối tượng v thuộc lớp Vector. Ở đây ta đã dùng một constructor
với 2 tham số là số phần tử ban đầu và số sẽ tăng khi Vector có nhu cầu tăng (mặc định sẽ gấp
đôi số hiện có). Lớp Vector có 3 constructor:
 Nếu dùng constructor không tham số thì Java tự động cấp cho ta số phần tử ban đầu
là 10 và nhân đôi mỗi khi có nhu cầu mở rộng.
 Nếu là 1 tham số thì đâu là số phần tử ban đầu, khi cần cũng tự động nhân đôi.
 Nếu là 2 tham số thì tham số thứ nhất chỉ số phần tử ban đầu, tham số thứ 2 chỉ số
phần tử được thêm vào mỗi khi dung lương của Vector bị hết.
Dòng 2-7: Vòng lặp để nhập và lưu kết quả nhập vào Vector. Ở đây ta phải khai báo một
biến đối tượng kiểu Integer chứ không phải là một kiểu nguyên thủy int. Sau đó dùng phương
thức addElement() để thêm đối tượng này vào vị trí cuối của Vector.

66
Ngoài ra ta cũng có thể sử dụng insertElementAt() để chèn phần tử vào một vị trí xác định
hoặc sử dụng setElementAt() để đặt giá trị cho một phần tử tại một vị trí.
Sử dụng removeElement(Object) để xóa một phần tử xuất hiện đầu tiên trong Vector có
giá trị bằng với Object.
Dòng 12: Dùng phương thức get(chỉ số) để in ra giá trị của phần tử có chỉ số tương ứng.
Thực ra thì phương thức get(chỉ số) cho ta một tham chiếu đến đối tượng có chỉ số tương ứng
được lưu trong Vector nhưng ở đây đối tượng số nguyên đã tự động in ra giá trị.
Dòng 14: Phương thức trả về kích thước thực tế của Vector, tức là số phần tử thực sự
Vector lưu trữ (=10) .
Dòng 15: Kích số lượng các phần tử tối đa hiện có của Vector (=11).
Một số phương thức của Vector:
removeElementAt: Xóa một phần tử ở một vị trí xác định.
removeAllElements(): Xóa tất cả các phần tử.
firstElement(): Trả về tham chiếu tới phần tử đầu tiên.
lastElement(): Trả về tham chiếu tới phần tử cuối cùng trong Vector.
isEmpty(): Xác định liệu Vector là trống.
contains(key): để kiểm tra xem trong vector có phần tử so khớp với key hay không. Đối
tượng key và các phần tử của Vector đươck so sánh với nhau bởi phương thức equals(). Các lớp
thường khai báo chồng phương thức equals() để dùng trong việc so sánh các đối tượng của mình.
indexOf(Object): Trả về chỉ số của đối tượng đầu tiên so khớp với Object.
trimToSize(): Giảm số lượng phần tử của Vector.

I.2 Giao diện Enumeration


Sử dụng phương thức elements() của một đối tượng Vector sẽ trả về một đối tượng
Enumeration cho phép chương trình duyệt lặp trên danh sách phần tử của Vector.
Đối tượng Enumeration có các phương thức quan trọng sau:
hasMoreElement(): Trả về true nếu vẫn còn phần tử trong Vector.
nextElement(): Trả về tham chiếu tới phần tử tiếp theo trong Vector.
Đoạn chương trình sau duyệt và in ra danh sách các sinh viên:
abstract class People
{
protected int NamSinh;//Nam Sinh
protected String HoVaTen; //Ho Va Ten
//constructor
public People(int ns,String ht)
{
NamSinh=ns;
HoVaTen=ht;
}
// Phuong thuc tinh tuoi
protected abstract int TinhTuoi();
public abstract String toString();
}
// Ke thua lop People
public class SinhVien extends People
{

67
private String Lop;// Ten lop
private double DiemTongKet; // Diem tong ket
public final String mauda ="vang";//Hang so
private int ID;// Ma so SinhVien
protected static int MaSo;// ma so chung de cap phat cho moi sinh vien
// constructor
public SinhVien(int ns,String ht,String l,double dtk)
{
super(ns,ht);//goi constructor cua lop cha la People
Lop = l;
DiemTongKet=dtk;
// Id cua SinhVien duoc gan bang gia tri MaSo hien thoi cua lop
ID=MaSo;
// Tang ma so len 1 den gan cho SinhVien sau
MaSo+=1;
}
// Phuong thuc tinh tuoi
protected int TinhTuoi()
{
java.util.Date homnay = new java.util.Date();
return (homnay.getYear() - NamSinh +1);
}
// Khai bao chong phuong thuc toString()
public String toString()
{
return "Ma so:" + String.valueOf(ID)+ "\n"
+"Tuoi:"+ String.valueOf(this.TinhTuoi()) + "\n"
+"Diem Tong Ket:"+ String.valueOf(DiemTongKet)+"\n"
+"Ho va ten:"+ HoVaTen;
}
// Ham main
public static void main(String[] argv)
{
// Dat gia tr? bien static, sinh vien dau tien co ma so 1
SinhVien.MaSo=1;
// bien doi tuong Vector
java.util.Vector sv = new java.util.Vector(5);
SinhVien k1 = new SinhVien(80,"Nguyen Thi Mai 1","Letio3",5);
sv.addElement(k1); // Them sinh vien vao Vector
SinhVien k2 = new SinhVien(81,"Tran Thi Mai 2","Letio3",6);
sv.addElement(k2);
SinhVien k3 = new SinhVien(82,"Pham Thi Mai 3","Letio3",7);
sv.addElement(k3);
SinhVien k4= new SinhVien(83,"Phan Thi Mai 4","Letio3",8);
sv.addElement(k4);
SinhVien k5= new SinhVien(84,"Hoang Thi Mai 5","Letio3",9);
sv.addElement(k5);

68
// Dung interface Enumeration de duyet cac phan tu cua Vector
java.util.Enumeration enu = sv.elements();
while (enu.hasMoreElements())
{
// Ep kieu, kieu Object la kieu cha cua moi kieu nen luon ep duoc
SinhVien g = (SinhVien)enu.nextElement();
System.out.println(g.toString());
}
//People p = new People(20,"Pham Anh Hoa"); bao loi vi People là lớp trừu
tượng

}
}

Kết quả thực hiện như sau:

II. Mảng trong Java và lớp ArrayList

II.1 Mảng trong Java


Trong phần “Ngôn ngữ Java căn bản” ta đã xem xét một biến mảng được khai báo và sử
dụng như thế nào. Trong phần này ta sẽ đi sâu khai thác các đặc tính của mảng và một số thuật
toán thông dụng thông qua các ví dụ.
Ví dụ: Đếm tần suất của các phần tử của một mảng:
import javax.swing.*;
public class StudentPoll {
// Hàm main
public static void main( String args[] )

69
{
// Khai báo một mảng các câu trả lời
int TraLoi[] = { 1, 2, 6, 4, 8, 5, 9, 7, 8, 10, 1, 6, 3, 8, 6, 10, 3, 8, 2, 7, 6, 5, 7, 6, 8, 6, 7, 5,
6, 6, 5, 6, 7, 5, 6, 4, 8, 6, 8, 10 };
// Mảng đếm tần suất
int TanSuat[] = new int[ 11 ];
// Với mỗi phần tử của mang TraLoi, sử dụng giá trị đó như là chỉ số của mảng TanSuat
sau đó đếm sự xuất hiện của nó trong TraLoi, lưu giá trị vào mảng TanSuat tại vị trí tương ứng.
for ( int answer = 0; answer < TraLoi.length; answer++ )
++frequency[ TraLoi[ answer ] ];
// Đưa kết quả vào một chuỗi
for ( int rating = 1; rating < TanSuat.length; rating++ )
output += rating + "\t" + TanSuat[ rating ] + "\n";
// In ra
System.out.print(out);
}}

Trong ví dụ này, các câu trả lời nằm trong khoảng từ 1 đến 10 nên khai báo một mảng 11
phần tử để có thể sử dụng chính giá trị 10 làm chỉ số của mảng. Chỉ cần duyệt mảng TraLoi 1 lần
ta đã có ngay kết quả.

II.2. Các thuật toán cơ bản trên mảng


Ví dụ: Sắp xếp tuyến tính
Đây là một ví dụ sắp xếp các phần tử của mảng tăng dần theo thuật toán “nổi bọt” - tức là
sau mỗi vòng lặp duyệt, phần tử nào lớn nhất sẽ bị đẩy xuống cuối cùng.
// Sắp xếp các phần tử của mảng
public void bubbleSort( int array2[] )
{
// Duyệt qua mỗi phần tử của mảng
for ( int pass = 1; pass < array2.length; pass++ ) {
// Vòng lặp so sánh và đổi chỗ
for ( int element = 0; element < array2.length - pass; element++ ) {
// So sánh 2 phần tử liền kề và đổi chỗ nếu cần
if ( array2[ element ] > array2[ element + 1 ] )
swap( array2, element, element + 1 );
}}
}
// Hàm đổi chỗ 2 phần tử của một mảng
public void swap( int array3[], int first, int second )
{
int hold; // Biến trung gian để hoán đổi
hold = array3[ first ];
array3[ first ] = array3[ second ];
array3[ second ] = hold;
}

70
Ví dụ: Tìm kiếm nhị phân trên mảng đã được sắp xếp:
Thông thường việc tìm kiếm diễn ra bằng phép duyệt lần lượt các phần tử của mảng. Ta
gọi phương pháp này là tìm kiếm tuyến tính.
Đối với các mảng đã được sắp xếp, ta có một phương pháp tìm kiếm hiệu quả hơn, gọi là
tìm kiếm nhị phân.
Giả sử ta có một mảng: int[] b={1,5,7,8,34,56,67,89,100};
Đã được sắp xếp tăng dần.
Giả sử ta cần tìm vị trí của một phần tử có giá trị 7. Trước tiên, ta so sánh 7 với vị trí đứng
giữa của mảng là 34 thì 7 < 34 nên ta chắc chắn nếu có thì 7 sẽ nằm ở nửa trước của mảng. Tiếp
tục làm việc này với nửa trước của mảng, so sánh 7 với 5 thì 7>5 nên phần tử cần tìm nếu có sẽ
nằm ở phần sau của nửa này gồm 2 phần tử 7,8. Lần phân đôi cuối cùng này cho ta kết quả.
Thủ tục này được cài đặt như sau:
public int binarySearch( int array2[], int key )
{
int low = 0; // Chỉ số dưới
int high = array.length - 1; // Chỉ số trên
int middle; // chỉ số của phần tử trung gian

// Lặp cho đến khi chỉ số dưới lớn hơn hoặc bằng chỉ số trên
while ( low <= high ) {
// Xác định chỉ số phần tử giữa
middle = ( low + high ) / 2;

// Nếu khóa cần tìm trùng với phần tử giữa thì trả về kết quả ngay
if ( key == array[ middle ] )
return middle;

// Nếu khóa nhở hơn phần tử giữa, đặt lại chỉ số trên
else if ( key < array[ middle ] )
high = middle - 1;
// Khóa lớn hơn phần tử giữa, đặt lại chỉ số dưới
else
low = middle + 1;
}
return -1; // Không tìm thấy
}

II.3 Class Arrays


Java hỗ trợ một lớp mảng trong thư viện java.util.Arrays để lập trình viên thao tác trên
mảng. Chủ yếu các phương thức của lớp này là static. Thay vì phải lập trình, Arrays hỗ trợ các
phương thức:
 fill: để nạp giá trị cho mảng.
 sort: để sắp xếp mảng
 binarySearch: Tìm kiếm nhị phân trên mảng đã được sắp.

71
 equals: So sánh mảng
Sau đây là ví dụ về sử dụng lớp Arrays:
import java.util.*;
public class UsingArrays {
private int intValues[] = { 1, 2, 3, 4, 5, 6 };
private double doubleValues[] = { 8.4, 9.3, 0.2, 7.9, 3.4 };
private int filledInt[], intValuesCopy[];

// Khởi tạo các mảng


public UsingArrays()
{
filledInt = new int[ 10 ];
intValuesCopy = new int[ intValues.length ];
Arrays.fill( filledInt, 7 ); // Nạp giá trị cho mảng filledInt với giá trị 7
Arrays.sort( doubleValues ); // Sắp xếp mảng doubleValues
System.arraycopy( intValues, 0, intValuesCopy, 0, intValues.length ); // Sao chép mảng
}

// In giá trị của các mảng


public void printArrays()
{
System.out.print( "doubleValues: " );

for ( int count = 0; count < doubleValues.length; count++ )


System.out.print( doubleValues[ count ] + " " );

System.out.print( "\nintValues: " );

for ( int count = 0; count < intValues.length; count++ )


System.out.print( intValues[ count ] + " " );

System.out.print( "\nfilledInt: " );

for ( int count = 0; count < filledInt.length; count++ )


System.out.print( filledInt[ count ] + " " );

System.out.print( "\nintValuesCopy: " );

for ( int count = 0; count < intValuesCopy.length; count++ )


System.out.print( intValuesCopy[ count ] + " " );

System.out.println();
}

// Tìm một giá trị trong mảng intValues


public int searchForInt( int value )

72
{
return Arrays.binarySearch( intValues, value );
}
// So sánh nội dung mảng
public void printEquality()
{
boolean b = Arrays.equals( intValues, intValuesCopy );
System.out.println( "intValues " + ( b ? "==" : "!=" ) + " intValuesCopy" );
b = Arrays.equals( intValues, filledInt );
System.out.println( "intValues " + ( b ? "==" : "!=" ) + " filledInt" );
}

// Hàm main
public static void main( String args[] )
{
UsingArrays usingArrays = new UsingArrays();
usingArrays.printArrays();
usingArrays.printEquality();
int location = usingArrays.searchForInt( 5 );
System.out.println( ( location >= 0 ? "Tìm thấy 5 tại vị trí " + location : "Không thấy" ) +
" trong mảng intValues" );
location = usingArrays.searchForInt( 8763 );
System.out.println( ( location >= 0 ? "Tìm thấy 8763 tại " + location : "Không thấy 8763"
) + " trong mảng intValues" );
}}

III Danh sách trong java và giao diện Lists


Lists là một chuỗi có thứ tự các phần tử cho phép các phần tử có thể giống nhau. Lists là
một giao diện kế thừa giao diện Collections nên nó cho phép lập trình viên truy cập các phần tử
qua chỉ số. Các lớp tập hợp cài đặt Lists là ArrayList, LinkedList và Vector.
ArrayList là một lớp tập hợp có thể thay đổi kích thước. Về cơ bản thì ArrayList giống
với Vector chúng ta đã xem xét ở trên.
Sau đây là chương trình ví dụ sử dụng ArrayList. Ở đây ta đặt các đối tượng String và
Color vào cùng một ArrayList sau đó xóa đi các phần tử String sử dụng một đối tượng Iterator.
import java.awt.Color;
import java.util.*;
public class CollectionTest {
private String colors[] = { "red", "white", "blue" };
// Tạo một ArrayList, thêm các phần tử cho nó
public CollectionTest()
{
ArrayList list = new ArrayList();
// Thêm đối tượng cho nó
list.add( Color.magenta ); // Thêm một đối tượng Color
// Thêm các đối tượng String

73
for ( int count = 0; count < colors.length; count++ )
list.add( colors[ count ] );
list.add( Color.cyan ); // Thêm một đối tượng Color
// In ra nội dung
System.out.println( "\nArrayList: " );
for ( int count = 0; count < list.size(); count++ )
System.out.print( list.get( count ) + " " );
// Xóa tất cảcác đối tượng String
removeStrings( list );
// output list contents
System.out.println( "\n\nArrayList sau khi gọi removeStrings: " );
for ( int count = 0; count < list.size(); count++ )
System.out.print( list.get( count ) + " " );
}
public void removeStrings( Collection collection )
{
// Khai báo đối tượng iterator
iterator iterator = collection.iterator();
// Lặp trong khi tập hợp vẫn còn phần tử
while ( iterator.hasNext() )
if ( iterator.next() instanceof String )
iterator.remove(); // Xóa phần tử
}
// Hàm main
public static void main( String args[] )
{
new CollectionTest();
}
}

Bài tập
1. Viết chương trình quản lý danh sách một lớp học với các chức năng
- Tìm kiếm sinh viên
- Thêm sinh viên.
- Xóa sinh viên
- Sắp xếp danh sách theo tên
2. Viết chương trình nhập vào n số nguyên và số thực, n nhập từ bàn phím. Sau đó:
- Tính tổng của n số này.
- Sắp xếp n số theo thứ tự tăng dần
- Nhập vào từ bàn phím 1 số khác và tìm xem số đó có trong số n số vừa nhập không.

74
Đề tài 6. Các luồng vào ra dữ liệu với file
Một khả năng quan trọng mà các ngôn ngữ lập trình phải có là việc quản lý các luồng dữ
liệu vào ra hệ thống máy tính giúp các chương trình có thể giao tiếp dữ liệu với thế giới bên
ngoài.

I. Khái niệm luồng vào ra (I/O stream)


Luồng là dòng chảy của dữ liệu. Có 2 loại luồng:
 Luồng nhập (input stream): là tất cả gì từ thế giới bên ngoài được đưa vào máy
tính, như đọc từ bàn phím, đọc từ tập tin...
 Luồng xuất (output stream): là tất cả những gì được gửi từ máy tính ra ngoài thông
qua các thiết bị ngoại vi, như màn hình, máy in, tệp tin, ...
Đối với mội luồng lại có các dòng bao gồm:
 Lớp System.out: Dòng xuất chuẩn dùng để hiển thị kết quả trên màn hình.
 Lớp System.in: Dòng nhập chuẩn thường đến từ bàn phím và được dùng để đọc
các ký tự dữ liệu.
 Lớp System.err: Đây là dòng lỗi chuẩn cho phép một chương trình đưa ra các
thông báo lỗi trên màn hình.
Các dòng này có thể bị đổi hướng đến nhiều nguồn khác nhau. Ví dụ System.err có thể
đưa thông báo lỗi ra một file.
Khi cần làm việc với luồng, ta khai báo khối thư viện:
import java.io.*;
Thư viện này cho phép ta làm việc với các luồng nhập xuất qua việc cung cấp các lớp:
FileInputStrem, FileOutputStream, FileReader, FileWriter và rất nhiều lớp khác.
Sự phân cấp các class trong gói java.io thể hiện qua bảng sau:
java.lang.Object
File
FileDescriptor
InputStream
ByteArrayInputStream
FileInputStream
FilterInputStream
BufferedInputStream
DataInputStream
PushbackInputStream
ObjectInputStream
PipedInputStream
SequenceInputStream
OutputStream
ByteArrayOutputStream
FileOutputStream
FilterOutputStream
BufferedOutputStream
DataOutputStream
PrintStream
ObjectOutputStream

75
PipedOutputStream
RandomAccessFile
Reader BufferedReader
LineNumberReader
CharArrayReader
FilterReader
PushbackReader
InputStreamReader
FileReader
PipedReader
StringReader
Writer
BufferedWriter
CharArrayWriter
FilterWriter
OutputStreamWriter
FileWriter
PipedWriter
PrintWriter
StringWriter

Sau đây chúng ta xem xét các class quan trọng thường được sử dụng:

II. Lớp InputStream:


Là một lớp trừu tượng định nghĩa cách thức nhận dữ liệu. Có các phương thức chính sau:
 read(): Đọc các byte dữ liệu từ một dòng. Nếu như không có byte dữ liệu nào, nó
phải chờ. Khi một phương thức phải chờ, các luồng đang thực hiện phải tạm dừng
cho đến khi có dữ liệu.
 read(byte[]): Trả về số byte đọc được hay „-1‟ nếu như đã đọc đến cuối dòng. Nó
gây ra ngoại lệ IOException nếu có lỗi xảy ra.
 read(byte[],int,int): Nó cũng đọc vào một mảng byte. Nó trả về số byte thực sự đọc
được cho đến khi kết thúc dòng. Nó gây ra ngoại lệ IOException nếu lỗi xảy ra.
 available(): Phương pháp này trả về số lượng byte có thể đọc được mà không phải
chờ. Nó trả về số byte hiện tại có trong dòng. Nó không phải là phương thức tin
cậy để thực hiện tiến trình xử lý đầu vào.
 close(): Phương thức này đóng dòng. Nó dùng để giải phóng mọi tài nguyên dòng
đã sử dụng. Luôn luôn đóng dòng để chắc chắn rằng dòng xử lý được kết thúc. Nó
gây ra ngoại lệ IOException nếu lỗi xảy ra.
 mark(): Đánh dấu vị trí hiện tại của dòng.
 markSupported(): Trả về giá trị boolean chỉ ra rằng dòng có hỗ trợ các khả năng
mark và reset hay không. Nó trả về true nếu dòng hỗ trợ ngược lại trả về false.
 reset(): Phương thức này định vị lại dòng theo vị trí được đánh lần cuối cùng. Nó
gây ra ngoại lệ IOException nếu lỗi xảy ra.
 skip(): Phương thức này bỏ qua „n‟ byte dòng vào. ‟-n‟ chỉ định số byte được bỏ
qua. Nó gây ra ngoại lệ IOException nếu lỗi xảy ra. Phương thức này sử dụng để
di chuyển tới vị trí đặc biệt bên trong dòng vào.

76
III. Lớp OutputStream
Là lớp trừu tượng định nghĩa cách ghi các kết xuất đến dòng. Nó cung cấp một tập các
phương thức trợ giúp tạo ra, ghi và xử lý kết xuất các dòng. Các phương thức bao gồm:
 write(int): Phương thức này ghi một byte.
 write(byte[]): Phương thức này phong toả cho đến khi một byte được ghi. Dòng
phải chờ cho đến khi tác vụ ghi hoàn tất. Nó gây ra ngoại lệ IOException nếu lỗi
xảy ra.
 write(byte[], int, int): Phương thức này ghi mảng các byte. Lớp OutputStream định
nghĩa ba dạng khác nhau của phương thức để có thể ghi một byte riêng lẻ, một
mảng các byte, hay một đoạn của một mảng byte.
 flush(): Phương thức này xả sạch dòng. Đệm dữ liệu được ghi ra dòng. Nó kích
hoạt IOException nếu lỗi xảy ra.
 close(): Phương thức đóng dòng. Nó được dùng để giải phóng mọi tài nguyên gắn
với dòng. Nó kích hoạt IOException nếu lỗi xảy ra.

IV. Lớp FileInputStream


Kế thừa từ InputStream, lớp này cho phép đọc vào từ một tập tin dưới dạng một stream.
Các đối tượng của lớp này được tạo ra nhờ đường dẫn tới file, đối tượng File, hoặc đối tượng
FileDescriptor làm một đối số.
Lớp này có 3 phương thức khởi tạo sau:
 FileInputStream(String name): Tham số là tên của tập tin để tạo luồng.
 FileInputStream(File f): Tham số là đối tượng file để tạo luồng.
 FileInputStream(FileDescriptor fdObj): Tham số là đối tượng FileDescriptor để
tạo luồng.
Ví dụ:
InputStream in = new FileInputStream(“C:\\LETI\\JAVA\\Account.txt”);

V. Lớp FileOutputStream
Lớp này cung cấp khả năng ghi dữ liệu xuống tập tin, được dẫn xuất từ lớp cha
OutputStream.
Có 3 phương thức khởi tạo:
 FileOutputStream(String name);
 FileOutputStream(File f);
 FileOutputStream(FileDescriptor fdObj)

VI. Lớp File


Lớp này được sử dụng để truy cập các đối tượng tập tin và thư mục. Các tập tin đặt tên
theo qui ước đặt tên tập tin của hệ điều hành. Lớp này cung cấp các phương thức thiết lập các tập
tin và các thư mục. Tất cả các thao tác thư mục và tập tin được thực hiện thông qua các phương
thức của lớp File. Như vậy, ta dùng File trong tất cả các thao tác quản lý file và thư mục.
Có 3 cách để tạo các đối tượng từ lớp File:
 File(String path);
 File(String path, String name);
 File(File dir, String name);
Ví dụ:File f = new File("C:\jdk1.4\bin\hello.java");

77
hoặc
File f = new File("C:\jdk1.4\bin", "hello.java");
hoặc
File curDir = new File(".");
File f = new File(curDir, "Hello.java");
Các phương thức:
 public String getName(): lấy tên đối tượng tập tin.
 public String gePath(): lấy đường dẫn của tập tin.
 public String getAbsolutePath(): lấy đường dẫn tuyệt đối của tập tin.
 public String getParent(): lấy tên thư mục cha.
 public Boolean createNewFile(): tạo một tập tin mới.
 public void createTempFile(String pattern, File dir): tạo tập tin tạm thời.
 public void deleteOnExit(): yêu cầu xoá tập tin khi chương trình chấm dứt.
 public boolean canWrite(): cho biết tin có cho phép ghi hay không (true nếu có).
 public boolean canRead(): cho biết tập tin được phép đọc hay không (true nếu có)
 public void setReadOnly(): đặt thuộc tính chỉ đọc.
 public boolean isFile(): cho biết tệp tin có hợp lệ hay không (true nếu có).
 public boolean isDirectory(): cho biết tập tin có phải thư mục hay không (true nếu
có).
 public boolean isHidden(): kiểm tra xem tập tin có ẩn hay không (true nếu có).
 public long length(): cho biết kích thước tập tin (byte).
 public boolean mkdir(): tạo một thư mục từ đối tượng file, true nếu thành công.
 renameTo(File dest): đổi tên tập tin hiện tại sang tên mới.
 public String[ ] list(): lấy danh sách các tập tin và thư mục.
 public String[ ] list(FilenameFilter filter): lấy danh sách tập tin thoả mãn điều kiện
lọc, ví dụ *.gif.
 public booean delete(): xoá tập tin, true nếu xoá thành công.
 public String toString(): trả về đường dẫn của tập tin.
 public String toURL(): trả về đối tượng URL tương ứng với tập tin.
Chương trình ví dụ:
public class Test
{
public static void main(String args[])
{
File f = new File("test");
System.out.println(f.getAbsolutePath()); // Lấy đường dẫn tuyệt đối
System.out.println(f.exists()); // Kiểm tra sự tồn tại
}
}

VII. Nhập xuất lọc


Là một kiểu dòng có được từ việc thay đổi cách xử lý dòng hiện có. Các lớp, các dòng
nhập xuất lọc của java sẽ giúp ta lọc vào/ra theo một số cách.
Bộ lọc nằm giữa một dòng nhập và một dòng xuất. Nó thực hiện xử lý một quá trình nào
đó trên các byte được truyền từ đầu vào đến đầu ra.

78
Các bộ lọc có thể ghép với nhau khi đó đầu ra của bộ lọc này trở thành đầu vào của bộ
lọc kia.

VII.1 Lớp FilterInputStream:


Đây là lớp trừu tượng. Nó là cha của tất cả các lớp dòng nhập lọc. Một dòng có thể được
đọc và đưa kết quả cho một dòng khác.
Lớp FilterInputStream được thiết kế sao cho có khả năng kết chuỗi nhiều bộ lọc. Để thực
hiện điều này chúng ta dùng vài tầng lồng nhau.

VII.2 Lớp FilterOutputStream


Lớp này là một dạng bổ trợ cho lớp FilterInputStream. Nó là lớp cha của tất cả các lớp
dòng xuất lọc. Dữ liệu ghi vào lớp này có thể sửa đổi theo nhu cầu để thực hiện tác vụ lọc và sau
đó được chuyển tới đối tượng OutputStream.

VIII. Vào/ra có sử dụng bộ đệm


Vùng đệm là kho lưu trữ dữ liệu. Chúng ta có thể lấy dữ liệu từ vùng đệm thay vì quay trở
lại nguồn ban đầu của dữ liệu.
Java sử dụng cơ chế nhập/xuất có lập vùng đệm để tạm thời lập cache dữ liệu vào/ra của
một dòng. Nó giúp chương trình đọc/ghi lượng dữ liệu nhỏ không ảnh hưởng lớn đến hiệu năng
chung của hệ thống.

VIII.1 Lớp BufferedInputStream:


Lớp này tự động tạo ra và duy trì vùng đệm để hỗ trợ thao tác vào. Nhờ đó chương trình
có thể đọc dữ liệu từ dòng từng byte một mà không ảnh hưởng đến tốc độ thực hiện của hệ thống.

VIII.2 Lớp BufferedOutputStream


Lớp này định nghĩa hai phương thức thiết lập, một cho phép chỉ định kích cỡ của vùng
đệm xuất, một sử dụng kích cỡ vùng đệm ngầm định. Lớp này định nghĩa chồng tất cả các
phương thức của OutputStream và không đưa thêm bất kì phương thức mới nào.
Chương trình sau đây đọc và ghi thông tin của đối tượng SinhVien theo một format, mỗi
sinh viên nằm trên một dòng trong file:

// Ke thua lop People


import java.io.*;
import java.util.StringTokenizer;
public class SinhVien extends People
{
private String Lop;// Ten lop
private double DiemTongKet; // Diem tong ket
public final String mauda ="vang";//Hang so
private int ID;// Ma so SinhVien
protected static int MaSo;// ma so chung de cap phat cho moi sinh vien
public SinhVien(int ns,String ht,String l,double dtk)
{
super(ns,ht);//goi constructor cua lop cha la People
Lop = l;

79
DiemTongKet=dtk;
// Id cua SinhVien duoc gan bang gia tri MaSo hien thoi cua lop
ID=MaSo;
// Tang ma so len 1 den gan cho SinhVien sau
MaSo+=1;
}
// Phuong thuc tinh tuoi
protected int TinhTuoi()
{
java.util.Date homnay = new java.util.Date();
return (homnay.getYear() - NamSinh +1);
}
// Khai bao chong phuong thuc toString()
public String toString()
{
return String.valueOf(ID)+ "|"
+ String.valueOf(this.TinhTuoi()) + "|"
+ String.valueOf(DiemTongKet)+"|"
+ HoVaTen;
}
// Ghi thông tin sinh viên vào file
public void GhiData(PrintWriter out) throws IOException
{
out.println(this.toString());
}
// Đọc thông tin 1 sinh viên từ bộ đệm đọc
public void DocData(BufferedReader in) throws IOException
{
String s = in.readLine(); // Đọc một dòng trong bộ đệm
StringTokenizer t = new StringTokenizer(s, "|");
MaSo = Integer.parseInt(t.nextToken());
NamSinh = Integer.parseInt(t.nextToken());
DiemTongKet = Double.parseDouble(t.nextToken());
HoVaTen = t.nextToken();

}
// Ham main
public static void main(String[] argv)
{
// Dat gia trị bien static, sinh vien dau tien co ma so 1
SinhVien.MaSo=1;
// bien doi tuong Vector lưu các sinh viên
java.util.Vector sv = new java.util.Vector(5);
SinhVien k1 = new SinhVien(80,"Nguyen Thi Mai 1","Letio3",5);
sv.addElement(k1); // Them sinh vien vao Vector
SinhVien k2 = new SinhVien(81,"Tran Thi Mai 2","Letio3",6);
sv.addElement(k2);

80
SinhVien k3 = new SinhVien(82,"Pham Thi Mai 3","Letio3",7);
sv.addElement(k3);
SinhVien k4= new SinhVien(83,"Phan Thi Mai 4","Letio3",8);
sv.addElement(k4);
SinhVien k5= new SinhVien(84,"Hoang Thi Mai 5","Letio3",9);
sv.addElement(k5);
// Dung interface Enumeration de duyet cac phan tu cua Vector
java.util.Enumeration enu = sv.elements();
try
{
PrintWriter out = new PrintWriter(new FileWriter("C:\\LETI\\JAVA\\Sinhvien.dat"));
while (enu.hasMoreElements())
{
// Ep kieu, kieu Object la kieu cha cua moi kieu nen luon ep duoc
SinhVien g = (SinhVien)enu.nextElement();
g.GhiData(out);
}
out.close();
} catch(Exception ep) {}
// Doc tu file ra
SinhVien[] svs = new SinhVien[5];
try
{
BufferedReader in = new BufferedReader(new
FileReader("C:\\LETI\\JAVA\\Sinhvien.dat"));
for(int i=0;i<svs.length;i++)
{
svs[i] = new SinhVien(0,"","",0.0);
svs[i].DocData(in);
}
}
catch(IOException ep)
{}
for(int i=0;i<svs.length;i++)
System.out.println(svs[i].toString());
}}

IX. Lớp RandomAccessFile


Lớp RandomAccessFile cung cấp khả năng thực hiện vào/ra theo một vị trí cụ thể bên
trong một tập tin. Trong lớp này, dữ liệu có thể đọc hoặc ghi ở vị trí ngẫu nhiên thay vì liên tục.
Lớp RandomAccessFile thực hiện cả hai việc nhập và xuất. Do vây, có thể thực hiện I/O
bằng các kiểu dữ liệu nguyên thuỷ. Lớp này cũng hỗ trợ các quyền cơ bản về đọc hoặc ghi tập
tin, điều này cho phép đọc tập tin theo chế độ chỉ đọc hoặc đọc-ghi.
Đối tượng thuộc lớp RandomAccessFile có thể được khởi tạo theo hai cách:
 RandomAccessFile(String name, String mode);

81
 RandomAccessFile(File f, String mode);
Trong đó mode là chế độ mở tập tin:
mode = "r" - chế độ chỉ đọc.
mode = "rw" - chế độ ghi và đọc.
Ví dụ:
RandomAccessFile file = new RandomAccessFile(“C:\\LETI\\JAVA\\Account.txt”, "r");
Lớp này hỗ trợ một số phương thức mới khác với phương thức đã thừa kế từ các lớp
DataInput và DataOutput.
Các phương thức mới thêm vào bao gồm:
 seek( ): Thiết lập con trỏ tập tin tới vị trí cụ thể bên trong tập tin.
 getFilePointer( ): Trả về vị trí hiện hành của con trỏ tập tin.
 length( ): Trả về chiều dài của tập tin tính theo byte.

X. Đối tượng System.in


Là một đối tượng đặc biệt được tạo ra từ lớp InputStream. Nó giúp ta đọc các ký tự được
người dùng gõ vào từ bàn phím, ta có thể sử dụng phương thức read() hoặc kết nối với một luồng
trung gian khác.

XI. Truy cập file ở chế độ tuần tự


Khi làm việc với Java, tất cả đều là đối tượng. Trong các ứng dụng quản lý ta thường
xuyên phải đọc và ghi dữ liệu về các đối tượng lên ổ cứng. Các lớp ObjectInputStream và
ObjectOutputStream cho phép chúng ta đọc và ghi đối tượng vào file.
Để cho phép một đối tượng của một lớp có thể sử dụng với ObjectInputStrems và
ObjectOutputStreams, lớp đó phải khai báo cài đặt giao diện Serializable.
Giao diện Serializable được biết đến như là một giao diện “đính kèm” bởi vì bản thân nó
không chứa bất kỳ một phương thức nào. Một class cài đặt giao diện này được gán một thông báo
rằng đối tượng của lớp là một đối tượng Serializable. Điều này rất quan trọng bởi vì một đối
tượng ObjectOutputStream sẽ không xuất một đối tượng trừ khi đó là một đối tượng Serializable.
Trong một lớp cài đặt Serializable, lập trình viên phải đảm bảo rằng mọi biến của lớp phải
có kiểu Serializable hoặc phải khai báo một biến là transient để bỏ qua trong quá trình tuần tự
hóa. Mặc định thì tất cả các kiểu dữ liệu nguyên thủy đều là tuần tự hóa. Với các kiểu không
nguyên thủy, phải kiểm tra xem định nghĩa của lớp (hoặc lớp cha) có cài đặt Serializable hay
không.
Sau đây là một ví dụ về xây dựng một lớp đối tượng khách hàng cho một ứng dụng ngân
hàng sau đó ghi chúng vào file:
import java.io.Serializable;
public class AccountRecord implements Serializable {
private int account;
private String firstName;
private String lastName;
private double balance; // Chỉ số nắm giữ hàng hóa và dịch vụ của khách hàng, số dư tài
khoản
public AccountRecord()// constructor không tham số
{
this( 0, "", "", 0.0 );
}

82
public AccountRecord( int acct, String first, String last, double bal ) // Constructor có
tham số
{
setAccount( acct );
setFirstName( first );
setLastName( last );
setBalance( bal );
}
public void setAccount( int acct ) // Ðặt số tài khoản
{
account = acct;
}
public int getAccount()// Lấy số tài khoản
{
return account;
}
public void setFirstName( String first ) // Ðặt giá trị cho phần họ đệm
{
firstName = first;
}
public String getFirstName()// Lấy phần họ đệm
{
return firstName;
}
public void setLastName( String last ) // Ðặt giá trị cho tên
{
lastName = last;
}
public String getLastName()// Lấy tên
{
return lastName;
}
public void setBalance( double bal ) // Đặt giá trị cho balance
{
balance = bal;
}
public double getBalance()// Lấy số dư
{
return balance;
}
}

Chương trình sau đây sẽ ghi các đối tượng vào file C:\LETI\JAVA\Account.txt

import java.io.*;
public class GhiFile

83
{
ObjectOutputStream output;
public void openFile()
{
try {
output = new ObjectOutputStream(new FileOutputStream(
"C:\\LETI\\JAVA\\Account.txt") );
}
catch ( IOException ioException ) {
System.out.print("Loi mo file");
}
}
public void closeFile() // Phương thức đóng file
{
try {
output.close();
System.exit( 0 );
}
// Nếu có lỗi đóng file
catch ( IOException ioException )
{
System.exit( 1 );
}
}
public void addRecord(AccountRecord record) // Phương thưc thêm 1 bản ghi vào file
{
try
{
output.writeObject( record );
output.flush();
}
catch(IOException ex)
{
System.out.print("Loi ghi file");
}
}
// Hàm main
public static void main(String[] argvs)
{
AccountRecord record1= new AccountRecord( 12345,"Nguyen Van ","Hai",5.12);
AccountRecord record2= new AccountRecord( 12346,"Phan Tien ","Minh",-5.12);
GhiFile g = new GhiFile();
g.openFile();
g.addRecord(record1);
g.addRecord(record2);
g.closeFile();
}}

84
Chương trình sau đây đọc ra 2 bản ghi vừa được ghi ở chương trình trên và in ra màn
hình:

import java.io.*;
public class DocFile
{
java.io.ObjectInputStream input;
public void openFile() // Mở file
{
try
{
input = new java.io.ObjectInputStream(new FileInputStream("C:\\LETI\\JAVA\\Account.txt"));
} catch(IOException e)
{
System.out.print("Loi mo file");
}
}
public void closeFile() //Đóng file
{
try {
input.close();
System.exit( 0 );
}
catch ( IOException ioException )
{
System.exit( 1 );
}
}
// Phương thức đọc 1 bản ghi
public String readFile()
{
String account="";
try {
AccountRecord a = (AccountRecord)input.readObject();
account =String.valueOf( a.getAccount() ) + "\n "
+ a.getFirstName() + "\n "
+ a.getLastName() + "\n "
+ String.valueOf( a.getBalance());
}
catch(Exception p){}
finally {return account;}

}
public static void main(String[] argvs)
{
DocFile d = new DocFile();

85
d.openFile();
System.out.println(d.readFile());
System.out.println(d.readFile());
d.closeFile();
}}

XII. Truy cập file nhị phân


Ngoài các công cụ thao tác trên file văn bản, Java hỗ trợ cả các thao tác với file nhị phân
dùng các đối tượng:
 DataOutputStream: Hỗ trợ ghi file ở chế độ nhị phân tức là từng byte.
 DataInputStream: Hỗ trợ đọc file ở chế độ nhị phân.
Tất nhiên là các file văn bản cũng có thể đọc ghi ở chế độ nhị phân.
Chương trình sau đây copy dữ liệu giữa hai file dạng .doc:

import java.io.*;
public class BinaryAccess
{
public static void main(String[] argvs)
{
java.io.DataOutputStream out; // Luồng ra hỗ trợ ghi file nhị phân
java.io.DataInputStream in; // Luồng vào hỗ trợ đọc file nhị phân
try
{
in = new java.io.DataInputStream(new FileInputStream("C:\\P.doc"));
out = new java.io.DataOutputStream(new FileOutputStream("C:\\G.doc"));
int bytesAvailable = in.available(); // số byte của file
if (bytesAvailable > 0)
{
byte[] data = new byte[bytesAvailable];
in.read(data); // Đọc các byte ra một mảng byte
out.write(data); // Ghi mảng byte này vào một file khác
}
in.close();
out.close();
}
catch (Exception ex)
{}
}}

Bài tập
1. Viết một chương trình nhập vào một chuỗi từ bàn phím sau đó ghi chuỗi ra file.
2. Cho một file có dữ liệu về thí sinh thi hoa hậu như sau:
001, Tran Thi Mong Mo, 1.98, Ha Tay
002, Bùi Thị Thanh Nhàn, 1.89, Ha Noi
003, Nguyen Thu Thuy, 1.70, Ha Nam

86
009, Phan Anh Thu, 1.78, Thanh Hoa
Các dữ liệu lần lượt mô tả về: Mã số, Họ và tên, chiều cao, Quê quán.
Viết một chương trình đọc dữ liệu của file ra và gán mỗi dòng cho một đối tượng
HOAHAU.
3. Viết một chương trình copy dữ liệu từ một file văn bản sang một file văn bản khác.
4. Viết chương trình copy dữ liệu từ một file ảnh GIF sang một file ảnh GIF khác.

87
Đề tài 7. Xử lý ngoại lệ
Khi viết một chương trình nói chung và trong Java nói riêng, lỗi có thể xảy ra với rất
nhiều lý do. Khi lỗi xảy ra, người sử dụng chương trình muốn rằng chương trình có thể:
 Quay lại trạng thái an toàn trước đó và cho phép người dùng thực hiện các công
việc khác
 Ghi lại các dữ liệu hiện thời và kết thúc chương trình
Tuy nhiên điều này là rất khó khăn bởi lỗi có thể đến do một trong các nguyên nhân:
 Người dùng nhập dữ liệu sai
 Lỗi thiết bị
 Lỗi phần cứng như thiếu ổ cứng hay bộ nhớ.
Trong một đoạn chương trình mà lập trình viên không thể biết trước được liệu có chuyện
gì xảy ra làm cho đoạn chương trình đó gây ra lỗi hay không , chúng ta gọi đó là một ngoại lệ
(exception) – những điều xảy ra khác thường. Java cho phép người lập trình quản lý các lỗi xảy
ra theo cách khi nó xảy ra, chương trình sẽ thực hiện những việc do chính lập trình viên đặt ra,
gọi là exception handling.

I. Các tình huống sử dụng ngoại lệ


Việc quản lý các ngoại lệ nên được sử dụng khi:
 Một phương thức không thể hoàn thành nhiệm vụ của nó vì một lý do rằng nó
không thể điều khiển được mọi chuyện.
 Xử lý các ngoại lệ từ một thành phần chương trình chưa được quản lý ngoại lệ
 Trong các dự án lớn, đưa vào các xử lý ngoại lệ để xử lý theo một cách riêng
thống nhất.

II. Cơ sở quản lý ngoại lệ trong Java


Khi một phương thức không thể hoàn thành nhiệm vụ của nó, nó có thể “quăng,
ném”(throws) ra một Exception, nếu có một Exception handler - đoạn code xử lý khi lỗi xảy ra –
thì Exception này sẽ bị “bắt” (catch) và “được quản lý”(handled).
Cú pháp để thực hiện điều này như sau:
try
{
// Đoạn chương trình có thể gây ra exception
}
catch (<Kiểu exception> <tên biến>)
{
// Đoạn code chạy khi exception xảy ra
}
finally
{
// Đoạn code luôn được thực hiện bất kể exception có xảy ra hay không
}
Một khối try có thể không có hoặc có nhiều khối catch. Mỗi khối catch xác định một kiểu
exception mà nó có thể bắt và chứa đoạn code xử lý trong trường hợp tương ứng.
try{
//code có thể throw exceptions

88
}
catch (MalformedURLException e1)
{
//code xử lý
}
catch (UnknownHostException e2)
{
//code xử lý
}
catch (IOException e3)
{
//code xử lý
}
Sau khối catch cuối cùng, có thể có hoặc không một khối finally chứa code xử lý bất kể
exception có xảy ra hay không.
Mỗi khối try bắt buộc phải có một khối catch hoặc finally.
Khi lỗi xảy ra, chương trình sẽ tìm đến khối catch có kiểu đối tượng Exception phù hợp
để thực hiện. Để lấy được thông báo từ hệ thống về lỗi, sử dụng phương thức getMessage() của
các lớp Exception, ví dụ:
e3.getMessage();
Trong một phương thức ta có thể khai báo một mệnh đề throws để phương thức “quăng”
ra các ngoại lệ. Các ngoại lệ này được quăng ra bởi các lệnh throw hoặc việc gọi một phương
thức khác trong phương thức này. Tại điểm quăng ra ngoại lệ (throw point), Java sử dụng cơ chế
kết thúc chương trình thay vì cơ chế qua trở lại điểm xảy ra ngoại lệ và tiếp tục.
Ví dụ:
int functionName( parameterList ) throws ExceptionType1, ExceptionType2,
ExceptionType3,..
{
// Thân phương thức
}

III. Cấu trúc cây kế thừa các xử lý ngoại lệ


Trong Java có hàng trăm lớp ngoại lệ để “bắt” các lỗi tương ứng như lỗi vào/ra file, lỗi
truy cập CSDL SQL,…Các ngoại lệ trong Java đều kế thừa từ lớp Throwable theo cấu trúc như
sau:

89
Danh sách các ngoại lệ thường dùng trong Java

Ngoại lệ Ý nghĩa
RuntimeException Lớp cơ sở cho nhiều ngoại lệ java.lang
ArthmeticException Lỗi về số học, ví dụ như „chia cho 0‟.
IllegalAccessException Lớp không thể truy cập.
IllegalArgumentException Đối số không hợp lệ.
ArrayIndexOutOfBoundsExeption Lỗi tràn mảng.
NullPointerException Khi truy cập đối tượng null.
SecurityException Cơ chế bảo mật không cho phép thực hiện.
ClassNotFoundException Không thể nạp lớp yêu cầu.
NumberFormatException Việc chuyển đối từ chuỗi sang số thực không thành công.
AWTException Ngoại lệ về AWT
IOException Lớp cha của các lớp ngoại lệ I/O
FileNotFoundException Không thể định vị tập tin
EOFException Kết thúc một tập tin.
NoSuchMethodException Phương thức yêu cầu không tồn tại.
InterruptedException Khi một luồng bị ngắt.

IV. Sử dụng ngoại lệ được kiểm soát


Trong phần trên đã bàn về cách sử dụng các exception để bắt các lỗi không được kiểm
soát. Trong phần này chúng ta sẽ xem cách sử dụng ngoại lệ để quản lý các tình huống có thể
kiểm soát được.
Các ngoại lệ dạng này được khai báo ở phần đầu các phương thức. Ví dụ trong phương
thức khởi tạo sau:
public FileInputStream(String name) throws FileNotFoundException
Ở đây phương thức nhận một tham số kiểu String như là tên file, tuy nhiên rất có thể file
không tồm tại hoặc là một file rỗng và không có gì để đọc. Khi gặp lỗi như vậy, chương trình sẽ
tìm tới vị trí định nghĩa để quản lý lỗi này.
Một vấn đề là lập trình viên không biết throws ra ngoại lệ nào hoặc không thể khai báo
hết tất cả các ngoại lệ. Tuy nhiên, có 4 tình huống để đưa ra ngoại lệ:
 Khi gọi một phương thức có throw một ngoại lệ, chẳng hạn như FileInputStream.
 Khi ta xác định được một lỗi và throws một exception bằng một lệnh throw
 Khi viết một chương trình bị lỗi, chẳng hạn a[-1] = 0, sẽ dẫn đến việc kích hoạt
một ngoại lệ, chẳng hạn ở đây là ArrayIndexOutOfBoundsException.
 Khi một lỗi bên trong hệ thống như trong JVM hoặc các thư viện động.
Một phương thức cần khai báo ngoại lệ phù hợp:
class MyAnimation
{
...
public Image loadImage(String s) throws IOException
{
...
}
}
Phương thức có thể throws nhiều ngoại lệ nếu cần:

90
class MyAnimation
{
...
public Image loadImage(String s) throws EOFException, MalformedURLException
{
...
}
}
Các ngoại lệ bắt lỗi nội bộ của Java thường không nên được khai báo, đó là các ngoại lệ
kế thừa từ lớp Error vì chúng vượt khỏi tầm kiểm soát của chúng ta. Đối với các ngoại lệ không
được kiểm soát, chúng kế thừa từ lớp RuntimeException cũng vậy, ta không nên khai báo chúng.
Ví dụ:
class MyAnimation
{
...
void drawImage(int i) throws ArrayIndexOutOfBoundsException // Không phù hợp
{
...
}
}
Đối với các ngoại lệ dạng RuntimeException, nếu chúng ta có kế hoạch để khai báo nó thì
tốt nhất là tìm cách để khắc phục không cho chúng xảy ra.

V. Xây dựng một ngoại lệ


Chúng ta có thể tự xây dựng một ngoại lệ để throws trong những trường hợp cụ thể. Các
class ngoại lệ này được kế thừa từ lớp Exception hoặc lớp con của nó như IOException chẳng
hạn. Ví dụ:
class FileFormatException extends IOException
{
public FileFormatException() {}
public FileFormatException(String gripe)
{
super(gripe);
}
}
Sau đó, ta có thể sử dụng ngoại lệ này trong chương trình:
String readData(BufferedReader in) throws FileFormatException
{
...
while (. . .)
{
if (ch = = -1) // Điểm cuối file
{
if (n < len)
throw new FileFormatException();
}

91
...
}
return s;
}

VI. Bài tập


1. Viết chương trình đọc một file trên ổ đĩa và in ra màn hình. Chú ý dùng các ngoại lệ để
kiểm soát tất cả các tình huống có thể.
2. Khai báo một lớp ngoại lệ để kiểm soát tình huống điểm một sinh viên vượt quá 10 hay
tuổi của sinh viên vượt quá 100 trong ứng dụng quản lý sinh viên ở đề tài “Lập trình hướng đối
tượng trong Java”.
3. Viết chương trình nhập một số nguyên từ bàn phím. Bắt ngoại lệ khi người dùng nhập
vào ký tự hoặc số thực có phần thập phân.

92
Đề tài 8. Xử lý các sự kiện trong Java
I. Khái niệm và cơ sở xử lý sự kiện
Bất kỳ chương trình có giao diện đồ họa nào thường hỗ trợ việc kích hoạt các sự kiện từ
con chuột hoặc bàn phím. Môi trường điều hành sẽ gửi các sự kiện này tới chương trình đang
chạy. Lập trình viên được hoàn toàn quyết định điều gì sẽ xảy ra khi các sự kiện này được kích
hoạt.
Trong Java sử dụng một cách tiếp cận dựa trên các khái niệm:
- Các nguồn của sự kiện (event source)
- Các bộ lắng nghe sự kiện (event listener)
Các nguồn sự kiện chính là các đối tượng điều khiển, chúng có các phương thức để lập
trình viên đăng ký các bộ lắng nghe cho chúng. Khi một sự kiện xảy ra đối với một nguồn sự
kiện, nó sẽ gửi thông báo này tới tất cả các bộ lắng nghe sự kiện được đăng ký cho sự kiện đó.
Đối với một ngôn ngữ OOP như Java thì toàn bộ thông tin về sự kiện được đóng gói trong
một lớp event. Tất cả các lớp event đều dẫn xuất từ lớp java.util.EventObject. Mỗi loại sự kiện
tương ứng với một lớp dẫn xuất, chẳng hạn như ActionEvent và WindowEvent. Sau đây là cây
phân cấp chỉ sự kế thừa giữa các lớp sự kiện trong Java:

93
Các sự kiện trên được gửi cho các đối tượng lắng nghe tương ứng như: ActionListener
MouseMotionListener
AdjustmentListener MouseWheelListener
FocusListener WindowListener
ItemListener WindowFocusListener
KeyListener WindowStateListener
MouseListener
Các nguồn sự kiện khác nhau có thể phát sinh các sự kiện khác nhau. Ví dụ một button
(nút bấm) thì phát sinh sự kiện ActionEvent trong khi một cửa sổ lại phát sinh sự kiện
WindowEvent.
Tóm lại, một sự kiện được quản lý dựa trên các khái niệm:
1. Một đối tượng lắng nghe là một biểu hiện của một lớp có cài đặt giao diện lắng
nghe sự kiện
2. Một nguồn sự kiện là một đối tượng mà có thể đăng ký các đối tượng lắng nghe và
gửi cho chúng các đối tượng sự kiện.
3. Nguồn sự kiện gửi đối tượng sự kiện tới tất cả các đối tượng lắng nghe được đăng
ký cho sự kiện đó.
4. Đối tượng lắng nghe sẽ sử dụng thông tin trong các đối tượng sự kiện để phản ứng
với các sự kiện đó.
Cú pháp để đăng ký các đối tượng lắng nghe cho các nguồn sự kiện theo cú pháp sau:
<nguồn sự kiện>.add<sự kiện>Listener(<đối tượng lắng nghe sự kiện>)
Ví dụ:
ActionListener listener = . . .; // đối tượng lắng nghe
JButton button = new JButton("Ok"); // Nguồn sự kiện
button.addActionListener(listener); // đăng ký đối tượng lắng nghe sự kiện
Trong đoạn code trên, vấn đề còn lại là đối tượng lắng nghe sự kiện phải có một phương
thức để nhận về sự kiện từ nguồn sự kiện đăng ký nó và xử lý. Ở đây, đối tượng listener phải
thuộc một lớp có cài đặt giao diện ActionListener. Trong giao diện này có khai báo một phương
thức actionPerformed để nhận về một sự kiện ActionEvent:
class MyListener implements ActionListener
{
...
public void actionPerformed(ActionEvent event)
{
// Đoạn mã xử ký sự kiện đặt ở đây
...
}
}
Bất cứ khi nào người dùng click chuột vào đối tượng Jbutton trên màn hình, một sự kiện
ActionEvent được phát sinh và gửi cho đối tượng lắng nghe listener. Đối tượng này có cài đặt
một phương thức để xử lý sự kiện là actionPerformed, lấy đối số là đối tượng ActionEvent nhận
được.
Có thể có nhiều bộ lắng nghe được đăng ký cho một nguồn sự kiện, khi đó sự kiện cũng
được gửi tới tất cả các bộ lắng nghe và tới phương thức actionPerformed của chúng.
Sơ đồ sequence cho đoạn code trên như sau:

94
Sau đây là một ví dụ về xử lý sự kiện click cho 3 đối tượng Jbutton:
Một Button được tạo ra như sau bằng cách gọi constructor của JButton với một chuỗi
nhãn hoặc một ảnh hay cả hai:
JButton yellowButton = new JButton("Yellow");
JButton blueButton = new JButton(new ImageIcon("blue-ball.gif"));
Thêm 3 Button vào 1 panel như sau:
Class ButtonPanel extends JPanel
{
public ButtonPanel()
{
JButton yellowButton = new JButton("Yellow");
JButton blueButton = new JButton("Blue");

95
JButton redButton = new JButton("Red");
add(yellowButton);
add(blueButton);
add(redButton);
}
}

Bây giờ chúng ta định nghĩacác bộ lắng nghe cho các Button này gọi là ColorAction như
sau:
public class ColorAction implements ActionListener
{
public ColorAction(Color c)
{
backgroundColor =c;
}
public void actionPerformed(ActionEvent e)
{
setBackground(backgroundColor); // Thay đổi màu nền của panel khi Button được click
}
private Color backgroundColor;
}

Mỗi đối tượng Button được đăng ký cho bộ lắng nghe này:
Class ButtonPanel extends JPanel
{
public ButtonPanel()
{
JButton yellowButton = new JButton("Yellow");
JButton blueButton = new JButton("Blue");
JButton redButton = new JButton("Red");
add(yellowButton);
add(blueButton);
add(redButton);
ColorAction yellowAction = new ColorAction(Color.Yellow);
ColorAction blueAction = new ColorAction(Color.Blue);
ColorAction redAction = new ColorAction(Color.Red);

yellowButton.addActionListener(yellowAction);
blueButton.addActionListener(blueAction);
redButton.addActionListener(redAction);
}
}

Một vấn đề đặt ra là phương thức setBackground() không được định nghĩa trong lớp
ColorAction mà chỉ được định nghĩa cho lớp Panel. Giải pháp ở đây là đưa lớp ColorAction

96
thành lớp nội của lớp ButtonPanel, khi đó trong lớp ColorAction, ta có thể truy cập các phương
thức của lớp Panel (được ButtonPanel kế thừa) như setBackground chẳng hạn.
Class ButtonPanel extends JPanel
{
public ButtonPanel()
{
JButton yellowButton = new JButton("Yellow");
JButton blueButton = new JButton("Blue");
JButton redButton = new JButton("Red");
add(yellowButton);
add(blueButton);
add(redButton);
ColorAction yellowAction = new ColorAction(Color.Yellow);
ColorAction blueAction = new ColorAction(Color.Blue);
ColorAction redAction = new ColorAction(Color.Red);

yellowButton.addActionListener(yellowAction); // Đăng ký bộ lắng nghe sự kiện


blueButton.addActionListener(blueAction);
redButton.addActionListener(redAction);
}
private class ColorAction implements ActionListener
{
public ColorAction(Color c)
{
backgroundColor =c;
}
public void actionPerformed(ActionEvent e)
{
setBackground(backgroundColor); // Thay đổi màu nền của panel khi Button được click
}
private Color backgroundColor;
}
}

Chương trình sau đây kiểm tra sự thực hiện đáp ứng các sự kiện vừa thiết kế ở trên:

import javax.swing.JFrame;
public class testPanel extends JFrame
{
public testPanel()
{
ButtonPanel pn = new ButtonPanel();
this.add(pn);
}
public static void main(String[] argvs)

97
{
testPanel p = new testPanel();
p.setSize(300,400);
p.setVisible(true);
}
}

Thông thường, các lớp lắng nghe sự kiện có thể được cài đặt ngay trong phương thức
addActionListener như sau:
yellowButton.addActionListener(
new ActionListener()
{
public void actionPerformed(ActionEvent event)
{
setBackground(Color.YELLOW);
}
});
Làm như vậy ta không cần thiết kế riêng một lớp ColorAction.

II. Truy cập thông tin sự kiện


Một phương pháp khác để lập trình sự kiện đó là gán cho tất cả các đối tượng nguồn sự
kiện chung một đối tượng lắng nghe. Tùy theo thông tin về sự kiện nhận được, phương thức
actionPerformed sẽ có sự hành xử khác nhau. Muốn vậy, lớp chứa các nguồn sự kiện phải cài đặt
giao diện ActionListener.
import javax.swing.*;
import java.awt.event.*;
public class ButtonPanel extends JPanel implements ActionListener
{ JButton yellowButton;
JButton blueButton;
JButton redButton;
public ButtonPanel()
{
yellowButton = new JButton("Yellow");
blueButton = new JButton("Blue");
redButton = new JButton("Red");
add(yellowButton);
add(blueButton);
add(redButton);
yellowButton.addActionListener(this);
blueButton.addActionListener(this);
redButton.addActionListener(this);
}
public void actionPerformed(ActionEvent event)
{
if (event.getSource()==yellowButton) setBackground(Color.YELLOW);
if (event.getSource()==redButton) setBackground(Color.RED);

98
if (event.getSource()==blueButton) setBackground(Color.BLUE);
}
}
Trong ví dụ trên, bản thân lớp component ButtonPanel đóng vai trò là lớp lắng nghe sự
kiện vì nó có cài đặt giao diện ActionListener. Do đó, khi đăng ký đối tượng lắng nghe cho các
Button, ta chỉ việc sử dụng biến this, đại diện cho đối tượng ngầm định của chính lớp
ButtonPanel. Các đối tượng Button bây giờ cũng phải khai báo là biến thành phần của lớp vì
chúng được truy cập trong 2 phương thức khác nhau. Tuy nhiên phương pháp này sẽ trở nên khó
dùng khi trên panel có nhiều đối tượng nguồn sự kiện.

III. Xử lý các sự kiện trên window


Phần trên đã cho thấy cách thức chúng ta làm việc với các nguồn sự kiện là các đối tượng
nằm trong một window. Phần này ta sẽ xem xét các sự kiện xảy ra đối với bản thân một window.
Muốn bắt các sự kiện của window, ta cũng qua các bước căn bản sau:
WindowListener listener = new .....
frame.addWindowListener(listener);
Trong đó frame là một đối tượng của lớp JFrame, là đối tượng nguồn sự kiện phát sinh
các sự kiện WindowEvent. Đối tượng listener bắt buộc phải thuộc một lớp có cài đặt giao diện
WindowListener. Cấu trúc của giao diện này như sau:
public interface WindowListener
{
void windowOpened(WindowEvent e);
void windowClosing(WindowEvent e);
void windowClosed(WindowEvent e);
void windowIconified(WindowEvent e); // thu nhỏ của sổ
void windowDeiconified(WindowEvent e);
void windowActivated(WindowEvent e);
void windowDeactivated(WindowEvent e);
}
Do đó các lớp cài đặt giao diện này phải khai báo định nghĩa tất cả các phương thức trên.
Tuy nhiên, ta chỉ viết code cho phương thức nào ta xử lý mà thôi, ví dụ:

class Terminator implements WindowListener


{
public void windowClosing(WindowEvent e)
{
System.exit(0);
}

public void windowOpened(WindowEvent e) {}


public void windowClosed(WindowEvent e) {}
public void windowIconified(WindowEvent e) {}
public void windowDeiconified(WindowEvent e) {}
public void windowActivated(WindowEvent e) {}
public void windowDeactivated(WindowEvent e) {}
}

99
IV. Các lớp thích nghi
Ta thấy rằng việc khai báo đầy đủ 7 phương thức, có những phương thức không xử lý gì
cả làm cho chương trình không được tối ưu. Giải pháp ở đây sẽ là định nghĩa một lớp trung gian,
khai báo cài đặt giao diện WindowListener với đầy đủ 7 phương thức nhưng các phương thức
này cũng không làm gì cả. Các lớp muốn cài đặt nội dung cho một sự kiện chỉ việc khai báo kế
thừa lớp này và định nghĩa chồng phương thức cần thiết. Các lớp có vai trò trung gian như vậy
gọi là các lớp thích nghi. Với mỗi giao diện lắng nghe, ta có một lớp thích nghi tương ứng. Ví dụ:
WindowListener có lớp thích nghi WindowAdapter, MouseListener có MouseAdapter. Các lớp
thích nghi thường dùng là:
FocusAdapter MouseMotionAdapter
KeyAdapter WindowAdapter
MouseAdapter

class Terminator extends WindowAdapter


{
public void windowClosing(WindowEvent e)
{
System.exit(0);
}
}

Vấn đề là các lớp window thường phải khai báo kế thừa từ JFrame rồi nên không thể khai
báo kế thừa thêm lớp WindowAdapter. Lúc này, kỹ thuật định nghĩa lớp nội nặc danh được phát
huy:

frame.addWindowListener(new WindowAdapter()
{
public void windowClosing(WindowEvent event)
{
System.exit(0);
}
public void windowIconified(WindowEvent e) {System.exit(0);}
} );
Đoạn code này thực hiện các công việc sau:
1. Định nghĩa một class nặc danh kế thừa lớp WindowAdapter
2. Thêm 2 phương thức windowClosing và windowIconified cho lớp nặc danh đó
3. Kế thừa 5 phương thức còn lại từ WindowAdapter
4. Tạo ra một đối tượng của lớp đó, đối tượng cũng không có tên
5. Gửi đối tượng này cho phương thức addWindowListener
Ta có thể định nghĩa chồng nhiều hơn một phương thức của WindowAdapter.
Tất nhiên là không phải khi nào ta cũng phải sử dụng lớp nội nặc danh. Một cách làm
khác là khai báo một lớp (thường là lớp nội) dạng như class Terminator ở trên, sau đó đăng ký
lắng nghe sự kiện cho frame mà không cần một đối tượng có tên rõ ràng của Terminator:
frame.addWindowListener(new Terminator());
Để kết thúc đề tài này ta liệt kê các phương thức xử lý sự kiện như bảng sau:

100
Interface Methods Parameter/Accessors Nguồn sự kiện
ActionListener actionPerformed ActionEvent AbstractButton
JComboBox
 getActionCommand JTextField
 getModifiers Timer

AdjustmentListener adjustmentvalueChanged AdjustmentEvent JScrollbar

 getAdjustable
 getAdjustmentType
 getValue

ItemListener ItemstateChanged ItemEvent AbstractButton


JComboBox
 getItem
 getItemSelectable
 getStateChange

FocusListener FocusGained FocusEvent Component


focusLost
 isTemporary

KeyListener keyPressed KeyEvent Component


keyReleased
keyTyped  getKeyChar
 getKeyCode
 getKeyModifiersText
 getKeyText
 isActionKey

MouseListener mousePressed MouseEvent Component


mouseReleased
mouseEntered  getClickCount
mouseExited  getX
mouseClicked  getY
 getPoint
 TRanslatePoint

MouseMotionListener mouseDragged MouseEvent Component


mouseMoved

101
Interface Methods Parameter/Accessors Nguồn sự kiện
MouseWheelListener mousewheelMoved MouseWheelEvent Component

 getWheelRotation
 getScrollAmount

WindowListener windowClosing WindowEvent Window


windowOpened
windowIconified  getWindow
windowDeiconified
windowClosed
windowActivated
windowDeactivated
WindowFocusListener windowGainedfocus WindowEvent Window
windowlostFocus
 getOppositeWindow

WindowStateListener WindowStateChanged WindowEvent Window

 getOldState
 getNewState

V. Xử lý các sự kiện chuột


Các sự kiện với chuột được xử lý bởi giao diện MouseListener. Khi một nguồn sự kiện
được kích hoạt bởi việc click chuột, các phương thức xử lý cho các sự kiện tương ứng được liệt
kê trong bảng trên, đặc biệt hay dùng là mouseClicked(). Thông thương ta cũng không cài đặt xử
lý cho tất cả các sự kiện này nên ta không cài đặt giao diện MouseListener mà khai báo một lớp
nội nặc danh kế thừa từ lớp thích nghi MouseAdapter.
Giả sử có một đối tượng JFrame tên là frame, sau đây là một ví dụ quản lý sự kiện click
chuột trên nó:
frame. addMouseListener( new MouseAdapter()
{
public void mouseClicked(MouseEvent e) // Mac dinh la chuot trai
{ // Kiem tra neu la chuot phai duoc click
if( (e.getModifiers() & InputEvent.BUTTON3_MASK)== InputEvent.BUTTON3_MASK)
// hien thi mot popup menu tai vi tri click
jp.show(getContentPane(),e.getX(),e.getY());
}
} );
Tham khảo thêm tại:
http://java.sun.com/docs/books/tutorial/uiswing/events/handling.html

102
và http://java.sun.com/docs/books/tutorial/uiswing/events/mouselistener.html

Bài tập
1. Viết chương trình xử lý sự kiện chuột click trên các Botton, nếu là chuột trái thì thông báo
là “Bạn vừa click chuột trai”, nếu là chuột phải thì thông báo “Bạn vừa click chuột phải”.
2. Viết chương trình sau khi chọn một Button trên màn hình, nhấn phím x thì chương trình
kết thúc.

103
Đề tài 9. Applet
Applet là một trong những kỹ thuật vượt trội của Java so với các ngôn ngữ khác. Một
Applet là một chương trình Java được nhúng vào trong các trang web, nó đứng một vị trí độc lập
bên cạnh các phần khác của trang web.

I. Xây dựng một Applet đơn giản


1. Soạn thảo một tệp có tên “Hello.java” như sau:
import java.applet.Applet; //khai báo thư viện
import java.awt.Graphics;
//Khai báo đồ hoạ
public class Hello extends Applet {
//cài đặt phương thức cho Applet
public void paint(Graphics g){
g.drawString("Hello!", 50, 25);
}
2. Biên dịch tệp Hello.java
3. Soạn thảo một trang HTML có tên hello.htm
<html><body>
<applet>
<applet code="Hello.class" width=150 height=25>
</applet>
</body></html>
4. Đặt file Hello.class và hello.htm vào trong cùng một thư mục
5. Mở file hello.htm.
Tuy nhiên để các applet có thể chạy được, nó cần một JVM trên máy cục bộ. Để có được
JVM cho các trình duyệt ta cần cài phiên bản Java lên máy tính cục bộ và tích hợp vào trình
duyệt như một plug-in.
Trong các phiên bản Java gần đây, người lập trình có thể sử dụng thư viện swing nằm
trong gói javax.swing để có được giao diện GUI thống nhất trên mọi hệ thống. Các lớp trong
swing có tên gọi khác so với các lớp trong awt bởi thêm ký tự đầu tiên “J”.

II. Cấu trúc cơ bản và vòng đời của một Applet


Một chương trình Applet đầy đủ sẽ bao gồm các thành phần như sau:
import java.applet.Applet;
public class myApplet extends Applet{
public void init(){ ...... }
public void start(){ ...... }
public void stop() { ...... }
public void destroy() { ...... }
.... //Các phương thức khác
}
Nếu dùng JApplet thì ta có khai báo phần đầu như sau:
import javax.swing.JApplet;
public class myApplet extends JApplet{…}

104
Vòng đời của một Applet được đánh dấu bởi các sự kiện diễn ra ở mỗi giai đoạn, lập trình
viên cần nắm được ý nghĩa của từng giai đoạn để có thể viết code điều khiển Applet:
1. Giai đoạn init: Thực hiện các công việc khởi tạo một Applet. Giai đoạn này được
bắt đầu ngay sau khi thẻ <param> nằm trong thẻ <applet> được xử lý. Các hành
động như thêm các thành phần giao diện người dùng GUI được thực hiện ở đây.
2. Giai đoạn start: Được thực hiện ngay sau khi giai đoạn init được thực hiện xong.
Nó cũng được gọi bất cứ khi nào người dùng chuyển sang một trang web khác rồi
quay lại trang có applet. Do đó, code nằm trong start có thể được thực hiện nhiều
lần trong khi code trong init chỉ được thực hiện 1 lần.
3. Giai đoạn stop: Được thực hiện khi người dùng rời khỏi trang web chứa applet.
Do đó, code nằm trong stop cũng có thể được thực hiện nhiều lần.
4. Giai đoạn destroy: Được thực hiện khi người sử dụng tắt trình duyệt.
Khi viết một applet, chúng ta không phải khi nào cũng cài đặt đầy đủ 4 phương thức mà
tùy theo nhu cầu.
Ví dụ sau xây dựng một applet cho phép vẽ liên tiếp bắt đầu từ vị trí giữa applet.
import java.applet.*;
import java.awt.*;
import java.awt.event.*;
public class AppletSample extends Applet
{
Point curentPoint; // Lưu tọa độ điểm hiện tại
public void init()
{
curentPoint = new Point(this.getWidth()/2,this.getHeight()/2);
this.setBackground(Color.CYAN); // đặt màu nền của applet
}
public void start()
{
this.addMouseListener( new MouseAdapter()
{
public void mouseClicked(MouseEvent e)
{
drawOnApplet(curentPoint,e.getPoint());
curentPoint = e.getPoint();
}
}
);
}
public void drawOnApplet(Point p1, Point p2)
{
Graphics g = this.getGraphics();
g.drawLine(p1.x,p1.y,p2.x,p2.y);
}
}

105
III. An ninh và khả năng của Applet
Vì applet được download từ xa và thực hiện trên máy tính cụ bộ nên vấn đề về an ninh
được quan tâm đặc biệt. Các nguyên tắc về an ninh của một applet là:
 Applet không bao giờ thực hiện một chương trình của máy cục bộ.
 Applet không thể giao tiếp với các máy tính khác trên mạng trừ máy tính mà nó
được download về.
 Applet không thể đọc và ghi dữ liệu lên máy tính cục bộ
 Applet không thể tìm thấy thông tin gì về máy tính cuc bộ, trừ các thông tin như
phiên bản Java, tên và phiên bản hệ điều hành, ký tự phân tách file, đường dẫn.
Applet không thể tìm thấy các thông tin cá nhân như email, password,…
 Tất cả các cửa sổ trình duyệt có applet được tải về đều có thông báo.
Những yêu cầu về an ninh của applet là rất quan trọng. Trong một số trường hợp ta có thể
cấu hình để applet được tin cậy và có thể truy cập vào máy tính cục bộ giống như một chương
trình đang chạy trên máy tính đó.
 Applet cũng có những khả năng như:
 Tạo kết nối đến máy đang chạy nó.
 Applet dễ dàng dùng các siêu văn bản hiển thị.
 Có thể gọi đến các phương thức toàn cục của các applet khác trên cùng trang web.
Applet có thẻ giao tiếp với các applet khác trên trang web cũng như với các đối
tượng khác trên trang web thông qua cơ chế riêng.
 Các applet được nạp từ hệ thống tập tin cục bộ sẽ không bị các giới hạn của các
applet được nạp từ mạng xuống.
 Không nhất thiết là các applet sẽ ngừng lại khi ta thoát khỏi các trang web chứa
nó.

IV. Ứng dụng Applet với của sổ Popup


Với Applet chúng ta hoàn toàn có thể có thêm các cửa sổ pop-up để mở rộng không gian
làm việc. Sau đây là một Japplet có thể mở một Frame mới.
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class PopupCalculatorApplet extends JApplet
{
public void init()
{
// Tạo một frame với một cửa sổ panel
final JFrame frame = new JFrame();
frame.setTitle("Calculator");
frame.setSize(200, 200);
// Thêm một Button để ẩn/ hiện frame pop-up
JButton calcButton = new JButton("Calculator");
add(calcButton);
calcButton.addActionListener(new
ActionListener()
{

106
public void actionPerformed(ActionEvent event)
{
frame.setVisible(!frame.isVisible());
}
});
}
}

V. Các thẻ HTML của Applet


Một khai báo applet trong trang HTML thường có dạng:
<applet code="NotHelloWorldApplet.class" width="300" height="100">
Thong tin duoc hien thi khi applet khong hien thi
</applet>
Sau đây ta sẽ tìm hiểu các thuộc tính của applet:
Các thuộc tính về vị trí:
 height: Chiều cao của applet. Các applet không thể thay đổi kích thước khi đã
chạy.
 width: Chiều rộng của applet
 align: Xác định vị trí của applet so với các văn bản xung quanh nó. Các giá trị của
align có thể là: left, right, middle, top, texttop, absmiddle, baseline, bottom,
absbottom, vspace và hspace.
Các thuộc tính về chương trình nguồn- code:
code : Thuộc tính này cho biết tên của class chứa chương tình applet. Tên này là tương
đối so với giá trị codebase hoặc với thư mục hiện hành nếu không có codebase. Đường dẫn phải
phù hợp với đường dẫn gói của class applet. Ví dụ: nếu applet có tên MyApplet.class được đặt
trong gói com.mycompany thì đường dẫn phải là:
code="com/mycompany/MyApplet.class".
hoặc:
code="com.mycompany.MyApplet.class" cũng được chấp nhận.
- codebase: Là một thuộc tính tùy chọn để xác định đường dẫn tuyệt đối nơi chứa class ở
giá trị code.
archive: Chứa tên các gói chương trình hoặc tài nguyên khác được tải về kèm theo applet.
Các gói chương trình được nén ở dạng file JAR và được đặt cách nhau bởi dấu ,. Ví dụ:
<applet code="CalculatorApplet.class"
archive="CalculatorClasses.jar,corejava/CoreJavaClasses.jar"
width="100" height="150">
object: Chứa tên file đã được tuần tự hóa của một applet.
name: Tên của applet. Trong các trình duyệt của Netscape hoặt Windows đều cho phép
applet giao tiếp với các đối tượng javascript khác trên trang web. Khi đó applet cần có tên để truy
cập.
Ngoài ra Java cho phép ta định nghĩa các tham số cho applet thông qua các thẻ <param>
Ví dụ:
<applet code="FontParamApplet.class" width="200" height="200">
<param name="font" value="Helvetica"/>
<param name="size" value="24"/>

107
</applet>
Khi đó trong chương trinh applet, ta có thể lấy ra để sử dụng:
public class FontParamApplet extends JApplet
{
public void init()
{
String fontName = getParameter("font");
int fontSize = Integer.parseInt(getParameter("size"));
...
}
}

VI. Các phương thức, lập trình đồ họa và bắt sự kiện của applet
 public void paint(Graphics g) {...}: Là phương thức hiển thị cơ bản, thường các
applet dùng paint() để biểu diễn các hoạt động của mình trên trang web.
 public void update(Graphics g) {...}: là phương thức ta dùng sau khi thực hiện
pain() nhằm làm tăng hiệu quả vẽ.
Ngoài ra Applet còn thừa hưởng các phương thức từ lớp AWT.
 Phương thức repaint() được dùng khi cửa sổ cần cập nhật lại. Phương thức này chỉ
cần một tham số là đối tượng của lớp Graphics.
 getDocumentBase(): cho biết địa chỉ dạng URL của thư mục chứa tập tin HTML
chứa applet.
 getCodeBase(): Cho biết địa chỉ dạng URL của thư mục chứa tập tin .CLASS của
applet.
 getImage(URL url, String name): trả về một đối tượng ảnh hiển thị trên nền.
 getLocale(): Xác định vị trí applet.
 getAudioClip(URL url, String name): cho đối tuợng audio.
 showStatus(String st): hiển thị xâu st trên thanh trang thái.
 drawChars(char array[ ], int offset, int length, int xCoor, int yCoor): xuất các ký
tự. Chú thích các tham số: array: Mảng các ký tự; offset: Vị trí bắt đầu, nơi các ký
tự được vẽ.; length: Số các ký tự cần được vẽ; xCoor: Toạ độ X, nơi các ký tự cần
được vẽ; yCoor: Toạ độ Y, nơi các ký tự cần được vẽ.
 drawBytes(byte array[ ], int offset, int length, int xCoor, int yCoor): xuất các byte
ra.Trong đó array: Mảng các byte.;Vị trí offset hay vị trí bắt đầu; length: Số byte
cần vẽ; xCoor: Toạ độ X; yCoor: Toạ độ Y.
 drawLine(int x1, int y1, int x2, int y2): Vẽ đường thẳng từ A(x1, y1) đến B(x2,
y2).
 drawOval(int xCoor, int yCoor, int width, int height): Vẽ đường oval.
 fillOval(int xCoor, int yCoor, int width, int height): Vẽ hình oval đặc.Trong đó:
width là Chiều rộng của hình, height là Chiều cao của hình.
 drawRect(int xCoor, int yCoor, int width, int height): Vẽ hình chữ nhật.
 fillRect(int xCoor, int yCoor, int width, int height): Vẽ hình chữ nhật đặc.
 drawRoundRect(int xCoor, int yCoor, int width, int height, int arcwidth, int
archeight): Vẽ hình chữ nhật có bo góc.

108
 fillRoundRect(int xCoor, int yCoor, int width, int height, int arcwidth, int
archeight): vẽ hình chữ nhật bo góc đặc.
Trong đó: arcwidth: làm tròn góc trái và góc phải của hình chữ nhật.
archeight: làm tròn góc trên đỉnh và góc đáy của hình chữ nhật.
Ví dụ, arcwidth = 20 có nghĩa là hình chữ nhật được làm tròn cạnh trái và cạnh phải mỗi
cạnh 10 pixel.
 draw3Drect(int xCoord, int yCoord, int width, int height, boolean raised): Vẽ hình
chữ nhật 3D.
 drawArc(int xCoord, int yCoord, int width, int height, int arcwidth, int archeight):
Vẽ hình cung.
 fillArc(int xCoord, int yCoord, int width, int height, int arcwidth, int archeight):
Vẽ hình cung đặc.
Trong đó: xCoord: Toạ độ x; yCoord: Toạ độ y.
width: Chiều rộng của cung được vẽ; height: Chiều cao của cung được vẽ.
arcwidth: Góc bắt đầu; archeight: Độ rộng của cung (góc của cung) so với góc ban đầu.
 drawPolyline(int xArray[ ], int yArray[ ], int totalPoints): Vẽ nhiều đường thẳng
Trong đó: xArray: Mảng lưu trữ toạ độ x của các điểm; yArray: Mảng lưu trữ toạ độ y của
các điểm; totalPoints: Tổng số điểm cần vẽ.
 setFont(new Font(“Times Roman”, Font.BOLD, 15)): Đặt Font cho chữ,
(Font.BOLD, Font.PLAIN, Font.ITALIC)
 drawPolygon(int x[ ], int y[ ], int numPoints):Vẽ đa giác.
 fillPolygon(int x[ ], int y[ ], int numPoints): Vẽ đa giác đặc.
Trong đó: x[ ]: Mảng lưu trữ toạ độ x của các điểm; y[ ]: Mảng lưu trữ toạ độ y của các
điểm; numPoints: Tổng số điểm cần vẽ; setColor(Color c): Đặt màu vẽ.
Bảng một số màu cơ bản:

Color.white Color.black
Color.orange Color.gray
Color.lightgray Color.darkgray
Color.red Color.green
Color.blue Color.pink
Color.cyan Color.magenta
Color.yellow

109
Đề tài 10. Lập trình giao diện đồ họa GUI
I. Giới thiệu AWT
Abstract Windows Toolkit – AWT: Là thư viện của Java cung cấp cho lập trình viên các
giải pháp giao diện người dùng đồ hoạ (Graphical User Interface - GUI) thân thiện.
Một giao diện người dùng được hình thành từ các phần tử của GUI. Một phần tử GUI
được thiết lập bằng cách sử dụng các thủ tục:
1. Tạo đối tượng.
2. Xác định sự xuất hiện ban đầu của đối tượng.
3. Chỉ ra nó nằm ở đâu.
4. Thêm phần tử vào giao diện trên màn hình.
Để làm việc với các đối tượng GUI chúng ta cần nhập gói java.awt.*.
AWT cung cấp các thành phần khác nhau để tạo GUI hiệu quả, các thành phần này có thể
là:
 Vật chứa (Container).
 Thành phần (Component).
 Trình quản lý cách trình bày (Layout manager).
 Đồ hoạ (Graphics) và các tính năng vẽ (draw).
 Phông chữ (Font).
 Sự kiện (Event).
Từ phiên bản 1.4, Java phát triển một thư viện mở rộng mới với đa số các lớp GUI kế
thừa từ AWT nhưng có khả năng di động tốt hơn. Các lớp GUI trong swing có thêm tiền tố “J” so
với các lớp trong AWT. Sau đây, chúng ta sẽ xét các thành phần GUI trong swing.
Sơ đồ phân cấp thừa kế các đối tượng GUI trong swing như sau:

110
II. Vật chứa (Container)
Là vùng mà ta có thể đặt các thành phần (component) của giao diện. Một vật chứa có thể
chứa nhiều phần tử. Vật chứa thường được sử dụng là:
 JPanel - khung chứa đơn giản nhất, để nhóm các đối tượng con lại và sắp xếp theo
cách thích hợp.
 JFrame - là một cửa sổ như mọi ứng dụng của windows, để tạo các ứng dụng
windows.
 JDialogs - là cửa sổ nhưng không đầy đủ chức năng như Frame, nó là cửa sổ hộp
thoại đưa ra những lời thông báo.
 JScrollPanel - khung chứa như Panel nhưng có hai thanh trượt.

II.1 JFrame
Tạo đối tượng khung chứa JFrame bằng phương thức khởi tạo: void JFrame();
Tạo Frame với tiêu đề bên trên: void JFrame(String FrameTitle);
Ví dụ:
JFrame frame1 = new JFrame();
JFrame frame2 = new JFrame("Cua so Frame");
import javax.swing.*;
public class FrameSample {
public static void main(String[] argvs)
{
JFrame f = new JFrame();
f.setSize(400,400);
f.setVisible(true);
}
}

Một số phương thức hay sử dụng:


 Đặt lại kích thước cho JFrame: frame1.setSize(int width, int height);
 Đưa cửa sổ hiện ra màn hình: frame1.show();
frame1.setVisible(boolean b); trong đó: b = true cho hiển thị, b = false cho
ẩn.
 Bỏ hoàn toàn đối tượng JFrame: frame1.dispose();
 Thay đổi tiêu đề cho JFrame: frame1.setTitle(String newTitle);
 Lấy tiêu đề của JFrame: public String getTitle();
 Co dãn JFrame: public void setResizable(boolean b); trong đó: b = true thì Frame
có thể thay đổi kích thước, b = false thì không đổi.
 Xác định JFrame đang ở tình trạng nào: public boolean isResizable();
 Thay đổi biểu tượng JFrame: public setIconImage(Image img);

II.2 JPanel
JPanel không thể nhìn thấy trực tiếp, nó là đối tượng dùng để chứa các thành phần GUI
trên màn hình. Do đó chúng ta cần gắn JPanel vào đối tượng nào đó như: JFrame, JApplet, ...
Tạo khung chứa JPanel bằng phương thức khởi tạo: JPanel panel1 = new JPanel();
Ví dụ sau tạo một đối tượng JPanel và đặt vào nó một đối tượng nút bấm JButton.

111
import javax.swing.*;
public class FrameSample {
public static void main(String[] argvs)
{
JFrame f = new JFrame();
JPanel p = new JPanel();
p.add(new JButton("OK"));
f.add(p); // gắn JPanel vào JFrame
f.setSize(400,400);
f.setVisible(true);
}
}

II.3 JDialog
Như JFrame, nhưng ít chức năng hơn, còn được gọi là popup-window.
Ta có thể tạo JDialog dưới hai hình thức:
 modal: là cửa sổ JDialog khi thực hiện sẽ khoá chặt các cửa sổ khác. Cửa sổ này
thường yêu cầu một tác vụ nào đó cần phải hoàn thành ngay.
 non-modal: ngược lại với modal, cửa sổ này thích ứng với các thao tác mang tính
tuỳ biến.
Cách tạo khung chứa JDialog từ phương thức khởi tạo:
public JDialog(JFrame parentWindow, boolean isModal);
trong đó: isModal - quyết định xem JDialog được tạo ra ở dạng nào: isModal = true cho
modal còn isModal = false cho non-modal.
Ví dụ: JDialog myDialog = new JDialog(myWindow, true)
Tạo JDialog có tiêu đề định trước:
public JDialog(Frame parentWindow, String title, boolean isModal)
JDialog không thể gắn với Applet, muốn đưa ra một JDialog từ JApplet ta phải tạo ra một
JFrame giả tạo.
Đưa đối tượng JDialog ra màn hình bằng phương thức show().
Giấu cửa sổ JDialog ta dùng phương thức: hide().
Ẩn hiện JDialog ta còn có phương thức: setVisible(boolean).
Các đặc điểm thường sử dụng với đối tượng JDialog thể hiện tương tự với JFrame.
Lấy và thay đổi kích thước JDialog: public setResizable(boolean), boolean isResizable()
Đặt và lấy tiêu đề của JDialog: void setTitle(String), String getTitle();
Xác minh một JDialog đang thuộc dạng nào ta gọi phương thức: public boolean isModal()
Chương trình sau đây sẽ ẩn hoặc hiện một Jdialog mỗi khi nhất vào một nút bấm:

import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
public class FrameSample extends JFrame
{ private JDialog d;
public FrameSample() // Constroctor
{

112
d = new JDialog(this,"Day la Jdialog",false);
d.setSize(100,100);
JPanel p = new JPanel();
JButton b = new JButton("Xem/Tat mot JDialog");
b.addActionListener( new ActionListener()
{
public void actionPerformed(ActionEvent e)
{
d.setVisible(!d.isVisible());
}
}
);
p.add(b);
add(p);
}
public static void main(String[] argvs)
{
FrameSample fs = new FrameSample();
fs.setSize(400,400);
fs.setVisible(true);
}
}

II.4 JScrollPane
Đây không phải là một cửa sổ nhưng có tính chất như khung chứa JPanel dùng để chứa
các đối tượng. Tính chất khác là khi đối tượng nó chứa quá lớn thì JScrollPane sẽ xuất hiện thanh
trượt đứng và ngang ở hai bên viền để ta có thể xem được toàn bộ. JScrollPane được ứng dụng
trong các danh sách chọn.

III. Giới thiệu về các thành phần GUI cơ bản


 Jlabel: Chỉ một vùng hiển thị văn bản hoặc các icon.
 JtextField: Chỉ một đối tượng cho phép nhập dữ liệu từ bàn phím.
 JButton: Đối tượng nút bấm
 JcheckBox: Đối tượng nút chọn.
 JcomboBox: Đối tượng ComboBox
 Jlist: Danh sách
 JPanel: Nơi đặt các thành phần GUI nói trên.

III.1 Nút nhấn


Cách tạo nút nhấn
Ta gọi phương thức khởi dựng: public Button(String label) label là tên nhãn của nút nhấn
Ví dụ: Button nutOK = new Button("OK");
Đư nút nhấn vào Applet hay cửa sổ chương trình bằng lệnh: add(nutOK);
Để đổi tên nhãn cho nút nhấn ta gọi phương thức public void setLabel(String newLabel).
Để xem tên nhãn hiện tại của nút nhấn, ta gọi phương thức public String getLabel()

113
Ví dụ: Button nutOK = new Button(); nutOK.setLabel("OK");
Sử dụng nút nhấn
Khi kích chuột vào nút nhấn sẽ thực hiện một công việc nào đó. Vấn đề này chúng ta đã
xem xét trong phần xử lý các sự kiện

III.2 Nhãn (Label)


Tạo nhãn không có nội dung gì: public Label() hoặc tạo nhãn có nội dung: public
Label(String nameLabel).
hoặc thêm chế độ căn lề: pubic Label(String name, int align)
Một số phương thức hay sử dụng:
setText(String nameLabel): Đặt lại nội dung mới cho nhãn.
Label.CENTER, Label.LEFT, Label.RIGHT: những giá trị căn lề.
Lấy nội dung hiện hành của nhãn: public String getText();
Xem nhãn đang được căn lề ở chế độ nào: public int getAlignment();
Thiết lập lại chế độ căn lề: public void setAlignment(int align) throws
IlligalArgumentException
Sau đây là ví dụ sử dụng JLabel:
// Minh họa về JLabel
// Nhập các gói thư viện
import java.awt.*;
import java.awt.event.*;

// Gói Java mở rộng


import javax.swing.*;

public class LabelTest extends JFrame {


private JLabel label1, label2, label3;

// Tạo giao diện GUI


public LabelTest()
{
super( "Testing JLabel" ); // Contructor của JFrame
// Lấy đối tượng pane của một JFrame
Container container = getContentPane();
container.setLayout( new FlowLayout() );

// constructor của JLabel


label1 = new JLabel( "Label with text" );
label1.setToolTipText( "This is label1" ); // hiển thị khi di chuột đến
container.add( label1 ); // thêm vào container

// Tạo Jlabel với icon hoặc text


Icon bug = new ImageIcon( "bug1.gif" );
label2 = new JLabel( "Label with text and icon", bug, SwingConstants.LEFT );
label2.setToolTipText( "This is label2" );
container.add( label2 );

114
// JLabel không tham số
label3 = new JLabel();
label3.setText( "Label with icon and text at bottom" );
label3.setIcon( bug );
label3.setHorizontalTextPosition( SwingConstants.CENTER );
label3.setVerticalTextPosition( SwingConstants.BOTTOM );
label3.setToolTipText( "This is label3" );
container.add( label3 );

setSize( 275, 170 );


setVisible( true );
}
// hàm main
public static void main( String args[] )
{
LabelTest application = new LabelTest();

application.setDefaultCloseOperation(
JFrame.EXIT_ON_CLOSE );
}
}

III.3 Nút đánh dấu (checkbox)


Một nút đánh dấu có hai phần: nhãn và dấu biểu hiện trạng thái.
Ta có thể tạo nút đánh dấu theo 5 phương thức khởi dựng:
 public Checkbox(): tạo ô đánh dấu không có nhãn.
 public Checkbox(String cbLabel): tạo ô đánh dấu với một nhãn cbLabel gắn kèm.
 public Checkbox(String cbLabel, boolean state): Tạo ô đánh dấu với một nhãn
cbLabel gắn kèm với trạng thái ban đầu của ô đó.
 public Checkbox(String cbLabel, boolean state, CheckboxGroup g);
 public Checkbox(String cbLabel , CheckboxGroup g, boolean state): dùng để tạo ô
đánh dấu có nhãn với trạng thái đánh dấu ban đầu, nút tạo ra được nhóm trong
nhóm g. Nhóm các ô đánh dấu trở thành nút chọn.
Kiểm tra và thiết lập trạng thái:
Để kiểm tra một ô có được đánh dấu không ta dùng phương thức: public boolean
getState();
Nếu muốn thiết lập trạng thái, ta dùng phương thức: setState(boolean state).
Xử lý tình huống khi ô đánh dấu thay đổi trạng thái:
Để lắng nghe ô đánh dấu, ta dùng phương thức: addItemListener(ItemListener L)
Để loại bỏ lắng nghe đó ta dùng phương thức: removeItemListener(ItemListener L)
// Java core packages
import java.awt.*;
import java.awt.event.*;

115
// Java extension packages
import javax.swing.*;
public class CheckBoxTest extends JFrame {
private JTextField field;
private JCheckBox bold, italic;
// Thiết lập GUI
public CheckBoxTest()
{
super( "JCheckBox Test" );
// lấy pane
Container container = getContentPane();
container.setLayout( new FlowLayout() );
// Đặt ô nhập và font chữ
field = new JTextField( "Theo dõi font chữ thay đổi", 20 );
field.setFont( new Font( "Serif", Font.PLAIN, 14 ) );
container.add( field );

// tạo đối tượng checkbox


bold = new JCheckBox( "Bold" );
container.add( bold );

italic = new JCheckBox( "Italic" );


container.add( italic );

// Đăng ký đối tượng lắng nghe


CheckBoxHandler handler = new CheckBoxHandler();
bold.addItemListener( handler );
italic.addItemListener( handler );
setSize( 275, 100 );
setVisible( true );
}

// execute application
public static void main( String args[] )
{
CheckBoxTest application = new CheckBoxTest();
application.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
}
// Lớp nội lắng nghe sự kiện
private class CheckBoxHandler implements ItemListener {
private int valBold = Font.PLAIN;
private int valItalic = Font.PLAIN;
// Đáp ứng sự kiện chọn ô
public void itemStateChanged( ItemEvent event )
{
// xử lý chọn ô chữ đậm
if ( event.getSource() == bold )

116
if ( event.getStateChange() = = ItemEvent.SELECTED )
valBold = Font.BOLD;
else
valBold = Font.PLAIN;

// Chọn ô chứ nghiêng


if ( event.getSource() == italic )
if ( event.getStateChange() == ItemEvent.SELECTED )
valItalic = Font.ITALIC;
else
valItalic = Font.PLAIN;
// Đặt lại font chữ của ô text
field.setFont(new Font( "Serif", valBold + valItalic, 14 ) );
}}}

III.4 Nút chọn (radio button)


Tạo nút chọn:
Đây là trường hợp đặc biệt của ô đánh dấu. Ta tạo ra ô đánh dấu và đặt nó vào một nhóm,
ta có phương thức sau: public CheckboxGroup();
Với một nhóm các nút ta có thể lấy ra đối tượng hiện tại đang được chọn bằng cách gọi
phương thức getSelectedCheckbox().
Đặt lựa chọn ta dùng: setSelectedCheckbox().
Cách sử dụng nút chọn:
Ta tạo một đối tượng giao tiếp ItemListener để lắng nghe tình huống.
Phương thức getItemSelectable sẽ trả về đối tượng nơi mà tình huống phát sinh:
public ItemSelectable getItemSelectable();
Phương thức getItem của đối tượng ItemEvent sẽ cho ta biết giá trị nút chọn:
public Object getItem();
Sau đây là ví dụ về sử dụng ô chọn:
// Đăng ký đối tượng lắng nghe cho mỗi ô chọn radio
RadioButtonHandler handler = new RadioButtonHandler();
plainButton.addItemListener( handler );
boldButton.addItemListener( handler );
italicButton.addItemListener( handler );
boldItalicButton.addItemListener( handler );

// Nhóm các ô lại


radioGroup = new ButtonGroup();
radioGroup.add( plainButton );
radioGroup.add( boldButton );
radioGroup.add( italicButton );
radioGroup.add( boldItalicButton );
Về bắt các sự kiện cũng tương tự ô đánh dấu.

117
III.5 Hộp thoại Combo
Java hỗ trợ hộp thoại Combo thông qua đối tượng của class JComboBox. Đây là một danh
sách xổ xuống, đưa ra một danh sách các mục và ta chỉ được chọn 1 mục trong đó. JComboBox
cũng phát sinh sự kiện ItemEvent giống như JRadioButton và JCheckBox.
Chương trình sau đây minh họa việc sử dụng JComboBox với các phần tử là các ảnh GIF.

// Java core packages


import java.awt.*;
import java.awt.event.*;

// Java extension packages


import javax.swing.*;

public class ComboBoxTest extends JFrame {


private JComboBox imagesComboBox;
private JLabel label;
private String names[] = { "bug1.gif", "bug2.gif", "travelbug.gif", "buganim.gif" };
private Icon icons[] = { new ImageIcon(names[0]), new ImageIcon(names[1]), new
ImageIcon(names[2]), new ImageIcon(names[3])};

// Thiết lập GUI


public ComboBoxTest()
{
super( "Testing JComboBox" );

// Đặt layout
Container container = getContentPane();
container.setLayout( new FlowLayout() );

// khởi tạo combo


imagesComboBox = new JComboBox( names );
imagesComboBox.setMaximumRowCount( 3 );

imagesComboBox.addItemListener( new ItemListener() {


// bắt sự kiện
public void itemStateChanged( ItemEvent event )
{
// liệu có phần tử được chọn
if ( event.getStateChange() == ItemEvent.SELECTED )
label.setIcon( icons[imagesComboBox.getSelectedIndex() ] );
} } );

container.add( imagesComboBox );

// đặt một JLabel hiển thi ImageIcons tương ứng


label = new JLabel( icons[ 0 ] );

118
container.add( label );

setSize( 350, 100 );


setVisible( true );
}

// hàm main
public static void main( String args[] )
{
ComboBoxTest application = new ComboBoxTest();
aplication.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
}}

III.6 Danh sách (Lists)


Lớp List cho phép ta tạo một danh sách các phần tử cho ta lựa chọn
Tạo một danh sách:
Phương thức khởi dựng mặc định: public List(). vd: List list = new List();
hoặc để chỉ định bao nhiêu phần tử sẽ được hiển thị cùng một lúc trong danh sách: public
List(int rows)
hoặc tạo một danh sách cho phép lựa chọn nhiều phần tử cùng một lúc: public List(int
rows, boolean multiMode)
Sử dụng danh sách chọn:
Đưa từng phần vào danh sách bằng lệnh add(String item).vd: list.add("Mau do");
Chèn một phần tử vào danh sách bằng phương thức add(String item, int index), index là
vị trí phần tử mà ta muốn chèn.
Thay thế một phần tử ở vị trí pos bằng phần tử mới: replaceItem(String newItem, int pos)
vd: list.replaceItem("Xanh", 4);
Loại bỏ một phần tử trong danh sách ta dùng phương thức remove(int pos).
Loại bỏ hết danh sách: removeAll().
Trả về vị trí của phần tử được chọn, nếu không có phần tử nào được chọn thì giá trị sẽ là -
1: getSelectedIndex(). Nếu được chọn nhiều thì dùng: int[] getSelectedIndex()
Lấy ra phần tử được chọn: getSelectedItem(). Chọn nhiều thì dùng: String[]
getSelectedItem().
Để chọn một phần tử mà ta biết vị trí: select(int index)
Nếu phần tử đang ở trạng thái chọn chuyển sang trạng thái bình thường: deselect(int
index)
Trạng thái của một phần tử: isSelected(int index)
Chuyển chế độ đơn chọn sang đa chọn: setMultiplesSelections(boolean bl)
Cho biết một danh sách là đơn chọn hay đa chọn: boolean isMultipleMode().
Sử dụng đối tượng danh sách:
Cài đặt giao tiếp ItemListener để tạo đối tượng biết lắng nghe sự thay đổi trạng thái của
các phần tử trong danh sách.
Sử dụng đối số ItemEvent trong phương thức itemStateChange để biết được chỉ số của
phần tử đang thay đổi trạng thái.

119
Để xử lý được tình huống nhấp đôi chuột, ta cài đặt giao tiếp ActionListener và gắn nó
vào danh sách.
Phương thức getActionCommand() trong ActionEvent sẽ trả về tên của phần tử bị nhấp.
// Java core packages
import java.awt.*;
// Java extension packages
import javax.swing.*;
import javax.swing.event.*;

public class ListTest extends JFrame {


private JList colorList;
private Container container;

private String colorNames[] = { "Black", "Blue", "Cyan", "Dark Gray", "Gray", "Green",
"Light Gray", "Magenta", "Orange", "Pink", "Red", "White", "Yellow" };

private Color colors[] = { Color.black, Color.blue, Color.cyan, Color.darkGray,


Color.gray, Color.green, Color.lightGray, Color.magenta, Color.orange, Color.pink, Color.red,
Color.white, Color.yellow };

// Thiết lập GUI


public ListTest()
{
super( "List Test" );

// lấy layout
container = getContentPane();
container.setLayout( new FlowLayout() );

// Tạo danh sách


colorList = new JList( colorNames );
colorList.setVisibleRowCount( 5 );

// Không cho chọn nhiều


colorList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION );

// thêm một JScrollPane chứa danh sách


container.add( new JScrollPane( colorList ) );

// đặt lắng nghe sự kiện


colorList.addListSelectionListener( new ListSelectionListener() {
public void valueChanged( ListSelectionEvent event )
{
container.setBackground( colors[ colorList.getSelectedIndex() ] );
} } );
setSize( 350, 150 );
setVisible( true );

120
}

// hàm main
public static void main( String args[] )
{
ListTest application = new ListTest();
application.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
}}

III.7 Ô văn bản (text field) và vùng văn bản (text areas)
Tạo ô văn bản:
 Khởi tạo một ô văn bản không xác định kích thước: public TextField();
 Tạo một ô văn bản hiển thị tối thiểu numCols ký tự: public TextField(int
numCols);
 Tạo một ô văn bản với nội dung là một chuỗi cho trước: public TextFiled(String
initText);
 Tạo ô văn bản kết hợp: public TextField(String initText, int numCols);
 Tạo vùng văn bản:
 Tạo vùng văn bản rỗng, kích thước bất kỳ: public TextArea();
 Tạo một vùng văn bản với nội dung cho trước: public TextArea(String initiaText);
 Tạo một vùng văn bản với số cột và số dòng định trước: public TextArea(int rows,
int cols);
 Tạo một vùng văn bản với số dòng số cột và nội dung cho trước: public
TextArea(String st, int rows, int cols)
Đặc điểm chung của các thành phần văn bản:
Hai lớp này được dẫn xuất từ lớp TextComponent
Muốn đưa nội dung văn bản vào các đối tượng này ta sử dụng phương thức setText:
public void setText(String newText);
Lấy nội dung văn bản trong các đối tượng: public String getText();
Lấy nội dung văn bản được đánh dấu: public String getSelectedText();
Để xác định xem vị trí đánh dấu khối văn bản bắt đầu và kết thúc ở đâu: public int
getSelectionStart(); //đầu
public int getSelectionEnd(); //cuối
Đánh dấu toàn bộ văn bản: public void selectAll();
Cho phép soạn thảo được: public void setEditable(boolean canEdited); canEdited = false:
chỉ đọc.
canEdited = true: thêm, sử được.
Xác định đối tượng đang ở trang thái nào: public boolean isEditable();
Đặc điểm riêng của TextField:
Đặt ký tự hiển thị cho ký tự nhập vào: public void setEchoChar(Char ch); ví dụ:
myTextField.setEchoChar('*'); //mã mKhẩu
Xác định xem ký tự làm ký tự hiển thị: public char getEchoChar();
Xác định chiều dài của ô văn bản theo ký tự: public int getColums();
Đặc điểm riêng của đối tượng TextArea:
Đưa thêm một dòng văn bản vào đối tượng: public void appendText(String newText);

121
Chèn một chuỗi văn bản vào vị trí bất kỳ: public void insertText(String newText, int pos);
Xác định kích thước hiển thị: public int getRows(); public int getColums();
Sử dụng đối tượng TextField và TextArea:
Muốn lấy nội dung văn bản của đối tượng, ta thiết lập ActionListener để gắn đối tượng xử
lý tình huống vào văn bản TextField. Khi người dung nhấn Enter thì tình huống được gọi.
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class TextFieldTest extends JFrame {
private JTextField textField1, textField2, textField3;
private JPasswordField passwordField;

// Thiết lập giao diện GUI


public TextFieldTest()
{
super( "Testing JTextField and JPasswordField" ); // constructor của JFrame
Container container = getContentPane();
container.setLayout( new FlowLayout() );
// ô nhập với kích thước mặc định
textField1 = new JTextField( 10 );
container.add( textField1 );
// ô nhập với văn bản có sẵn
textField2 = new JTextField( "Enter text here" );
container.add( textField2 );

// ô nhập không cho phép nhập, chỉ hiên thị thông tin
textField3 = new JTextField( "Uneditable text field", 20 );
textField3.setEditable( false );
container.add( textField3 );

// ô nhập password
passwordField = new JPasswordField( "Hidden text" );
container.add( passwordField );

// Đăng ký bộ lắng nghe sự kiện


TextFieldHandler handler = new TextFieldHandler();
textField1.addActionListener( handler );
textField2.addActionListener( handler );
textField3.addActionListener( handler );
passwordField.addActionListener( handler );

setSize( 325, 100 );


setVisible( true );
}

// hàm main
public static void main( String args[] )

122
{
TextFieldTest application = new TextFieldTest();
application.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
}
// Lớp nội cho bộ lắng nghe sự kiện
private class TextFieldHandler implements ActionListener {
// phương thức xử lý sự kiện
public void actionPerformed( ActionEvent event )
{
String string = "";
// khi người dùng ấn enter
if ( event.getSource() == textField1 )
string = "textField1: " + event.getActionCommand();

// user pressed Enter in JTextField textField2


else if ( event.getSource() == textField2 )
string = "textField2: " + event.getActionCommand();

// user pressed Enter in JTextField textField3


else if ( event.getSource() == textField3 )
string = "textField3: " + event.getActionCommand();

// user pressed Enter in JTextField passwordField


else if ( event.getSource() == passwordField ) {
JPasswordField pwd = ( JPasswordField ) event.getSource();
string = "passwordField: " + new String( passwordField.getPassword() );
}
JOptionPane.showMessageDialog( null, string ); // Hiên thị cửa sổ thông báo
}
}
}

III.8 Thanh trượt (Scrollbar)


Ta có thể đặt thanh trượt các giá trị giới hạn tối thiểu (min) và tối đa (max) cùng với vị trí
hiện tại trên thanh trượt.
Khi người dùng kích chuột vào mũi tên ở hai đầu thì tình huống line nảy sinh, thanh trượt
sẽ tự động cộng thêm hay trừ đi một đơn vị.
Tình huống page nảy sinh khi người dùng kích chuột vào khoảng giữa thanh trượt, vị trí
hiện hành sẽ thay đổi.
Tình huống absolute nảy sinh khi người dùng nắm vào vị trí hiện hành của thanh trượt và
kéo nó đi.
Đối tượng Scrollbar chỉ chịu trách nhiệm di chuyển và cung cấp cho ta giá trị thay đổi.
Tạo thanh trượt:
Phương thức khởi tạo: public Scrollbar();

123
Tạo thanh trượt đứng hay ngang ta dùng: public Scrollbar(int orienttation); orienttation
nhận giá trị Scrollbat.VERTICAL - cho đứng, Scrollbar.HORIZONTAL cho nằm ngang.
Tạo thanh trượt có đầy đủ thông số ban đầu:
public Scrollbar(int orienttation,
int pos, //vị trí khởi đầu
int pageUnit, //bước nhảy
int minValue, //giá trị min
int maxValue); //giá trị max
Một số phương thức hay dùng:
Thay đổi giá trị tăng giảm (mặc định là 1): public void setUnitIncrement(int increment);
Muốn biết hiện thời tăng giảm bao nhiêu: public int getUnitIncrement()
Chỉ định con số khi tình huống page xảy ra: pubic void setBlockIncrement(int b);
Muốn biết hiện thời đơn vị thay đổi là bao nhiêu: public int getBlockIncrement();
Lấy giá trị lớn nhất và nhỏ nhất: public int getMaximum(); public int getMinimum();
Đặt lại vị trí hiện hành cho thanh trượt: public void setValue(int newPos);
Cho ta vị trí hiện hành: public int getValue();
Muốn biết thanh trượt đứng hay ngang: public int getOrientation() trả về giá trị là một
trong hai hằng số Scrollbar.VERTICAL và Scrollbar.HORIZONTAL.
Thiết lập lại các thông số cho thanh trượt: public void setValues(int position, int
pageUnit, int minimum, int maximum);
ví dụ: myScrollbar.setValues(75, 10, 0, 500);
Sử dụng thanh trượt:
Ta nhờ đối tượng xử lý tình huống xử lý: public void
adjustmentValueChanged(AdjustmentEvent evt);
Có 3 trạng thái: line, page và absolute. Ta sử dụng hàm getAdjustmentType() để xác định
xem tình huống nào đang xảy ra.
AdjustmentEvent.UNIT_INCREMENT: tình huống line xảy ra, vị trí thanh trượt tăng 1
đơn vị.
AdjustmentEvent.UNIT_DECREMENT: tình huống line xảy ra, vị trí thanh trượt giảm 1
đơn vị.
AdjustmentEvent.BLOCK_INCREMENT: tình huống page xảy ra, vị trí hiện hành tăng 1
đv.
AdjustmentEvent.Track: tình huống absolute xảy ra, ta lấy vị trí thanh trượt bằng hàm
getValue().

IV. Thành phần Menu


Lớp JMenuBar cài đặt thanh thực đơn và trong đó có thể chứa các thực đơn pull-down.
Các JMenuBar được tạo ra để gắn vào một của sổ JFrame bằng một lệnh gọi:
JMenuBar menuBar = new JMenuBar();
JFrame frm = new JFrame(“Day la mot Frame”);
frm.setJMenuBar(menuBar); // MenuBar này chưa có phần tử nào
Sau khi có MenuBar, ta cần thêm vào đó các JMenu là các mục trên thanh Menu, khi
người dùng chọn vào đó sẽ sổ xuống danh sách các mục chọn khác.
JMenu file = new JMenu(“File”);
Ta đặt cho nó một phím tắt, ví dụ là „F‟ để khi ấn Alt + F thì JMenu này được kích hoạt:
file.setMnemonic('F');

124
Sau đó thì thêm nó vào JMenuBar: menuBar.add(file);
Bây gời là lúc ta cài đặt các mục chọn trong một JMenu. Java dùng lớp JMenuItem định
nghĩa từng mục của thực đơn.
JMenuItem open = new JMenuItem(“Open”,‟O‟);
Ở đây ta đã gọi một constructor với một tham số là nhãn của mục chọn, một tham số là ký
tự phím tắt. Sau đó ta add vào JMenu:
file.add(open);
Nếu ta cần biểu diễn các dòng kẻ ngang để phân tách các mục chọn thì ta có thể gọi
phương thức addSeparator() của một đối tượng JMenu:
file.addSeparator();
Lớp JPopUpMenu biểu diễn cho thực đơn pop-up. Những thực đơn này có thể xuất hiện
bất cứ vị trí nào trong cửa sổ hiện tại.
Lớp JCheckBoxMenuItem chứa các mục được chọn để kiểm tra trong các mục thực đơn.
Tạo thanh trình đơn: JMenuBar myMenubar = new JMenuBar();
Đặt trạng thái cho mỗi mục chọn sử dụng enable() hoặc disable():
Vi dụ: openItem.enable(); //được chọn
openItem.disable(); //mờ mục chọn
Ta có thể dùng đường phân cách để tách các mục chọn:
FileMenu.addSeparator();
Tạo mục chọn có chứa menu con khác, ta tạo một menu bình thường rồi đưa vào
menu đã có.
Ví dụ:
//tạo mục chọn có menu con
JMenu printItem = new JMenu("Print");
//tạo các mục chọn cho menu con
printItem.add("Print preview");
printItem.add("to preview");
//đưa mục chọn có menu con vào menu chính
FileMenu.add(printItem);
Tạo một mục chọn có khả năng đánh dấu (check Item), một dấu kiểm tra (check mark) sẽ
hiện bên trái mục chọn:
Mục đánh dấu: CheckboxMenuItem Autosave = new CheckboxMenuItem("Auto save");
FileMenu.add(Autosave);
Xác định xem mục chọn Autosave đang ở trạng thái nào, ta dùng: Autosave.getState();
Khi sử dụng, để mục chọn tương tác và nhận được sự kiện ta cần cài đặt giao diện
ActionListener và gắn vào từng mục chọn.
Sau đây là một ví dụ xây dựng một JFrame với thanh menu và các mục chọn trong đó có
cài đặt xử lý các sự kiện.
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
class MenuBarSample extends JFrame {
//Khai bao mot hop thoai
JOptionPane op = new JOptionPane();
// Khai bao mot thuc don pop-up
JPopupMenu jp;
public MenuBarSample()

125
{
// Lay ve doi tuong chua cua Frame hien tai
Container pane = this.getContentPane();
// Dat kieu trinh bay
pane.setLayout(new FlowLayout(FlowLayout.LEADING));
// Khai bao mot thanh menu
JMenuBar menuBar= new JMenuBar();
// Khai bao muc File trong thanh menu
JMenu menuFile= new JMenu("File");menuBar.add(menuFile);
// Dat ky tu kich hoat tat cung Alt
menuFile.setMnemonic('F');
// Khai bao mot muc chon voi phim tat O
JMenuItem fileOpen = new JMenuItem("Open",'O');
// Them muc chon vao muc File
menuFile.add(fileOpen);
// Them mot muc chon khac voi phim tat N
menuFile.add(new JMenuItem("New",'N'));
// Them dong ngan cach
menuFile.addSeparator();
// Them mot muc chon co ten Save
menuFile.add("Save");
// Xu ly su kien chon muc Open
fileOpen.addActionListener( new ActionListener()
{
public void actionPerformed(ActionEvent event)
{
// Hien thi thong bao, getContentPane() de lay Container cua Frame
op.showMessageDialog(getContentPane(),"Ban vua chon Open");
}
} );
// Xu ly su kien chon New
menuFile.getItem(1).addActionListener(new ActionListener()
{
public void actionPerformed(ActionEvent event)
{
op.showMessageDialog(getContentPane(),"Ban vua chon New");
}
} );
// Xu ly su kien chon Save
menuFile.getItem(3).addActionListener(new ActionListener()
{
public void actionPerformed(ActionEvent event)
{
op.showMessageDialog(getContentPane(),"Ban vua chon Save");
}
} );
// Khai bao mot muc chon kieu checkbox

126
JCheckBoxMenuItem chk = new JCheckBoxMenuItem("is editting");
// Them phan xu ly su kien chon hay khong chon cho no
chk.addActionListener( new ActionListener()
{
public void actionPerformed(ActionEvent e)
{
// Lay ve doi tuong nguon su kien va ep kieu
JCheckBoxMenuItem ck = (JCheckBoxMenuItem)e.getSource();
if (ck.isSelected())
//Neu duoc chon
op.showMessageDialog(getContentPane(),"Ban vua chon is editting");
else
// Neu khong duoc chon
op.showMessageDialog(getContentPane(),"Ban vua bo chon is editting");
}
} );
// Them muc chon checkbox vao menu File
menuFile.add(chk);
// Dat thanh Menu cho Frame la doi tuong menuBar
this.setJMenuBar(menuBar);
// Cap phat bo nho cho popup menu
jp = new JPopupMenu("Popup");
jp.add("Create");
jp.addSeparator();
jp.add("New Document");
this.addMouseListener( new MouseAdapter()
{
public void mouseClicked(MouseEvent e)
{
if( (e.getModifiers() & InputEvent.BUTTON3_MASK)== InputEvent.BUTTON3_MASK)
jp.show(getContentPane(),e.getX(),e.getY());
}
}
);
}
public static void main(String[] argvs)
{
MenuBarSample m = new MenuBarSample();
m.setSize(300,400);
m.setVisible(true);
}
}

V. Bộ quản lý cách trình bày (Layout Manager)


Dùng để sắp xếp chỗ và định vị cho các đối tượng GUI.

127
Có 5 cách trình bày:
 FlowLayout: sắp xếp các đối tượng từ trái qua phải và từ trên xuống dưới. Các đối
tượng giữ nguyên kích thước.
 GridLayout: tạo một khung lưới vô hình với các ô bằng nhau, các đối tượng sẽ đặt
vừa kích thước với các ô đó.
 BorderLayout: Các đối tượng được đặt theo đường viền của khung chứa theo các
cạnh West, East, South, Nort và Center.
 CardLayout: Các đối tượng được đặt vừa vặn với khung chứa và nằm chồng lên
nhau như các lá bài.
 GridBadLayout: các đối tượng trong khung chứa cũng được đưa vào các ô của
một khung lưới vô hình, kích thước các đối tượng không nhất thiết phải vừa một
ô.
Muốn áp dụng ta tạo ra đối tượng trên rồi chuyển nó cho khung chứa.
Ví du: FlowLayout objectsLayout = new FlowLayout();
myWindow.setLayout(objectsLayout);
hay có thể viết gọn hơn:
myWindow.setLayout(new FlowLayout());

V.1 Cách trình bày FlowLayout:


Tạo đối tượng FlowLayout, ta sử dụng phương thức: public FlowLayout();
hoặc chỉ định luôn cách căn lề bằng phương thức khởi tạo: public FlowLayout(int align);
align là FlowLayout.LEFT, FlowLayout.RIGHT, FlowLayout.CENTER hoặc chỉ ra khoảng các
giữa các đối tượng trên cùng một hàng: public FlowLayout(int align, int hgap, int vgap);

V.2 Cách trình bày GridLayout:


Phương thức khởi tạo: public GridLayout(int Rows, int Cols);
Ví dụ: GridLayout layout = new GridLayout(3, 2);
Xác định khoảng cách giữa các đối tượng quản lý:
public GridLayout(int Rows, int Cols, int hgap, int vgap);

V.3 Cách trình bày BorderLayout


Đối tượng được đặt theo đường viền khung chứa, theo các cạnh của khung.
Tạo bộ quản lý kiểu BorderLayout: public BorderLayout();
Ví dụ: myWindow.setLayout(new BorderLayout());
Khi đưa đối tượng vào khung chứa, ta phải định hình trước đối tượng đó đặt ở đâu:
myWindow.add("North", new Button("on Top"));
có 5 kiểu: "North", "South", "West", "East", "Center".

VI. Các hộp thoại

VI.1 Hộp thoại thông báo


Khi cần đưa ra thông báo hay hỏi người dùng một vấn đề gì đó và nhận về câu trả lời, ta
dùng hộp thoại thông báo. Java hỗ trợ lớp JOptionPane để ta thực hiện công việc này.
Lớp JOptionPane có 4 phương thức tĩnh sau:
Tên phương thức Giá trị trả về Ý nghĩa

128
showMessageDialog Không có Hiển thị một thông báo và đợi ấn OK
showConfirmDialog Số int Hiển thị thông báo và đợi xác nhận Ok hay Cancel
showOptionDialog Số int Hiển thị thông báo và nhận về trả lời từ một danh sách chọn
showInputDialog Chuỗi Hiển thị thông báo và nhận về một dòng do user nhập vào

icon hiển thị phụ thuộc vào loại thông báo:


ERROR_MESSAGE : Thông báo lỗi
INFORMATION_MESSAGE : Thông tin
WARNING_MESSAGE :Cảnh báo
QUESTION_MESSAGE: Hỏi
PLAIN_MESSAGE: Giải thích
Các hằng số này được truy cập qua JOptionPane.
Ví dụ:
int selection = JOptionPane.showConfirmDialog(parent, "Message", "Title",
JOptionPane.OK_CANCEL_OPTION, JOptionPane.WARNING_MESSAGE);
if (selection == JOptionPane.OK_OPTION) . . .

VI.2 Hộp thoại chọn File


Khi viết ứng dụng ta gặp các tình huống mở file để đọc hoặc ghi. Swing hỗ trợ một lớp
JFileChooser để ta thực hiện công việc này. Hai phương thức quan trọng là:
- showOpenDialog: Để hiện thị hộp thoại chọn mở file
- showSaveDialog: Để hiện thị hộp thoại chọn ghi file
Sau đây là các bước để làm việc với hộp thoại chọn file:
1- Tạo một đối tượng JFileChooser: JFileChooser chooser = new JFileChooser();
2- Đặt thư mục cho hộp thoại: chooser.setCurrentDirectory(new File("."));
3- Nếu định chọn trước một file, có thể đặt: chooser.setSelectedFile(new File(filename));
4- Để cho phép chọn nhiều file: chooser.setMultiSelectionEnabled(true);
5- Nếu muốn giới hạn kiểu file, dùng bộ lọc file. Để dùng được bộ lọc file ta phải thiết kế
một lớp kế thừa từ lớp javax.swing.filechooser.FileFilter. Ví dụ bộ lọc file Gif:
public class GifFilter extends FileFilter
{
public boolean accept(File f)
{
return f.getName().toLowerCase().endsWith(".gif") || f.isDirectory();
}

public String getDescription()


{

129
return "GIF Image";
}
}
Sau đó gán cho hộp thoại: chooser.setFileFilter(new GifFilter());
6- Mặc định thì chỉ cho phép chọn file, nếu muốn cho phép chọn thư mục dùng phương
thức: setFileSelectionMode() với các tham số: JFileChooser.DIRECTORIES_ONLY hoặc
JFileChooser.FILES_AND_DIRECTORIES.
7- Hiển thị hộp thoại bằng lệnh gọi:
int result = chooser.showOpenDialog(parent);
hoặc
int result = chooser.showSaveDialog(parent);
Các giá trị trả về là JFileChooser.APPROVE_OPTION,
JFileChooser.CANCEL_OPTION, or JFileChooser.ERROR_OPTION
8- Lấy vè file hoặc các file được chọn dùng: getSelectedFile() hoặc getSelectedFiles().
Neus chỉ muốn lấy tên file thì ta dùng phương thức getPath().
String filename = chooser.getSelectedFile().getPath();

VI.3 Hộp thoại chọn màu


Swing hỗ trợ lớp JColorChooser để người dùng chọn màu. Cú pháp sử dụng như sau:
Color selectedColor = JColorChooser.showDialog(parent,title, initialColor);
Khi làm việc với hộp thoại chọn màu ta cần cung câp:
- parent: một companent cha;
- title: Tiêu đề hộp thoại;
- chế độ hộp thoại modal hay modeless;
- một đối tượng chọn màu
- bộ lắng nghe cho nút bấm Ok và Cancel.
chooser = new JColorChooser();
dialog = JColorChooser.createDialog(parent,"Background Color", false /* not modal */, chooser,
new ActionListener() // Lắng nghe cho OK
{
public void actionPerformed(ActionEvent event)
{
setBackground(chooser.getColor());
}
},
null /* Không có lắng nghe cho Cancel */);

Bài tập
1. Hãy thiết kế một giao diện có nội dung như sau:

130
2. Hãy lập trình để xử lý sự kiện click chuột phải lên nền của sổ sẽ hiện một menu Popup.
3. Viết chương trình giải phương trình bậc 2 với giao diện GUI. Yêu cầu chương trình có các ô
nhập các hệ số a, b, c và cho phép xem kết quả. Trường hợp không có nghiệm sẽ hiển thị một hộp
thoại thông báo “chương trình vô nghiệm”.

131
Đề tài 11. Threading
Java là ngôn ngữ thông dụng duy nhất hỗ trợ cho lập trình viên các giải pháp lập trình
song song. Lập trình viên xác định số lượng các thread được chạy trong chương trình và chúng
được thực hiện song song với nhau ở những giai đoạn nào. Khả năng này của Java gọi là tính đa
luồng.

I. Khái niệm thread

I.1 Khái niệm:


Thread – luồng chỉ một đơn vị chương trình có thể tách khỏi các phần khác của chương
trình để thực hiện song song với các phần khác ở từng khoảng thời gian hoặc trong toàn bộ quá
trình nó thực hiện.
Trong mỗi chương trình Java, có một luồng chính được bắt đầu bằng việc gọi hàm main(),
các luồng khác có thể phát sinh từ luồng chính này. Các câu lệnh trong hàm main() nếu không
được đưa vào một luồng riêng thì sẽ được thực hiện tuần tự.

I.2. Lớp Thread


Lớp Thread trong gói java.lang có một số constructor:
Public Thread (<tên thread>)
Public thread(): Khi này thread sẽ có tên là Thread_1, Thread_2,….
Các công việc của một thread được đặt trong phương thức run(). Phương thức này có thể
được khai báo chồng trong lớp khai báo kế thừa Thread hoặc khai báo cài đặt giao diện Runable.
Người lập trình bắt đầu một thread bằng các gọi phương thức start(), phương thức start()
sau đó sẽ gọi run(). Sau khi start() thực hiện xong, nó trả về điều khiển cho chương trình gọi nó.
Do đó, chương trình gọi nó và thread được thực hiện một cách đồng thời.
 Phương thức start() sẽ ném ra một ng oại lệ IllegalThreadStateException nếu
thread cần bắt đầu đã bắt đầu rồi.
 Phương thức tĩnh sleep của Thread được gọi với một tham số xác định rằng một
thread đang chạy sẽ dừng lại trong bao lâu theo đơn vị phần nghìn giây. Trong khi
một thread “sleep”, nó sẽ không tiêu tốn tài nguyên CPU và thread khác có thể
chạy.
 Phương thức interrupt() được gọi để để ngắt một thread. Phương thức này trả về
true nếu ngắt thành công. Phương thức isInterrupted() dùng để kiểm tra xem
thread có bị ngắt hay không.
 Phương thức isAlive() trả về true nếu start() đã được gọi và phương thức run() vẫn
đang thực hiện.
 Phương thức setName() đặt tên cho một thread, getName() lấy tên một thread.
Phương thức toString() trả về tên phương thức, độ ưu tiên và nhóm của thread.
 Phương thức static currentThread() trả về tham chiếu tới thread hiện hành đang
thực hiện.
 Phương thức joint đợi (wait) một phản hổi của một thông điệp được gửi đi trước
khi thực hiện thread.

I.3 Các bước để tạo một thread


Sau đây là các bước đơn giản để thực hiện một nhiệm vụ trong một thread riêng:

132
1. Khai báo một lớp có cài đặt giao diện Runable với phương thức run() được định nghĩa:
class MyRunnable implements Runnable
{
public void run()
{
// code thực hiện
}
}
2. Tạo một đối tượng của lớp
Runnable r = new MyRunnable();
3. Tạo một đối tượng Thread từ đối tượng Runable trên:
Thread t = new Thread(r);
4. Gọi phương thức start()
t.start();
Một thread cũng có thể được tạo ra bằng cách xây dựng một lớp con của lớp Thread:
class MyThread extends Thread
{
public void run()
{
// code của thread
}
}
Sau đó ta cũng làm theo các bước 2,3 và 4 để thực hiện thread.

II. Các trạng thái của thread.


Trạng thái born: Khi thread chưa gọi start().
Trạng thái ready: Khi phương thức start() được gọi.
Trạng thái running: Một Thread ở trạng thái ready có quyền ưu tiên cao nhất sẽ
được chuyển sang trạng thái này.
 Trạng thái dead: Khi một Thread ngưng hoặc hoàn thành nhiệm vụ của nó.
Ngoài ra, một thread running cũng có thể bị chặn (blocked) trong các thao tác vào ra. Lúc
này nó cũng không sử dụng tài nguyên CPU.
 Trạng thái sleep: Khi Thread gọi phương thức sleep. Nếu đang ở trạng thái sleep
mà gọi phương thức interrupt(), tread sẽ thoát khỏi trạng thái sleep và chuyển sang
trạng thái ready.
Sau đây là sơ đồ trạng thái của một thread.

133
Khi một thread đang chạy gọi phương thức wait(), thread sẽ rơi vào một trạng thái wait để
đợi một đối tượng mà nó đã gọi.

III. Các thuộc tính của thread

III.1 Độ ưu tiên của thread


Mỗi Thread có một độ ưu tiên. Mặc định thì thread thừa kế độ ưu tiên từ thread cha của
nó- là thread đã gọi nó. Ta có thể tăng hoặc giảm độ ưu tiên của bất kỳ thread nào với phương
thức setPriority() bằng một giá trị nằm giữa MIN_PRIORITY(1) và MAX_PRIORITY (10).
Trình từ thực hiện của các thread theo độ ưu tiên của nó được minh họa như sau:

134
Các thread có độ ưu tiên càng cao thì khả năng thực hiện càng cao.

III.2 Nhóm thread


Các thread có thể được đưa vào cùng một nhóm để có thể đồng thời làm việc với cả nhóm
thread này.
Khai báo một nhóm Thread:
String groupName = . . .;
ThreadGroup g = new ThreadGroup(groupName)
Sau đó, với mỗi thread được tạo ra ta dùng constructor có khai báo nhóm thread:
Thread t = new Thread(g, threadName);
Để kiểm tra xem có thread nào trong nhóm g còn hoạt động không, ta dùng phương thức:
g. activeCount();
Để ngắt tất cả các thread trong nhóm g ta gọi:
g.interrupt();

III.3 Quản lý các ngoại lệ của thread


Phương thức run() không thể ném ra một ngoại lệ được kiểm soát mà bị kết thúc bởi một
ngoại lệ không kiểm soát được mỗi khi có lỗi. Không có mệnh đề catch nào được thực hiện mà
thay vào đó, trước khi thread bị dead, ngoại lệ này sẽ được gửi cho một bộ xử lý ngoại lệ không
được kiểm soát.

135
Bộ xử lý này phải thuộc một lớp có cài đặt giao diện Thread.UncaughtExceptionHandler
chỉ với 1 phương thức:
void uncaughtException(Thread t, Throwable e);
Từ phiên bản Java 1.5, ta có thể cài bộ xử lý ngoại lệ cho bất cứ thread nào bằng phương
thức: setUncaughtExceptionHandler(). Mgoaif ra ta có thể cài bộ xử lý ngoại lệ mặc định cho tất
cả các thread bằng cách gọi phương thức setDefaultUncaughtExceptionHandler của lớp Thread.
Nếu không cài bộ xử lý ngoại lệ cho một thread thì mặc định là null. Tuy nhiên, các
thread riêng lại phụ thuộc vào bộ xử lý của nhóm thread chứa nó. Khi này, phương thức
uncaughtException sẽ làm các việc sau:
1- Nếu nhóm thread có cài đặt uncaughtException thì phương thức đó được gọi.
2- Ngược lại, nếu phương thức Thread.getDefaultExceptionHandler trả về một bộ xử lý
không phải là null thì bộ xử lý này được gọi.
3- Nếu là null, nếu Throwable là một đối tượng của TheadDead, không có gì xảy ra.
4- Nếu không, tên của thread và stack trace được gửi tới System.err và đưa ra màn hình.

IV. Điều khiển các thread


Sau khi đã khởi động được một thread rồi, vấn đề tiếp theo sẽ là điều khiển thread.

IV.1 Interrupt một thread


Khi sử dụng phương thức Thread.sleep(int) thì chương trình thường phải bắt các ngoại lệ
để xử lý. Lý do là thread bị dừng lại trong một khoảng thời gian lâu và trong khoảng thời gian đó
nó không thể tự đánh thức nó được. Tuy nhiên, nếu một thread cần phải được đánh thức sớm hơn,
ta có thể ngắt nó dùng phương thức interrupt().
public class SleepyHead extends Thread
{
// Phương thức được chạy khi thread khởi động lần đầu
public void run()
{
System.out.println ("Thread đang ngủ. Hãy đánh thức nó");
try
{
// Ngủ trong 8 tiếng
Thread.sleep( 1000 * 60 * 60 * 8 );
System.out.println ("Đó là một giấc ngủ trưa");
}
catch (InterruptedException ie)
{
System.err.println ("Chỉ mới được hơn 5 phút thôi....");
}
}
// Phương thức main tạo và bắt đầu thread
public static void main(String args[]) throws
java.io.IOException
{
// Tạo một thread
Thread sleepy = new SleepyHead();

136
// bắt đầu thread
sleepy.start();
// Nhắc người dùng để dừng thread
System.out.println ("Nhấn Enter để ngưng thread");
System.in.read();
// Ngắt thread
sleepy.interrupt();
}}
Cách thực hiện ở đây là gửi một thông điệp từ một thread khác để đánh thức một thread
đang ngủ. Ở đây, thread chính- hàm main- sẽ đợi cho người dùng ấn enter, sau đó gửi một thông
điệp ngắt tới thread đang ngủ.

IV.2 Dừng một thread


Đôi khi ta muốn một thread dừng lại trước khi nhiệm vụ của nó hoàn thành. Một thread
(gọi là thread điều khiển) có thể gửi một thông điệp dừng tới một thread khác bằng việc gọi
phương thức Thread.stop(). Điều này yêu cầu thread điều khiển phải giữ một tham chiếu tới
thread muốn dừng.
public class StopMe extends Thread
{
// Phương thức được thực hiện khi thread khởi động lần đầu
public void run()
{
int count = 1;
System.out.println ("Thread đếm!");
for (;;)
{
// In ra biến count và tăng giá trị cho nó
System.out.print (count++ + " ");
// Ngủ nửa giây
try { Thread.sleep(500); } catch
(InterruptedException ie) {}
}}
// phương thức main
public static void main(String args[]) throws java.io.IOException
{
// Tạo và bắt đầu thread
Thread counter = new StopMe();
counter.start();
// Nhắc người dùng
System.out.println ("Nhấn Enter để dừng đếm");
System.in.read();
// Dừng thread
counter.stop();
}}

137
IV.3 Tạm dừng và phục hồi một thread
Các phương thức suspend() dùng để tạm dừng một thread trong khi resume() dùng để tiếp
tục một thread đã bị suspend. Tuy nhiên sử dụng các thread này rất hay gây ra tình trạng
deadlock do thread bị suspend đang chiếm giữ một tài nguyên và không giải phóng trong khi
nhiều thread khác có thể đang đợi sử dụng tài nguyên đó.

IV.4 Giải phóng thời gian cho CPU


Khi một thread rơi vào trạng thái đợi một sự kiện xảy ra hoặc đi vào một vùng mã lệnh
mà nếu giải phóng thời gian cho CPU sẽ nâng cao hiệu quả của hệ thống. Trong trường hợp này,
thread nên giải phóng thời gian hệ thống hơn là dùng sleep() để nghỉ trong thời gian dài. Để làm
điều này ta dùng phương thức static yield() của Thread để giải phóng thời gian CPU cho thread
hiện thời. Ta không thể giải phóng thời gian CPU của bất kỳ thread nào mà chỉ đối với thread
hiện thời.

IV.5 Đợi một thread kết thúc công việc


Đôi khi ta cần đợi cho một thread kết thúc một công việc nào đó như gọi một phương
thức hay đọc một thuộc tính thành phần,…Ta dùng phương thức isAlive() để xác định thread còn
chạy không. Tuy nhiên, nếu thường xuyên dùng phương thức này để kiểm tra sau đó dùng sleep()
hoặc yield() thì hiệu quả sử dụng CPU sẽ rất thấp. Một cách hiệu quả là gọi phương thức joint()
để đợi một thread kết thúc công việc.
public class WaitForDeath extends Thread
{
// Phương thức run()
public void run()
{
System.out.println ("thread chuẩn bị nghỉ....");
// Nghỉ trong 5 giây
try
{
Thread.sleep(5000);
}
catch (InterruptedException ie) {}
}
// phương thức main
public static void main(String args[]) throws
java.lang.InterruptedException
{
// Tạo thread
Thread dying = new WaitForDeath();
dying.start();
// Nhắc user
System.out.println ("Đợi cho thread kết thúc");
dying.join();
System.out.println ("Thread đã dừng");}}

138
V. Đồng bộ thread
Trong hầu hết các chương trình đa luồng, các thread có thể cần phải truy cập đến cùng
một đối tượng. Điều gì xảy ra nếu cả hai thread cùng truy cập đến đối tượng và làm thay đổi
thuộc tính đối tượng. Kết quả có thể là các thread bị ảnh hưởng lẫn nhau và không đạt được kết
quả mong muốn. Tình trạng này gọi là “đua tranh”.

V.1 Tình trạng “đua tranh”


Trong phần tiếp theo, chúng ta mô phỏng một ngân hàng có rất nhiều tài khoản khách
hàng. Chúng ta thử thiết kế một chương trình để chuyển tiền ngẫu nhiên giữa các tài khoản. Mỗi
tài khoản có một thread riêng. Mỗi giao dịch thực hiện việc chuyển tiền từ account sang một
account ngẫu nhiên khác.
Ta có một class Bank với phương thức transfer để chuyển tiền. Nếu account chuyển
không đủ tiền, giao dịch kết thúc ngay.
public void transfer(int from, int to, double amount)
{
System.out.print(Thread.currentThread());
accounts[from] -= amount;
System.out.printf(" %10.2f from %d to %d", amount, from, to);
accounts[to] += amount;
System.out.printf(" Số dư: %10.2f%n", getTotalBalance());
}

Sau đây là đoạn code cho class transferRunnable:


class TransferRunnable implements Runnable
{
...
public void run()
{
try
{
int toAccount = (int) (bank.size() * Math.random());
double amount = maxAmount * Math.random();
bank.transfer(fromAccount, toAccount, amount);
Thread.sleep((int) (DELAY * Math.random()));
}
catch(InterruptedException e) {}
}
}
Khi chương trình chạy, ta không biết được trong mỗi tài khoản còn bao nhiêu tiền nhưng
tổng số tiền của tất cả các tài khoản là không đổi. Chương trình trên chạy và không bao giờ dừng
lại, ta phải ấn Ctrl + C để dừng nó.
Tuy nhiên, trong một số tình huống chương trình trên có thể gây lỗi và in ra tổng số tiền
khác nhau, đó là khi có nhiều hơn một tài khoản cùng chuyển tiền đến một tài khoản khác, tức là
cùng thực hiện câu lệnh:
account[to] += amount;

139
Giả sử đây là một hành vi đơn vị (atomic- hành vi chỉ chiếm một chu kỳ thực hiện lệnh
của CPU), khi đó mỗi thread sẽ thực hiện phép tính này một cách tuần tự mà không gây ảnh
hưởng vì trong một thời điểm, CPU cũng chỉ phục vụ được cho một thread.
Tuy nhiên, phép tính này lại không phải là atomic, công việc có thể phải làm là:
1- Load giá trị account[to] vào thanh ghi
2- Cộng với account
3- Load kết quả ngược trả lại cho account[to]
Giả sử có 2 thread là thread_1 và thread_2 cùng thực hiện đến dòng lệnh này. Thread_1
thực hiện xong bước 1 và 2 thì bị ngắt, sau đó thread_2 sẽ thực hiện câu lệnh trên với đầy đủ 3
bước tức là account[to] đã bị thay đổi giá trị. Tiếp theo, thread_1 thức dậy và làm tiếp bước 3,
account[to] lúc này đã ghi đè giá trị do thread_2 cập nhật. Điều này dẫn tới việc tổng số tiền bị
thay đổi, phần tiền do thread_2 chuyển cho account[to] đã bị mất.

V.2 Khóa đối tượng


Trong ví dụ trên, nếu ta đảm bảo rằng phương thức transfer() được thực hiện thành công
trước khi thread bị ngắt thì sẽ không có vấn đề gì xảy ra. Có nghĩa là, trong một thời điểm sẽ chỉ
có một thread được truy cập vào một đoạn mã lệnh nào đó.
Từ phiên bản Java 5.0 có hỗ trợ hai cơ chế để bảo vệ một khối lệnh khỏi sự truy cập đồng
thời. Các phiên bản trước của Java sử dụng từ khóa synchronized, Java 5.0 giới thiệu thêm lớp
ReentrantLock cho mục đích này.
Cấu trúc đoạn lệnh căn bản để sử dụng cơ chế này là:
myLock.lock(); // đối tượng ReentrantLock
try
{
//Đoạn lệnh cần bảo vệ
}
finally
{
myLock.unlock(); // đảm bảo khóa được mở kể cả khi exception xảy ra
}
Đoạn lệnh này đảm bảo rằng chỉ có một thread tại một thời điểm có thể đi vào vùng được
bảo vệ. Khi một thread đầu tiên đã gọi lock thì không một thread nào sau đó có thể vượt qua lệnh
lock().
Bây giờ chúng ta sẽ khóa phương thức transfer() trong lớp Bank:
public class Bank
{
public void transfer(int from, int to, int amount)
{
bankLock.lock();
try
{
if (accounts[from] < amount) return;
System.out.print(Thread.currentThread());
accounts[from] -= amount;
System.out.printf(" %10.2f from %d to %d", amount, from, to);
accounts[to] += amount;
System.out.printf(" Total Balance: %10.2f%n", getTotalBalance());

140
}
finally
{
bankLock.unlock();
}
}
...
private Lock bankLock = new ReentrantLock(); // ReentrantLock là một lớp cài đặt giao
diện lock.
}

V.3 Đối tượng điều kiện


Trong trường hợp thread đi vào vùng bảo vệ, nó cần hoàn thành công việc để có thể mở
khóa cho các thread khác đi vào. Tuy nhiên, nếu trong đó công việc chính cần thực hiện bị giới
hạn bởi một điều kiện nào đó, chẳng hạn ở đây, việc gọi transfer() để chuyển tiền chỉ được thực
hiện khi số tiền của account gửi lớn hơn số tiền được gửi:
if (bank.getBalance(from) >= amount)
bank.transfer(from, to, amount);
Khi có điều kiện này, một thread có thể không thỏa mãn và nó không thực hiện việc
chuyển tiền, tức là công việc của nó là không hữu ích. Đến đây, ta có thể nghĩ ra một giải pháp là
để thread đợi cho đến khi nó có đủ tiền để chuyển:
public void transfer(int from, int to, int amount)
{
bankLock.lock();
try
{
while (accounts[from] < amount) // Đợi cho đến khi tài khoản của nó có đủ tiền
{
// đợi
...
}
// Chuyển tiền
...
}
finally
{
bankLock.unlock();
}
}
Việc đợi ở đây sẽ kết thúc khi một thread khác chuyển tiền vào cho nó. Tuy nhiên ta thấy
rằng vùng chứa lệnh chuyển tiền lại đang bị khóa bởi chính thread này nên các thread khác không
thể chuyển tiền được. Kết quả là thread này không bao giờ thoát khỏi tình trạng đợi, chương trình
rơi vào deadlock.
Đây là tình huống mà ta phải sử dụng đối tượng điều kiện:
class Bank
{
public Bank()

141
{
...
sufficientFunds = bankLock.newCondition();
}
...
private Condition sufficientFunds;
}
Nếu phương thức transfer() kiểm tra và thấy rằng nó chưa đủ tiền để chuyển thì nó sẽ gọi:
sufficientFunds.await();Lúc này nó sẽ nằm chờ thread khác chuyển tiền.
Có một sự khác nhau căn bản giữa một thread đang đợi để khóa (gọi phương thức lock()
để đi vào vùng bảo vệ) và thread gọi phương thức await(). Nó sẽ không thể mở khóa khi đang
nằm trong vùng khóa cho đến khi một thread khác gọi signalAll(). Một thread thực hiện xong
việc chuyển tiền, nó nên gọi: sufficientFunds.signalAll() để mở khóa cho các thread đang nằm
đợi thỏa mãn điều kiện đủ tiền. Một khi thread đã thoát khỏi tình trạng đợi do await() gây ra, nó
sẽ được chạy tiếp và cố gắng đi vào vùng khóa và bắt đầu từ vị trí mà nó chờ đợi trước đó - vị trí
gọi phương thức await() và kiểm tra lại điều kiện.
Một phương thức await() nên được gọi trong một vòng lặp đợi:
while (!(đủ tiền))
condition.await();
Nếu không có thread nào gửi signalAll() thì thread đang đợi không có cách nào chạy tiếp
được. Nếu tất cả các thread khác đều đang bị chặn, thread gọi await() không giả phóng tình trạng
này cho một thread nào đó thì bản thân nó cũng bị chặn, chương trình sẽ bị treo.
Vấn đề là khi nào thì gọi signalAll()? Nguyên tắc chung là khi một thread hoàn thành việc
thay đổi trạng thái đối tượng thì nó nên gọi signalAll(). Ở đây, sau khi cộng tiền vào cho tài
khoản nhận và thì signalAll() sẽ được gọi. Việc gọi này sẽ giúp các thread cũng đang trong tình
trạng chờ đợi này không bị chặn nữa và tiếp tục công việc với một giá trị tài khoản có thể đã bị
thay đổi.
public void transfer(int from, int to, int amount)
{
bankLock.lock();
try
{
while (accounts[from] < amount)
sufficientFunds.await();
// Chuyển tiền
...
sufficientFunds.signalAll();
}
finally
{
bankLock.unlock();
}
}
Chú ý là việc gọi signalAll() không phải là kích hoạt thread bị chặn ngay lập tức. Nó chỉ
giải phóng tình trạng bị chặn của các thread khác cho đến khi nó kết thúc đoạn chương trình được
bảo vệ. Một phương thức khác là sign() chỉ trao khóa cho một thread xác định thay vì tất cả. Một
thread chỉ có thể gọi các phương thức signalAll(), sign() và await() trong vùng được bảo vệ.

142
Chúng ta có thể kết thúc phần này bằng một câu chuyện minh họa: Có 10 người cùng đến
ngân hàng để chuyển tiền cho nhau, mỗi người chuyển cho một người khác trong số 9 người còn
lại. Mỗi thời điểm ngân hàng chỉ chuyển tiền cho một người, người được gửi tiền sẽ được vào
trong ngân hàng và giữ khóa cổng, 9 người khác phải chờ ngoài cổng. Nếu chẳng may người
đang thực hiện chuyển số tiền lớn hơn anh ta có, anh ta sẽ đợi ở trong ngân hàng (gọi await()) và
ném khóa ra ngoài cổng cho tất cả 9 người kia. Một trong số 9 người ngoài cổng sẽ được đi vào
trong ngân hàng, thực hiện việc chuyển tiền. Sau khi chuyển xong, anh ta thông báo cho tất cả
các anh khác đang phải đợi trong ngân hàng là mình vừa chuyển tiền (gọi signalAll()) rồi đi ra
ngoài, sau đó những người đang đợi cũng quay ra cổng để đợi đến lượt vào.

Bài tập
1. Viết một chương trình tạo một thread thực hiện việc giải một phương trình bậc 2 với
các hệ số cho trước.
2. Viết chương trình song song với 10 thread để cộng 10000 số tự nhiên liên tiếp từ 1 đến
10000.

143
Phụ lục A. Các từ khóa của Java
Từ khóa Ý nghĩa
abstract Một lớp hoặc phương thức trừu tượng
assert Được sử dụng để định vị lỗi nội bộ chương trình
boolean Kiểu boolean
break Thoát khỏi lệnh switch hoặc các vòng lặp
byte Số nguyên 1 byte
case Một lựa chon của switch
catch Một mệnh đề của khối Try để bắt một ngoại lệ
char Kiểu ký tự Unicode
class Định nghĩa một class
const Không được sử dụng
continue Tiếp tục tại vị trí cuối vòng lặp hiện thời
default Mệnh đề mặc định của switch
do Phần trên của vòng lặp do/while
double Kiểu số thực floating-number 8 byte
else Mệnh đề else của một lệnh if
extends Xác định lớp cha của một lớp
final Một hằng số, hoặc một lớp hay phương thức không thể bị khai báo chống.
finally Một phần của khối try mà luôn được thực hiện
float Số thực 4 byte
for Một kiểu vòng lặp
goto Không sử dụng
if Một lệnh điều khiển rẽ nhánh
implements Chỉ ra các interfaces mà lớp sẽ cài đặt.
import Nhập một package
instanceof Kiểm tra xem đối tượng có phải là biểu hiện của một lớp
int Số nguyên 4 byte
interface Một kiểu trừu tượng với các phương thức được cài đặt tại các class

144
Từ khóa Ý nghĩa
long Sô nguyên 8 byte
Native a method implemented by the host system
new Cấp phát vùng nhớ cho một đối tượng hoặc mảng mới
null Một tham chiếu null
package Một gói các lớp
private a feature that is accessible only by methods of this class
protected a feature that is accessible only by methods of this class, its children, and other
classes in the same package
public a feature that is accessible by methods of all classes
return returns from a method
short the 16-bit integer type
static a feature that is unique to its class, not to objects of its class
strictfp Use strict rules for floating-point computations
super the superclass object or constructor
switch a selection statement
synchronized a method or code block that is atomic to a thread
this the implicit argument of a method, or a constructor of this class
throw throws an exception
tHRows the exceptions that a method can throw
transient marks data that should not be persistent
try a block of code that traps exceptions
void denotes a method that returns no value
volatile ensures that a field is coherently accessed by multiple threads
while a loop

Phụ lục B Một số hàm hay sử dụng


a) Hàm toán học, lớp java.lang.Math; {java.math.*;}
1. abs(number x): lấy |x|, x có thể là các kiểu long, int, float, double.
2. acos(double x) = arccos(x). x từ 0.0 ... PI
3. asin(double x) = arcsin(x). x từ -PI/2 ... PI/2
4. atan(double x) = arctag(x).
5. ceil(double x): làm cận trên hoặc cận dưới của số x.
6. cos(double x) = cos(x). x là radian.

145
7. exp(double x) = ex.
8. log(double x) = Ln(x).
9. max(number x, number y) = max{x, y}, trong đó x, y có thể là kiểu int, float,
double, long.
10. min(number x, number y) = min{x, y}, trong đó x, y có thể là kiểu int, float,
double, long.
11. pow(double x, double y) = xy.
12. random(): cho một số ngẫu nhiên trong [0.0, 1.0].
13. round(number x): làm tròn số x, nếu x là kiểu double thì thành kiểu long, nếu x là
kiểu float thì thành kiểu int.
14. sin(double x) = sin(x).
15. sqrt(double x) =
16. tan(double x) = tg(x).
Khi sử dụng ta phải dùng Math.tên_hàm.
b) Lớp String, khai báo java.lang.String;
1. String() : Khởi động một đối tượng chuỗi.
2. String(String s) : Khởi động một chuỗi bằng chuỗi s.
3. length() : trả về giá trị int là độ dài một chuỗi.
4. charAt(int i): trả về một ký tự thứ sau i.
5. indexOf(int ch): trả về vị trí trước của ký tự ch đầu tiên tìm thấy.
6. concat(String str): nối thêm chuỗi str vào sau.
7. toLowerCase(): thay đổi chuỗi thành chữ thường.
8. toUpperCase(): chuyển đổi chuỗi thành chữ HOA.
Lớp Date khai báo java.util.Date;
1. Date(): cho thứ, tháng, ngày, giờ, năm.
2. getDate(): trả về giá trị của ngày trong tháng.
3. getDay(): trả về giá trị int là thứ trong tuần, 0 = CN, 1 = mon, ..., 6 = Sat.
4. getMonth(): trả về giá trị int là tháng trong năm (0..11).
5. getYear(): trả về giá trị int là năm. Lấy mốc là 1900, năm thực =
date.getYear()+1900.
6. getTime(): trả về là một số được tính theo giây đồng hồ.
7. getHours(): trả về giá trị int chỉ giờ trong ngày.
8. getMinutes(): trả về giá trị int chỉ phút trong giờ.
9. getSeconds(): trả về giá trị int chỉ giây đồng hồ trong phút.
10. setDate(int date) đặt lại ngày trong biến. date lấy giá trị từ 1..31
11. setMonth(int month): thay đổi giá trị tháng trong biến date. month có giá trị 0..11.
12. setYear(int year): thay đổi giá trị năm trong biến date. (year + 1900).
13. setHours(int hour): thay đổi giá trị giờ trong biến date.
14. setMinutes(int i): thay đổi giá trị phút trong biến date.
15. setSeconds(int i): thay đổi giá trị giây trong biến date.

Tài liệu tham khảo


[1] Deitel & Associates (2001), “Java, How to program”, fourth edition.
[2] Cay S. Horstmann, Gary Cornell (2004), “Core Java™ 2”, volume 1,2, Prentice Hall PTR
[3] David Reilly, Michael Reilly (2002), “Network Programming and Distributed Computing”,
Addison Wesley

146

You might also like