Lab 05 — Abstract, kế thừa, đa hình
LAB 05 — GIẢI CHI TIẾT (LỚP TRỪU TƯỢNG, KẾ THỪA, ĐA HÌNH, BƯU PHẨM)
Nguồn:
lab05.doc(Bài thực hành số 05). Bám lý thuyết buổi 11 và 12. Đây là lab "đỉnh" của môn: dùng đồng thời abstract + kế thừa + đa hình + mảng đối tượng.
0. Bản đồ bài làm
| Phần | Lớp | Quan hệ | Tệp |
|---|---|---|---|
| Chính | DiemHP (abstract) |
cha | DiemHP.java |
| Chính | HPLyThuyet |
IS-A DiemHP |
HPLyThuyet.java |
| Chính | HPThucHanh |
IS-A DiemHP |
HPThucHanh.java |
| Chính | SinhVien |
HAS-A 2 DiemHP |
SinhVien.java |
| Chạy | — | — | QuanLyHP.java |
| Mở rộng | HPDoAn |
IS-A DiemHP |
HPDoAn.java |
| Ở nhà | BuuPham (abstract) |
cha | BuuPham.java |
| Ở nhà | Thu, BuuKien |
IS-A BuuPham |
Thu.java, BuuKien.java |
| Ở nhà | ThungThu |
HAS-A mảng | ThungThu.java |
PHẦN 1 —
DiemHP, HPLyThuyet,
HPThucHanh
0. Đề bài
DiemHP là lớp trừu tượng dùng làm cơ sở tính điểm: có
tenHP, soTinChi, phương thức trừu tượng
tinhDiem(). HPLyThuyet kế thừa, có
chuyenCan, giuaKy, cuoiKy (0..10), điểm =
cc*0.1 + gk*0.2 + ck*0.7. HPThucHanh kế thừa,
có db1, db2, db3 (0..10), điểm = trung bình cộng 3 bài.
1. Yêu cầu ẩn
DiemHPkhông đượcnewtrực tiếp → phảiabstract.tinhDiem()trong lớp cha không có thân (nếu cho thânreturn 0là nói dối: mỗi loại tính khác nhau).soTinChivẫn kiểm tra 1..4; điểm 0..10 (dù đề không nhắc, dữ liệu sai sẽ làm DTB vô nghĩa).- Lớp con bắt buộc override
tinhDiem(); nếu không, compiler báo lỗi. - Constructor lớp con phải gọi
super(tenHP, soTinChi)ở dòng đầu.
2. Input → Output
| Lớp | Dữ liệu | Công thức |
|---|---|---|
HPLyThuyet |
cc, gk, ck |
0.1·cc + 0.2·gk + 0.7·ck |
HPThucHanh |
db1, db2, db3 |
(db1 + db2 + db3) / 3 |
SinhVien |
hp1, hp2 |
(d1·tc1 + d2·tc2)/(tc1 + tc2) |
3. Thiết kế
DiemHP «abstract»
- tenHP : String
- soTinChi : int
+ DiemHP(tenHP, tc)
+ tinhDiem() : double «abstract»
^
+-----------+-----------+
| |
HPLyThuyet HPThucHanh
- chuyenCan, giuaKy, - db1, db2, db3
cuoiKy + tinhDiem()
+ tinhDiem()
SinhVien
- hoTen : String
- hp1, hp2 : DiemHP <- kiểu CHA, object CON (da hinh)
+ tinhDTB() : double
Quyết định thiết kế:
abstract class(không phảiinterface) vì cần chia sẻ code + trạng thái (tenHP,soTinChi,toString).SinhVienkhai báoDiemHP hp1chứ không phảiHPLyThuyet→ nhận được cả hai loại, và mở rộng thêmHPDoAnmà không sửaSinhVien.- Không dùng
instanceofđể tính điểm — dùnghp1.tinhDiem()(đa hình).
4. Code từng bước
Bước 1 — lớp trừu tượng DiemHP.
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(); // KHONG co thanBước 2 — HPLyThuyet gọi super và
override.
public class HPLyThuyet extends DiemHP {
private double chuyenCan, giuaKy, cuoiKy;
public HPLyThuyet(String tenHP, int tc,
double cc, double gk, double ck) {
super(tenHP, tc); // dong dau tien
this.chuyenCan = cc; this.giuaKy = gk; this.cuoiKy = ck;
}
@Override
public double tinhDiem() {
return chuyenCan * 0.1 + giuaKy * 0.2 + cuoiKy * 0.7;
}
}Bước 3 — HPThucHanh.
@Override
public double tinhDiem() {
return (db1 + db2 + db3) / 3;
}Bước 4 — toString dùng chung ở lớp cha (lợi ích
của abstract class).
@Override
public String toString() {
double d = Math.round(tinhDiem() * 100) / 100.0; // goi da hinh
return tenHP + " (" + soTinChi + " TC): " + d;
}Lưu ý: toString gọi tinhDiem() trừu
tượng — khi chạy, nó tự chọn bản của lớp con. Đây là đa hình
ngay trong lớp cha.
5. Lời giải hoàn chỉnh
// ===== DiemHP.java =====
public abstract class DiemHP {
private String tenHP;
private int soTinChi;
public DiemHP(String tenHP, int soTinChi) {
this.tenHP = tenHP;
setSoTinChi(soTinChi);
}
public String getTenHP() { return tenHP; }
public int getSoTinChi() { return soTinChi; }
public void setSoTinChi(int tc) {
if (tc >= 1 && tc <= 4) this.soTinChi = tc;
}
public abstract double tinhDiem();
@Override
public String toString() {
double d = Math.round(tinhDiem() * 100) / 100.0;
return tenHP + " (" + soTinChi + " TC): " + d;
}
}// ===== HPLyThuyet.java =====
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);
setChuyenCan(chuyenCan);
setGiuaKy(giuaKy);
setCuoiKy(cuoiKy);
}
public void setChuyenCan(double d) { if (d >= 0 && d <= 10) chuyenCan = d; }
public void setGiuaKy(double d) { if (d >= 0 && d <= 10) giuaKy = d; }
public void setCuoiKy(double d) { if (d >= 0 && d <= 10) cuoiKy = d; }
@Override
public double tinhDiem() {
return chuyenCan * 0.1 + giuaKy * 0.2 + cuoiKy * 0.7;
}
}// ===== HPThucHanh.java =====
public class HPThucHanh extends DiemHP {
private double db1, db2, db3;
public HPThucHanh(String tenHP, int soTinChi,
double db1, double db2, double db3) {
super(tenHP, soTinChi);
setDb1(db1); setDb2(db2); setDb3(db3);
}
public void setDb1(double d) { if (d >= 0 && d <= 10) db1 = d; }
public void setDb2(double d) { if (d >= 0 && d <= 10) db2 = d; }
public void setDb3(double d) { if (d >= 0 && d <= 10) db3 = d; }
@Override
public double tinhDiem() {
return (db1 + db2 + db3) / 3;
}
}6. Cách chạy
javac DiemHP.java HPLyThuyet.java HPThucHanh.java
(cần SinhVien và QuanLyHP để chạy — phần
2)
7. Kết quả chạy mẫu
(ở phần 2)
8. Bảng kiểm thử
| # | Ca | Kỳ vọng |
|---|---|---|
| 1 | new HPLyThuyet("X",3,9,8,7).tinhDiem() |
7.4 |
| 2 | new HPThucHanh("Y",2,8,9,7).tinhDiem() |
8.0 |
| 3 | new DiemHP("Z",3) |
lỗi biên dịch (abstract) |
| 4 | new HPThucHanh(...,11,9,7) |
điểm 11 bị từ chối, tính lại theo giá trị cũ (0) |
9. Trace
HPLyThuyet("Lap trinh Java", 3, 9, 8, 7):
9×0.1 = 0.9; 8×0.2 = 1.6;
7×0.7 = 4.9; tổng 7.4. toString()
→ "Lap trinh Java (3 TC): 7.4".
10. Lỗi thường gặp
| Code sai | Thông báo | Sửa |
|---|---|---|
new DiemHP("X", 3) |
DiemHP is abstract; cannot be instantiated |
Tạo lớp con |
Lớp con không viết tinhDiem |
HPLyThuyet is not abstract and does not override abstract method tinhDiem() |
Override |
public abstract double tinhDiem() { return 0; } |
abstract methods cannot have a body |
Bỏ thân |
super(tenHP, tc) ở dòng 2 |
call to super must be first statement in constructor |
Dòng đầu |
private field ở cha rồi truy cập chuyenCan
từ con |
cannot find symbol |
Khai báo field ở đúng lớp |
@Override sai chữ ký (tinhDiem(int)) |
method does not override or implement a method from a supertype |
Đúng chữ ký |
11. Câu hỏi kiểm tra hiểu
- Vì sao
DiemHPnênabstractthay vì cótinhDiem()trả 0? - Vì sao
toStringđặt ở lớp cha vẫn in đúng điểm của từng loại? - Nếu
DiemHPlàinterface, ta mất gì? (gợi ý: thuộc tính + code dùng chung)
12. Mở rộng
- Thêm
HPDoAn(5 điểm hội đồng) — không sửa lớp nào khác (phần 3). - Thêm
HPSeminar(điểm = trung bình 2 bài, hệ số 1 tín chỉ).
PHẦN 2 —
SinhVien VÀ CHƯƠNG TRÌNH CHÍNH
0. Đề bài
SinhVien: hoTen, hp1,
hp2 (mỗi học phần có thể lý thuyết hoặc thực hành);
constructor; tinhDTB() theo số tín chỉ:
(diem1·tc1 + diem2·tc2)/(tc1 + tc2). Tạo mảng 3 sinh viên
có cả lý thuyết và thực hành; in họ tên, điểm 2 học phần, DTB.
1. Yêu cầu ẩn
hp1,hp2phải khai báo kiểu cha (DiemHP) — đây là điểm mấu chốt của đa hình.- Tổng tín chỉ 0 → chặn chia 0.
- In DTB làm tròn 2 chữ số.
- Mảng
SinhVien[]phảinewtừng ô (buổi 8).
2. Input → Output
| Vào | Ra |
|---|---|
3 SinhVien, mỗi người 2 DiemHP |
họ tên + điểm từng HP + DTB |
3. Thiết kế
SinhVien
- hoTen : String
- hp1, hp2 : DiemHP
+ SinhVien(hoTen, hp1, hp2)
+ tinhDTB() : double
+ toString() : String
Không cần biết hp1 là lý thuyết hay thực hành: chỉ gọi
hp1.tinhDiem().
4. Code từng bước
Bước 1 — constructor + DTB.
public double tinhDTB() {
int tc1 = hp1.getSoTinChi();
int tc2 = hp2.getSoTinChi();
if (tc1 + tc2 == 0) return 0;
return (hp1.tinhDiem() * tc1 + hp2.tinhDiem() * tc2) / (tc1 + tc2);
}Bước 2 — toString nhiều dòng.
@Override
public String toString() {
double dtb = Math.round(tinhDTB() * 100) / 100.0;
return hoTen + "\n " + hp1 + "\n " + hp2 + "\n DTB = " + dtb;
}Bước 3 — mảng 3 sinh viên (chú ý new từng
ô).
5. Lời giải hoàn chỉnh
// ===== SinhVien.java =====
public class SinhVien {
private String hoTen;
private DiemHP hp1, hp2;
public SinhVien(String hoTen, DiemHP hp1, DiemHP hp2) {
this.hoTen = hoTen;
this.hp1 = hp1;
this.hp2 = hp2;
}
public String getHoTen() { return hoTen; }
public DiemHP getHp1() { return hp1; }
public DiemHP getHp2() { return hp2; }
public double tinhDTB() {
int tc1 = hp1.getSoTinChi();
int tc2 = hp2.getSoTinChi();
if (tc1 + tc2 == 0) return 0;
return (hp1.tinhDiem() * tc1 + hp2.tinhDiem() * tc2) / (tc1 + tc2);
}
@Override
public String toString() {
double dtb = Math.round(tinhDTB() * 100) / 100.0;
return hoTen + "\n " + hp1 + "\n " + hp2 + "\n DTB = " + dtb;
}
}// ===== QuanLyHP.java =====
public class QuanLyHP {
public static void main(String[] args) {
DiemHP lt1 = new HPLyThuyet("Lap trinh Java", 3, 9, 8, 7);
DiemHP th1 = new HPThucHanh("Thuc hanh Java", 2, 8, 9, 7);
DiemHP th2 = new HPThucHanh("Thuc hanh Java", 2, 6, 7, 8);
DiemHP lt2 = new HPLyThuyet("Toan roi rac", 3, 10, 9, 8);
DiemHP lt3 = new HPLyThuyet("Co so du lieu", 4, 5, 6, 7);
DiemHP th3 = new HPThucHanh("Thuc hanh CSDL", 1, 10, 10, 9);
SinhVien[] ds = new SinhVien[3];
ds[0] = new SinhVien("Nguyen Van An", lt1, th1);
ds[1] = new SinhVien("Tran Thi Binh", th2, lt2);
ds[2] = new SinhVien("Le Van Chi", lt3, th3);
for (int i = 0; i < ds.length; i++) {
System.out.println(ds[i]);
System.out.println();
}
// Da hinh: cung mot loi goi, moi loai tu tinh
DiemHP[] cacHP = {lt1, th1, th2, lt2, lt3, th3};
double tong = 0;
for (int i = 0; i < cacHP.length; i++) {
tong += cacHP[i].tinhDiem();
}
System.out.println("Tong diem 6 hoc phan: "
+ Math.round(tong * 100) / 100.0);
}
}6. Cách chạy
javac DiemHP.java HPLyThuyet.java HPThucHanh.java SinhVien.java QuanLyHP.java
java QuanLyHP
7. Kết quả chạy mẫu
Nguyen Van An
Lap trinh Java (3 TC): 7.4
Thuc hanh Java (2 TC): 8.0
DTB = 7.64
Tran Thi Binh
Thuc hanh Java (2 TC): 7.0
Toan roi rac (3 TC): 8.4
DTB = 7.84
Le Van Chi
Co so du lieu (4 TC): 6.6
Thuc hanh CSDL (1 TC): 9.67
DTB = 7.21
Tong diem 6 hoc phan: 47.07
Kiểm tra tay:
- An:
(7.4×3 + 8.0×2)/5 = (22.2 + 16)/5 = 7.64✓ - Binh:
(7.0×2 + 8.4×3)/5 = (14 + 25.2)/5 = 7.84✓ - Chi:
(6.6×4 + 9.6667×1)/5 = (26.4 + 9.6667)/5 = 36.0667/5 = 7.2133→ 7.21 ✓ - Tổng 6 HP:
7.4 + 8.0 + 7.0 + 8.4 + 6.6 + 9.6667 = 47.0667→ 47.07 ✓
8. Bảng kiểm thử
| # | Ca | Kỳ vọng |
|---|---|---|
| 1 | 2 môn đều lý thuyết, cùng tín chỉ | DTB = trung bình cộng |
| 2 | Tín chỉ 3 và 1, điểm 9 và 5 | (27+5)/4 = 8.0 |
| 3 | hp1 = null |
NullPointerException → cần chặn ở constructor (nâng
cao) |
| 4 | Tổng tín chỉ 0 (dữ liệu sai) | DTB = 0, không chia 0 |
9. Trace tinhDTB của
An
| Bước | tc1 |
tc2 |
hp1.tinhDiem() |
hp2.tinhDiem() |
Tử | Mẫu | DTB |
|---|---|---|---|---|---|---|---|
| đọc | 3 | 2 | 7.4 | 8.0 | — | — | — |
| tính | 22.2 | 3 | |||||
| tính | 38.2 | 5 | 7.64 |
10. Lỗi thường gặp
| Code sai | Thông báo / hiện tượng | Sửa |
|---|---|---|
HPLyThuyet hp1 thay vì DiemHP hp1 |
Không nhận được HPThucHanh |
Khai báo kiểu cha |
SinhVien[] ds = new SinhVien[3]; ds[0].tinhDTB() |
NullPointerException |
new SinhVien(...) cho từng ô |
DTB chia hp1.getSoTinChi() |
Sai công thức | Chia tổng tín chỉ |
hp1.tinhDiem thiếu () |
cannot find symbol / in địa chỉ phương thức |
Thêm () |
toString thiếu \n |
Cả 3 sinh viên dính một dòng | Thêm \n |
11. Câu hỏi kiểm tra hiểu
- Vì sao khai báo
DiemHP hp1thay vìHPLyThuyet hp1lại quan trọng? - Nếu thêm
HPDoAn, cần sửa những tệp nào? (Đáp án: chỉ thêm tệp mới.) - Vì sao
SinhVienkhông nên có phương thứctinhDiem()? (nó không phải học phần)
12. Mở rộng
getHocBong()nếu DTB ≥ 8.- Sắp xếp mảng sinh viên theo DTB.
demHPSinhVien()đếm số học phần thực hành/lý thuyết của một sinh viên (dùnginstanceof).
PHẦN 3 — MỞ RỘNG CỦA ĐỀ
3.1
HPDoAn — trung bình 5 thành viên hội đồng
// ===== HPDoAn.java =====
public class HPDoAn extends DiemHP {
private double[] diemHoiDong;
public HPDoAn(String tenHP, int soTinChi, double[] diemHoiDong) {
super(tenHP, soTinChi);
this.diemHoiDong = diemHoiDong;
}
@Override
public double tinhDiem() {
if (diemHoiDong == null || diemHoiDong.length == 0) return 0;
double s = 0;
for (int i = 0; i < diemHoiDong.length; i++) s += diemHoiDong[i];
return s / diemHoiDong.length;
}
}Điểm quan trọng: SinhVien không
đổi một dòng nào. Đây là bằng chứng đa hình có giá trị
thật.
Kiểm thử: 5 điểm 9, 8, 8, 9, 7 →
41/5 = 8.2. Nếu
SinhVien("X", new HPDoAn("Do an", 3, new double[]{9,8,8,9,7}), lt2)
→ DTB =
(8.2×3 + 8.4×3)/6 = (24.6 + 25.2)/6 = 49.8/6 = 8.3.
3.2 Số bài thực hành thay đổi
public class HPThucHanh extends DiemHP {
private double[] db; // thay 3 biến bằng mảng
public HPThucHanh(String tenHP, int soTinChi, double[] db) {
super(tenHP, soTinChi);
this.db = db;
}
@Override
public double tinhDiem() {
if (db == null || db.length == 0) return 0;
double s = 0;
for (int i = 0; i < db.length; i++) s += db[i];
return s / db.length; // chia theo so bai thuc te
}
}Lưu ý: đổi constructor của HPThucHanh
sẽ phải sửa QuanLyHP (vì cách gọi thay
đổi) — đây là khác biệt giữa "mở rộng thêm lớp" (không sửa gì) và "đổi
chữ ký lớp cũ" (phải sửa nơi gọi).
3.3 Nhiều học phần
Đổi SinhVien:
private DiemHP[] dsHP;
private int soHP;
public boolean themHP(DiemHP hp) {
if (hp == null || soHP >= dsHP.length) return false;
dsHP[soHP++] = hp;
return true;
}
public double tinhDTB() {
double t = 0; int tc = 0;
for (int i = 0; i < soHP; i++) {
t += dsHP[i].tinhDiem() * dsHP[i].getSoTinChi();
tc += dsHP[i].getSoTinChi();
}
return (tc == 0) ? 0 : t / tc;
}PHẦN 4 (Ở NHÀ) — HỆ THỐNG BƯU PHẨM
0. Đề bài
Thùng thư chứa 2 loại bưu phẩm: Thư (thường: dán tem 800đ; bảo đảm: 2000đ) và Bưu kiện (5000đ). Thùng thư cho phép bỏ vào, liệt kê toàn bộ hoặc theo loại, tính tổng tiền, tổng theo từng loại. Chương trình: tạo thùng chứa 100 bưu phẩm; bỏ vào đủ loại; liệt kê; liệt kê thư bảo đảm; tính tổng tiền; tính tổng theo từng loại.
1. Yêu cầu ẩn
- Ba loại phí khác nhau → mỗi lớp con tự quyết định
phiGui(), không dùngswitchtrongThungThu. - Thùng đầy (100) →
themtrảfalse. - Thùng rỗng → tổng = 0, liệt kê không in gì (hoặc in "trống").
- "Tổng theo từng loại" phải gộp đúng: thư thường, thư bảo đảm, bưu kiện.
2. Input → Output
| Vào | Ra |
|---|---|
BuuPham[] (tối đa 100) |
danh sách + tổng tiền + tổng theo loại |
3. Thiết kế
BuuPham «abstract»
- ma : String
+ phiGui() : double «abstract»
+ loai() : String «abstract»
^ ^
| |
Thu BuuKien
- baoDam : boolean (phi 5000)
phi 800/2000
ThungThu
- ds : BuuPham[100]
- soLuong : int
+ them(p) : boolean
+ lietKe() / lietKeTheoLoai(String)
+ tongTien() : double
+ tongTheoLoai(String) : double
Vì sao loai() là phương thức chứ không phải thuộc tính:
mỗi lớp tự trả lời, ThungThu không cần biết chi tiết.
4. Code từng bước
Bước 1 — lớp trừu tượng.
public abstract class BuuPham {
private String ma;
public BuuPham(String ma) { this.ma = ma; }
public String getMa() { return ma; }
public abstract double phiGui();
public abstract String loai();
@Override
public String toString() {
return ma + " | " + loai() + " | " + phiGui();
}
}Bước 2 — Thu với hai mức phí.
public double phiGui() { return baoDam ? 2000 : 800; }
public String loai() { return baoDam ? "Thu bao dam" : "Thu thuong"; }Bước 3 — BuuKien.
public double phiGui() { return 5000; }
public String loai() { return "Buu kien"; }Bước 4 — ThungThu: thêm, liệt kê,
tổng.
public double tongTien() {
double t = 0;
for (int i = 0; i < soLuong; i++) t += ds[i].phiGui();
return t;
}
public double tongTheoLoai(String loai) {
double t = 0;
for (int i = 0; i < soLuong; i++)
if (ds[i].loai().equals(loai)) t += ds[i].phiGui();
return t;
}5. Lời giải hoàn chỉnh
// ===== BuuPham.java =====
public abstract class BuuPham {
private String ma;
public BuuPham(String ma) { this.ma = ma; }
public String getMa() { return ma; }
public abstract double phiGui();
public abstract String loai();
@Override
public String toString() {
return ma + " | " + loai() + " | " + phiGui();
}
}// ===== Thu.java =====
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"; }
}// ===== BuuKien.java =====
public class BuuKien extends BuuPham {
public BuuKien(String ma) { super(ma); }
@Override
public double phiGui() { return 5000; }
@Override
public String loai() { return "Buu kien"; }
}// ===== ThungThu.java =====
public class ThungThu {
private BuuPham[] ds;
private int soLuong;
public ThungThu(int sucChua) {
ds = new BuuPham[sucChua];
soLuong = 0;
}
public boolean them(BuuPham p) {
if (p == null || soLuong >= ds.length) return false;
ds[soLuong] = p;
soLuong++;
return true;
}
public void lietKe() {
for (int i = 0; i < soLuong; i++) System.out.println(ds[i]);
}
public void lietKeTheoLoai(String loai) {
for (int i = 0; i < soLuong; i++) {
if (ds[i].loai().equals(loai)) System.out.println(ds[i]);
}
}
public double tongTien() {
double t = 0;
for (int i = 0; i < soLuong; i++) t += ds[i].phiGui();
return t;
}
public double tongTheoLoai(String loai) {
double t = 0;
for (int i = 0; i < soLuong; i++) {
if (ds[i].loai().equals(loai)) t += ds[i].phiGui();
}
return t;
}
public int getSoLuong() { return soLuong; }
}// ===== QuanLyBuuPham.java =====
public class QuanLyBuuPham {
public static void main(String[] args) {
ThungThu thung = new ThungThu(100);
thung.them(new Thu("T1", false));
thung.them(new Thu("T2", false));
thung.them(new Thu("T3", false));
thung.them(new Thu("T4", true));
thung.them(new Thu("T5", true));
thung.them(new BuuKien("B1"));
thung.them(new BuuKien("B2"));
thung.them(new BuuKien("B3"));
System.out.println("--- Tat ca buu pham ---");
thung.lietKe();
System.out.println("--- Thu bao dam ---");
thung.lietKeTheoLoai("Thu bao dam");
System.out.println("Tong tien: " + thung.tongTien());
System.out.println("Thu thuong : " + thung.tongTheoLoai("Thu thuong"));
System.out.println("Thu bao dam: " + thung.tongTheoLoai("Thu bao dam"));
System.out.println("Buu kien : " + thung.tongTheoLoai("Buu kien"));
System.out.println("So luong : " + thung.getSoLuong());
}
}6. Cách chạy
javac BuuPham.java Thu.java BuuKien.java ThungThu.java QuanLyBuuPham.java
java QuanLyBuuPham
7. Kết quả chạy mẫu
--- Tat ca buu pham ---
T1 | Thu thuong | 800.0
T2 | Thu thuong | 800.0
T3 | Thu thuong | 800.0
T4 | Thu bao dam | 2000.0
T5 | Thu bao dam | 2000.0
B1 | Buu kien | 5000.0
B2 | Buu kien | 5000.0
B3 | Buu kien | 5000.0
--- Thu bao dam ---
T4 | Thu bao dam | 2000.0
T5 | Thu bao dam | 2000.0
Tong tien: 21400.0
Thu thuong : 2400.0
Thu bao dam: 4000.0
Buu kien : 15000.0
So luong : 8
Kiểm tra tay: 3×800 = 2400; 2×2000 = 4000;
3×5000 = 15000; tổng 21400 ✓;
2400 + 4000 + 15000 = 21400 ✓.
8. Bảng kiểm thử
| # | Ca | Kỳ vọng |
|---|---|---|
| 1 | 8 bưu phẩm như trên | 21400 |
| 2 | Thùng rỗng | tổng 0, liệt kê không in gì |
| 3 | Thêm quá 100 | false |
| 4 | Liệt kê "Thu thuong" | 3 dòng |
| 5 | Thêm null |
false |
9. Trace
tongTheoLoai("Thu bao dam")
i |
ds[i].loai() |
So khớp? | t |
|---|---|---|---|
| 0 | Thu thuong | không | 0 |
| 1 | Thu thuong | không | 0 |
| 2 | Thu thuong | không | 0 |
| 3 | Thu bao dam | có | 2000 |
| 4 | Thu bao dam | có | 4000 |
| 5–7 | Buu kien | không | 4000 |
10. Lỗi thường gặp
| Code sai | Thông báo / hiện tượng | Sửa |
|---|---|---|
ThungThu tự if (p instanceof Thu) để tính
tiền |
Chạy được nhưng phá đa hình | Gọi p.phiGui() |
BuuPham không abstract |
new BuuPham() tạo object vô nghĩa |
Thêm abstract |
Lớp con thiếu loai() |
Thu is not abstract and does not override abstract method loai() |
Override |
ds[soLuong++] = p không kiểm tra đầy |
ArrayIndexOutOfBoundsException |
Kiểm tra trước |
So sánh ds[i].loai() == loai |
Không khớp | .equals(loai) |
phiGui trả int |
cannot override ... return type int is not compatible with double |
Trả double |
11. Câu hỏi kiểm tra hiểu
- Vì sao
ThungThukhông cần biết "thư bảo đảm giá 2000"? - Nếu thêm loại "Chuyển phát nhanh 12000đ", cần sửa những tệp nào?
- Vì sao
loai()không nên là thuộc tínhString loaigán trong constructor?
12. Mở rộng
xoa(String ma),timTheoMa(String ma).thongKe()in bảng: loại | số lượng | tổng tiền.- Sắp xếp bưu phẩm theo phí giảm dần.