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 đó PS có 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

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ế:

  1. private int tu, mau — buổi 10: không cho bên ngoài gán mau = 0 tùy tiện.
  2. rutGon là private? Đề ghi công khai, nên để public nhưng gọi nội bộ sau mỗi phép toán.
  3. soSanh trả int (không trả boolean) để dùng được cho sắp xếp.
  4. tang/gap sửa this; các phép cong/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 lan

Bướ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

  1. Vì sao PS(PS p) cần tồn tại, trong khi PS q = p; đã "sao chép" được? (Gợi ý: q = p chỉ copy tham chiếu — buổi 5.)
  2. Vì sao tang sửa this còn cong trả về mới? Nếu đổi cong thành sửa this thì chương trình nào hỏng?
  3. So sánh bằng double có 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

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

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

  1. Vì sao phải rút trước khi nạp trong chuyenTien?
  2. Nếu bỏ kiểm tra st <= 0, rút −100 sẽ làm gì số dư?
  3. Vì sao soTK nên final mà soDu thì không?

12. Mở rộng

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

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

  1. Vì sao lớp PTB2 nên chứa giai() thay vì để main tính?
  2. Vì sao delta == 0 dùng được dù là số thực? Khi nào phép so sánh này sai?
  3. Nếu a rất nhỏ (1e-12), có nên coi là 0 không?

12. Mở rộng


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

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

  1. Vì sao phuCap() không lưu thành thuộc tính?
  2. Nếu đổi 2026 thành ngaySinh của năm sau thì tuoi() âm — cần chặn thế nào?
  3. 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


Checklist nộp Lab 02

🎧
Lồng tiếng tóm tắt bài giảng (ZeroTTS AI)
Đang đọc: Mai Chi (Nữ) · Thời lượng ~1–2 phút
ZeroTTS AI
00:00
--:--
📝 Lời thuyết minh (Tự động tô sáng câu đang đọc • Bấm vào câu để tua tới):
Đang nạp dữ liệu lời đọc...