Java — Từ Zero đến Senior/Cú pháp Java & Kiểu dữ liệu/Toán tử — số học, logic, so sánh, bitwise và assignment
4/8
~17 phútCú pháp Java & Kiểu dữ liệu

Toán tử — số học, logic, so sánh, bitwise và assignment

Nắm vững toàn bộ hệ thống toán tử Java: arithmetic, comparison, logical (với short-circuit), bitwise/shift, assignment compound, ternary, operator precedence và những pitfall kinh điển như integer division hay == với String/Integer.

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

Bài này đi theo 4 lớp: từ phép tính hàng ngày, đến những pitfall ẩn, đến short-circuit và bitwise, đến thứ tự ưu tiên và compound assignment.

1. Analogy — Máy tính văn phòng và công tắc điện

Nhìn toán tử Java qua 3 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.
  • Công tắc mạch điện — hai công tắc nối tiếp (AND): phải cả hai bật thì đèn sáng. Nối song song (OR): chỉ cần một bật. Logical operators.

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 Layer 1 — 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

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 xuat_sac = diem >= 90;  // false

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

5. Logical operators — kết hợp điều kiện

OperatorÝ nghĩaKhi nào trueShort-circuit
&&AND logicCả hai vế true✅ Có
||OR logicÍt nhất một vế true✅ Có
!NOT logicĐảo ngược booleanN/A
&AND bitwise/logicCả hai vế true❌ Không
|OR bitwise/logicÍt nhất một vế true❌ Không
boolean a = true, b = false;
System.out.println(a && b);  // false
System.out.println(a || b);  // true
System.out.println(!a);      // false

5.1 Short-circuit evaluation — bộ não lười

&&|| dừng sớm khi kết quả đã xác định được:

  • &&: nếu vế trái là false → kết quả chắc chắn falsekhông evaluate vế phải.
  • ||: nếu vế trái là true → kết quả chắc chắn truekhông evaluate vế phải.
flowchart LR
  A["Evaluate left operand"]
  B{"AND or OR?"}
  C{"left == false?"}
  D{"left == true?"}
  E["Skip right -- return false"]
  F["Skip right -- return true"]
  G["Evaluate right operand"]
  H["Return result"]

  A --> B
  B -->|"AND"| C
  B -->|"OR"| D
  C -->|"yes"| E
  C -->|"no"| G
  D -->|"yes"| F
  D -->|"no"| G
  G --> H

Ứng dụng thực tế — null-safe check:

String s = null;

// AN TOAN: neu s == null thi khong goi s.isEmpty() (short-circuit)
if (s != null && s.isEmpty()) {
    System.out.println("Chuoi rong");
}

// NGUY HIEM: & khong short-circuit -- luon evaluate ca hai ve
// if (s != null & s.isEmpty())  // NPE neu s == null!
// Short-circuit trong OR: neu isAdmin() tra true, checkPermission() khong chay
boolean coQuyen = isAdmin() || checkPermission(userId);

💡 💡 Cách nhớ — short-circuit

&& như bộ não lười khi xét điều kiện AND: "vế đầu đã sai rồi — thôi không cần xem tiếp".

|| cũng lười theo cách khác: "vế đầu đúng rồi — không cần xem tiếp".

Ngược lại &| (bitwise) luôn tính cả hai vế — dùng khi bạn cần side effect của vế phải xảy ra bất kể kết quả.

✅/❌ Khi nào dùng & vs &&?

Tình huốngDùng gìLý do
Kết hợp điều kiện boolean thông thường&&Short-circuit an toàn hơn, hiệu quả hơn
Vế phải là biểu thức có thể null/throw&&Tránh NPE khi vế trái đã loại trừ
Bitwise operation trên số nguyên&Toán tử bitwise đúng mục đích
Cần vế phải LUÔN được evaluate (side effect)&Ít gặp, thường là code smell
Điều kiện boolean bình thường&Không short-circuit, không cần thiết

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

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

7. Ternary operator ? : — gán có điều kiện

Syntax: condition ? valueIfTrue : valueIfFalse

int diem = 75;
String ketQua = (diem >= 50) ? "Dat" : "Roi";
System.out.println(ketQua);  // Dat

// Thay the if/else don gian -- gon hon
int max = (a > b) ? a : b;

Khi nào KHÔNG nên dùng ternary:

