Java Foundations/Toán tử logic và bitwise — short-circuit, cờ bit và thứ tự ưu tiên
12/37
Bài 12 / 37~13 phútCú pháp Java & Kiểu dữ liệuMiễn phí lượt xem

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

Vì sao && và || dừng sớm (short-circuit) và đó là pattern null-safe chuẩn của Java; bitwise/shift cho flags và bitmask; ternary cho gán có điều kiện; bảng operator precedence và quy tắc 'khi nghi ngờ, dùng ngoặc'.

TL;DR: &&|| evaluate short-circuit: vế phải bị bỏ qua khi vế trái đã quyết định được kết quả — đây là nền tảng của pattern null-safe s != null && s.isEmpty(). Ngược lại, &| luôn tính cả hai vế, đồng thời là toán tử bitwise trên số nguyên — công cụ chuẩn cho flags/bitmask. Với shift: >> dịch phải giữ dấu, >>> điền 0 vào bit cao nhất. Ternary ? : gọn cho phép chọn 1-trong-2 giá trị để gán, nhưng nested ternary khó đọc hơn if/else. Và khi không chắc thứ tự ưu tiên toán tử: cứ thêm ngoặc — compiler không tốn thêm chi phí nào.

Bài trước bạn đã nắm toán tử số học, so sánh và gán. Bài này hoàn thành hệ thống toán tử Java với nhóm còn lại: logic (và cơ chế short-circuit đứng sau pattern null-safe kinh điển), bitwise/shift cho thao tác từng bit, ternary cho gán có điều kiện — và cuối cùng là bảng thứ tự ưu tiên để bạn biết Java tính một biểu thức theo trật tự nào.

1. Analogy — Công tắc mạch điện

Hãy nghĩ về 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 mới sáng. Nối song song (OR) — chỉ cần một cái bật. Đó là logical operators.

Và mạch điện có một "thợ điện lười" rất thông minh: ở mạch nối tiếp, thấy công tắc đầu tiên đang tắt thì khỏi cần kiểm tra công tắc thứ hai — đèn chắc chắn không sáng. Đó chính là short-circuit.

Đời thườngConcept
Hai công tắc nối tiếp&& — cả hai vế true mới true
Hai công tắc song song|| — một vế true là đủ
Thợ điện lười dừng kiểm tra sớmShort-circuit evaluation
Bảng cầu chì bật/tắt từng nhánhBitwise flags trên từng bit

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

2.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ả.

2.2 ✅/❌ 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à dấu hiệu cần refactor
Điều kiện boolean bình thường&Không short-circuit, không cần thiết

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

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

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

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

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

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

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

    public static void main(String[] args) {

        // --- 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 permissions = FLAG_READ | FLAG_WRITE;
        System.out.println("Co READ: " + ((permissions & FLAG_READ) != 0));       // true
        System.out.println("Co EXECUTE: " + ((permissions & FLAG_EXECUTE) != 0)); // false

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

        // --- Shift: giu dau vs dien 0 ---
        int negative = -8;
        System.out.println(negative >> 1);   // -4
        System.out.println(negative >>> 1);  // 2147483644

        // --- Ternary ---
        int score = 75;
        String result = (score >= 50) ? "Pass" : "Fail";
        System.out.println("Ket qua: " + result);
    }
}

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

NhómOperatorsOperandTrả vềGhi chú
Logical&& || !booleanbooleanShort-circuit
Bitwise/logical& | ^ ~int/long/booleanint/long/booleanKhông short-circuit
Shift<< >> >>>int/longint/long>> giữ dấu, >>> điền 0
Ternary? :boolean + anysame as branchesGán có điều kiện gọn

8. Deep Dive Oracle

📚 Deep Dive Oracle (optional)

Spec chính thức Java 21:

Diễn giải đơn giản: §15.23/§15.24 nói rõ short-circuit là hành vi được spec đảm bảo, không phải optimization tuỳ JVM — bạn có thể dựa vào nó cho null-safe check. §15.22 giải thích vì sao &/| dùng được cho cả boolean lẫn integer: với boolean chúng là logic không short-circuit, với integer chúng là phép AND/OR trên từng bit.

