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ường | Concept |
|---|---|
| Máy tính văn phòng | Arithmetic: + - * / % |
| So đếm hai hộp táo | Comparison: == != < > <= >= |
| Ghi đè số mới lên bảng | Assignment: = += -=... |
2. Các toán tử số học cơ bản hoạt động ra sao trong Java?
| Operator | Ý nghĩa | Operand type | Ví dụ | Kết quả |
|---|---|---|---|---|
+ | Cộng (hoặc nối chuỗi) | numeric / String | 3 + 4 | 7 |
- | Trừ | numeric | 10 - 3 | 7 |
* | Nhân | numeric | 4 * 5 | 20 |
/ | Chia | numeric | 10 / 3 | 3 (nguyên!) |
% | Chia lấy dư (modulo) | numeric | 10 % 3 | 1 |
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. 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):
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

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
// 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ặcObjects.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
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ĩa | Ví dụ | Kết quả |
|---|---|---|---|
== | Bằng nhau | 5 == 5 | true |
!= | Khác nhau | 5 != 3 | true |
< | Nhỏ hơn | 3 < 5 | true |
> | Lớn hơn | 5 > 3 | true |
<= | Nhỏ hơn hoặc bằng | 5 <= 5 | true |
>= | Lớn hơn hoặc bằng | 4 >= 5 | false |
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. Phép gán và toán tử gán kết hợp hoạt động ra sao?
| Operator | Tương đương | Ví dụ |
|---|---|---|
= | (gán trực tiếp) | x = 5 |
+= | x = x + n | x += 3 |
-= | x = x - n | x -= 2 |
*= | x = x * n | x *= 4 |
/= | x = x / n | x /= 2 |
%= | x = x % n | x %= 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ĩa | Ghi chú |
|---|---|---|
x++ | Tăng x lên 1 (post-increment) | Trả giá trị cũ trước khi tăng |
++x | Tă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 |
--x | Giả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
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:
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
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?
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óm | Operators | Operand | Trả về | Ghi chú |
|---|---|---|---|---|
| Arithmetic | + - * / % | numeric | numeric | / là integer division nếu cả hai int/long |
| String concat | + | String + any | String | Tính từ trái sang phải |
| Comparison | == != < > <= >= | numeric/reference | boolean | == so sánh reference với object |
| Assignment | = += -= *= /= %= | matching type | (side effect) | Compound ngầm narrow cast |
| Increment | ++ -- | numeric | numeric | Pre 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?
Spec chính thức Java 21:
- JLS §15 — Expressions: toàn bộ quy tắc evaluate biểu thức.
- JLS §15.17 — Multiplicative Operators: integer division truncate toward zero, modulo definition.
- JLS §15.18.1 — String Concatenation Operator +: quy tắc nối chuỗi khi một operand là String.
- JLS §15.26.2 — Compound Assignment Operators: quy tắc cast ngầm trong
+=,-=... — lý dobyte b += 1hợp lệ nhưngbyte b = b + 1thì không.
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 sangdoublenếu cần số thực. - Modulo (
%): Giữ nguyên dấu của số bị chia (dividend); dùngMath.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 dungStringhoặcInteger. - 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?
- Q1Vì sao
7 / 2 = 3chứ không phải3.5? Sửa thế nào để ra3.5? - Q2
-7 % 3cho kết quả gì? Giải thích theo công thức JLS. - Q3Đoạn
System.out.println("Tong: " + 1 + 2);in gì? Vì sao không phảiTong: 3? - Q4
int i = 5;rồiSystem.out.println(i++);vàSystem.out.println(++i);in lần lượt gì? Giải thích cơ chế post/pre-increment. - Q5
byte b = 100; b += 100;— kết quả là gì? Có exception không? Vì sao? - Q6Vì sao
Integer a = 200; Integer b = 200; a == bchofalsenhưnga.equals(b)chotrue?
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.
Kiểu tham chiếu — null, NPE, wrapper class và autoboxing
Bài tiếpToán tử logic và bitwise — short-circuit, cờ bit và thứ tự ưu tiên
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