Ch 3.6 Other Operators
Beyond the arithmetic, comparison, logical, and assignment operators covered earlier, we will explore the ternary operator, bitwise operators, and instanceof operator that are frequently used in practice. Bitwise operators are especially useful in performance-critical system programming and flag management.
1. Ternary Operatorโ
The ternary operator is the only operator in Java with three operands. It can express a simple if-else on a single line.
Syntax:
condition ? value_if_true : value_if_false
public class TernaryBasic {
public static void main(String[] args) {
int score = 85;
// if-else approach
String result1;
if (score >= 80) {
result1 = "Pass";
} else {
result1 = "Fail";
}
// Ternary operator approach (exactly equivalent to above)
String result2 = (score >= 80) ? "Pass" : "Fail";
System.out.println(result1); // Pass
System.out.println(result2); // Pass
// Returning a number
int a = 10, b = 20;
int max = (a > b) ? a : b;
System.out.println("Max: " + max); // 20
// Absolute value
int n = -15;
int abs = (n >= 0) ? n : -n;
System.out.println("Absolute value: " + abs); // 15
}
}
When to Use Ternary vs if-elseโ
| Situation | Recommendation |
|---|---|
| Simple value selection (fits on one line) | Ternary operator |
| Complex logic, multiple statements | if-else |
| Requires 2+ levels of nesting | if-else |
| Includes method calls or exception handling | if-else |
Nested Ternary and Readability Issuesโ
public class NestedTernary {
public static void main(String[] args) {
int score = 75;
// Bad: nested ternary (very poor readability)
String grade = (score >= 90) ? "A" :
(score >= 80) ? "B" :
(score >= 70) ? "C" :
(score >= 60) ? "D" : "F";
System.out.println("Grade: " + grade); // C (works but...)
// Good: clearly expressed with if-else
String grade2;
if (score >= 90) grade2 = "A";
else if (score >= 80) grade2 = "B";
else if (score >= 70) grade2 = "C";
else if (score >= 60) grade2 = "D";
else grade2 = "F";
System.out.println("Grade: " + grade2); // C
}
}
The ternary operator is most effective for simple binary choices. If more than 2 options or nesting is needed, use if-else or switch.
2. Bitwise Operators โ Complete Guideโ
Bitwise operators operate on integers at the binary (bit) level. They are very fast and memory-efficient, used in system programming, encryption, and flag management.
| Operator | Name | Description |
|---|---|---|
& | AND | 1 only when both bits are 1 |
| | OR | 1 when at least one bit is 1 |
^ | XOR | 1 when bits differ |
~ | NOT | Inverts all bits (0->1, 1->0) |
<< | Left Shift | Shift bits left by n positions (ร 2โฟ) |
>> | Right Shift | Shift bits right by n positions (รท 2โฟ), preserves sign |
>>> | Unsigned Right Shift | Shift right by n, fills vacated bits with 0 |
public class BitwiseBasic {
public static void main(String[] args) {
int a = 0b1010; // 10
int b = 0b1100; // 12
System.out.printf("a = %4s (%d)%n", Integer.toBinaryString(a), a);
System.out.printf("b = %4s (%d)%n", Integer.toBinaryString(b), b);
System.out.println();
System.out.printf("a & b = %4s (%d)%n", Integer.toBinaryString(a & b), a & b); // 1000 = 8
System.out.printf("a | b = %4s (%d)%n", Integer.toBinaryString(a | b), a | b); // 1110 = 14
System.out.printf("a ^ b = %4s (%d)%n", Integer.toBinaryString(a ^ b), a ^ b); // 0110 = 6
System.out.printf("~a = %s (%d)%n", Integer.toBinaryString(~a), ~a); // -11
}
}
3. Bitwise AND for Checking Specific Bits (Masking)โ
Use bitwise AND with a mask to check whether a specific bit is 1 or 0.
public class BitMasking {
public static void main(String[] args) {
int value = 0b10110101; // 181
// Check bit 0 (LSB): odd/even check
System.out.println("Bit 0: " + ((value & 0b00000001) != 0)); // true (odd)
// Check bit 2
System.out.println("Bit 2: " + ((value & 0b00000100) != 0)); // true
// Check bit 3
System.out.println("Bit 3: " + ((value & 0b00001000) != 0)); // false
// Extract specific bits (mask & shift combination)
int rgb = 0xFF8040; // R=255, G=128, B=64
int red = (rgb >> 16) & 0xFF;
int green = (rgb >> 8) & 0xFF;
int blue = rgb & 0xFF;
System.out.printf("R=%d, G=%d, B=%d%n", red, green, blue); // R=255, G=128, B=64
}
}
4. Bitwise OR for Setting Flagsโ
Use bitwise OR to manage multiple flags (options) as a single integer.
public class BitOrFlag {
public static void main(String[] args) {
// Represent each permission as a bit (powers of 2)
final int READ = 0b0001; // 1
final int WRITE = 0b0010; // 2
final int EXECUTE = 0b0100; // 4
final int ADMIN = 0b1000; // 8
// Set flags: combine multiple permissions with OR
int userPerms = READ | WRITE; // 0011 = 3
int adminPerms = READ | WRITE | EXECUTE | ADMIN; // 1111 = 15
System.out.println("User permissions: " + Integer.toBinaryString(userPerms)); // 11
System.out.println("Admin permissions: " + Integer.toBinaryString(adminPerms)); // 1111
// Add permission: set bit with OR
userPerms |= EXECUTE; // Add EXECUTE permission
System.out.println("After adding: " + Integer.toBinaryString(userPerms)); // 111
// Check permission: test bit with AND
System.out.println("Has WRITE: " + ((userPerms & WRITE) != 0)); // true
System.out.println("Has ADMIN: " + ((userPerms & ADMIN) != 0)); // false
// Remove permission: clear bit with AND NOT
userPerms &= ~WRITE; // Remove WRITE permission
System.out.println("After removing: " + Integer.toBinaryString(userPerms)); // 101
}
}
5. Bitwise XOR: Toggle and Simple Encryptionโ
public class BitXor {
public static void main(String[] args) {
// Toggle specific bits with XOR
int flags = 0b1010;
int toggleMask = 0b0110;
System.out.println("Original: " + Integer.toBinaryString(flags));
flags ^= toggleMask;
System.out.println("Toggled: " + Integer.toBinaryString(flags)); // 1100
flags ^= toggleMask;
System.out.println("Restored: " + Integer.toBinaryString(flags)); // 1010
// XOR encryption idea (simple example)
String message = "Hello";
int key = 0x42; // Encryption key
// Encrypt: apply XOR to each character
char[] encrypted = new char[message.length()];
for (int i = 0; i < message.length(); i++) {
encrypted[i] = (char)(message.charAt(i) ^ key);
}
System.out.println("Encrypted: " + new String(encrypted));
// Decrypt: apply the same key again with XOR (XOR is its own inverse)
char[] decrypted = new char[encrypted.length];
for (int i = 0; i < encrypted.length; i++) {
decrypted[i] = (char)(encrypted[i] ^ key);
}
System.out.println("Decrypted: " + new String(decrypted)); // Hello
}
}
6. Shift Operators: <<, >>, >>>โ
public class ShiftOperator {
public static void main(String[] args) {
int n = 8; // 0000 1000
// Left Shift (<<): n * 2^k
System.out.println(n << 1); // 16 (8 * 2^1 = 16)
System.out.println(n << 2); // 32 (8 * 2^2 = 32)
System.out.println(n << 3); // 64 (8 * 2^3 = 64)
// Right Shift (>>): n / 2^k (preserves sign)
System.out.println(n >> 1); // 4 (8 / 2^1 = 4)
System.out.println(n >> 2); // 2 (8 / 2^2 = 2)
System.out.println(n >> 3); // 1 (8 / 2^3 = 1)
// Negative with >> (sign bit copied, arithmetic shift)
int neg = -8;
System.out.println(neg >> 1); // -4 (sign preserved)
System.out.println(neg >> 2); // -2
System.out.println(neg >>> 1); // 2147483644 (sign ignored, filled with 0)
// Shift is faster than multiplication/division (for optimization)
int x = 100;
int doubled = x << 1; // x * 2 = 200
int halved = x >> 1; // x / 2 = 50
System.out.println("Doubled: " + doubled + ", Halved: " + halved);
}
}
Difference Between >> and >>>โ
public class SignedUnsignedShift {
public static void main(String[] args) {
int negative = -1;
// -1 in binary: 1111 1111 1111 1111 1111 1111 1111 1111
// >> (Signed): fills vacated bits with sign bit (1) -> remains -1
System.out.println(negative >> 1); // -1 (11111111...1111)
// >>> (Unsigned): always fills vacated bits with 0 -> positive number
System.out.println(negative >>> 1); // 2147483647 (01111111...1111)
// Use case: average of two integers (avoids overflow)
int a = Integer.MAX_VALUE;
int b = Integer.MAX_VALUE - 1;
// int wrong = (a + b) / 2; // Overflow occurs!
int correct = (a + b) >>> 1; // Unsigned right shift prevents overflow
System.out.println("Safe average: " + correct); // 2147483646
}
}
>>>Java's standard library classes like HashMap and Arrays.sort() internally use >>> for midpoint calculation. When a + b might overflow, use the pattern (a + b) >>> 1.
7. instanceof Operatorโ
instanceof checks whether an object is an instance of a specific class or interface. It returns true or false.
public class InstanceofBasic {
public static void main(String[] args) {
Object obj = "Hello";
System.out.println(obj instanceof String); // true
System.out.println(obj instanceof Integer); // false
System.out.println(obj instanceof Object); // true (all objects are instances of Object)
// null always returns false with instanceof
String s = null;
System.out.println(s instanceof String); // false (no NPE)
// instanceof with polymorphism
Object num = Integer.valueOf(42);
if (num instanceof Integer) {
int value = (Integer) num; // Explicit cast
System.out.println("Integer value: " + value); // 42
}
}
}
Pattern Matching instanceof (Java 16+)โ
Since Java 16, pattern matching combines instanceof with variable declaration. It handles type checking and casting in a single step.
public class PatternMatchingInstanceof {
public static void main(String[] args) {
Object[] items = {"Hello", 42, 3.14, true, null};
for (Object item : items) {
// Pre-Java 16 approach
if (item instanceof String) {
String s = (String) item; // Explicit cast required
System.out.println("String (length " + s.length() + "): " + s);
}
// Java 16+ pattern matching (type check + cast at once)
else if (item instanceof Integer i) {
System.out.println("Integer: " + i);
} else if (item instanceof Double d) {
System.out.printf("Double: %.2f%n", d);
} else if (item instanceof Boolean b) {
System.out.println("Boolean: " + b);
} else {
System.out.println("null or unknown type");
}
}
}
}
Output:
String (length 5): Hello
Integer: 42
Double: 3.14
Boolean: true
null or unknown type
8. String + Operator and StringBuilder Performanceโ
Concatenating strings with + inside a loop creates a new String object every iteration, causing significant performance degradation.
public class StringConcatPerformance {
public static void main(String[] args) {
int ITERATIONS = 10_000;
// Method 1: String + (slow - new object created every iteration)
long start1 = System.currentTimeMillis();
String s = "";
for (int i = 0; i < ITERATIONS; i++) {
s += i; // New String object created each iteration
}
long time1 = System.currentTimeMillis() - start1;
// Method 2: StringBuilder (fast - appends to internal buffer)
long start2 = System.currentTimeMillis();
StringBuilder sb = new StringBuilder();
for (int i = 0; i < ITERATIONS; i++) {
sb.append(i); // Appends to the same object's buffer
}
String result = sb.toString();
long time2 = System.currentTimeMillis() - start2;
System.out.println("String + : " + time1 + "ms");
System.out.println("StringBuilder: " + time2 + "ms");
System.out.println("Same length: " + (s.length() == result.length())); // true
}
}
- Simple concatenation (compile-time constants):
String s = "Hello" + " " + "World";-> compiler automatically optimizes to"Hello World". OK. - Concatenation inside loops: Use
StringBuilder(important for performance). - Multi-threaded environments: Use
StringBuffer(thread-safe).
9. Practical Example: User Permission System with Bit Flagsโ
public class PermissionSystem {
// Permission flag constants (bit positions)
static final int PERM_READ = 1 << 0; // 0001 = 1
static final int PERM_WRITE = 1 << 1; // 0010 = 2
static final int PERM_DELETE = 1 << 2; // 0100 = 4
static final int PERM_ADMIN = 1 << 3; // 1000 = 8
// Predefined roles
static final int ROLE_GUEST = PERM_READ; // Read only
static final int ROLE_EDITOR = PERM_READ | PERM_WRITE; // Read + Write
static final int ROLE_MANAGER = PERM_READ | PERM_WRITE | PERM_DELETE; // Read + Write + Delete
static final int ROLE_ADMIN = PERM_READ | PERM_WRITE | PERM_DELETE | PERM_ADMIN; // All
// Check permission
static boolean hasPermission(int userPerms, int requiredPerm) {
return (userPerms & requiredPerm) != 0;
}
// Grant permission
static int grantPermission(int userPerms, int perm) {
return userPerms | perm;
}
// Revoke permission
static int revokePermission(int userPerms, int perm) {
return userPerms & ~perm;
}
// Toggle permission
static int togglePermission(int userPerms, int perm) {
return userPerms ^ perm;
}
// Print permissions
static void printPermissions(String name, int perms) {
System.out.println("[" + name + "] Permissions (binary: " + String.format("%4s", Integer.toBinaryString(perms)).replace(' ', '0') + ")");
System.out.println(" Read: " + (hasPermission(perms, PERM_READ) ? "O" : "X"));
System.out.println(" Write: " + (hasPermission(perms, PERM_WRITE) ? "O" : "X"));
System.out.println(" Delete: " + (hasPermission(perms, PERM_DELETE) ? "O" : "X"));
System.out.println(" Admin: " + (hasPermission(perms, PERM_ADMIN) ? "O" : "X"));
System.out.println();
}
public static void main(String[] args) {
// Set initial permissions
int alicePerms = ROLE_EDITOR; // Read + Write
int bobPerms = ROLE_GUEST; // Read only
int carolPerms = ROLE_ADMIN; // All
printPermissions("Alice (Editor)", alicePerms);
printPermissions("Bob (Guest)", bobPerms);
printPermissions("Carol (Admin)", carolPerms);
// Grant: add Write permission for Bob
System.out.println(">>> Granting Write permission to Bob");
bobPerms = grantPermission(bobPerms, PERM_WRITE);
printPermissions("Bob (Upgraded)", bobPerms);
// Revoke: remove Write permission from Alice
System.out.println(">>> Revoking Write permission from Alice");
alicePerms = revokePermission(alicePerms, PERM_WRITE);
printPermissions("Alice (Downgraded)", alicePerms);
// Access control with ternary operator
System.out.println("=== File Delete Access Control ===");
String[] users = {"Alice", "Bob", "Carol"};
int[] perms = {alicePerms, bobPerms, carolPerms};
for (int i = 0; i < users.length; i++) {
String access = hasPermission(perms[i], PERM_DELETE) ? "Allowed" : "Denied";
System.out.println(users[i] + " delete access: " + access);
}
}
}
Output:
[Alice (Editor)] Permissions (binary: 0011)
Read: O
Write: O
Delete: X
Admin: X
[Bob (Guest)] Permissions (binary: 0001)
Read: O
Write: X
Delete: X
Admin: X
[Carol (Admin)] Permissions (binary: 1111)
Read: O
Write: O
Delete: O
Admin: O
>>> Granting Write permission to Bob
[Bob (Upgraded)] Permissions (binary: 0011)
Read: O
Write: O
Delete: X
Admin: X
>>> Revoking Write permission from Alice
[Alice (Downgraded)] Permissions (binary: 0001)
Read: O
Write: X
Delete: X
Admin: X
=== File Delete Access Control ===
Alice delete access: Denied
Bob delete access: Denied
Carol delete access: Allowed
10. Key Summaryโ
| Operator/Concept | Description | Primary Use |
|---|---|---|
? : (ternary) | Select value based on condition | Replace simple if-else |
& (bitwise AND) | Keep only common bits | Check specific bit (masking) |
| (bitwise OR) | Combine bits | Set flags |
^ (bitwise XOR) | Only differing bits are 1 | Toggle, encryption |
~ (bitwise NOT) | Invert all bits | Remove flag (& ~mask) |
<< | Left shift (ร 2โฟ) | Fast power-of-2 multiplication |
>> | Right shift (รท 2โฟ), preserves sign | Fast division |
>>> | Right shift, fills with 0 | Overflow-safe midpoint calculation |
instanceof | Type check | Type branching in polymorphic code |
Java's EnumSet is a type-safe and more readable alternative to raw bit flags. Directly managing flags with bitwise operations is a C/C++ style; in Java, prefer EnumSet. However, for extremely performance-sensitive scenarios like hardware control or network packet processing, bitwise operations are still very useful.