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: && và || 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, & và | 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ường | Concept |
|---|---|
| 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ớm | Short-circuit evaluation |
| Bảng cầu chì bật/tắt từng nhánh | Bitwise flags trên từng bit |
2. Logical operators — kết hợp điều kiện
| Operator | Ý nghĩa | Khi nào true | Short-circuit |
|---|---|---|---|
&& | AND logic | Cả hai vế true | ✅ Có |
|| | OR logic | Ít nhất một vế true | ✅ Có |
! | NOT logic | Đảo ngược boolean | N/A |
& | AND bitwise/logic | Cả 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
&& và || 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ắnfalse→ không evaluate vế phải.||: nếu vế trái làtrue→ kết quả chắc chắntrue→ khô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);
&& 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 & và | (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ống | Dù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";
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ĩa | Ví dụ | Kết quả |
|---|---|---|---|
& | AND từng bit | 5 & 3 | 1 |
| | OR từng bit | 5 | 3 | 7 |
^ | XOR từng bit | 5 ^ 3 | 6 |
~ | NOT từng bit (complement) | ~5 | -6 |
<< | Left shift | 1 << 3 | 8 |
>> | Arithmetic right shift (giữ dấu) | -8 >> 1 | -4 |
>>> | Logical right shift (điền 0) | -8 >>> 1 | 2147483644 |
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
>>(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 — Postfix | x++, 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 &&
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óm | Operators | Operand | Trả về | Ghi chú |
|---|---|---|---|---|
| Logical | && || ! | boolean | boolean | Short-circuit |
| Bitwise/logical | & | ^ ~ | int/long/boolean | int/long/boolean | Không short-circuit |
| Shift | << >> >>> | int/long | int/long | >> giữ dấu, >>> điền 0 |
| Ternary | ? : | boolean + any | same as branches | Gán có điều kiện gọn |
8. Deep Dive Oracle
Spec chính thức Java 21:
- JLS §15.19 — Shift Operators:
<<,>>,>>>— quy tắc điền bit và shift distance. - JLS §15.22 — Bitwise and Logical Operators:
&,|,^— cả dạng boolean và integer. - JLS §15.23 — Conditional-And Operator && và §15.24 — Conditional-Or Operator ||: spec hoá short-circuit — vế phải "is not evaluated" khi vế trái đã quyết định.
- JLS §15.25 — Conditional Operator ? :: quy tắc type của ternary expression.
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-safes != 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 sangif/else. - Khi nghi ngờ precedence: dùng ngoặc.
10. 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) { ... }
▸
String s = getInput(); // co the tra null
if (s != null && s.length() > 0) { ... }&& là 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.Q2Khi nào dùng & thay vì && với hai vế boolean? Vì sao trường hợp đó hiếm gặp?▸
& 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.▸
(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.
Q4Với int negative = -8; — negative >> 1 và negative >>> 1 cho kết quả khác nhau thế nào? Vì sao >>> ra một số dương rất lớn?▸
int negative = -8; — negative >> 1 và negative >>> 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 -8 là 111...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).Q5Cho biết: 2 + 3 * 4 = bao nhiêu? Tại sao không phải 20?▸
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
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