☕ Java

Arithmetic Operators

Arithmetic operators perform mathematical operations on numeric values. Java's arithmetic operators look simple — but integer division, modulo with negatives, overflow behavior, and floating-point precision all have specific rules that trip up developers at every level.

The Five Basic Arithmetic Operators

Java has five binary arithmetic operators that work on all numeric primitive types:
Java
int a = 17, b = 5;

System.out.println(a + b);   // 22 — addition
System.out.println(a - b);   // 12 — subtraction
System.out.println(a * b);   // 85 — multiplication
System.out.println(a / b);   // 3  — integer division (truncates)
System.out.println(a % b);   // 2  — modulo (remainder)

// With doubles:
double x = 17.0, y = 5.0;
System.out.println(x + y);   // 22.0
System.out.println(x - y);   // 12.0
System.out.println(x * y);   // 85.0
System.out.println(x / y);   // 3.4 — floating-point division
System.out.println(x % y);   // 2.0 — floating-point remainder

Integer Division — Truncation, Not Rounding

When both operands are integer types, / performs integer division: the result is truncated toward zero, discarding any remainder. This is one of the most common sources of subtle bugs in Java.
Java
// Integer division truncates toward zero:
System.out.println(7 / 2);     // 3  — not 3.5
System.out.println(7 / 4);     // 1  — not 1.75
System.out.println(-7 / 2);    // -3 — truncated toward zero, not -4
System.out.println(1 / 3);     // 0  — less than 1, truncates to 0

// The silent bug — computing an average wrong:
int total = 7, count = 2;
double average = total / count;       // 3.0 — WRONG: integer division first
double correct = (double) total / count; // 3.5 — correct: cast before dividing

// Other ways to force floating-point division:
double d1 = total / (double) count;  // 3.5
double d2 = (double) total / count;  // 3.5
double d3 = 1.0 * total / count;     // 3.5 — multiply by 1.0 first

// Division by zero:
int x = 5 / 0;        // throws ArithmeticException: / by zero at runtime
double y = 5.0 / 0.0; // Infinity — floating-point division by zero does NOT throw
double z = 0.0 / 0.0; // NaN — not a number
System.out.println(Double.isInfinite(y));  // true
System.out.println(Double.isNaN(z));       // true

Modulo — Remainder Operator

The % operator returns the remainder after division. Its behavior with negative numbers is specific in Java: the sign of the result matches the sign of the dividend (left operand).
Java
// Basic modulo:
System.out.println(10 % 3);    // 110 = 3*3 + 1
System.out.println(10 % 5);    // 010 = 5*2 + 0
System.out.println(7 % 10);    // 77 = 10*0 + 7

// Negative dividend — result has same sign as dividend:
System.out.println(-10 % 3);   // -1 — result is negative (matches -10)
System.out.println(10 % -3);   //  1 — result is positive (matches 10)
System.out.println(-10 % -3);  // -1 — result is negative (matches -10)

// Floating-point modulo:
System.out.println(10.5 % 3.0);  // 1.5
System.out.println(5.5 % 2.2);   // 1.1000000000000005 — floating-point imprecision

// Common uses of modulo:
// Check if even or odd:
int n = 42;
if (n % 2 == 0) System.out.println("even");
else System.out.println("odd");

// Cycle through values 0 to N-1:
int[] slots = new int[7];
for (int i = 0; i < 20; i++) {
    slots[i % 7]++;   // cycles: 0,1,2,3,4,5,6,0,1,2...
}

// Ensure positive modulo (when dividend may be negative):
int mod = ((n % 7) + 7) % 7;   // always 0-6, even if n is negative

Increment and Decrement Operators

++ and -- add or subtract 1 from a variable. They exist in two forms — prefix and postfix — that differ in when the value is updated relative to when it's read.
Java
// Prefix — increment THEN return new value:
int a = 5;
int b = ++a;    // a becomes 6, then b = 6
System.out.println(a);  // 6
System.out.println(b);  // 6

// Postfix — return current value THEN increment:
int x = 5;
int y = x++;    // y = 5 (original), then x becomes 6
System.out.println(x);  // 6
System.out.println(y);  // 5

// Decrement — same logic with --:
int c = 10;
System.out.println(--c);  // 9  — prefix: decrement then return
System.out.println(c--);  // 9  — postfix: return then decrement
System.out.println(c);    // 8