9. Tóm tắt

  • &&/|| short-circuit: vế phải không evaluate nếu kết quả đã xác định — spec đảm bảo, là nền của pattern null-safe s != null && s.isEmpty().
  • Thứ tự vế quan trọng: điều kiện "gác cổng" (null check) phải đứng bên trái.
  • &/| không short-circuit: evaluate cả hai vế — vai trò chính là bitwise operator cho số nguyên.
  • Flags/bitmask: bật flag bằng |=, kiểm tra bằng (x & FLAG) != 0, tắt bằng &= ~FLAG, toggle bằng ^=.
  • >> giữ dấu (chia 2 số có dấu), >>> điền 0 (xử lý bit pattern thô).
  • Ternary ? : gọn cho gán 1-trong-2; nested ternary → đổi sang if/else.
  • Khi nghi ngờ precedence: dùng ngoặc.

10. Tự kiểm tra

Tự kiểm tra
Q1
Đ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) { ... }
An toàn. &&short-circuit: nếu s != null false, vế phải s.length() > 0 không evaluate — tránh NPE. Thứ tự quan trọng: nếu viết s.length() > 0 && s != null — gọi s.length() trên null trước → NPE. Đây là pattern null-safe check chuẩn trong Java.
Q2
Khi nào dùng & thay vì && với hai vế boolean? Vì sao trường hợp đó hiếm gặp?
& trên boolean luôn evaluate cả hai vế — chỉ cần đến khi vế phải có side effect bắt buộc phải chạy bất kể kết quả vế trái (ví dụ vế phải vừa kiểm tra vừa ghi log hay cập nhật trạng thái). Trường hợp này hiếm vì side effect giấu trong điều kiện làm code khó đọc — tách side effect ra statement riêng rõ ràng hơn. Mặc định luôn dùng &&: an toàn hơn (né NPE nhờ short-circuit) và hiệu quả hơn (bỏ qua vế phải khi không cần).
Q3
(quyen & FLAG_READ) != 0 làm gì? Giải thích từng bước bitwise.

Kiểm tra bit READ có bật trong quyen không — pattern bitmask flag check.

Ví dụ FLAG_READ = 0b0001, quyen = 0b0101 (READ + EXEC):

  • quyen & FLAG_READ = 0b0101 & 0b0001 = 0b0001 → giữ lại đúng bit READ, tắt mọi bit khác.
  • != 0 → bit READ đang bật → true.

Nếu quyen = 0b0100 (chỉ EXEC): 0b0100 & 0b0001 = 0b0000 = 0!= 0 cho false.

Q4
Với int negative = -8;negative >> 1negative >>> 1 cho kết quả khác nhau thế nào? Vì sao >>> ra một số dương rất lớn?
-8 >> 1 = -4: arithmetic shift giữ nguyên sign bit (điền bit 1 vào vị trí cao nhất) — tương đương chia 2 cho số có dấu. -8 >>> 1 = 2147483644: logical shift luôn điền bit 0 vào vị trí cao nhất. Bit pattern của -8111...11000 (two's complement); sau khi điền 0 vào đầu, bit dấu thành 0 → JVM diễn giải pattern đó như một số dương khổng lồ. Dùng >> khi làm toán trên số có dấu; >>> khi xử lý bit pattern thô (hash, network protocol).
Q5
Cho biết: 2 + 3 * 4 = bao nhiêu? Tại sao không phải 20?
2 + 3 * 4 = 14. Operator * có precedence cao hơn + (JLS §15) → nhân trước: 3 * 4 = 12, rồi 2 + 12 = 14. Muốn 20: dùng ngoặc (2 + 3) * 4. Quy tắc vàng: khi nghi ngờ precedence, thêm ngoặc cho rõ.

Bài tiếp theo: Ép kiểu — widening, narrowing, autoboxing và safe cast patterns

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