Java Foundations
13/39
Bài 13 / 39~13 phútCú pháp Java & Kiểu dữ liệuLộ trình · chặng 02/21Miễn phí… lượt xem

Toán tử số học và so sánh — phép chia nguyên, modulo âm và bẫy ==

Giải thích toán tử số học, so sánh và gán trong Java: cơ chế integer division, modulo với số âm, bẫy so sánh == và ép kiểu ngầm trong compound assignment.

TL;DR: Toán tử số học, so sánh và gán là nhóm bạn gõ nhiều nhất mỗi ngày — và cũng là nhóm chứa nhiều bẫy im lặng nhất. / giữa hai số nguyên là integer division: truncate về phía 0, không làm tròn. % với số âm giữ dấu của số bị chia — khác modulo toán học. == trên String/Integer so sánh địa chỉ chứ không phải giá trị. Compound assignment (+=, -=...) ngầm narrowing cast về kiểu vế trái — byte b += 100 có thể overflow mà không một lời cảnh báo. Bài này mổ từng bẫy theo spec JLS, kèm cách viết đúng.

Bạn đã biết khai báo biến (Bài 1), chọn đúng Kiểu nguyên thuỷ, hiểu kiểu tham chiếu (Bài 3). Bây giờ đến lúc làm việc với giá trị — đó là nhiệm vụ của toán tử.

Hệ thống toán tử Java chia làm hai nhóm lớn. Bài này đi nhóm thứ nhất — số học, so sánh và gán — những phép tính dùng hằng ngày kèm các pitfall ẩn của chúng. Nhóm thứ hai — logic/short-circuit, bitwise, ternary và thứ tự ưu tiên — nằm ở bài kế tiếp.

1. Analogy máy tính và hộp táo mô tả các nhóm toán tử như thế nào?

Nhìn nhóm toán tử của bài này qua 2 hình ảnh quen thuộc:

  • Máy tính văn phòng — cộng, trừ, nhân, chia, chia lấy dư. Bạn bấm số, bấm phép tính, bấm = ra kết quả. Đó là arithmetic operators.
  • Hộp có X quả táo — "hộp A có nhiều hơn hộp B không?" — đó là comparison operators, luôn trả true/false.
Đời thườngConcept
Máy tính văn phòngArithmetic: + - * / %
So đếm hai hộp táoComparison: == != < > <= >=
Ghi đè số mới lên bảngAssignment: = += -=...

2. Các toán tử số học cơ bản hoạt động ra sao trong Java?

OperatorÝ nghĩaOperand typeVí dụKết quả
+Cộng (hoặc nối chuỗi)numeric / String3 + 47
-Trừnumeric10 - 37
*Nhânnumeric4 * 520
/Chianumeric10 / 33 (nguyên!)
%Chia lấy dư (modulo)numeric10 % 31
Java
int a = 10;
int b = 3;

System.out.println(a + b);   // 13
System.out.println(a - b);   // 7
System.out.println(a * b);   // 30
System.out.println(a / b);   // 3  -- integer division: phan nguyen, bo phan du
System.out.println(a % b);   // 1  -- phan du cua phep chia

2.1 String concatenation với +

Khi một trong hai operand là String, + trở thành nối chuỗi:

Java
String ten = "An";
int tuoi = 25;
System.out.println("Xin chao, " + ten + ", " + tuoi + " tuoi.");
// Xin chao, An, 25 tuoi.

// Canh giac: + tinh tu trai sang phai
System.out.println("Tong: " + 1 + 2);  // "Tong: 12" -- khong phai "Tong: 3"!
System.out.println("Tong: " + (1 + 2)); // "Tong: 3" -- dung ngoac

3. Tại sao phép chia nguyên và modulo với số âm lại dễ gây bẫy lỗi?

3.1 Integer division: kết quả bị truncate

Khi cả hai operand là kiểu nguyên (int, long), phép / cho kết quả nguyên — phần thập phân bị bỏ hoàn toàn (truncate toward zero, không phải làm tròn):

Java
System.out.println(5 / 2);    //  2  -- KHONG phai 2.5!
System.out.println(-5 / 2);   // -2  -- truncate ve phia 0 (khong phai -3)
System.out.println(7 / 10);   //  0  -- nho hon 1, bo het

Cây quyết định cho biểu thức a chia b: nếu cả hai toán hạng là kiểu nguyên thì chạy chia nguyên cắt về 0 cho ra 5 chia 2 bằng 2, ngược lại chạy chia thực cho ra 2.5

Muốn kết quả thực, ít nhất một operand phải là double hoặc float:

Java
System.out.println(5.0 / 2);    // 2.5
System.out.println(5 / 2.0);    // 2.5
System.out.println((double) 5 / 2); // 2.5 -- ep kieu truoc khi chia
Pitfall hay gặp — tính trung bình sai
Java
// SAI: ca hai la int, chia nguyen truoc roi moi cong -> mat do chinh xac
int a = 1, b = 2;
double trungBinh = (a + b) / 2;      // = 1.0 (khong phai 1.5!)

