Bài giảng Lớp và các thành phần của lớp

Trước tiên cần phân biệt rõ hai cơ chế viết đè và nạp chồng là khác nhau trong Java. + Viết đè yêu cầu cùng định danh hàm (cùng tên gọi, cùng danh sách tham số), cùng kiểu trả lại kết quả và có thể xác định tất cả hoặc tập con các lớp ngoại lệ được kể ra trong mệnh đề cho qua ngoại lệ đã được định nghĩa tại lớp cha. + Nạp chồng yêu cầu khác nhau về định danh, nhưng giống nhau về tên gọi của hàm, vì thế chúng sẽ khác nhau về số lượng, kiểu, hay thứ tự của các tham biến. + Hàm có thể nạp chồng ở trong cùng lớp hoặc ở các lớp con cháu, còn hàm viết đè chỉ ở các lớp con cháu.

ppt42 trang | Chia sẻ: haohao89 | Lượt xem: 2301 | Lượt tải: 0download
Bạn đang xem trước 20 trang tài liệu Bài giảng Lớp và các thành phần của lớp, để xem tài liệu hoàn chỉnh bạn click vào nút DOWNLOAD ở trên
Cấu trúc lớp và khai báo các thành phần của lớp, Định nghĩa hàm thành phần và cơ chế nạp chồng, viết đè trong Java, Các thuộc tính kiểm soát truy nhập các thành phần của lớp, Các đối số của chương trình Toán tử tạo lập các đối tượng, Kế thừa giữa các lớp đối tượng, Các giao diện (interface) và sự mở rộng quan hệ kế thừa (đa kế thừa) trong Java. CHƯƠNG IV Lớp và các thành phần của lớp 1. Định nghĩa lớp Cú pháp: [] class [extends ] [implements [, Tên giao diện 2, …, Tên giao diện n]] { } Ví dụ: class SinhVien{ String hoTen; int tuoi; void display() { } } 2. Định nghĩa hàm thành phần Cú pháp: [] ([]) []{ } 3. Nạp chồng các hàm thành phần Những hàm có nhiều nội dung thực hiện khác nhau, được gọi là hàm nạp chồng hay hàm tải bội (overloading). Cơ chế nạp chồng cho phép một hàm có cùng một tên gọi nhưng danh sách tham biến khác nhau, do vậy sẽ có các định danh khác nhau. Những hàm được nạp chồng với các định danh khác nhau có các phần cài đặt thực hiện những công việc khác nhau và có kiểu trả lại khác nhau. Ví dụ: Lớp java.lang.Math có hàm nạp chồng min() xác định giá trị cực tiểu của 2 số: public static double min(double a, double b) public static float min(float a, float b) public static int min(int a, int b) public static long min(long a, long b) 4. Viết đè các hàm thành phần và vấn đề che bóng các biến Hàm viết đè phải có cùng định danh (tên gọi và danh sách tham biến) và cùng kiểu trả lại giá trị. Định nghĩa mới của hàm viết đè trong lớp con chỉ có thể xác định tất cả hoặc tập con các lớp ngoại lệ được kể ra trong mệnh đề cho qua ngoại lệ (throws). Định nghĩa của những hàm sẽ viết đè không được khai báo final ở lớp cha. Đối với cơ chế che bóng của các biến thì về nguyên tắc, một lớp con không được phép viết đè các biến thành phần của lớp cha, nhưng các biến thành phần của lớp cha có thể bị che khuất bởi các biến cục bộ trong lớp con. Ví dụ Viết đè và nạp chồng các hàm thành phần import java.io.*; class Den { protected String loaiHoaDon = “Hoa don nho: ”; // (1) protected double docHoaDon(int giaDien) throws Exception { // (2) double soGio = 10.0; double hoaDonNho = giaDien* soGio; System.out.println(loaiHoaDon + hoaDonNho); return hoaDonNho; } } class DenTuyp extends Den { public String loaiHoaDon =“Hoa don lon:”;// Bị che bóng (3) public double docHoaDon (int giaDien) throws Exception { // Viết đè hàm (4) double soGio = 100.0; double hoaDonLon = giaDien* soGio; System.out.println(loaiHoaDon + hoaDonLon); return hoaDonLon; } public double docHoaDon (){ System.out.println(“Khong co hoa don!”); return 0.0; } }   public class KhachHang { public static void main(String args[]) throws Exception { // (6) DenTuyp den1 = new DenTuyp(); // (7) Den den2 = den1; // (8) Den den3 = new Den(); // (9) // Gọi các hàm đã viết đè den1.docHoaDon(1000); // (10) den2.docHoaDon(1000); // (11) den3.docHoaDon(1000); // (12) // Truy nhập tới các biến thành phần đã bị viết đè (bị che bóng) System.out.println(den1.loaiHoaDon); // (13) System.out.println(den2.loaiHoaDon); // (14) System.out.println(den3.loaiHoaDon); // (15) // Gọi các hàm nạp chồng den1.docHoaDon(); } } Kết quả? Viết đè và nạp chồng là khác nhau: Trước tiên cần phân biệt rõ hai cơ chế viết đè và nạp chồng là khác nhau trong Java. + Viết đè yêu cầu cùng định danh hàm (cùng tên gọi, cùng danh sách tham số), cùng kiểu trả lại kết quả và có thể xác định tất cả hoặc tập con các lớp ngoại lệ được kể ra trong mệnh đề cho qua ngoại lệ đã được định nghĩa tại lớp cha. + Nạp chồng yêu cầu khác nhau về định danh, nhưng giống nhau về tên gọi của hàm, vì thế chúng sẽ khác nhau về số lượng, kiểu, hay thứ tự của các tham biến. + Hàm có thể nạp chồng ở trong cùng lớp hoặc ở các lớp con cháu, còn hàm viết đè chỉ ở các lớp con cháu. Từ những lớp con khi muốn gọi tới các hàm ở lớp cha mà bị viết đè thì phải gọi qua toán tử đại diện cho lớp cha, đó là super. Đối với hàm nạp chồng thì không cần như thế. Lời gọi hàm nạp chồng được xác định thông qua danh sách các đối số hiện thời sánh với đối số hình thức để xác định nội dung tương ứng. 5. Phạm vi của các thành phần        Phạm vi lớp của các thành phần,        Phạm vi khối của các biến cục bộ (local),        Phạm vi gói (package) a/ Phạm vi lớp Phạm vi lớp xác định những thành phần được truy nhập bên trong của một lớp (kể cả lớp được kế thừa). Quyền truy nhập của chúng thường được xác định thông qua các bổ ngữ (modifier): public, protected, private. b/ Phạm vi khối Trong chương trình, các lệnh khai báo và các lệnh thực hiện tuần tự có thể gộp lại thành từng khối (block) bằng cách sử dụng {, }. Một biến được khai báo ở trong một khối có phạm vi xác định bên trong khối và không xác định ở bên ngoài khối đó. Chú ý: + Trong các khối thì lệnh khai báo là tự do, thứ tự không quan trọng, muốn khai báo ở chỗ nào cũng được miễn là phải khai báo và khởi tạo trước khi sử dụng. + Có thể có nhiều khối lồng nhau nhưng không được cắt nhau và những biến khai báo ở khối ngoài đều có phạm vi xác định ở trong mọi khối bao bên trong nó. Nhận xét gì đối với biến phạm vi lớp? c/ Phạm vi gói Trong chương trình, một số lớp có liên quan với nhau (hệ thống con) thường được tổ chức thành một gói (package). Một gói là một tuyển tập các lớp, giao diện có quan hệ với nhau nhằm quản lý không gian tên gọi (namespace) và bảo vệ các truy cập. Lý do phải tổ chức thành các gói: Bạn và những người lập trình khác dễ phát hiện được các lớp trong một gói có liên quan với nhau. Bạn và những người lập trình khác biết được cần tìm các lớp, giao diện liên quan ở đâu, để thực hiện được các chức năng mong muốn. Khi tên gọi các lớp bị xung đột thì nên tổ chức chúng ở các gói khác nhau. Tên của package sẽ tạo ra không gian tên mới. Bạn có thể cho phép các đối tượng trong một gói truy cập không hạn chế tới lớp khác, trừ khi quyền truy cập đó bị giới hạn. 6. Các thuộc tính kiểm soát truy nhập các thành phần của lớp Một trong những ưu điểm của phương pháp hướng đối tượng là có thể tổ chức dữ liệu theo nguyên lý bao gói và che giấu thông tin. Các thuộc tính và hàm thành phần của lớp có thể khai báo thêm một số bổ ngữ để kiểm soát quyền truy nhập đối với những thành phần đó. Khi thiết kế các thành phần của lớp, chúng ta có thể sử dụng những bổ ngữ sau:        public        protected        mặc định (không sử dụng thêm bổ ngữ)        private        static        final        abstract        synchronized        native        transient        volatile a.  Các thành phần public Thành phần của lớp khai được báo công khai (public) cho phép nó có thể được truy nhập từ bất kỳ lớp nào (kể cả lớp cùng gói (package) lẫn lớp khác gói). Lớp, giao diện khai báo công khai (public) cho phép sử dụng (bao gồm dùng để khai báo và kế thừa) ở bất kỳ lớp nào. Chú ý: Biến cục bộ không được khai báo công khai Trong một tệp java có nhiều nhất là một hoặc lớp hoặc giao diện (có tên trùng với tên tệp) được khai báo công khai. b.  Các thành phần protected Thành phần của lớp được khai báo được bảo vệ (protected) cho phép truy nhập bởi các lớp con (lớp kế thừa) trong bất kỳ gói nào, hoặc các lớp trong cùng gói. Chú ý: Biến cục bộ không được khai báo được bảo vệ. c.  Các thành phần mặc định Thành phần của lớp được khai báo mặc định (không khai báo) cho phép truy nhập từ bất kỳ lớp nào trong cùng gói, nhưng không thể từ gói khác. Lớp, giao diện khai báo mặc định thì chỉ cho phép sử dụng ở các lớp trong cùng gói. Chú ý: Các thành phần khai báo mặc định sẽ không được kế thừa. d.  Các thành phần private Thành phần của lớp được khai báo riêng (private) thì chỉ được truy nhập trong lớp khai báo. Chú ý: Các thành phần khai báo riêng sẽ không được kế thừa. Biến cục bộ không được khai báo riêng. Bảng điều khiển truy xuất các thành phần của lớp e.  Các thành phần static Thành phần của lớp khai báo tĩnh (static) sẽ là chung cho tất cả các đối tượng trong một lớp. Chú ý: Hàm tĩnh của lớp chỉ truy cập được những thành phần tĩnh trong lớp. Biến cục bộ không được khai báo tĩnh. Có thể truy cập tới những thành phần tĩnh qua đối tượng hoặc qua tên lớp. import java.io.*; class MyInput { private static BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); private static String line; public static int readInt(){ int i = 0; try{ System.out.print("Nhap so: "); line = br.readLine(); i = Integer.parseInt(line); return i; }catch(Exception ex){} return 0; } public static String readStr(){ try{ line = br.readLine(); return line; }catch(Exception ex){} return ""; } } Ví dụ: Tạo lớp đọc dữ liệu vào với tên MyInput.java class MyClass { public static void main(String args[]){ int so1, so2, tong; so1 = MyInput.readInt(); so2 = MyInput.readInt(); System.out.print(so1 + " + " + so2 + " = " + (so1 + so2)); } } Tạo tệp MyClass.java như sau: f.  Các thành phần final Đối với các biến final kiểu nguyên thủy, khi chúng được khởi tạo giá trị thì sẽ không thay đổi được. Đối với các biến final kiểu tham chiếu (reference), giá trị tham chiếu sẽ không thay đổi được, nhưng các thành phần (trạng thái) của đối tượng có thể thay đổi được. Các biến final static được sử dụng chung trong chương trình như các hằng (constant). Nhận xét g.  Các thành phần abstract Hàm thành phần khai báo trừu tượng (abstract) có dạng: abstract ([])[]; Hàm abstract là hàm prototype, chỉ khai báo phần định danh hàm mà không định nghĩa nội dung thực hiện, do vậy nó là hàm không đầy đủ. Lưu ý: + Hàm final không thể khai báo abstract và ngược lại + Các hàm trong interface đều là các hàm abstract. Lớp abstract phải chứa ít nhất một hàm abstract. Những hàm này sau đó sẽ được cài đặt nội dung thực hiện trong các lớp con cháu của lớp chứa chúng. Chú ý: Không thể tạo đối tượng từ lớp abstract, nhưng có thể khai báo biến có kiểu từ lớp abstract. Khi tạo lớp có thể hiện kế thừa từ lớp abstract thì lớp có thể hiện phải định nghĩa tất cả các hàm thành phần abstract. h.  Các thành phần synchronized Java hỗ trợ chương trình thực hiện đa luồng (multi threads). Tại mỗi thời điểm, chỉ một luồng (tiến trình) được khai báo đồng bộ synchronized được thực hiện trên đối tượng chỉ định. Ví dụ: Minh họa họat động của các hàm synchronized class Stack{ private Object[] stackArray = new stackArray[100]; private int topOfStack; synchronized public void push(Object elem){ // (1) stackArray[++topOfStack] = elem; } synchronized public Object pop(){ // (2) Object obj = stackArray[topOfStack]; stackArray[topOfStack] = null; return obj; } // Những hàm khác public Object peek(){ return stackArray[topOfStack]; } } Chú ý: Từ khóa synchronized chỉ khai báo cho hàm. i. Kiểm soát truy nhập các thành phần class TinhTong{ public static void main(String args[]){ float f = 0.0F; for (int i = 0; i ] ([]) { // Nội dung cần tạo lập } chỉ có thể sử dụng: public, protected, private hoặc mặc định. luôn trùng với tên của lớp chứa toán tử đó. Trong đó: Chú ý: Không sử dụng các thuộc tính khác với toán tử tạo lập. Toán tử tạo lập là loại hàm đặc biệt không có kiểu trả lại, ngay cả kiểu void cũng không được sử dụng. Toán tử tạo lập chỉ sử dụng được với toán tử new. Toán tử tạo lập có thể nạp chồng. * Toán tử tạo lập mặc định Khi một lớp không có toán tử tạo lập nào thì hệ thống sẽ tự động cung cấp cho một toán tử tạo lập với cấu trúc như sau: () { } // Không làm gì cả Toán tử tạo lập được thực thi khi nào? class SoPhuc { private double phanThuc, phanAo; public SoPhuc(){ phanThuc = phanAo = 0; } public SoPhuc(double a){ phanThuc = phanAo = a; } public SoPhuc(double a, double b){ phanThuc = a; phanAo = b; } } Ví dụ: 9. 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 chỉ kế thừa được từ một lớp cha. Mọi lớp của Java đều là lớp con cháu mặc định của Object.  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: extends { // Các thuộc tính dữ liệu bổ sung // Các hàm thành phần bổ sung hay viết đè }  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 (có thể mất thông tin) 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 thì đối tượng của lớp cha phải được khởi tạo từ lớp con và phải thực hiện ép kiểu. Khi viết đè hàm thành phần ở lớp con thì thuộc tính kiểm soát truy nhập ở lớp con phải có phạm vi lớn hơn ở lớp cha. Chú ý: class ConNguoi{ protected String stTen = "Con nguoi"; public String getTen(){ return stTen; } public void setTen(String st){ stTen = st; } public void hienThi(){ System.out.println(stTen); } private void thongBao(){ System.out.println("Thong bao CN"); } } Ví dụ: class SinhVien extends ConNguoi{ protected String stTen = "Sinh vien"; private String stMaSV = "Ma SV"; public String getTen(){ return stTen; } public void hienThi(){ System.out.println(stMaSV + ", " + stTen); } public void thongBao(){ System.out.println("Thong bao SV"); } } class QLSV { public static void main(String args[]){ ConNguoi cn1; SinhVien sv1, sv2; cn1 = new ConNguoi(); cn1.hienThi(); sv1 = (SinhVien)cn1; sv1 = new SinhVien(); sv1.thongBao(); sv1.hienThi(); System.out.println(sv1.getTen()); cn1 = sv1; // Khong can ep kieu cn1.thongBao(); cn1.hienThi(); System.out.println(cn1.stTen); sv2 = (SinhVien)cn1; sv2.thongBao(); sv2.hienThi(); sv2.setTen("TEN"); System.out.println(sv2.stTen); System.out.println(cn1.stTen); } } 10. Toán tử móc xích giữa các lớp kế thừa this(), super() Toán tử tạo lập this() được sử dụng để tạo ra đối tượng của lớp hiện thời. 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 các toán tử tạo lập của lớp cha (superclass) trực tiếp Chúng được 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 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 đó. Chú ý: // Tên tệp là: HinhTron.java có hai class sau: class Hinh{ private String stTen; public Hinh(){ this(""); } public Hinh(String st){ stTen = st; } public void setInfo(String st){ stTen = st; } public String getInfo(){ return stTen; } public void display(){ System.out.println(this.getInfo()); } } Ví dụ: class HinhTron extends Hinh{ private double r; public HinhTron(){ this("", 0); } public HinhTron(double d){ this("", d); } public HinhTron(String st, double d){ super(st); r = d; } public void setInfo(String st, double d){ super.setInfo(st); r = d; } public String getInfo(){ return super.getInfo() + ", " + r; } public void display(){ System.out.println(this.getInfo()); } public static void main(String args[]){ HinhTron ht = new HinhTron(5); ht.display(); } } 11. Quan hệ kế thừa và quan hệ kết tập Quan hệ kế thừa, ký hiệu , nghĩa là lớp B sẽ có những thành phần của lớp A cho phép. Quan hệ kết tập, ký hiệu , nghĩa là lớp A sẽ có những đối tượng thuộc lớp B. A B 12. Giao diện và sự mở rộng quan hệ kế thừa trong Java Định nghĩa interface interface { // Các thuộc tính (hằng - ngầm định) // Các hàm mẫu prototype } Java chỉ cho phép kế thừa từ một lớp nên không kế thừa bội. Để có kế thừa bội Java hỗ trợ khái niệm giao diện (interface). Giao diện interface là loại 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. 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 implements { // Bổ sung các thành phần; // Cài đặt các hàm mẫu đã cho trong ; } 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. 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. 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ác hàm mẫu của giao diện được mặc định là abstract và không được khai báo static, final; Trong giao diện, tất cả dữ liệu là public final static và phương thức là public abstract. Do vậy không được khai báo private, protected ở các thành phần của giao diện Chú ý:
Tài liệu liên quan