01 · Java basics — note 4 of 12
Operators
Predict every operator's result exactly, from arithmetic to masked bitwise shifts.
Operators transform values and drive decisions, governed by strict rules for truncation, bit masking, and left-to-right evaluation.
The idea in real life
Think of a checkout counter with a barcode scanner, a weighing scale, and a cash register. Each tool performs one specific action on an item: the scale measures weight, the scanner verifies a price, and the register calculates change. Operators work the same way in code, taking inputs, applying one defined rule, and producing a predictable result.
Why it exists
- Writing verbose method calls for everyday calculations and comparisons clutters your source code.
- Symbolic operators let you express arithmetic, comparisons, and boolean logic in clear, compact expressions.
- Computers work with fixed-width binary words, so operations follow exact rounding, sign, and boundary rules rather than pure mathematical ideals.
- Knowing these exact rules prevents hidden bugs in array index calculations, binary searches, and conditional branches.
How it works
Arithmetic
Five core operators perform integer calculations with truncation toward zero.
Java provides five arithmetic operators for integers: addition (+), subtraction (-), multiplication (*), division (/), and remainder (%).
When dividing two integers, Java performs integer division. It truncates any fractional part toward zero, discarding the decimal fraction rather than rounding to the nearest integer. For example, 14 / 4 evaluates to 3, not 3.5.
The remainder operator % computes the leftover amount after integer division. For positive numbers, 14 % 4 evaluates to 2 because 4 * 3 + 2 = 14.
Dividing by zero or taking the remainder by zero with integers causes the Java Virtual Machine to halt and throw an ArithmeticException with the message / by zero.
The following program demonstrates these five operators on positive integers. In this program:
firstValueis the dividend, set to 14.secondValueis the divisor, set to 4.sum,difference,product,quotient, andremainderstore the results of each arithmetic operation.
public class Main {
public static void main(String[] args) {
int firstValue = 14;
int secondValue = 4;
int sum = firstValue + secondValue;
int difference = firstValue - secondValue;
int product = firstValue * secondValue;
int quotient = firstValue / secondValue;
int remainder = firstValue % secondValue;
System.out.println("Sum: " + sum);
System.out.println("Difference: " + difference);
System.out.println("Product: " + product);
System.out.println("Quotient: " + quotient);
System.out.println("Remainder: " + remainder);
}
}Sum: 18
Difference: 10
Product: 56
Quotient: 3
Remainder: 2Increment and decrement
Prefix updates before yielding a value; postfix updates after.
The increment operator (++) increases an integer variable by 1, and the decrement operator (--) decreases it by 1. Both operators can appear in two forms:
- Prefix (
++countor--count): updates the variable first, then yields the new value. - Postfix (
count++orcount--): yields the current value first, then updates the variable in memory.
When used as an independent statement (such as count++; on its own line), prefix and postfix forms produce the exact same outcome: the variable changes by 1.
The difference matters when you place the expression inside a larger statement or assignment. In int prefixResult = ++count;, count increments from 5 to 6, and prefixResult receives 6. In int postfixResult = count++;, postfixResult receives the current value 6, and count then increments to 7.
The following program traces prefix and postfix evaluation step by step. In this program:
countis the variable being modified.prefixResult,postfixResult, anddecrementResultcapture the value returned by each expression.
public class Main {
public static void main(String[] args) {
int count = 5;
int prefixResult = ++count;
System.out.println("Prefix evaluated: " + prefixResult + ", count: " + count);
int postfixResult = count++;
System.out.println("Postfix evaluated: " + postfixResult + ", count: " + count);
int decrementResult = --count;
System.out.println("Decrement evaluated: " + decrementResult + ", count: " + count);
}
}Prefix evaluated: 6, count: 6
Postfix evaluated: 6, count: 7
Decrement evaluated: 6, count: 6Compound assignment
Combine calculation with assignment while applying an implicit cast.
Compound assignment operators combine an operation with assignment in a single token. Java includes +=, -=, *=, /=, %=, &=, |=, ^=, <<=, >>=, and >>>=.
The expression total += 5; is equivalent in value to total = total + 5;.
However, compound assignment operators contain a hidden narrowing cast defined in section 15.26.2 of the Java Language Specification (JLS). An expression of the form E1 op= E2 is equivalent to E1 = (T)((E1) op (E2)), where T is the type of E1.
This implicit cast means compound assignments compile in situations where a standard assignment fails. Adding a literal integer to a byte or short normally produces an int result that cannot be assigned back to the smaller type without an explicit cast. With compound assignment, Java inserts the cast automatically. Note 5, Casting and type promotion, examines these numeric promotion rules in depth.
The following program demonstrates compound assignment on an int and illustrates the automatic narrowing cast on a byte. In this program:
totalis an integer accumulator updated with+=and*=.smallValueis a byte starting at 120. Adding 10 exceeds the maximum positive byte value of 127 and wraps to -126 due to the implicit(byte)cast.
public class Main {
public static void main(String[] args) {
int total = 10;
total += 5;
total *= 2;
System.out.println("Total: " + total);
byte smallValue = 120;
smallValue += 10;
System.out.println("Wrapped byte value: " + smallValue);
}
}Total: 30
Wrapped byte value: -126Comparison and logical operators
Test relationships between values and combine conditions into booleans.
Comparison operators compare two primitive values and evaluate to a boolean result (true or false):
==(equal to) and!=(not equal to)<(less than) and>(greater than)<=(less than or equal to) and>=(greater than or equal to)
Logical operators combine boolean expressions:
&&(conditional AND): returnstrueonly when both operands evaluate totrue.||(conditional OR): returnstrueif at least one operand evaluates totrue.!(logical NOT): inverts a boolean value, turningtrueintofalseandfalseintotrue.
On primitive types taught in variables and primitive types, == tests whether the stored binary values are equal. On objects, == tests whether both variables point to the same object, not whether the contents match. Comparing object contents, such as text strings, requires .equals(), as covered in note 10, Strings.
The following program tests comparisons and logical operators. In this program:
leftandrightare integer quantities being compared.isLessandisEqualhold relational comparison results.inRangecombines two relational checks using&&.isBoundarychecks whether either boundary matches using||.
public class Main {
public static void main(String[] args) {
int left = 15;
int right = 20;
boolean isLess = left < right;
boolean isEqual = left == right;
boolean inRange = left > 10 && right <= 25;
boolean isBoundary = left == 0 || right == 20;
System.out.println("Is less: " + isLess);
System.out.println("Is equal: " + isEqual);
System.out.println("In range: " + inRange);
System.out.println("Is boundary: " + isBoundary);
}
}Is less: true
Is equal: false
In range: true
Is boundary: trueGoing deeper
Division and remainder with negative numbers
Integer division truncates toward zero, leaving negative remainders.
When working with negative numbers, Java follows strict rules defined in JLS 15.17.2 (Division Operator) and JLS 15.17.3 (Remainder Operator).
Integer division always rounds toward zero, known as truncation toward zero. The result is the integer closest to zero that does not exceed the mathematical quotient in magnitude:
-7 / 2evaluates to-3(not-4).7 / -2evaluates to-3.-7 / -2evaluates to3.
The remainder operator % is strictly bound to the division operator by the algebraic identity:
(a / b) * b + (a % b) == a
Because -7 / 2 is -3, the product (-3) * 2 is -6. To satisfy the identity and reach -7, the remainder must be -1:
-7 % 2evaluates to-1.7 % -2evaluates to1.
The sign of a % b always matches the sign of the dividend a, regardless of the sign of the divisor b.
This behavior creates a trap when wrapping array indexes in circular buffers or cyclic data structures. If you step backward with (index - 1) % length and index is 0, the expression evaluates to -1 % length, which is -1. Passing -1 as an array index throws an ArrayIndexOutOfBoundsException.
To solve this, Java 8 added Math.floorDiv and Math.floorMod to java.lang.Math:
Math.floorDiv(a, b)rounds down toward negative infinity (floor rounding).Math.floorDiv(-7, 2)gives-4.Math.floorMod(a, b)returns the remainder using floor division.Math.floorMod(-7, 2)gives1.
When the divisor b is positive, Math.floorMod(a, b) is guaranteed to return a non-negative value between 0 and b - 1.
The following program compares standard division and remainder with Math.floorDiv and Math.floorMod. In this program:
dividendis the numerator, set to -7.divisoris the denominator, set to 2.standardDivandstandardRemhold the results from/and%.floorDivResultandfloorModResulthold the results from theMathmethods.
public class Main {
public static void main(String[] args) {
int dividend = -7;
int divisor = 2;
int standardDiv = dividend / divisor;
int standardRem = dividend % divisor;
int floorDivResult = Math.floorDiv(dividend, divisor);
int floorModResult = Math.floorMod(dividend, divisor);
System.out.println("Standard division: " + standardDiv);
System.out.println("Standard remainder: " + standardRem);
System.out.println("Math.floorDiv: " + floorDivResult);
System.out.println("Math.floorMod: " + floorModResult);
}
}Standard division: -3
Standard remainder: -1
Math.floorDiv: -4
Math.floorMod: 1Short-circuiting
Logical operators skip right-hand operands when the outcome is certain.
Java's conditional operators && (JLS 15.23) and || (JLS 15.24) employ short-circuit evaluation. They only evaluate the right-hand operand if the left-hand operand does not fully determine the result:
- In
left && right, ifleftevaluates tofalse, the entire expression must befalse. The right-hand operand is skipped completely. - In
left || right, ifleftevaluates totrue, the entire expression must betrue. The right-hand operand is skipped completely.
This guarantee makes short-circuiting a standard guard pattern across algorithms. You can place a bounds check or a null check on the left, and an array read or method call on the right. If the boundary check fails, the dangerous access on the right never executes.
The following program demonstrates short-circuit protection when inspecting an array. In this program:
numsis an array of 3 elements, valid from index 0 to 2.indexis set to 5, which is outside the array.- The condition
index < nums.length && nums[index] > 0evaluatesindex < nums.lengthtofalseand skipsnums[index], avoiding an index exception.
public class Main {
public static void main(String[] args) {
int[] nums = {10, 20, 30};
int index = 5;
if (index < nums.length && nums[index] > 0) {
System.out.println("Element is positive: " + nums[index]);
} else {
System.out.println("Index is outside array bounds; access skipped safely.");
}
}
}Index is outside array bounds; access skipped safely.Bit operators
Manipulate individual bits directly with bitwise logic and shifts.
Bitwise operators manipulate the underlying binary representation of integer values:
&(bitwise AND): bit is 1 if both corresponding input bits are 1.|(bitwise OR): bit is 1 if either input bit is 1.^(bitwise XOR): bit is 1 if the input bits differ.~(bitwise NOT): inverts every bit in the number, turning 0s to 1s and 1s to 0s.<<(signed left shift): shifts bits left, filling emptied low bits with 0. Shifting left by 1 multiplies an integer by 2 (as long as it does not overflow).>>(signed right shift): shifts bits right, copying the sign bit into vacated high positions. This arithmetic shift preserves negative signs.>>>(unsigned right shift): shifts bits right, filling vacated high positions with 0 regardless of the sign bit.
The following table summarizes these operations on small binary values:
| Operator | Name | Example | Binary operation | Result |
|---|---|---|---|---|
& | Bitwise AND | 5 & 3 | 0101 & 0011 | 1 (0001) |
| | Bitwise OR | 5 | 3 | 0101 | 0011 | 7 (0111) |
^ | Bitwise XOR | 5 ^ 3 | 0101 ^ 0011 | 6 (0110) |
~ | Bitwise NOT | ~5 | ~(...00000101) | -6 (...11111010) |
<< | Signed left shift | 5 << 1 | 0101 << 1 | 10 (1010) |
>> | Signed right shift | -8 >> 1 | 11111000 >> 1 | -4 (11111100) |
>>> | Unsigned right shift | -8 >>> 1 | 1111...1000 >>> 1 | 2147483644 |
Chapter 09 explores bit manipulation techniques, bit masks, and binary flags in depth.
The following program tests each bitwise and shift operator. In this program:
firstMaskis 5 (binary 0101).secondMaskis 3 (binary 0011).negativeValueis -8, used to contrast signed right shift (>>) with unsigned right shift (>>>).
public class Main {
public static void main(String[] args) {
int firstMask = 5;
int secondMask = 3;
System.out.println("AND: " + (firstMask & secondMask));
System.out.println("OR: " + (firstMask | secondMask));
System.out.println("XOR: " + (firstMask ^ secondMask));
System.out.println("NOT: " + (~firstMask));
System.out.println("Left shift: " + (firstMask << 1));
int negativeValue = -8;
System.out.println("Signed right shift: " + (negativeValue >> 1));
System.out.println("Unsigned right shift: " + (negativeValue >>> 1));
}
}AND: 1
OR: 7
XOR: 6
NOT: -6
Left shift: 10
Signed right shift: -4
Unsigned right shift: 2147483644Precedence
Precedence rules decide evaluation grouping, but parentheses make it certain.
When an expression contains several operators, Java uses precedence rules to determine how operands group together. Operators on higher precedence levels bind tighter than operators on lower levels.
The following table lists the operator precedence levels that appear most frequently in algorithm implementations, ordered from highest priority (evaluates first) to lowest:
| Level | Operators | Description | Associativity |
|---|---|---|---|
| 1 | expr++ expr-- | Postfix increment and decrement | Left to right |
| 2 | ++expr --expr + - ~ ! | Prefix operators, unary plus/minus, NOT | Right to left |
| 3 | * / % | Multiplicative | Left to right |
| 4 | + - | Additive | Left to right |
| 5 | << >> >>> | Bitwise shifts | Left to right |
| 6 | < > <= >= | Relational comparisons | Left to right |
| 7 | == != | Equality checks | Left to right |
| 8 | & | Bitwise AND | Left to right |
| 9 | ^ | Bitwise XOR | Left to right |
| 10 | | | Bitwise OR | Left to right |
| 11 | && | Conditional AND | Left to right |
| 12 | || | Conditional OR | Left to right |
| 13 | ? : | Ternary conditional | Right to left |
| 14 | = += -= *= etc. | Assignment operators | Right to left |
Notice that additive operators (+ and -) have higher precedence than shift operators (<< and >>), and relational comparisons have higher precedence than bitwise AND (&).
In 1 << 2 + 1, the addition 2 + 1 runs first, yielding 1 << 3, which evaluates to 8. If your intent was to shift 1 by 2 and then add 1, you must write (1 << 2) + 1 to get 5.
Never rely on subtle precedence rules in interview code. Add parentheses () to make grouping clear and unambiguous to both the compiler and the interviewer.
The following program contrasts default precedence grouping against explicit parenthesization. In this program:
unbracketedrelies on default operator precedence (+before<<).bracketeduses parentheses to force the bit shift to run before the addition.
public class Main {
public static void main(String[] args) {
int unbracketed = 1 << 2 + 1;
int bracketed = (1 << 2) + 1;
System.out.println("Without brackets (1 << 2 + 1): " + unbracketed);
System.out.println("With brackets ((1 << 2) + 1): " + bracketed);
}
}Without brackets (1 << 2 + 1): 8
With brackets ((1 << 2) + 1): 5Under the hood
Evaluation order
Java strictly evaluates expression operands from left to right.
Precedence specifies which operator groups with which operands, but it does not dictate the order in which individual operands are evaluated.
Section 15.7 of the Java Language Specification mandates that Java evaluates the operands of an operator strictly from left to right. Every operand expression is evaluated before any part of the operation itself takes place.
This rule explains why the common puzzle index = index++; leaves index unchanged:
- The assignment operator
=determines its target variable,index. - The right-hand side expression
index++is evaluated. The value of postfix increment is the original value ofindex(for example, 0). - The side effect of
index++runs in memory, incrementing the variableindexto 1. - The assignment operator
=executes, storing the value evaluated in step 2 (0) intoindex, overwriting the 1.
The variable index ends up with 0. In contrast, index = ++index; evaluates ++index, which updates index to 1 and yields 1, so the assignment writes 1 back to index.
The following program demonstrates left-to-right evaluation order and verifies the behavior of index = index++. In this program:
indexis initialized to 0 and reassigned withindex = index++.indexis then reassigned withindex = ++indexto highlight the contrast.
public class Main {
public static void main(String[] args) {
int index = 0;
index = index++;
System.out.println("After index = index++: " + index);
index = ++index;
System.out.println("After index = ++index: " + index);
}
}After index = index++: 0
After index = ++index: 1Shift distances are masked
The JVM keeps only the lowest bits of the shift distance.
JLS 15.19 says that Java uses only the lowest bits of the shift distance.
Java masks the right-hand shift distance operand to keep only its lowest 5 bits for an int (distance & 0x1F, which is distance & 31). For a 64-bit long, it keeps only the lowest 6 bits (distance & 0x3F, which is distance & 63).
This bit masking produces counter-intuitive results:
1 << 32is computed as1 << (32 & 31), which is1 << 0 = 1.1 << 33is computed as1 << (33 & 31), which is1 << 1 = 2.1 << 64is computed as1 << (64 & 31), which is1 << 0 = 1.
If you write a loop intending to clear all bits by shifting left by 32 positions (value <<= 32), the value remains completely unchanged. To shift a 64-bit quantity across more than 31 positions, you must use a long literal (1L << 32), which masks by 63 instead of 31.
The following program demonstrates masked shift distances on a 32-bit int. In this program:
baseValueis 1.shiftedBy32shiftsbaseValueby 32 positions, which masks to a shift of 0.shiftedBy33shiftsbaseValueby 33 positions, which masks to a shift of 1.
public class Main {
public static void main(String[] args) {
int baseValue = 1;
int shiftedBy32 = baseValue << 32;
int shiftedBy33 = baseValue << 33;
System.out.println("1 << 32: " + shiftedBy32);
System.out.println("1 << 33: " + shiftedBy33);
}
}1 << 32: 1
1 << 33: 2Boolean bitwise operators
Single ampersand and pipe on booleans evaluate both sides unconditionally.
The operators &, |, and ^ are not restricted to integers. When applied to boolean operands (JLS 15.22.2), they perform logical operations:
flag1 & flag2producestrueif both flags aretrue.flag1 | flag2producestrueif either flag istrue.flag1 ^ flag2producestrueif the flags have different values (logical XOR).
The critical difference between boolean & and && (and between | and ||) is that single & and | operators never short-circuit. They always evaluate both the left-hand and right-hand operands, regardless of the left-hand result.
If the right-hand operand contains a method call or an array index check, that code executes even when the left side is already false. Writing index < nums.length & nums[index] > 0 causes an ArrayIndexOutOfBoundsException when index equals nums.length, because nums[index] is evaluated unconditionally.
The following program demonstrates boolean bitwise operators. In this program:
firstFlagis true andsecondFlagis false.andResult,orResult, andxorResultdemonstrate non-short-circuiting logical operations.
public class Main {
public static void main(String[] args) {
boolean firstFlag = true;
boolean secondFlag = false;
boolean andResult = firstFlag & secondFlag;
boolean orResult = firstFlag | secondFlag;
boolean xorResult = firstFlag ^ secondFlag;
System.out.println("Bitwise AND on booleans: " + andResult);
System.out.println("Bitwise OR on booleans: " + orResult);
System.out.println("Bitwise XOR on booleans: " + xorResult);
}
}Bitwise AND on booleans: false
Bitwise OR on booleans: true
Bitwise XOR on booleans: trueOverflow with Integer.MIN_VALUE
Dividing the minimum integer by negative one overflows silently.
In Java's two's complement representation, a 32-bit signed int ranges from -2,147,483,648 (Integer.MIN_VALUE) to +2,147,483,647 (Integer.MAX_VALUE). There are more negative numbers than positive numbers, so the negation of Integer.MIN_VALUE cannot fit in a 32-bit signed integer.
According to JLS 15.17.2, integer division does not throw an exception on overflow. When you evaluate Integer.MIN_VALUE / -1, mathematical division produces +2,147,483,648. Because this exceeds Integer.MAX_VALUE, the result wraps around in two's complement arithmetic back to Integer.MIN_VALUE (-2,147,483,648).
Similarly, JLS 15.17.3 specifies that remainder with overflow evaluates to zero: Integer.MIN_VALUE % -1 evaluates to 0.
Only division or remainder by zero throws an ArithmeticException. All other boundary overflows wrap silently without generating runtime errors.
The following program demonstrates division and remainder overflow with Integer.MIN_VALUE. In this program:
minIntholdsInteger.MIN_VALUE.divisionResultshows the silent wrap-around from dividing by -1.remainderResultshows the remainder evaluating to 0.
public class Main {
public static void main(String[] args) {
int minInt = Integer.MIN_VALUE;
int divisionResult = minInt / -1;
int remainderResult = minInt % -1;
System.out.println("Integer.MIN_VALUE / -1: " + divisionResult);
System.out.println("Integer.MIN_VALUE % -1: " + remainderResult);
}
}Integer.MIN_VALUE / -1: -2147483648
Integer.MIN_VALUE % -1: 0In Java
The following complete program combines safe index wrapping, short-circuit guard checks, and midpoint calculation without overflow into three utility methods.
public class Main {
// Returns a valid non-negative index in the range [0, length - 1].
static int wrapIndex(int index, int length) {
return Math.floorMod(index, length);
}
// Returns the element at index, or -1 if the index is out of bounds.
static int safeElement(int[] nums, int index) {
if (index >= 0 && index < nums.length) {
return nums[index];
}
return -1;
}
// Returns the average of left and right without integer overflow.
static int calculateMidpoint(int left, int right) {
return (left + right) >>> 1;
}
public static void main(String[] args) {
int[] nums = {10, 20, 30, 40, 50};
int stepBack = wrapIndex(-1, nums.length);
int validItem = safeElement(nums, 2);
int missingItem = safeElement(nums, 10);
int left = 2_000_000_000;
int right = 2_000_000_000;
int mid = calculateMidpoint(left, right);
System.out.println("Wrapped index for -1: " + stepBack);
System.out.println("Safe element at 2: " + validItem);
System.out.println("Safe element at 10: " + missingItem);
System.out.println("Midpoint without overflow: " + mid);
}
}Wrapped index for -1: 4
Safe element at 2: 30
Safe element at 10: -1
Midpoint without overflow: 2000000000Traps
Using % to wrap around array indexes with negative values
The remainder operator keeps the sign of the left operand. When
indexis 0,(0 - 1) % nis-1, and using it as an index throwsArrayIndexOutOfBoundsException. UseMath.floorMod(index - 1, n)or((index - 1) % n + n) % n.✕ WRONGint prev = (index - 1) % n;✓ RIGHTint prev = Math.floorMod(index - 1, n);Assigning a postfix increment back to the same variable
Java evaluates the right-hand expression first and saves the original value. The postfix operator increments the variable in memory, but the assignment immediately overwrites the variable with the saved original value, leaving it unchanged.
✕ WRONGcount = count++;✓ RIGHTcount++;Using single & instead of && in guard checks
A single
&on booleans does not short-circuit: it always evaluates both sides. Ifindexis out of bounds,nums[index]still runs and throwsArrayIndexOutOfBoundsException.✕ WRONGif (index < nums.length & nums[index] > 0)✓ RIGHTif (index < nums.length && nums[index] > 0)Treating ^ as exponentiation instead of bitwise XOR
In Java,
^is exclusive OR, not a power.base ^ 2flips one bit ofbaseinstead of squaring it.✕ WRONGint squared = base ^ 2;✓ RIGHTint squared = base * base;Shifting by 32 bits or more on an int
For an
int, the shift distance is masked with 31, somask << 32runs asmask << 0and leaves the value unchanged.✕ WRONGint cleared = mask << 32;✓ RIGHTint cleared = 0;Mixing addition and bit shifts without parentheses
+binds tighter than<<, so1 << 2 + 1groups as1 << 3, which is 8, not 5.✕ WRONGint result = 1 << 2 + 1;✓ RIGHTint result = (1 << 2) + 1;Calculating binary search midpoint with (left + right) / 2
This overflow is explained in variables and primitive types. The operator view:
(left + right) >>> 1also works, because>>>treats the wrapped sum as unsigned.✕ WRONGint mid = (left + right) / 2;✓ RIGHTint mid = left + (right - left) / 2;Integer division discarding fractional parts
When both operands are integers,
/does integer division before the result is stored in thedouble. Cast one operand todoublefirst.✕ WRONGdouble ratio = numerator / denominator;✓ RIGHTdouble ratio = (double) numerator / denominator;