// TRANH: nested ternary -- kho doc
String muc = (diem >= 90) ? "Xuat sac" : (diem >= 70) ? "Gioi" : (diem >= 50) ? "Trung binh" : "Roi";

// NEN: if/else if ro rang hon
String muc;
if      (diem >= 90) muc = "Xuat sac";
else if (diem >= 70) muc = "Gioi";
else if (diem >= 50) muc = "Trung binh";
else                 muc = "Roi";

💡 💡 Cách nhớ — ternary

Ternary phù hợp khi chọn 1 trong 2 giá trị đơn giản để gán. Khi logic phức tạp hoặc nested, dùng if/else rõ ràng hơn.

8. Bitwise operators — thao tác trên từng bit

OperatorÝ nghĩaVí dụKết quả
&AND từng bit5 & 31
|OR từng bit5 | 37
^XOR từng bit5 ^ 36
~NOT từng bit (complement)~5-6
<<Left shift1 << 38
>>Arithmetic right shift (giữ dấu)-8 >> 1-4
>>>Logical right shift (điền 0)-8 >>> 12147483644
int a = 5;  // binary: 0101
int b = 3;  // binary: 0011

System.out.println(a & b);   // 0001 = 1
System.out.println(a | b);   // 0111 = 7
System.out.println(a ^ b);   // 0110 = 6
System.out.println(~a);      // ...11111010 = -6 (two's complement)
System.out.println(1 << 3);  // 0001 << 3 = 1000 = 8 (nhan 2^3)
System.out.println(8 >> 1);  // 1000 >> 1 = 0100 = 4 (chia 2)

8.1 Use case thực tế — flags và bitmask

Bitwise operators phổ biến khi dùng các bit của một int như tập hợp flags (cờ):

// Dinh nghia flags (moi flag la mot bit rieng)
static final int FLAG_READ    = 0b001;  // bit 0 = 1
static final int FLAG_WRITE   = 0b010;  // bit 1 = 2
static final int FLAG_EXECUTE = 0b100;  // bit 2 = 4

int quyen = FLAG_READ | FLAG_WRITE;  // 0b011 = 3 (co ca READ va WRITE)

// Kiem tra mot flag
boolean coRead = (quyen & FLAG_READ) != 0;    // true
boolean coExec = (quyen & FLAG_EXECUTE) != 0; // false

// Bat them flag
quyen |= FLAG_EXECUTE;  // them EXECUTE

// Tat flag
quyen &= ~FLAG_WRITE;   // xoa WRITE (AND voi NOT WRITE)

// Toggle flag
quyen ^= FLAG_READ;     // bat neu tat, tat neu bat
// Kiem tra so chan: bit cuoi == 0
boolean laSoChan = (n & 1) == 0;

// Chia doi nhanh bang shift (chi dung voi so duong)
int halved = n >> 1;  // tuong duong n / 2 nhung nhanh hon

8.2 >> vs >>> — giữ dấu hay không?

int negative = -8;  // binary: 11111111111111111111111111111000

System.out.println(negative >> 1);   // -4: dich phai giu bit dau (1)
System.out.println(negative >>> 1);  // 2147483644: dich phai dien 0 vao bit cao nhat

💡 💡 Cách nhớ — >> vs >>>

  • >> (arithmetic): giữ nguyên dấu — dùng khi chia 2 số nguyên có dấu.
  • >>> (logical): luôn điền 0 — dùng khi cần xử lý bit pattern thô (hash function, network protocol), bất kể dấu.

9. Operator precedence — thứ tự ưu tiên

Java evaluate theo thứ tự ưu tiên từ cao đến thấp. Các operator cùng mức thực hiện từ trái sang phải (trừ assignment từ phải sang trái):

Mức (cao → thấp)Operators
1 — Postfixx++, x--
2 — Prefix / Unary++x, --x, +x, -x, ~, !
3 — Multiplicative*, /, %
4 — Additive+, -
5 — Shift<<, >>, >>>
6 — Relational<, >, <=, >=, instanceof
7 — Equality==, !=
8 — Bitwise AND&
9 — Bitwise XOR^
10 — Bitwise OR|
11 — Logical AND&&
12 — Logical OR||
13 — Ternary? :
14 — Assignment=, +=, -=, *=, /=, %=, <<=, >>=, >>>=, &=, ^=, |=
// Khong ngoac -- dua vao precedence
int result = 2 + 3 * 4;   // = 14 (3*4 truoc, roi cong 2)