// In loops — prefix and postfix make no difference when used alone:
for (int i = 0; i < 5; i++) { }   // same as ++i here
for (int i = 0; i < 5; ++i) { }   // identical behavior in this context

// Where it matters — inside expressions:
int n = 3;
int result = n++ * 2;   // result = 3*2 = 6, then n = 4
int result2 = ++n * 2;  // n becomes 5 first, result2 = 5*2 = 10

// Classic confusion:
int i = 5;
i = i++;   // i is still 5! postfix returns original value before increment
// Sequence: temp = i (5), i = i+1 (6), i = temp (5)

Arithmetic with Mixed Types

When operands have different types, Java automatically promotes the smaller type to match the larger one before performing the operation.
Java
// int + long = long:
int i = 100;
long l = 200L;
long result = i + l;    // i promoted to long

// int + double = double:
int x = 5;
double d = 2.5;
double r = x + d;       // x promoted to double: 7.5

// byte/short arithmetic always produces int:
byte b1 = 10, b2 = 20;
// byte sum = b1 + b2;   // COMPILE ERROR
int sum = b1 + b2;       // 30

// Compound arithmetic preserves type:
byte b = 10;
b += 5;    // equivalent to b = (byte)(b + 5) — implicit cast in compound op
b++;       // works — ++ on byte stays byte (implicit cast)

// char arithmetic produces int:
char c = 'A';
int code = c + 1;          // 66
char next = (char)(c + 1); // 'B' — need explicit cast back to char

Overflow and Underflow

Java integer arithmetic silently overflows — when a result exceeds the type's range, it wraps around with no exception and no warning. Floating-point uses Infinity and NaN instead.
Java
// Integer overflow — wraps silently:
int max = Integer.MAX_VALUE;  // 2,147,483,647
System.out.println(max + 1);  // -2,147,483,648 — wrapped to MIN_VALUE

int min = Integer.MIN_VALUE;  // -2,147,483,648
System.out.println(min - 1);  // 2,147,483,647 — wrapped to MAX_VALUE

// Long overflow — same wrapping:
long lMax = Long.MAX_VALUE;
System.out.println(lMax + 1); // Long.MIN_VALUE

// Detect overflow safely with Math methods (Java 8+):
try {
    int safe = Math.addExact(Integer.MAX_VALUE, 1);
} catch (ArithmeticException e) {
    System.out.println("Overflow detected");  // thrown instead of wrapping
}
Math.subtractExact(a, b);   // safe subtraction
Math.multiplyExact(a, b);   // safe multiplication

// Floating-point — uses special values instead of wrapping:
System.out.println(Double.MAX_VALUE * 2);   // Infinity
System.out.println(-Double.MAX_VALUE * 2);  // -Infinity
System.out.println(Double.MAX_VALUE + 1);   // Double.MAX_VALUE — too small a change
System.out.println(0.0 / 0.0);             // NaN

// NaN comparisons are always false:
double nan = Double.NaN;
System.out.println(nan == nan);     // false — NaN is not equal to itself
System.out.println(nan > 0);        // false
System.out.println(Double.isNaN(nan)); // true — correct way to check

String Concatenation with +

The + operator is overloaded for String concatenation. When either operand is a String, + concatenates rather than adds. The left-to-right evaluation order means the placement of Strings in an expression matters significantly.
Java
// String + anything = String:
String s = "Value: " + 42;       // "Value: 42"
String s2 = "Pi: " + 3.14;      // "Pi: 3.14"
String s3 = "Flag: " + true;    // "Flag: true"

// Left-to-right evaluation — order matters:
System.out.println("Sum: " + 1 + 2);    // "Sum: 12""Sum:1" then concat 2
System.out.println("Sum: " + (1 + 2));  // "Sum: 3"  — parens force int add first
System.out.println(1 + 2 + " sum");     // "3 sum"int add first, then concat

// null concatenation — null becomes the String "null":
String name = null;
System.out.println("Name: " + name);    // "Name: null"

// Compiler optimization — consecutive String literals are folded at compile time:
String s4 = "Hello" + " " + "World";   // compiled as "Hello World" — no runtime concat

// For repeated concatenation, use StringBuilder:
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 100; i++) {
    sb.append(i).append(", ");
}
String result = sb.toString();