d) Chương trình ứng dụn gở dạng Applet lẫn dạng độc lập
4.6 Quan hệ kế thừa giữa các lớp
Java chỉ hỗ trợ kế thừa đơn (tuyến tính), nghĩa là một lớp kế thừa được từ một lớp cha. Do vậy, giữa các lớp có quan hệ kế thừa với nhau có thể tổ chức thành cấu
trúc phân cấp có thứ bậc.
Chúng ta nhắc lại là mọi lớp của Java đều là lớp con cháu mặc định của lớp Object.
Ví dụ: Các lớp ở ví dụ trên: BongDen, DenTuyp, NhaKho có thể mô tả quan hệ kế thừa theo ký pháp UML như hình 4.6
Hình 4.1 – Quan hệ kế thừa giữa các lớp
Cú pháp qui định quan hệ kế thừa trong Java là sự mở rộng của lớp cha, có dạng:
<Tên lớp con> extends <Tên lớp cha>{ //Các thuộc tính dữ liệu bổ sung
//Các hàm thành phần bổ sung hay viết đè }
Lưu ý:
Mọi đối tượng của lớp con cũng sẽ là đối tượng thuộc lớp cha. Do vậy việc gán một đối tượng của lớp con sang cho biến tham chiếu đối tượng của lớp cha là sự mở rộng kiểu và do đó không cần ép kiểu.
Ngược lại gán một đối tượng của lớp cha cho biến tham chiếu đối tượng thuộc lớp con sẽ phải thực hiện ép kiểu. Lưu ý khi đó sẽ có thể bị tổn thất thông tin.
Ví dụ:
class SuperClass{/*…*/}
class SubClass extends SuperClass {/*…*/} class UserClass{
//…
public static void main (String args[]){
SuperClass super1 = new SuperClass(); //Tạo ra đối tượng lớp cha SubClass sub1 = new SubClass(); //Tạo ra đối tượng lớp con SuperClass super2 = sub1; //Mở rộng kiểu
SubClass sub2 = (SubClass) super1; //Thu hẹp kiểu nên phải ép kiểu } 4.6.1 Toán tử móc xích giữa các lớp kế thừa this() và super()
Các toán tử tạo lập không thể viết đè ở các lớp dẫn xuất (lớp con). Chúng có thể được nạp chồng nhưng phải trong cùng lớp.
Trong Java có 2 toán tử tạo lập đặc biệt có tên là this() và super() được sử dụng để móc xích giữa các lớp có quan hệ kế thừa với nhau.
a. Toán tử tạo lập this()
Toán tử tạo lập này được sử dụng để tạo ra đối tượng của lớp hiện thời. Ví dụ: Nạp chồng toán tử tạo lập
//NhaKho.java
class BongDen{
//Cac bien thanh phan (1)
private int soWatts; private boolean batTat;
String viTri;
//Dinh nghia toan tu tao lap mac dinh, so 1 (2) BongDen(){
soWatts = 40; batTat = true;
viTri = new String("XX");
System.out.println("Toan tu so 1"); }
//Dinh nghia toan tu tao lap khong mac dinh, so 2 (3) BongDen (int w, boolean s){
soWatts = w; batTat = s;
viTri = new String("XX");
System.out.println("Toan tu so 2"); }
//Dinh nghia toan tu tao lap khong mac dinh, so 3 (4) BongDen (int soWatts, boolean batTat, String viTri){
this.soWatts = soWatts; this.batTat = batTat;
this.viTri = new String(viTri); System.out.println("Toan tu so 3"); }
//...}
public class NhaKho {
public static void main(String[] args) {
BongDen d1 = new BongDen(); //OK
BongDen d2 = new BongDen(100,true,"Nha bep"); //OK BongDen d3 = new BongDen(100, true); //OK //... } }
Trong lớp BongDen có 3 toán tử tạo lập được nạp chồng ở (2), (3) và (4). Tham chiếu this cũng được sử dụng trong (4) để xác định các đối số tương ứng đối của toán tử tạo lập.
b. Toán tử tạo lập super()
Toán tử super() được sử dụng trong các toán tử tạo lập của lớp con (subclass) để gọi tới toán tử tạo lập của lớp cha (superclass) trực tiếp.
Ví dụ: Sử dụng toán tử super()
//NhaKho.java
class BongDen{
//Cac bien thanh phan (1)
private int soWatts; private boolean batTat;
String viTri;
//Dinh nghia toan tu tao lap mac dinh, so 1 (2) BongDen{
this(40,true);
System.out.println("Toan tu so 1"); }
//Dinh nghia toan tu tao lap khong mac dinh, so 2 (3) BongDen (int w, boolean s){
System.out.println("Toan tu so 2"); }
//Dinh nghia toan tu tao lap khong mac dinh, so 3 (4) BongDen (int soWatts, boolean batTat, String viTri){
this.soWatts = soWatts; this.batTat = batTat;
this.viTri = new String(viTri); System.out.println("Toan tu so 3"); }}
class DenTuyp extends BongDen{ private int doDai;
private int mau;
DenTuyp(int leng, int colo){ //(5)
this(leng,colo,100,true,"Chua biet"); }
DenTuyp(int leng, int colo, int soWatts, boolean bt, String noi){ //(6) super(soWatts,bt,noi);
this.doDai = leng; this.mau = colo; }}
public class NhaKho {
public static void main(String[] args) {
System.out.println("Tao ra bong den tuyp"); DenTuyp d = new DenTuyp(20,5);
} }
BongDen là lớp cha (trực tiếp) của lớp DenTuyp. Toán tử super() ở (6) là đại diện cho toán tử tạo lập của lớp cha BongDen.
Một số lưu ý khi sử dụng this() và super():
Chúng chỉ sử dụng để xây dựng các toán tử tạo lập của các lớp và khi sử dụng thì chúng luôn phải là lệnh đầu tiên trong định nghĩa của toán tử tạo lập.
this() được sử dụng để móc xích với cùng lớp chứa nó còn super() lại
được sử dụng để móc xích với lớp cha của lớp đó.
4.7 Giao diện và sự mở rộng quan hệ kế thừa trong Java
4.7.1 Định nghĩa interface
Ngôn ngữ Java bao gồm khái niệm về một giao diện (interface), nó chỉ đơn giản là một tập hợp có tên của các hành vi có sẵn công khai và các hằng. Nó không chỉ rõ các chi tiết hành vi.
interface định nghĩa thông qua việc phác thảo các hàm mẫu (prototype) và không cần cài đặt nội dung thực hiện.
interface <Tên interface>{ <Nội dung của interface> }
<Nội dung của interface>: Thường chứa danh sách các hàm mẫu và các hằng.
Giao diện là kiểu lớp đặc biệt, trong đó tất cả các hàm thành phần đều là trừu tượng. Do vậy, không thể tạo ra đối tượng của interface. Những hàm trong interface sẽ được cài đặt ở những lớp xây dựng mới theo dạng:
class <Tên lớp> implements <Tên interface>{ //Bổ sung các thành phần
//Cài đặt các hàm mẫu đã cho trong <Tên interface>;}
4.7.2 Sự mở rộng (kế thừa) của các interface
Giống như cấu trúc lớp, interface cũng có thể được mở rộng từ nhiều interface khác bằng mệnh đề extends. Tuy nhiên, khác với sự mở rộng tuyến tính của các lớp, các interface được phép mở rộng không tuyến tính, nghĩa là có thể mở rộng nhiều hơn một interface.
Ví dụ:
class MyClass implements Interface1, Interface2{ //Cài đặt một số hàm mẫu của Interface1, Interface2; }
interface Interface1{
//Khai báo các hằng và hàm mẫu; }
interface Interface2{
//Khai báo các hằng và hàm mẫu; }
Ví dụ: Cách xây dựng và sử dụng các interface
interface IStack{ //(1)
void push(Object item); Object pop();
}
class StackImp implements IStack{ //(2)
protected Object[] stackArray; //Bo sung mang doi tuong
protected int tos; //Bo sung bien tos
public StackImp(int capacity){
stackArray = new Object[capacity]; tos = -1;
public void push(Object item){ //Cai dat ham push (3) stackArray[++tos]=item;
}
public Object pop(){ //Cai dat ham pop (4) Object obj = stackArray[tos];
stackArray[tos] = null; tos--;
return obj; }
public Object peek(){ //Bo sung ham peek (5) return stackArray[tos];
} }
//Xay dung them interface ISafeStack ke thua IStack de kiem tra cac trang thai //cua Stack dam bao an toan khi thao tac
interface ISafeStack extends IStack{ //(6)
boolean isEmpty(); boolean isFull(); }
//Xay dung lop de cai dat cac ham cua ISafeStack (7)
class SafeStackImp extends StackImp implements ISafeStack{ public SafeStackImp(int capacity){super(capacity);}
public boolean isEmpty(){return tos<0;}
public boolean isFull(){return tos == stackArray.length - 1;}
}
//Xay dung lop su dung cac lop va giao dien da cai dat o tren
public class StackUser {
public static void main(String[] args) {
SafeStackImp safe1 = new SafeStackImp(80); //(8)
StackImp s1 = safe1; //(9)
ISafeStack iSafe1 = safe1; //10
IStack iS1 = safe1; Object obj = safe1;
safe1.push("Khoa CNTT"); s1.push("DHQG");
System.out.println(iSafe1.pop()); //(11) System.out.println(iS1.pop()); //(12) System.out.println(obj.getClass()); //(13)
} }
Kết quả thực hiện chương trình:
Lưu ý:
Các hàm mẫu của interface được mặc định là abstact và không được khai báo static.
Một lớp có thể chọn một số hàm mẫu để cài đặt, nghĩa là có thể chỉ cài đặt một phần của giao diện interface (nếu là lớp abstract, nếu không thì phải cài đặt tất cả hàm trong interface).
Tất cả các phương thức trong các giao diện phải là kiểu public. Bạn không được sử dụng các bổ ngữ (modifers) chuẩn khác như protected, private… khi khai báo các phương thức trong một giao diện.
Một giao diện có thể kế thừa từ nhiều hơn một giao diện.
Một giao diện có thể kế thừa từ nhiều hơn một giao diện và lớp.
Ngoài ra cũng cần chú ý thêm rằng, các giao diện interface có thể định nghĩa các hằng (constant). Tất cả các thuộc tính dữ liệu trong interface luôn được xem mặc định là public static final.
Ví dụ: Các biến hằng trong interface //Tinh.java interface Constants{ double PI = 3.14; String DON_VI_DT = "cm2"; String DON_VI_CV = "cm"; }
public class Tinh implements Constants { public static void main(String[] args) {
double banKinh = 15.00;
System.out.println("Dien tich hinh tron ban kinh " + banKinh + " la " + (PI*banKinh*banKinh) + DON_VI_DT); //(1)
System.out.println("Chu vi hinh tron ban kinh " + banKinh + " la " + (2*PI*banKinh) + Constants.DON_VI_CV); //(2)
} }
Các biến trong interface mặc định là public static final do vậy khi truy nhập chúng có thể truy nhập trực tiếp (1) hoặc thông qua tên interface (2).
CHƯƠNG 5
CÁC LỆNH CÓ CẤU TRÚC VÀ XỬ LÝ NGOẠI LỆ
5.1 Các câu lệnh điều khiển rẽ nhánh chương trình
5.1.1 Câu lệnh if-else
Câu lệnh if-else kiểm tra kết quả của một điều kiện và thực thi một thao tác phù hợp trên cơ sở kết quả đó.
Cú pháp
If (condition)
{ action 1 statements; } else
{ action 2 statements; }
Đoạn chương trình sau kiểm tra xem các số là chẵn hay lẻ và hiển thị thông báo phù hợp
class CheckNumber
{
public static void main(String args[] {int num =10;
if(num %2 = = 0)
System.out.println (num+ “is an even number”); else
System.out.println (num +”is an odd number”); }}
5.1.2 Câu lệnh switch-case
Phát biểu switch-case có được sử dụng trong tình huống một biểu thức cho ra nhiều kết quả. Việc sử dụng câu lệnh switch-case cho phép việc lập trình dễ dàng và đơn giản hơn.
Cú pháp
swich (expression) {
case value:action 1 statement; break;
case value:action 2 statement; break;
: :
case valueN: actionN statement (s); break;
[default: action default statement] }
Đoạn chương trình sau xác định giá trị trong một biến nguyên và hiển thị ngày trong tuần được thể hiện dưới dạng chuỗi. Để kiểm tra các giá trị nằm trong khoảng 0 đến 6. Chương trình sẽ thông báo lỗi nếu nằm ngoài phạm vi trên.
class SwitchDemo
{
public static void main(String agrs[]) {int day =4; switch(day) {case 0 : system.out.println(“Sunday”);break; case 1 : System.out.println(“Monday”);break; case 2 : System.out.println(“Tuesday”);break; case 3 : System.out.println(“Wednesday”);break; case 4 : System.out.println(“Thursday”);break; case 5 :System.out.println(“Friday”);break; case 6 :System.out.println(“Satuday”);break; case 7 :System.out.println(“Saturday”);break; default :System.out.println(“Invalid day of week”);
} } }
Nếu giá trị của bíến day là 4, chương trình sẽ hiển thị Thursday, và cứ tiếp như vậy.
5.2 Các câu lệnh lặp
5.1.2 Vòng lặp While
Vòng lặp while được sử dụng khi vòng lặp được thực hiện mãi cho đến khi điều kiện thực thi vẫn là true. Số lượng vòng lặp không đựơc xác định trước, nó phụ thuộc vào từng điều kiện.
Cú pháp while(condition) { action statement; : : }
Đoạn chương trình sau tính giai thừa của số 5. Giai thừa được tính như tích 5*4*3*2*1.
Class WhileDemo
{
public static void main(String args[]) {int a = 5, fact = 1;
while (a>= 1) { fact *=a;
a--; }
System.out.println(“The Factorial of 5 is”+fact); } }
5.2.2 Vòng lặp do-while
Vòng lặp do-while thực thi khối lệnh khi mà điều kiện là true, tương tự như vòng lặp while. Ngoại trừ do-while thực hiện lệnh ít nhất một lần ngay cả khi điều kiện là false. Cú pháp:
do{
action statements; }while(condition);
Ví dụ sau tính tổng của 5 số tự nhiên đầu tiên dùng cấu trúc do-while.
package vidu.chuong3; class DoWhileDemo {
public static void main(String args[])
{ int a = 1, sum = 0; do{ sum += a; a++; }while (a <= 5); System.out.println(“Sum of 1 to 5 is “ + sum); } }
5.2.3 Vòng lặp for
Vòng lặp for cung cấp một dạng kết hợp tất cả các đặc điểm chung của tất cả các loại vòng lặp: Giá trị khởi tạo của biến chạy, điều kiện dừng của vòng lặp và lệnh thay đổi giá trị của biến chạy.
Cú pháp:
for(initialization statements; condition; increment statements) {
action statements; }
Ví dụ: Đoạn chương trình sau hiển thị tổng của 5 số đầu tiên dùng vòng lặp for.
package vidu3; class ForDemo
{
public static void main(String args[])
{
int sum = 0;
for (int i=1; i<=5; i++) sum += i;
System.out.println (“The sum is “ + sum); } }
5.3 Các câu lệnh chuyển vị
5.3.1 Lệnh break
Trong cấu trúc switch chúng ta dùng câu lệnh break để thoát thỏi cấu trúc switch trong cùng chứa nó. Tương tự như vậy, trong cấu trúc lặp, câu lệnh break dùng để thoát khỏi cấu trúc lặp trong cùng chứa nó.
5.3.2 Lệnh continue
Dùng để tiếp tục vòng lặp trong cùng chứa nó (ngược với break). 5.3.3 Nhãn (label)
Java không hỗ trợ lệnh goto để nhảy đến 1 vị trí nào đó của chương trình. Java dùng kết hợp nhãn (label) với từ khóa break và continue để thay thế cho lệnh goto. Ví dụ: label: for (…) { for (…) {
break label; else
continue label; }
}
Lệnh “label:” xác định vị trí của nhãn và xem như tên của vòng lặp ngoài. Nếu <biểu thức điều kiện> đúng thì lệnh break label sẽ thực hiện việc nhảy ra khỏi vòng lặp có nhãn là “label”, ngược lại sẽ tiếp tục vòng lặp có nhãn “label” (khác với break và continue thông thường chỉ thoát khỏi hay tiếp tục vòng lặp trong cùng chứa nó.).
5.4 Xử lý ngoại lệ (Exception)
Exception là một lỗi đặc biệt. Lỗi này xuất hiện vào lúc thực thi chương trình. Các trạng thái không bình thường xảy ra trong khi thi hành chương trình tạo ra các exception. Những trạng thái này không được biết trước trong khi ta đang xây dựng chương trình. Nếu bạn không xử lí các trạng thái này thì exception có thể bị kết thúc đột ngột. Ví dụ, việc chia cho 0 sẽ tạo một lỗi trong chương trình. Ngôn ngữ Java cung cấp bộ máy dùng để xử lý ngoại lệ rất tuyệt vời. Việc xử lý này làm hạn chế tối đa trường hợp hệ thống bị phá vỡ (crash) hay hệ thống bị ngắt đột ngột. Tính năng này làm cho Java là một ngôn ngữ lập trình mạnh.
5.4.1 Khối lệnh try, catch và finally
Một chương trình nên có cơ chế xử lý ngoại lệ thích hợp. Nếu không, chương trình sẽ bị ngắt khi một exception xảy ra. Trong trường hợp đó, tất cả các nguồn tài nguyên mà hệ thống trước kia phân phối sẽ không được thu hồi. Điều này gây lãng phí tài nguyên. Để tránh trường hợp này, tất cả các nguồn tài nguyên mà hệ thống phân phối nên được thu hồi lại. Tiến trình này đòi hỏi cơ chế xử lý ngoại lệ thích hợp.
Cho ví dụ, xét thao tác nhập xuất (I/O) trong một tập tin. Nếu việc chuyển đổi kiểu dữ liệu không thực hiện đúng, một ngoại lệ sẽ xảy ra và chương trình bị hủy mà không đóng lại tập tin. Lúc đó tập tin dễ bị hư hại và các nguồn tài nguyên được cấp phát cho tập tin không được thu hồi lại cho hệ thống.
Mô hình xử lý ngoại lệ
Trong Java, mô hình xử lý ngoại lệ kiểm tra việc xử lý những hiệu ứng lề (lỗi), được biết đến là mô hình ‘catch và throw’. Trong mô hình này, khi một lỗi xảy ra, một ngoại lệ sẽ bị chặn và được đưa vào trong một khối. Người lập trình viên nên xét các trạng thái ngoại lệ độc lập nhau từ việc điều khiển thông thường trong chương trình. Các ngoại lệ phải được bắt giữ nếu không chương trình sẽ bị ngắt.
Ngôn ngữ Java cung cấp 5 từ khoá sau để xử lý các ngoại lệ: try, catch, throw, throws, finally.
Khối ‘try’ chứa một bộ các lệnh có thể thi hành được. Các ngoại lệ có thể bị chặn khi thi hành những câu lệnh này. Một hay nhiều khối ‘catch’ có thể theo sau khối ‘try’ để bắt và xử lí khi ngoại lệ xảy ra. Các khối ‘catch’ này bắt các ngoại lệ bị chặn trong khối ‘try’. Hãy nhìn khối ‘try’ dưới đây:
try
{ doFileProcessing(); // user-defined method displayResults();
}
catch (Exception e) // exception object
{ System.err.println(“Error :” + e.toString()); e.printStackTrace();
}
Ở đây, ‘e’ là đối tượng của lớp ‘Exception’. Chúng ta có thể sử dụng đối tượng này để in các chi tiết về ngoại lệ. Các phương thức ‘toString’ và ‘printStackTrace’ được sử dụng để mô tả các exception phát sinh ra. Hình sau chỉ ra kết xuất của phương thức ‘printStackTrace()’.
Để bắt giữ bất cứ ngoại lệ nào, ta phải chỉ ra kiểu ngoại lệ là ‘Exception’.
catch(Exception e)
Khi ngoại lệ bị bắt giữ không biết thuộc kiểu nào, chúng ta có thể sử dụng lớp ‘Exception’ để bắt ngoại lệ đó.
- Các khối chứa nhiều catch
Các khối chứa nhiều ‘catch’ xử lý các kiểu ngoại lệ khác nhau một cách độc lập. Chúng được liệt kê trong đoạn mã sau:
try
{ doFileProcessing(); // user defined mothod displayResults(); // user defined method
}
catch(LookupException e) // e – Lookupexception object { handleLookupException(e); // user defined handler }
catch(Exception e)
{ System.err.println(“Error:” + e.printStackTrace()); }
Trong trường hợp này, khối ‘catch’ đầu tiên sẽ bắt giữ một ‘LockupException’.