Lab 05 cho người mới: kế thừa và đa hình
Bài quan trọng nhất môn học. Hai cụm độc lập: điểm học phần (trừu tượng → kế thừa → ghi đè) và bưu phẩm (đa hình quản lý chung). Hiểu được "một danh sách, nhiều cách tính" là qua môn.
Bài 1. DiemHP — lớp trừu tượng là bản cam kết
Đề nói gì
Viết khung tính điểm dùng chung cho mọi loại học phần
(Lý thuyết, Thực hành, Đồ án). Khung giữ tên + tín chỉ,
để trống đúng một chỗ tinhDiem() cho lớp con điền.
Từ mới
abstract(trừu tượng/dở dang): lớp/phương thức chưa hoàn chỉnh. Lớpabstractkhôngnewtrực tiếp được — thử là lỗi biên dịch.- Phương thức trừu tượng: chỉ khai báo tên, không có thân
(kết thúc bằng
;, không có{ }).
Ý tưởng
- Cha giữ phần chung: tên, tín chỉ + kiểm tra 1..4,
toString(). - Cha khai báo
tinhDiem()trừu tượng — "đứa con nào cũng phải có". toString()của cha gọi sẵntinhDiem()để con hưởng.
Code mổ xẻ
public abstract class DiemHP {
private String tenHP;
private int soTinChi;
public DiemHP(String tenHP, int soTinChi) {
this.tenHP = tenHP;
setSoTinChi(soTinChi);
}
public abstract double tinhDiem(); // cam kết, không có thân
@Override
public String toString() {
double d = Math.round(tinhDiem() * 100) / 100.0;
return tenHP + " (" + soTinChi + " TC): " + d;
}
}
Lớp trừu tượng vẫn có constructor — nó dựng phần chung
khi new lớp con. toString() gọi tinhDiem(): lúc chạy với object
con, Java tự gọi bản của con. Đa hình gói trong 1 dòng, ngấm được
chỗ này là hiểu nửa bài.
Bẫy hay gặp
new DiemHP("...", 3): lỗi biên dịch ngay — lớp dở dang không đúc được.- Quên
abstracttrên lớp mà có phương thức trừu tượng: lỗi biên dịch. Hai chữ này đi đôi với nhau.
Tự luyện
- Giải thích vì sao
toString()của cha in đúng điểm của con. - Thêm thuộc tính
giangVienvào cha để cả 3 con dùng chung.
Bài 2. Ba lớp con — ghi đè là viết lại theo ý mình
Đề nói gì
HPLyThuyet (cc0.1 + gk0.2 + ck*0.7),
HPThucHanh (trung bình 3 đầu điểm),
HPDoAn (trung bình điểm hội đồng, rỗng thì 0).
Từ mới
extends: kế thừa — có sẵn mọi thứpubliccủa cha.super(...): gọi constructor cha, bắt buộc dòng đầu tiên.@Override: dán lên phương thức viết lại để trình biên dịch kiểm tra giúp (sai tên/sai tham số là báo ngay).
Ý tưởng
Mỗi con: constructor gọi super + nhận thêm dữ liệu riêng,
setter kiểm tra 0..10, tinhDiem() viết ruột riêng.
Code mổ xẻ
public class HPLyThuyet extends DiemHP {
private double chuyenCan, giuaKy, cuoiKy;
public HPLyThuyet(String tenHP, int soTinChi,
double chuyenCan, double giuaKy, double cuoiKy) {
super(tenHP, soTinChi); // đúc phần cha TRƯỚC, dòng đầu
setChuyenCan(chuyenCan); // rồi mới lo phần riêng
setGiuaKy(giuaKy);
setCuoiKy(cuoiKy);
}
@Override
public double tinhDiem() {
return chuyenCan * 0.1 + giuaKy * 0.2 + cuoiKy * 0.7;
}
}
HPDoAn nhận mảng double[]: trung bình cộng, nhưng chặn
null và rỗng trả 0 trước — mảng người ta truyền vào có thể rỗng.
Chạy tay (bạn An)
LT Java (9, 8, 7, 3TC): 0.9 + 1.6 + 4.9 = 7.4.
TH Java (8, 9, 7, 2TC): 24 / 3 = 8.0.
ĐTB = (7.4*3 + 8.0*2) / 5 = 38.2 / 5 = 7.64. Khớp output mẫu.
Bạn Bình: TH (6+7+8)/3 = 7.0 (2TC), LT 1.0+1.8+5.6 = 8.4 (3TC),
ĐTB (14 + 25.2)/5 = 7.84. Bạn Chi tự tính ra 7.21.
Bẫy hay gặp
superkhông ở dòng đầu: lỗi biên dịch. Java bắt dựng cha trước con.- Quên
@Overriderồi gõ sai tên (tinhdiemthường): thành hàm mới tinh, bản trừu tượng của cha vẫn trống → lỗi biên dịch khó hiểu. Có@Overridethì sai tên báo ngay dòng đó.
Tự luyện
- Thêm
HPNgoaiKhoa(điểm = điểm rèn luyện nhập vào). - Thêm kiểm tra: đầu điểm thực hành cũng chỉ nhận 0..10 (đã có, giải thích vì sao cần).
Bài 3. Mảng đa hình — một danh sách, nhiều cách tính
Đề nói gì
Quản lý 3 sinh viên, mỗi người 2 học phần đủ loại. In từng người, rồi cộng tổng điểm 6 học phần = 47.07.
Từ mới
- Đa hình: cùng một lời gọi
tinhDiem(), object nào tính theo ruột đứa nấy. - Khai báo kiểu cha (
DiemHP), object thật kiểu con (new HPLyThuyet(...)) — hợp lệ vì con là một cha.
Ý tưởng
- Dựng 6 học phần, biến khai báo kiểu
DiemHPcho thấy tính chung. - Mỗi sinh viên giữ 2 học phần, ĐTB trọng số như Lab 03.
- Gom 6 học phần vào mảng
DiemHP[], một vòng cộng hết.
Code mổ xẻ
DiemHP lt1 = new HPLyThuyet("Lap trinh Java", 3, 9, 8, 7); // khai cha, đúc con
DiemHP[] cacHP = {lt1, th1, th2, lt2, lt3, th3}; // lẫn lộn vẫn chung 1 mảng
double tong = 0;
for (int i = 0; i < cacHP.length; i++)
tong += cacHP[i].tinhDiem(); // đứa nào tính theo ruột đứa nấy
Tổng = 7.4 + 8.0 + 7.0 + 8.4 + 6.6 + 9.667 = 47.07.
Thêm loại học phần thứ tư sau này: vòng lặp này không sửa một chữ —
đó là lý do người ta dùng đa hình thay vì if kiểm tra loại.
Bẫy hay gặp
- Gọi phương thức chỉ con mới có (ví dụ
getDb1()) qua biến kiểu cha: lỗi biên dịch, vì kiểu khai báo quyết định gọi được gì. instanceof+ ép kiểu rải khắp code để xử lý từng loại: chạy được nhưng mất hết ý nghĩa đa hình, bị trừ điểm thiết kế.
Tự luyện
- In riêng tổng điểm Lý thuyết và tổng điểm Thực hành từ mảng chung.
- Tìm học phần điểm cao nhất trong mảng (mẫu giữ-max Lab 02).
Bài 4. Bưu phẩm — đa hình áp vào bài toán thật
Đề nói gì
BuuPham (mã + cước + loại), Thu (thường/bảo đảm),
BuuKien. ThungThu chứa tối đa N bưu phẩm:
thêm, liệt kê, liệt kê theo loại, tổng tiền, tổng theo loại, đếm.
Từ mới
- Toán tử 3 ngôi
?::baoDam ? 2000 : 800đọc là "bảo đảm thì 2000, không thì 800".
Ý tưởng
- Cha
BuuPhamtrừu tượng với 2 phương thức trừu tượng. Thuthêm cờbaoDam,BuuKiengiá cố định.ThungThulà danh sáchBuuPham[]+soLuong(mẫu Lab 03, 04).
Code mổ xẻ
public class Thu extends BuuPham {
private boolean baoDam;
public Thu(String ma, boolean baoDam) {
super(ma);
this.baoDam = baoDam;
}
@Override
public double phiGui() { return baoDam ? 2000 : 800; }
@Override
public String loai() { return baoDam ? "Thu bao dam" : "Thu thuong"; }
}
public double tongTien() {
double t = 0;
for (int i = 0; i < soLuong; i++) t += ds[i].phiGui(); // mỗi loại tự tính
return t;
}
toString() của cha (ma + loai + cước) cũng đa hình:
in Thu ra "Thu bao dam", in BuuKien ra "Buu kien" mà cha
không hề biết có 2 loại con.
Chạy tay
8 bưu phẩm: 3 thường 3*800 = 2400, 2 bảo đảm 2*2000 = 4000,
3 bưu kiện 3*5000 = 15000. Tổng 21400.0, số lượng 8.
Khớp output mẫu từng dòng. lietKeTheoLoai("Thu bao dam")
duyệt và chỉ in đứa nào loai().equals(...) khớp.
Bẫy hay gặp
loai() == "Thu bao dam": so địa chỉ, lọc ra rỗng. Chuỗi luôn.equals— lần thứ tư gặp bẫy này trong bộ lab.them(null):lietKegọiphiGui()trên null là nổ. Code mẫu đã chặn null và chặn đầy trongthem.
Tự luyện
- Thêm
BuuKienNhanh(cước gấp đôi bưu kiện thường). - Thêm
loaiDatNhat(): loại nào đóng góp nhiều tiền nhất.
Phụ lục OOP: trace đa hình từng bước
Cây kế thừa của bài điểm
DiemHP (abstract: tên, tín chỉ, tinhDiem?)
/ | \
HPLyThuyet HPThucHanh HPDoAn
(cc,gk,ck) (db1,db2,db3) (mảng hội đồng)
Mũi tên đọc là "là một": HPLyThuyet là một DiemHP.
Code nào cần DiemHP đều nhận được cả 3 con — đó là lý do
SinhVien constructor nhận DiemHP hp1, DiemHP hp2
mà truyền Lý thuyết/Thực hành vào vẫn chạy.
Trace 6 phần tử ra 47.07: đứa nào tính kiểu nấy
Mảng cacHP khai báo kiểu cha, object thật đủ loại.
Cùng một dòng cacHP[i].tinhDiem(), Java nhìn object thật
để chọn ruột (gọi là gắn kết động):
| Phần tử | Object thật | Ruột được chạy | Giá trị |
|---|---|---|---|
lt1 |
Lý thuyết | cc*0.1+gk*0.2+ck*0.7 |
7.4 |
th1 |
Thực hành | trung bình 3 đầu điểm | 8.0 |
th2 |
Thực hành | trung bình 3 đầu điểm | 7.0 |
lt2 |
Lý thuyết | trọng số | 8.4 |
lt3 |
Lý thuyết | trọng số | 6.6 |
th3 |
Thực hành | trung bình 3 đầu điểm | 9.667 |
Tổng 47.07. Không một chữ if kiểm tra loại nào ở vòng lặp —
loại bỏ if theo loại chính là thước đo đa hình:
càng ít if (x instanceof ...), thiết kế càng đúng.
Chuỗi constructor: cha dựng trước con
new HPLyThuyet(...) chạy: DiemHP(...) dựng tên + tín chỉ
(có kiểm tra 1..4) → về con nhận 3 đầu điểm (có kiểm tra 0..10).
Luật của cha áp cho cả 3 con mà không viết lại — đó là ý nghĩa
super dòng đầu: phần chung dựng trước, phần riêng dựng sau.
Đặt super dòng 2 là lỗi biên dịch, Java không cho con chạy
trước khi cha xong.
Bưu phẩm: cùng một kịch bản, sân khấu khác
Cây BuuPham → Thu / BuuKien giống hệt cây điểm:
cha trừu tượng (phiGui?, loai?), con điền ruột,
ThungThu đựng mảng cha. tongTien() duyệt và gọi phiGui() —
8 object, 3 ruột khác nhau, 1 vòng lặp duy nhất.
Học một mẫu mà giải được hai bài: đó là lúc OOP bắt đầu có lãi.