// DUNG: ep kieu de lam phep chia thuc
double trungBinhDung = (a + b) / 2.0;  // = 1.5
double trungBinhDung2 = (double)(a + b) / 2;  // = 1.5

3.2 Modulo với số âm

Java định nghĩa % theo công thức: (a / b) * b + a % b == a. Kết quả % giữ dấu của dividend (số bị chia):

Java
System.out.println( 7 %  3);  //  1  (7 = 2*3 + 1)
System.out.println(-7 %  3);  // -1  (KHONG phai 2 nhu toan hoc!)
System.out.println( 7 % -3);  //  1  (dau la dau cua 7 -- duong)
System.out.println(-7 % -3);  // -1

Nếu cần kết quả modulo luôn dương (như trong vòng lặp index):

Java
int n = -7, m = 3;
int modDuong = ((n % m) + m) % m;  // 2 -- modulo duong

3.3 So sánh String và Integer bằng == — sai!

Như bài 1 và 3 đã giải thích:

  • == so sánh địa chỉ (reference), không phải giá trị.
  • ✅ So sánh chuỗi: dùng .equals() hoặc Objects.equals().
  • ✅ So sánh wrapper Integer, Long...: dùng .equals().
Java
String s1 = new String("hello");
String s2 = new String("hello");
System.out.println(s1 == s2);      // false -- khac object
System.out.println(s1.equals(s2)); // true  -- cung noi dung

Integer a = 200;
Integer b = 200;
System.out.println(a == b);      // false -- ngoai Integer Cache
System.out.println(a.equals(b)); // true

Thử ngẫmbạn tính chỉ số vòng lặp cho một hàng đợi tuần hoàn bằng %, và giá trị đầu vào có thể âm — công thức modulo Java có tự cho kết quả không âm, hay bạn phải cộng thêm?

4. Các toán tử so sánh đánh giá điều kiện như thế nào?

OperatorÝ nghĩaVí dụKết quả
==Bằng nhau5 == 5true
!=Khác nhau5 != 3true
<Nhỏ hơn3 < 5true
>Lớn hơn5 > 3true
<=Nhỏ hơn hoặc bằng5 <= 5true
>=Lớn hơn hoặc bằng4 >= 5false

Kết quả luôn là boolean. Dùng trong if, while, for, và làm giá trị gán cho biến boolean.

Java
int diem = 75;
boolean dat = diem >= 50;       // true
boolean xuatSac = diem >= 90;   // false

if (diem >= 50 && diem < 90) {
    System.out.println("Dat -- chua xuat sac");
}

5. Phép gán và toán tử gán kết hợp hoạt động ra sao?

OperatorTương đươngVí dụ
=(gán trực tiếp)x = 5
+=x = x + nx += 3
-=x = x - nx -= 2
*=x = x * nx *= 4
/=x = x / nx /= 2
%=x = x % nx %= 3
Java
int x = 10;
x += 5;   // x = 15
x -= 3;   // x = 12
x *= 2;   // x = 24
x /= 4;   // x = 6
x %= 4;   // x = 2

5.1 Increment và Decrement

OperatorÝ nghĩaGhi chú
x++Tăng x lên 1 (post-increment)Trả giá trị cũ trước khi tăng
++xTăng x lên 1 (pre-increment)Tăng rồi trả giá trị mới
x--Giảm x xuống 1 (post-decrement)Trả giá trị cũ trước khi giảm
--xGiảm x xuống 1 (pre-decrement)Giảm rồi trả giá trị mới
Java
int i = 5;
System.out.println(i++);  // in 5 (gia tri cu), sau do i = 6
System.out.println(i);    // 6
System.out.println(++i);  // tang truoc: i = 7, in 7
System.out.println(i);    // 7

Thử ngẫmbạn thấy một dòng gộp cả i++ lẫn ++i trong cùng biểu thức khi review code đồng nghiệp — bạn để nguyên, hay đề nghị tách thành 2 dòng riêng? Vì sao?

6. Tại sao toán tử gán kết hợp lại tự động ép kiểu ngầm?

Đây là pitfall ít người biết:

Java
byte b = 10;

// SAI -- COMPILE ERROR
// b = b + 1;
// Ly do: b + 1 la int (byte duoc promote len int theo integer promotion)
// int khong tu dong thu hep xuong byte

// DUNG -- compound assignment ngam cast ket qua ve kieu goc
b += 1;  // OK -- tuong duong (byte)(b + 1)
b++;     // OK -- tuong tu

Compound assignment operators (+=, -=, *=, /=, %=) ngầm thực hiện narrowing cast về kiểu của vế trái. Điều này có thể gây data loss ngầm:

Java
byte b = 100;
b += 100;  // 100 + 100 = 200, nhung byte chi chua den 127!
System.out.println(b);  // -56 -- overflow ngam, khong bao loi!
Java
// Tuong tu voi short
short s = 30_000;
s += 10_000;  // 40_000 vuot short range (32_767) -- overflow ngam
System.out.println(s);  // -25536
Pitfall — compound assignment và narrowing