// Ro rang hon voi ngoac
int result2 = 2 + (3 * 4); // = 14 -- cung ket qua, ro hon
int result3 = (2 + 3) * 4; // = 20 -- thay doi thu tu

// Ket hop comparison va logical
boolean check = x > 0 && x < 100;  // > va < truoc &&

💡 💡 Cách nhớ — precedence

Khi nghi ngờ, dùng dấu ngoặc. Code có ngoặc rõ ràng hơn là code "đúng nhờ nhớ thứ tự". Ngoặc không tốn gì về performance — compiler optimize hết.

10. Layer 4 — 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.

11. Code example — demo tổng hợp

public class ToanTuDemo {
    static final int FLAG_READ    = 0b001;
    static final int FLAG_WRITE   = 0b010;
    static final int FLAG_EXECUTE = 0b100;

    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)

        // --- Short-circuit null safe ---
        String s = null;
        if (s != null && s.isEmpty()) {
            System.out.println("Chuoi rong");
        } else {
            System.out.println("s la null hoac co noi dung");
        }

        // --- Bitwise flags ---
        int quyen = FLAG_READ | FLAG_WRITE;
        System.out.println("Co READ: " + ((quyen & FLAG_READ) != 0));     // true
        System.out.println("Co EXECUTE: " + ((quyen & FLAG_EXECUTE) != 0)); // false

        // Kiem tra so chan
        for (int i = 0; i < 6; i++) {
            System.out.println(i + " la so " + ((i & 1) == 0 ? "chan" : "le"));
        }

        // --- Compound assignment va byte ---
        byte b = 10;
        b += 1;  // OK -- compound ngam cast
        System.out.println("byte b = " + b);  // 11

        // --- Ternary ---
        int diem = 75;
        String ketQua = (diem >= 50) ? "Dat" : "Roi";
        System.out.println("Ket qua: " + ketQua);  // Dat
    }
}

12. Bảng tổng hợp toán tử

NhómOperatorsOperandTrả vềGhi chú
Arithmetic+ - * / %numericnumeric/ là integer division nếu cả hai int/long
String concat+String + anyStringƯu tiên thấp hơn arithmetic
Comparison== != < > <= >=numeric/referenceboolean== so sánh reference với object
Logical&& || !booleanbooleanShort-circuit
Bitwise/logical& | ^ ~int/long/booleanint/long/booleanKhông short-circuit
Shift<< >> >>>int/longint/long>> giữ dấu, >>> điền 0
Assignment= += -= *= /= %=matching type(side effect)Compound ngầm narrow cast
Ternary? :boolean + anysame as branchesGán có điều kiện gọn
Increment++ --numericnumericPre vs post khác thứ tự

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

14. Tóm tắt

  • Arithmetic / giữa hai nguyên → integer division (truncate). 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.
  • &&/|| short-circuit: vế phải không evaluate nếu kết quả đã xác định. Dùng cho null-safe check.
  • &/| không short-circuit: evaluate cả hai vế — bitwise operator cho số nguyên.
  • Compound assignment (+=, -=...) ngầm cast về kiểu vế trái — byte b += 1 OK, byte b = b + 1 không.
  • Khi nghi ngờ precedence: dùng ngoặc.
  • Không dùng == để so sánh String hay wrapper như Integer — dùng .equals().

15. Tự kiểm tra

  1. Vì sao 7 / 2 = 3 chứ không phải 3.5? Sửa thế nào để ra 3.5?
  2. -7 % 3 cho kết quả gì? Giải thích theo công thức JLS.
  3. Đoạn sau an toàn hay có thể NPE? Vì sao?
    String s = getInput();  // co the tra null
    if (s != null && s.length() > 0) { ... }
    
  4. byte b = 100; b += 100; — kết quả là gì? Có exception không? Vì sao?
  5. (quyen & FLAG_READ) != 0 làm gì? Giải thích từng bước bitwise.
  6. Cho biết: 2 + 3 * 4 = bao nhiêu? Tại sao không phải 20?
  7. Vì sao Integer a = 200; Integer b = 200; a == b cho false nhưng a.equals(b) cho true?

Bài tiếp theo: Ép kiểu — widening, narrowing và type casting