Lab 02 — Phân số, ATM, PTB2, Nhân viên
LAB 02 — GIẢI CHI TIẾT (PHÂN SỐ, ATM, PHƯƠNG TRÌNH BẬC 2, NHÂN VIÊN)
Nguồn:
labOOP_BASIC_.doc(Bài thực hành số 02). Bám lý thuyết buổi 8, 9, 10. Đây là lab dài nhất: 4 bài, trong đóPScó 20 phương thức.
Bản đồ bài làm
| Bài | Lớp | Tệp | Điểm khó |
|---|---|---|---|
| 1 | PS |
PS.java + QuanLyPS.java |
4 constructor, rút gọn ƯCLN, nạp chồng 8 phép toán |
| 2 | ATM |
ATM.java + QuanLyATM.java |
boolean khi thất bại, số dư tối thiểu |
| 3 | PTB2 |
PTB2.java + GiaiPT.java |
chia trường hợp a = 0, delta âm |
| 4 | NhanVien |
NhanVien.java + QuanLyNV.java |
Date, hệ số phụ cấp, 3 loại tiền |
BÀI 1 — LỚP PS (PHÂN
SỐ)
0. Đề bài
Khai báo lớp PS gồm int tu,
int mau và các phương thức: PS(),
PS(int n), PS(int t, int m),
PS(PS p), ganTu, layTu,
ganMau, layMau, hienThi,
rutGon, tang(int), gap(int),
cong(int), cong(PS), tru(int),
tru(PS), nhan(int), nhan(PS),
chia(int), chia(PS), toString,
soSanh(PS).
1. Yêu cầu ẩn
- Mẫu bằng 0 là không hợp lệ — phải chặn ngay trong constructor và setter.
- Rút gọn phải xử lý
tu = 0(kết quả0/1) và mẫu âm (đưa dấu lên tử). chiacho phân số0/1→ không xác định; phải chặn.tang(n)là cộng thêm n đơn vị, không phải cộng vào tử.- Các phép toán trả về
PSmới, không sửathis(trừtang,gap,rutGon— đề ghi rõ là sửa tại chỗ).
2. Input → Output
| Phương thức | Vào | Ra | Ghi chú |
|---|---|---|---|
PS(t, m) |
int, int |
object | m = 0 → đưa về 1 |
rutGon() |
— | void (sửa this) |
ƯCLN bằng thuật toán Euclid |
cong(PS p) |
PS |
PS mới |
không đổi this |
tang(int n) |
int |
void |
tu += n*mau |
soSanh(PS p) |
PS |
int (−1/0/1) |
theo giá trị thực |
3. Thiết kế
PS
- tu : int
- mau : int
+ PS() PS(int n) PS(int t, int m) PS(PS p)
+ ganTu/layTu ganMau/layMau
+ hienThi() : void
+ rutGon() : void
+ tang(int) : void + gap(int) : void
+ cong/tru/nhan/chia(int) : PS
+ cong/tru/nhan/chia(PS) : PS
+ toString() : String
+ soSanh(PS) : int
Quyết định thiết kế:
private int tu, mau— buổi 10: không cho bên ngoài gánmau = 0tùy tiện.rutGonlàprivate? Đề ghi công khai, nên đểpublicnhưng gọi nội bộ sau mỗi phép toán.soSanhtrảint(không trảboolean) để dùng được cho sắp xếp.tang/gapsửathis; các phépcong/tru/...trả mới — không trộn hai kiểu.
4. Code từng bước
Bước 1 — thuộc tính + constructor có kiểm tra.
public class PS {
private int tu;
private int mau;
public PS() { this(0, 1); } // 0/1
public PS(int n) { this(n, 1); } // n/1
public PS(PS p) { this(p.tu, p.mau); } // sao chép
public PS(int tu, int mau) {
this.tu = tu;
this.mau = (mau == 0) ? 1 : mau; // chặn mẫu 0
chuanHoa(); // đưa dấu lên tử
}Tự kiểm tra: new PS(1, 0) phải ra 1/1,
không lỗi.
Bước 2 — chuẩn hoá dấu và rút gọn.
private void chuanHoa() {
if (mau < 0) { tu = -tu; mau = -mau; }
}
public void rutGon() {
int a = Math.abs(tu), b = Math.abs(mau);
while (b != 0) { int r = a % b; a = b; b = r; } // a = UCLN
if (a != 0) { tu /= a; mau /= a; }
chuanHoa();
}Trace 6/9: a=6,b=9 →
r=6, a=9, b=6 → r=3, a=6, b=3 →
r=0, a=3, b=0 → dừng, a=3 →
6/9 → 2/3.
Bước 3 — getter/setter + hiển thị.
public int layTu() { return tu; }
public int layMau() { return mau; }
public void ganTu(int tu) { this.tu = tu; }
public void ganMau(int mau) { if (mau != 0) this.mau = mau; }
public void hienThi() { System.out.println(tu + "/" + mau); }
@Override
public String toString() { return tu + "/" + mau; }Bước 4 — tang, gap (sửa tại
chỗ).
public void tang(int n) { tu = tu + n * mau; rutGon(); } // +n don vi
public void gap(int n) { tu = tu * n; rutGon(); } // x n lanBước 5 — 4 phép toán với int (trả
PS mới).
public PS cong(int n) { return new PS(tu + n * mau, mau); }
public PS tru(int n) { return new PS(tu - n * mau, mau); }
public PS nhan(int n) { return new PS(tu * n, mau); }
public PS chia(int n) {
if (n == 0) return null; // bao loi o noi goi
return new PS(tu, mau * n);
}Bước 6 — 4 phép toán với PS.
public PS cong(PS p) {
return new PS(tu * p.mau + p.tu * mau, mau * p.mau);
}
public PS tru(PS p) {
return new PS(tu * p.mau - p.tu * mau, mau * p.mau);
}
public PS nhan(PS p) {
return new PS(tu * p.tu, mau * p.mau);
}
public PS chia(PS p) {
if (p.tu == 0) return null;
return new PS(tu * p.mau, mau * p.tu);
}Bước 7 — so sánh.
public int soSanh(PS p) {
double a = (double) tu / mau;
double b = (double) p.tu / p.mau;
if (a < b) return -1;
if (a > b) return 1;
return 0;
}5. Lời giải hoàn chỉnh
// ===== PS.java =====
public class PS {
private int tu;
private int mau;
public PS() { this(0, 1); }
public PS(int n) { this(n, 1); }
public PS(PS p) { this(p.tu, p.mau); }
public PS(int tu, int mau) {
this.tu = tu;
this.mau = (mau == 0) ? 1 : mau;
chuanHoa();
}
private void chuanHoa() {
if (mau < 0) { tu = -tu; mau = -mau; }
}
public int layTu() { return tu; }
public int layMau() { return mau; }
public void ganTu(int tu) { this.tu = tu; }
public void ganMau(int m) { if (m != 0) { this.mau = m; chuanHoa(); } }
public void hienThi() { System.out.println(toString()); }
@Override
public String toString() { return tu + "/" + mau; }
public void rutGon() {
int a = Math.abs(tu);
int b = Math.abs(mau);
while (b != 0) {
int r = a % b;
a = b;
b = r;
}
if (a != 0) { tu /= a; mau /= a; }
chuanHoa();
}
public void tang(int n) { tu = tu + n * mau; rutGon(); }
public void gap(int n) { tu = tu * n; rutGon(); }
public PS cong(int n) { return new PS(tu + n * mau, mau); }
public PS tru(int n) { return new PS(tu - n * mau, mau); }
public PS nhan(int n) { return new PS(tu * n, mau); }
public PS chia(int n) {
if (n == 0) return null;
return new PS(tu, mau * n);
}
public PS cong(PS p) { return new PS(tu * p.mau + p.tu * mau, mau * p.mau); }
public PS tru(PS p) { return new PS(tu * p.mau - p.tu * mau, mau * p.mau); }
public PS nhan(PS p) { return new PS(tu * p.tu, mau * p.mau); }
public PS chia(PS p) {
if (p.tu == 0) return null;
return new PS(tu * p.mau, mau * p.tu);
}
public int soSanh(PS p) {
double a = (double) tu / mau;
double b = (double) p.tu / p.mau;
if (a < b) return -1;
if (a > b) return 1;
return 0;
}
}// ===== QuanLyPS.java =====
public class QuanLyPS {
public static void main(String[] args) {
PS p = new PS(1, 2);
PS q = new PS(5, 7);
// r = p + q
PS r = p.cong(q);
r.rutGon();
System.out.println("p + q = " + r); // 17/14
// t = 2*p - p/q
PS t = p.nhan(2).tru(p.chia(q));
t.rutGon();
System.out.println("t = 2*p - p/q = " + t); // 3/10
// so sanh
int ss = p.soSanh(q);
if (ss < 0) System.out.println("p < q");
else if (ss == 0) System.out.println("p = q");
else System.out.println("p > q");
// mong 5 phan so, sap tang dan
PS[] ds = {
new PS(1, 2), new PS(13, 2), new PS(5, 7),
new PS(6, 9), new PS(30, 23)
};
for (int i = 0; i < ds.length; i++) ds[i].rutGon();
for (int i = 0; i < ds.length - 1; i++) {
for (int j = 0; j < ds.length - 1 - i; j++) {
if (ds[j].soSanh(ds[j + 1]) > 0) {
PS tmp = ds[j];
ds[j] = ds[j + 1];
ds[j + 1] = tmp;
}
}
}
System.out.print("Sau khi sap xep: ");
for (int i = 0; i < ds.length; i++) System.out.print(ds[i] + " ");
System.out.println();
}
}6. Cách chạy
javac PS.java QuanLyPS.java
java QuanLyPS
7. Kết quả chạy mẫu
p + q = 17/14
t = 2*p - p/q = 3/10
p < q
Sau khi sap xep: 1/2 2/3 5/7 30/23 13/2
Kiểm tra tay: 1/2 + 5/7 = (7 + 10)/14 = 17/14 ✓.
2p = 2 × 1/2 = 1; p/q = (1/2)·(7/5) = 7/10;
1 − 7/10 = 3/10 ✓. 6/9 = 2/3 ≈ 0.667;
5/7 ≈ 0.714; 30/23 ≈ 1.304 — đúng thứ tự
✓.
8. Bảng kiểm thử
| # | Ca | Kỳ vọng |
|---|---|---|
| 1 | new PS(1,2).cong(new PS(5,7)) |
17/14 |
| 2 | new PS(6,9).rutGon() |
2/3 |
| 3 | new PS(4,-6) |
-2/3 (dấu đưa lên tử, sau rút gọn) |
| 4 | new PS(1,2).chia(new PS(0,1)) |
null |
| 5 | new PS(3,4).tang(2) |
11/4 (3/4 + 2 = 11/4) |
| 6 | new PS(0,5).rutGon() |
0/1 |
9. Trace rutGon với
-6/9
| Bước | tu |
mau |
a |
b |
Ghi chú |
|---|---|---|---|---|---|
| vào | −6 | 9 | 6 | 9 | lấy trị tuyệt đối |
| lặp 1 | 9 | 6 | r = 6 |
||
| lặp 2 | 6 | 3 | r = 3 |
||
| lặp 3 | 3 | 0 | r = 0 |
||
| thoát | 3 | ƯCLN = 3 | |||
| chia | −2 | 3 | tu/3 = −2, mau/3 = 3 |
||
| chuẩn hoá | −2 | 3 | mẫu dương, giữ nguyên |
10. Lỗi thường gặp
| Code sai | Thông báo / hiện tượng | Sửa |
|---|---|---|
public void PS(int t, int m) |
Không lỗi biên dịch — nhưng không phải constructor | Bỏ void |
PS() không tồn tại vì đã viết
PS(int,int) |
constructor PS ... cannot be applied to given types |
Thêm public PS() { this(0,1); } |
tu = tu; trong constructor |
Phân số luôn 0/0 |
this.tu = tu; |
rutGon chia cho a = 0 khi
tu = 0 |
ArithmeticException: / by zero |
Bọc if (a != 0) |
soSanh dùng tu < p.tu |
So sánh sai (1/2 với 1/3) | Nhân chéo hoặc so double |
cong(int) viết new PS(tu + n, mau) |
1/2 + 1 = 2/2 thay vì 3/2 |
tu + n*mau |
nhan(PS) không rút gọn |
Kết quả 2/4 thay vì 1/2 |
Rút gọn trong constructor hoặc sau phép tính |
11. Câu hỏi kiểm tra hiểu
- Vì sao
PS(PS p)cần tồn tại, trong khiPS q = p;đã "sao chép" được? (Gợi ý:q = pchỉ copy tham chiếu — buổi 5.) - Vì sao
tangsửathiscòncongtrả về mới? Nếu đổicongthành sửathisthì chương trình nào hỏng? - So sánh bằng
doublecó rủi ro gì với phân số rất lớn? Nêu cách chính xác hơn.
12. Mở rộng
- Thêm
PS nghichDao(),boolean laToiGian(). - Sắp xếp giảm dần bằng cách đổi
> 0thành< 0. - Cộng dồn mảng:
PS tong = new PS(0,1); for (...) tong = tong.cong(ds[i]);
TỰ KIỂM TRA 60 GIÂY: 1/2 + 1 = ?
(3/2); 6/9 rút gọn? (2/3); mẫu âm
xử lý ở đâu?
BÀI 2 — LỚP ATM
0. Đề bài
ATM có soTK (String), soDu
(double). Constructor ATM(stk, sd) với số dư tối thiểu 100.
Phương thức laySTK, laySoDu,
toString, napTien(st),
rutTien(st) trả boolean,
chuyenTien(ATM tk, st) trả boolean. Chương
trình: 5 tài khoản; 1111111 nạp 1000; 1111111
chuyển cho 2222222 500; 2222222 rút 200; in số
dư từng tài khoản và tổng.
1. Yêu cầu ẩn
- Số dư ban đầu < 100 → nâng lên 100 (đề nói "tối thiểu là 100").
rutTienvượt số dư → trảfalse, không trừ tiền, không ném ngoại lệ.- Nạp/rút số âm hoặc 0 phải bị từ chối.
chuyenTienphải rút trước, chỉ nạp khi rút thành công (nếu không sẽ sinh tiền từ không khí).- Không cho chuyển cho chính mình (
tk == this).
2. Input → Output
| Phương thức | Vào | Ra | Trạng thái sau |
|---|---|---|---|
napTien(st) |
st > 0 |
void | soDu += st |
rutTien(st) |
0 < st ≤ soDu |
true |
soDu -= st |
rutTien(st) |
st > soDu |
false |
không đổi |
chuyenTien(tk, st) |
đủ tiền | true |
trừ this, cộng tk |
3. Thiết kế
ATM
- soTK : String (final: khong doi)
- soDu : double
+ ATM(stk, sd)
+ laySTK() : String
+ laySoDu() : double
+ napTien(double) : void
+ rutTien(double) : boolean
+ chuyenTien(ATM, double) : boolean
+ toString() : String
Vì sao rutTien trả boolean: người gọi
phải biết giao dịch có thành công hay không để in thông
báo. Nếu trả void, chương trình sẽ in "Rút thành công" dù
thất bại.
4. Code từng bước
Bước 1 — thuộc tính, hằng số, constructor.
public class ATM {
private static final double MIN = 100;
private final String soTK;
private double soDu;
public ATM(String soTK, double soDu) {
this.soTK = soTK;
this.soDu = (soDu < MIN) ? MIN : soDu;
}Bước 2 — nạp tiền (chỉ nhận số dương).
public void napTien(double st) {
if (st > 0) soDu += st;
}Bước 3 — rút tiền, kiểm tra đủ số dư.
public boolean rutTien(double st) {
if (st <= 0 || st > soDu) return false;
soDu -= st;
return true;
}Bước 4 — chuyển tiền: rút rồi mới nạp.
public boolean chuyenTien(ATM tk, double st) {
if (tk == null || tk == this) return false;
if (!rutTien(st)) return false; // rut that bai -> khong nap
tk.napTien(st);
return true;
}5. Lời giải hoàn chỉnh
// ===== ATM.java =====
public class ATM {
private static final double SO_DU_TOI_THIEU = 100;
private final String soTK;
private double soDu;
public ATM(String soTK, double soDu) {
this.soTK = soTK;
this.soDu = (soDu < SO_DU_TOI_THIEU) ? SO_DU_TOI_THIEU : soDu;
}
public String laySTK() { return soTK; }
public double laySoDu() { return soDu; }
public void napTien(double st) {
if (st > 0) soDu += st;
}
public boolean rutTien(double st) {
if (st <= 0 || st > soDu) return false;
soDu -= st;
return true;
}
public boolean chuyenTien(ATM tk, double st) {
if (tk == null || tk == this) return false;
if (!rutTien(st)) return false;
tk.napTien(st);
return true;
}
@Override
public String toString() {
return "TK " + soTK + " - so du: " + soDu;
}
}// ===== QuanLyATM.java =====
public class QuanLyATM {
public static void main(String[] args) {
ATM[] ds = new ATM[5];
ds[0] = new ATM("1111111", 1000);
ds[1] = new ATM("2222222", 100);
ds[2] = new ATM("3333333", 100);
ds[3] = new ATM("4444444", 50); // < 100 -> thanh 100
ds[4] = new ATM("5555555", 100);
ds[0].napTien(1000);
System.out.println("Chuyen 500: " + ds[0].chuyenTien(ds[1], 500));
System.out.println("Rut 200 : " + ds[1].rutTien(200));
System.out.println("Rut 99999: " + ds[1].rutTien(99999));
double tong = 0;
for (int i = 0; i < ds.length; i++) {
System.out.println(ds[i]);
tong += ds[i].laySoDu();
}
System.out.println("Tong tien: " + tong);
}
}6. Cách chạy
javac ATM.java QuanLyATM.java
java QuanLyATM
7. Kết quả chạy mẫu
Chuyen 500: true
Rut 200 : true
Rut 99999: false
TK 1111111 - so du: 1500.0
TK 2222222 - so du: 400.0
TK 3333333 - so du: 100.0
TK 4444444 - so du: 100.0
TK 5555555 - so du: 100.0
Tong tien: 2200.0
Kiểm tra tay: ban đầu 1000 + 100·4 = 1400; nạp 1000 →
2400; chuyển 500 (không đổi tổng); rút 200 → 2200 ✓.
8. Bảng kiểm thử
| # | Ca | Kỳ vọng |
|---|---|---|
| 1 | Tạo ATM("X", 50) |
số dư 100 |
| 2 | Rút 200 khi dư 100 | false, dư vẫn 100 |
| 3 | Nạp −50 | không đổi |
| 4 | Chuyển 500 khi dư 100 | false, cả hai không đổi |
| 5 | Chuyển cho chính mình | false |
| 6 | Chuyển 100 từ A(100) sang B(100) | A=0, B=200 |
9. Trace chuyenTien
ds[0] dư 2000, ds[1] dư 100, chuyển
500:
| Bước | ds[0].soDu |
ds[1].soDu |
Kết quả |
|---|---|---|---|
gọi chuyenTien(ds[1], 500) |
2000 | 100 | — |
rutTien(500): 500 ≤ 2000 |
1500 | 100 | true |
tk.napTien(500) |
1500 | 600 | — |
| trả về | true |
10. Lỗi thường gặp
| Code sai | Thông báo / hiện tượng | Sửa |
|---|---|---|
soDu = soDu - st; không kiểm tra |
Số dư âm | Kiểm tra trước, trả false |
chuyenTien: nạp trước rồi rút |
Sinh tiền khi rút thất bại | Rút trước |
if (tk == this) so sánh soTK bằng
== |
Sai/chập chờ | So tk == this hoặc
tk.soTK.equals(soTK) |
private final String soTK; không gán trong
constructor |
variable soTK might not have been initialized |
Gán trong mọi constructor |
void rutTien(...) |
Không biết giao dịch thành công | Trả boolean |
11. Câu hỏi kiểm tra hiểu
- Vì sao phải rút trước khi nạp trong
chuyenTien? - Nếu bỏ kiểm tra
st <= 0, rút −100 sẽ làm gì số dư? - Vì sao
soTKnênfinalmàsoDuthì không?
12. Mở rộng
- Thêm
static int soLuongTKđếm số tài khoản (buổi 9). - Thêm lịch sử giao dịch bằng
String[] lichSu+int soGiaoDich. - Tính lãi suất:
guiLai(double laiSuat).
TỰ KIỂM TRA 60 GIÂY: rút thất bại trả gì? Tổng tiền có đổi khi chuyển khoản không? Số dư tối thiểu xử lý ở đâu?
BÀI 3 (Ở NHÀ) — GIẢI PHƯƠNG TRÌNH BẬC 2 THEO OOP
0. Đề bài
Viết chương trình giải phương trình ax² + bx + c = 0
theo cách lập trình hướng đối tượng.
1. Yêu cầu ẩn
a = 0→ phương trình bậc 1, không được chia cho 0.a = 0, b = 0→ vô nghiệm hoặc vô số nghiệm tùyc.delta < 0→ vô nghiệm thực;delta = 0→ nghiệm kép.- Không in
-0.0.
2. Input → Output
| Vào | Ra |
|---|---|
a, b, c (double) |
mô tả nghiệm: 2 nghiệm / nghiệm kép / vô nghiệm / vô số / bậc 1 |
3. Thiết kế
Lớp PTB2 chứa hệ số và hành vi
giải; main chỉ nhập và in. Không để
main chứa công thức (vì như vậy không phải OOP, chỉ là code
thủ tục bọc trong class).
PTB2
- a, b, c : double
+ PTB2(a, b, c)
+ giai() : String
4. Code từng bước
Bước 1 — thuộc tính + constructor.
public class PTB2 {
private double a, b, c;
public PTB2(double a, double b, double c) {
this.a = a; this.b = b; this.c = c;
}Bước 2 — xử lý a = 0.
public String giai() {
if (a == 0) {
if (b == 0) {
return (c == 0) ? "Vo so nghiem" : "Vo nghiem";
}
return "Bac 1, nghiem x = " + (-c / b);
}Bước 3 — delta và 3 trường hợp.
double delta = b * b - 4 * a * c;
if (delta < 0) return "Vo nghiem";
if (delta == 0) return "Nghiem kep x = " + (-b / (2 * a));
double x1 = (-b + Math.sqrt(delta)) / (2 * a);
double x2 = (-b - Math.sqrt(delta)) / (2 * a);
return "x1 = " + x1 + ", x2 = " + x2;
}5. Lời giải hoàn chỉnh
// ===== PTB2.java =====
public class PTB2 {
private double a, b, c;
public PTB2(double a, double b, double c) {
this.a = a;
this.b = b;
this.c = c;
}
public String giai() {
if (a == 0) {
if (b == 0) return (c == 0) ? "Vo so nghiem" : "Vo nghiem";
return "Bac 1, nghiem x = " + (-c / b);
}
double delta = b * b - 4 * a * c;
if (delta < 0) return "Vo nghiem";
if (delta == 0) return "Nghiem kep x = " + (-b / (2 * a));
double x1 = (-b + Math.sqrt(delta)) / (2 * a);
double x2 = (-b - Math.sqrt(delta)) / (2 * a);
return "x1 = " + x1 + ", x2 = " + x2;
}
}// ===== GiaiPT.java =====
import java.util.Scanner;
public class GiaiPT {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
System.out.print("a = "); double a = sc.nextDouble();
System.out.print("b = "); double b = sc.nextDouble();
System.out.print("c = "); double c = sc.nextDouble();
PTB2 pt = new PTB2(a, b, c);
System.out.println(pt.giai());
sc.close();
}
}6. Cách chạy
javac PTB2.java GiaiPT.java
java GiaiPT
7. Kết quả chạy mẫu
a = 1
b = -3
c = 2
x1 = 2.0, x2 = 1.0
Kiểm tra: 1 − 3 + 2 = 0 ✓, 4 − 6 + 2 = 0
✓.
8. Bảng kiểm thử
| # | a, b, c | Kỳ vọng |
|---|---|---|
| 1 | 1, −3, 2 | x1 = 2.0, x2 = 1.0 |
| 2 | 1, 2, 1 | Nghiem kep x = -1.0 |
| 3 | 1, 0, 1 | Vo nghiem |
| 4 | 0, 2, −4 | Bac 1, nghiem x = 2.0 |
| 5 | 0, 0, 0 | Vo so nghiem |
| 6 | 0, 0, 5 | Vo nghiem |
9. Trace
a=1, b=-3, c=2 → delta = 9 − 8 = 1 > 0 →
x1 = (3 + 1)/2 = 2.0,
x2 = (3 − 1)/2 = 1.0.
10. Lỗi thường gặp
| Code sai | Hiện tượng | Sửa |
|---|---|---|
Không xét a == 0 |
Chia 0 → NaN/Infinity |
Xét trước |
Math.sqrt(delta) khi delta âm |
NaN |
Kiểm tra delta |
-b + Math.sqrt(delta) / 2*a |
Sai thứ tự phép toán | Ngoặc đầy đủ |
if (a = 0) |
Lỗi biên dịch | == |
11. Câu hỏi kiểm tra hiểu
- Vì sao lớp
PTB2nên chứagiai()thay vì đểmaintính? - Vì sao
delta == 0dùng được dù là số thực? Khi nào phép so sánh này sai? - Nếu
arất nhỏ (1e-12), có nên coi là 0 không?
12. Mở rộng
- Trả về
double[]các nghiệm thay vìString(mảng rỗng nếu vô nghiệm). - Thêm
nghiemNguyen()kiểm tra nghiệm nguyên.
BÀI 4 (Ở NHÀ) — QUẢN LÝ NHÂN VIÊN
0. Đề bài
NhanVien: họ tên, ngày sinh, chức vụ, hệ số lương, lương
cơ bản. Tính:
luong = (heSoLuong + heSoPhuCap) * luongCoBan, phụ cấp GĐ
1.0, PGD 0.8, TP 0.5, PP 0.4; BHXH = 6% lương; BHTN = 1% lương; thực
lĩnh = lương − BHXH − BHTN. Chương trình: mảng 5 nhân viên, in họ
tên/lương/BHXH/BHTN/thực lĩnh, tổng lương, người lớn tuổi nhất, lương
trung bình.
1. Yêu cầu ẩn
- Chức vụ lạ → phụ cấp 0 (hoặc báo lỗi), không được
null. ngaySinhdùngjava.util.Date:getYear() + 1900, tháng 0–11.- Tuổi = năm hiện tại − năm sinh; phải xử lý người sinh năm nay.
- Tính tiền nên làm tròn khi in (tránh
5370000.000000001).
2. Input → Output
| Vào | Kiểu | Ra |
|---|---|---|
| 5 nhân viên | object | bảng lương + tổng + người lớn tuổi nhất + TB |
3. Thiết kế
NhanVien
- hoTen : String
- ngaySinh : Date
- chucVu : String
- heSoLuong, luongCoBan : double
+ NhanVien(...)
+ phuCap() : double
+ luong() : double
+ bhxh() : double
+ bhtn() : double
+ thucLinh() : double
+ tuoi() : int
phuCap() là phương thức suy ra từ
chucVu — không lưu thành thuộc tính (tránh dữ liệu không
đồng bộ).
4. Code từng bước
Bước 1 — constructor, Date.
public NhanVien(String hoTen, Date ngaySinh, String chucVu,
double heSoLuong, double luongCoBan) { ... }Bước 2 — phụ cấp theo chức vụ.
public double phuCap() {
if (chucVu.equals("GD")) return 1.0;
if (chucVu.equals("PGD")) return 0.8;
if (chucVu.equals("TP")) return 0.5;
if (chucVu.equals("PP")) return 0.4;
return 0;
}Bước 3 — lương, bảo hiểm, thực lĩnh.
public double luong() { return (heSoLuong + phuCap()) * luongCoBan; }
public double bhxh() { return luong() * 0.06; }
public double bhtn() { return luong() * 0.01; }
public double thucLinh() { return luong() - bhxh() - bhtn(); }Bước 4 — tuổi.
public int tuoi() {
return 2026 - (ngaySinh.getYear() + 1900);
}5. Lời giải hoàn chỉnh
// ===== NhanVien.java =====
import java.util.Date;
public class NhanVien {
private String hoTen;
private Date ngaySinh;
private String chucVu;
private double heSoLuong;
private double luongCoBan;
public NhanVien(String hoTen, Date ngaySinh, String chucVu,
double heSoLuong, double luongCoBan) {
this.hoTen = hoTen;
this.ngaySinh = ngaySinh;
this.chucVu = chucVu;
this.heSoLuong = heSoLuong;
this.luongCoBan = luongCoBan;
}
public String getHoTen() { return hoTen; }
public Date getNgaySinh() { return ngaySinh; }
public String getChucVu() { return chucVu; }
public double phuCap() {
if (chucVu.equals("GD")) return 1.0;
if (chucVu.equals("PGD")) return 0.8;
if (chucVu.equals("TP")) return 0.5;
if (chucVu.equals("PP")) return 0.4;
return 0;
}
public double luong() { return (heSoLuong + phuCap()) * luongCoBan; }
public double bhxh() { return luong() * 0.06; }
public double bhtn() { return luong() * 0.01; }
public double thucLinh() { return luong() - bhxh() - bhtn(); }
public int tuoi() { return 2026 - (ngaySinh.getYear() + 1900); }
@Override
public String toString() {
return hoTen + " | " + chucVu + " | luong " + Math.round(luong())
+ " | BHXH " + Math.round(bhxh())
+ " | BHTN " + Math.round(bhtn())
+ " | thuc linh " + Math.round(thucLinh());
}
}// ===== QuanLyNV.java =====
import java.util.Date;
public class QuanLyNV {
public static void main(String[] args) {
NhanVien[] ds = new NhanVien[5];
ds[0] = new NhanVien("Nguyen Van A", new Date("01/15/1960"), "GD", 5.0, 1500000);
ds[1] = new NhanVien("Tran Thi B", new Date("05/20/1975"), "PGD", 4.0, 1500000);
ds[2] = new NhanVien("Le Van C", new Date("11/02/1990"), "TP", 3.0, 1200000);
ds[3] = new NhanVien("Pham Thi D", new Date("03/08/1985"), "PP", 2.0, 1200000);
ds[4] = new NhanVien("Hoang Van E", new Date("07/19/2000"), "PP", 1.5, 1000000);
double tong = 0;
for (int i = 0; i < ds.length; i++) {
System.out.println(ds[i]);
tong += ds[i].luong();
}
NhanVien gia = ds[0];
for (int i = 1; i < ds.length; i++) {
if (ds[i].tuoi() > gia.tuoi()) gia = ds[i];
}
System.out.println("Tong luong : " + Math.round(tong));
System.out.println("Luong trung binh: " + Math.round(tong / ds.length));
System.out.println("Lon tuoi nhat : " + gia.getHoTen() + " (" + gia.tuoi() + " tuoi)");
}
}6. Cách chạy
javac NhanVien.java QuanLyNV.java
java QuanLyNV
7. Kết quả chạy mẫu
Nguyen Van A | GD | luong 9000000 | BHXH 540000 | BHTN 90000 | thuc linh 8370000
Tran Thi B | PGD | luong 7200000 | BHXH 432000 | BHTN 72000 | thuc linh 6696000
Le Van C | TP | luong 4200000 | BHXH 252000 | BHTN 42000 | thuc linh 3906000
Pham Thi D | PP | luong 2880000 | BHXH 172800 | BHTN 28800 | thuc linh 2678400
Hoang Van E | PP | luong 1900000 | BHXH 114000 | BHTN 19000 | thuc linh 1767000
Tong luong : 25180000
Luong trung binh: 5036000
Lon tuoi nhat : Nguyen Van A (66 tuoi)
Kiểm tra tay dòng 1:
(5.0 + 1.0) × 1 500 000 = 9 000 000; BHXH 6% = 540 000;
BHTN 1% = 90 000; thực lĩnh 8 370 000 ✓. Tổng:
9.0 + 7.2 + 4.2 + 2.88 + 1.9 = 25.18 triệu ✓. TB = 5.036
triệu ✓.
8. Bảng kiểm thử
| # | Ca | Kỳ vọng |
|---|---|---|
| 1 | GD, hsl 5, lcb 1.5tr | lương 9.000.000, thực lĩnh 8.370.000 |
| 2 | Chức vụ lạ "XX" |
phụ cấp 0, lương = hsl × lcb |
| 3 | Người sinh 2000 | 26 tuổi (năm 2026) |
| 4 | hsl 0 | lương = phụ cấp × lcb |
9. Trace
Tran Thi B: phuCap = 0.8;
hsl + pc = 4.8;
luong = 4.8 × 1 500 000 = 7 200 000;
bhxh = 432 000; bhtn = 72 000;
thucLinh = 7 200 000 − 504 000 = 6 696 000.
10. Lỗi thường gặp
| Code sai | Thông báo / hiện tượng | Sửa |
|---|---|---|
chucVu == "GD" |
Luôn sai | chucVu.equals("GD") |
ngaySinh.getYear() dùng trực tiếp |
Tuổi lệch 1900 | + 1900 |
new Date(1960, 0, 15) |
Deprecated, năm lệch | new Date("01/15/1960") |
luong = hsl + pc * lcb |
Thiếu ngoặc | (hsl + pc) * lcb |
Math.round cho cả BHXH |
Mất số lẻ, sai tổng | Chỉ làm tròn khi in |
11. Câu hỏi kiểm tra hiểu
- Vì sao
phuCap()không lưu thành thuộc tính? - Nếu đổi
2026thànhngaySinhcủa năm sau thìtuoi()âm — cần chặn thế nào? - Vì sao phải làm tròn khi in chứ không làm tròn từng khoản?
12. Mở rộng
- Thêm
static double LUONG_CO_BAN_CHUANvà kiểm tra lương cơ bản tối thiểu. - Sắp xếp nhân viên theo thực lĩnh giảm dần.
- Thêm chức vụ mới bằng
switchthay chuỗiif(buổi 6).