b += 1 ngầm giống b = (byte)(b + 1). Nếu kết quả vượt range của kiểu, overflow xảy ra im lặng — không exception, không warning từ compiler.

Khi dùng byte/short trong vòng lặp cộng dồn, hãy cẩn thận overflow ngầm này.

7. Mã nguồn ví dụ minh họa tổng hợp hoạt động như thế nào?

Java
public class ArithmeticDemo {
    public static void main(String[] args) {

        // --- Integer division pitfall ---
        System.out.println("5 / 2 = " + (5 / 2));       // 2
        System.out.println("5.0 / 2 = " + (5.0 / 2));   // 2.5

        // --- Modulo am ---
        System.out.println("-7 % 3 = " + (-7 % 3));      // -1 (dau cua dividend)

        // --- String concat tinh trai sang phai ---
        System.out.println("Tong: " + 1 + 2);            // Tong: 12
        System.out.println("Tong: " + (1 + 2));          // Tong: 3

        // --- Comparison tra boolean ---
        int score = 75;
        boolean passed = score >= 50;
        System.out.println("Passed: " + passed);         // true

        // --- Increment: post vs pre ---
        int i = 5;
        System.out.println(i++);  // 5 -- tra gia tri cu roi moi tang
        System.out.println(++i);  // 7 -- tang truoc roi tra gia tri moi

        // --- Compound assignment va byte overflow ngam ---
        byte b = 100;
        b += 100;  // (byte)(200) = -56
        System.out.println("byte b = " + b);  // -56
    }
}

Thử ngẫmđoạn demo trên không throw exception ở bước overflow byte — nếu đây là code tính lương thật trong dự án của bạn, bạn thêm bước kiểm tra nào để bug này không lặng lẽ lọt qua production?

8. Bảng tổng hợp các toán tử số học, so sánh và gán gồm những gì?

NhómOperatorsOperandTrả vềGhi chú
Arithmetic+ - * / %numericnumeric/ là integer division nếu cả hai int/long
String concat+String + anyStringTính từ trái sang phải
Comparison== != < > <= >=numeric/referenceboolean== so sánh reference với object
Assignment= += -= *= /= %=matching type(side effect)Compound ngầm narrow cast
Increment++ --numericnumericPre vs post khác thứ tự

9. Tài liệu Deep Dive Oracle quy định gì về các toán tử này?

📚 Deep Dive Oracle (optional)

Spec chính thức Java 21:

Diễn giải đơn giản: JLS §15.17 spec rõ ràng rằng integer division "truncates toward zero" — đây là nguyên nhân 5/2=2, -5/2=-2. §15.26.2 quy định compound assignment thực hiện narrowing cast ngầm về kiểu vế trái — lý do byte b += 1 được compile trong khi b = b + 1 thì lỗi.

10. Điểm cốt lõi cần nhớ về các nhóm toán tử là gì?

  • Integer division (/): Cắt bỏ phần thập phân về 0 khi chia hai số nguyên; ép kiểu sang double nếu cần số thực.
  • Modulo (%): Giữ nguyên dấu của số bị chia (dividend); dùng Math.floorMod() nếu cần kết quả luôn dương.
  • So sánh đối tượng: Luôn dùng .equals() thay vì == khi kiểm tra nội dung String hoặc Integer.
  • Compound assignment (+=): Tự động narrowing cast về vế trái, tiềm ẩn nguy cơ tràn số âm thầm.

11. Tự kiểm tra kiến thức về toán tử số học và so sánh như thế nào?

Tự kiểm tra
0/6 câu đã trả lời
  1. Q1
    Vì sao 7 / 2 = 3 chứ không phải 3.5? Sửa thế nào để ra 3.5?
  2. Q2
    -7 % 3 cho kết quả gì? Giải thích theo công thức JLS.
  3. Q3
    Đoạn System.out.println("Tong: " + 1 + 2); in gì? Vì sao không phải Tong: 3?
  4. Q4
    int i = 5; rồi System.out.println(i++); và System.out.println(++i); in lần lượt gì? Giải thích cơ chế post/pre-increment.
  5. Q5
    byte b = 100; b += 100; — kết quả là gì? Có exception không? Vì sao?
  6. Q6
    Vì sao Integer a = 200; Integer b = 200; a == b cho false nhưng a.equals(b) cho true?

Bài tiếp theo: Toán tử logic và bitwise — short-circuit, cờ bit và thứ tự ưu tiên

Bài này có giúp bạn hiểu bản chất không?

Bài này đáng gửi cho bạn học cùng?

Copy link đã gắn nguồn — dán group, chat, hoặc LinkedIn.

Lộ trình Junior Java Backend Developer

Chặng 2/21 · Lập trình từ số 0 với Java — bài 13/32 của chặng

Thảo luận & góp ý

Hỏi đáp về bài này

Bài tiếp theo

Toán tử logic và bitwise — short-circuit, cờ bit và thứ tự ưu tiên