Java Foundations/Toán tử số học và so sánh — phép chia nguyên, modulo âm và bẫy ==
11/37
Bài 11 / 37~13 phútCú pháp Java & Kiểu dữ liệuMiễ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 ==

Arithmetic, comparison và assignment trong Java: vì sao 5/2 ra 2 chứ không phải 2.5, modulo âm giữ dấu dividend, compound assignment ngầm narrowing cast, và vì sao không bao giờ so sánh String/Integer bằng ==.

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 (bài 2), hiểu reference vs primitive (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ăn phòng và hộp tá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. Arithmetic operators — phép tính cơ bản

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

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. Pitfall — integer division và modulo âm

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

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
flowchart LR
  e1["5 / 2 -- ca hai la int"]
  d1["integer division<br/>truncate toward zero"]
  r1["ket qua: 2"]
  e2["5.0 / 2 -- mot ve la double"]
  d2["floating-point division"]
  r2["ket qua: 2.5"]

  e1 --> d1 --> r1
  e2 --> d2 --> r2

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

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
// 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):

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

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().
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

4. Comparison operators — so sánh trả boolean

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.

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. Assignment operators — gán và compound assignment

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

6. Compound assignment có casting ngầm

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

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:

byte b = 100;
b += 100;  // 100 + 100 = 200, nhung byte chi chua den 127!
System.out.println(b);  // -56 -- overflow ngam, khong bao loi!
// 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. Code example — demo tổng hợp

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
    }
}

8. Bảng tổng hợp nhóm toán tử bài này

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. Deep Dive Oracle

📚 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. Tóm tắt

  • Arithmetic / giữa hai nguyên → integer division (truncate toward zero). Muốn kết quả thực: một operand phải là double.
  • Modulo % với số âm: kết quả giữ dấu dividend — không phải modulo toán học. Cần modulo luôn dương: Math.floorMod().
  • + với một operand Stringnối chuỗi, tính từ trái sang phải — "Tong: " + 1 + 2 ra Tong: 12.
  • Comparison luôn trả boolean. Không dùng == để so sánh String hay wrapper như Integer — dùng .equals().
  • x++ trả giá trị cũ rồi tăng; ++x tăng rồi trả giá trị mới.
  • Compound assignment (+=, -=...) ngầm cast về kiểu vế trái — byte b += 1 OK, byte b = b + 1 không; overflow xảy ra im lặng.

11. Tự kiểm tra

Tự kiểm tra
Q1
Vì sao 7 / 2 = 3 chứ không phải 3.5? Sửa thế nào để ra 3.5?
Cả hai operand là int/ thực hiện integer division: truncate phần thập phân về 0 (JLS §15.17.2). 7/2 = 3.5 → cắt → 3. Sửa: đảm bảo ít nhất một operand là floating: 7 / 2.0, 7.0 / 2, hoặc (double) 7 / 23.5.
Q2
-7 % 3 cho kết quả gì? Giải thích theo công thức JLS.
-7 % 3 = -1. JLS §15.17.3: a % b = a - (a / b) * b. -7 / 3 = -2 (integer division truncate toward 0, không phải floor). -2 * 3 = -6. -7 - (-6) = -1. Java modulo giữ dấu của dividend — khác modulo toán học (kết quả luôn ≥ 0). Muốn kết quả luôn dương: Math.floorMod(-7, 3) = 2.
Q3
Đoạn System.out.println("Tong: " + 1 + 2); in gì? Vì sao không phải Tong: 3?
In Tong: 12. + cùng mức precedence evaluate từ trái sang phải: "Tong: " + 1 có một operand là String nên là nối chuỗi → "Tong: 1". Tiếp tục "Tong: 1" + 2 → lại nối chuỗi → "Tong: 12". Phép cộng số không bao giờ xảy ra. Muốn cộng trước: thêm ngoặc "Tong: " + (1 + 2) — ngoặc ép Java tính 1 + 2 = 3 dạng int trước khi đụng tới String.
Q4
int i = 5; rồi System.out.println(i++);System.out.println(++i); in lần lượt gì? Giải thích cơ chế post/pre-increment.
In 5 rồi 7. i++ (post-increment) trả về giá trị cũ trước, rồi mới tăng — nên in 5, sau lệnh đó i = 6. ++i (pre-increment) tăng trước rồi trả giá trị mớii từ 6 lên 7, in 7. Cả hai đều tăng biến lên 1; khác nhau duy nhất ở giá trị mà biểu thức trả về. Khi đứng một mình như statement (i++;), hai dạng tương đương — nhưng tránh nhét ++ vào giữa biểu thức phức tạp vì khó đọc.
Q5
byte b = 100; b += 100; — kết quả là gì? Có exception không? Vì sao?
Compile OK, chạy ra b = -56, không exception. JLS §15.26.2: compound assignment b += 100 có ngầm cast về kiểu vế trái — tương đương b = (byte)(b + 100). 100 + 100 = 200, vượt range byte (−128..127) → narrowing wrap quanh: 200 - 256 = -56. Nếu viết b = b + 100 thì compile error (vì b + 100int, không tự narrow vào byte).
Q6
Vì sao Integer a = 200; Integer b = 200; a == b cho false nhưng a.equals(b) cho true?
Integer cache (JLS §5.1.7) chỉ cache -128..127. 200 ngoài range → Integer.valueOf(200) tạo object mới mỗi lần → ab là 2 reference khác nhau. == so reference → false. .equals() so giá trị intValue()true. Bài học: luôn dùng .equals() cho wrapper.

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?

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

Chưa có câu hỏi

Đặt câu hỏi

Có gì chưa rõ trong bài? Đặt câu hỏi đầu tiên — câu trả lời từ cộng đồng giúp bạn (và người sau).

Đặt câu hỏi đầu tiên