Primitives, wrappers and the == trap
The eight primitives and their sizes, autoboxing, why Integer 127 equals Integer 127 but Integer 128 does not, casting rules and integer overflow.
Java has two kinds of value, and almost every confusing behaviour in this lesson comes from the boundary between them.
The eight primitives
byte b = 100; // 8 bits, -128 .. 127
short s = 30000; // 16 bits, -32,768 .. 32,767
int i = 2_000_000_000; // 32 bits, about ±2.1 billion
long l = 9_000_000_000L; // 64 bits — note the L suffix
float f = 3.14f; // 32 bits — note the f suffix
double d = 3.14; // 64 bits, the default for decimals
char c = 'A'; // 16 bits, UNSIGNED — a UTF-16 code unit
boolean t = true; // true or false onlyThree of those need a comment.
long needs an L. Without it, the literal is an int and overflows before assignment:
long wrong = 9000000000; // compile error: integer number too large
long right = 9000000000L; // finefloat needs an f. float x = 3.14; is a compile error, because 3.14 is a double and
narrowing is never implicit.
char is unsigned and is a number. This is genuinely useful:
char c = 'A';
System.out.println((int) c); // 65
System.out.println((char) (c + 1)); // B
System.out.println(c + 1); // 66 — arithmetic promotes to intThat last line catches people out: char + int is int, so printing it gives a number, not a
letter. Cast back to char when you want a letter.
There is no unsigned in Java, unlike C. byte is signed, which is why reading bytes from a file
gives you negative numbers.
Overflow is silent
int max = Integer.MAX_VALUE; // 2147483647
System.out.println(max + 1); // -2147483648No exception, no warning — it wraps. This is the single most common cause of a wrong answer in a
coding round: a sum or a product of int values that exceeds two billion.
int a = 100000, b = 100000;
int wrong = a * b; // 1410065408 — overflowed
long right = (long) a * b; // 10000000000 — cast BEFORE multiplyingNote where the cast goes. (long) (a * b) is too late: the multiplication already happened in
int. And the classic binary-search bug is exactly this:
int mid = (lo + hi) / 2; // overflows when lo + hi > 2^31 - 1
int mid = lo + (hi - lo) / 2; // safeIf you need to know, Math.addExact throws rather than wrapping:
Math.addExact(Integer.MAX_VALUE, 1); // ArithmeticException: integer overflowWrappers and autoboxing
Each primitive has a class version: int/Integer, char/Character, boolean/Boolean, and
so on. Collections can only hold objects, so List<int> is illegal and List<Integer> is not.
Autoboxing converts automatically in both directions:
List<Integer> marks = new ArrayList<>();
marks.add(87); // int → Integer, boxed automatically
int first = marks.get(0); // Integer → int, unboxed automaticallyTwo things this hides, and both are asked about.
Unboxing null throws a NullPointerException:
Map<String, Integer> counts = new HashMap<>();
int n = counts.get("missing"); // NullPointerException
// get returns null, and null cannot unbox to int
Integer safe = counts.get("missing"); // null, fine
int ok = counts.getOrDefault("missing", 0); // 0 — the right wayThis is a very common real bug and a very common interview question. getOrDefault is the fix.
Boxing in a loop is slow, because each boxed value is an object allocation:
Long sum = 0L; // Long, not long
for (long i = 0; i < 10_000_000; i++) {
sum += i; // unbox, add, box — ten million objects
}Change Long to long and the loop gets dramatically faster. If asked why, that is the reason.
The == trap
This is the highest-frequency Java interview question after the string pool, and it is the same idea:
== on primitives compares values. == on objects compares references.
int a = 128, b = 128;
System.out.println(a == b); // true — primitives, values compared
Integer x = 127, y = 127;
System.out.println(x == y); // true ← surprising
System.out.println(x.equals(y)); // true
Integer p = 128, q = 128;
System.out.println(p == q); // false ← surprising
System.out.println(p.equals(q)); // trueWhy the difference at 128? The Integer cache. Integer.valueOf — which autoboxing calls —
keeps a cached object for every value from −128 to 127 and returns the same object each time. Above
127 it allocates a new object, so the two references differ.
Integer p = new Integer(128); // always a new object (and deprecated)
Integer q = Integer.valueOf(128); // cached only within -128..127The rule to take away is not "remember 127". It is:
Never compare wrapper objects with
==. Useequals, or unbox to a primitive first.
Long, Short, Byte and Character cache the same low range. Boolean caches both values.
Double and Float cache nothing at all, so Double d1 = 1.0, d2 = 1.0; d1 == d2 is false.
Casting
Widening is automatic and lossless — the target type is bigger:
byte → short → int → long → float → double
char → intint i = 100;
long l = i; // automatic
double d = l; // automaticNarrowing requires an explicit cast and can lose data silently:
double d = 3.99;
int i = (int) d; // 3 — truncates towards zero, never rounds
int big = 300;
byte b = (byte) big; // 44 — the high bits are simply discarded(int) truncates. For rounding, use Math.round, and note it returns long for a double
argument:
System.out.println((int) 3.99); // 3
System.out.println(Math.round(3.99)); // 4
System.out.println(Math.round(-3.5)); // -3 — rounds towards positive infinityInteger division truncates, which produces the most common arithmetic mistake in Java:
System.out.println(5 / 2); // 2 — int division
System.out.println(5 / 2.0); // 2.5 — one double makes it double division
System.out.println((double) 5 / 2); // 2.5
System.out.println(5 % 2); // 1
System.out.println(-5 % 2); // -1 — sign follows the LEFT operand, unlike PythonAnd division by zero behaves differently for integers and floating point:
System.out.println(5 / 0); // ArithmeticException
System.out.println(5.0 / 0); // Infinity
System.out.println(0.0 / 0); // NaN
System.out.println(Double.NaN == Double.NaN); // false — NaN equals nothing, including itselfOperators worth a mention
int i = 5;
System.out.println(i++); // prints 5, then i becomes 6 (post-increment)
System.out.println(++i); // i becomes 7, then prints 7 (pre-increment)
boolean ok = (a != 0) && (100 / a > 2); // && short-circuits, so no divide by zero
boolean bad = (a != 0) & (100 / a > 2); // & does NOT short-circuit — evaluates both
int x = 5;
System.out.println(x << 1); // 10 — shift left, ×2
System.out.println(x >> 1); // 2 — shift right, ÷2 keeping sign
System.out.println(-8 >>> 1); // 2147483644 — unsigned shift, fills with zero
System.out.println(x & 1); // 1 — test the lowest bit: odd or evenx & 1 for odd/even and x << 1 for doubling come up in "do it without arithmetic operators"
questions. >>> exists only in Java and only for int and long.
var
Since Java 10, a local variable's type can be inferred:
var marks = new ArrayList<Integer>(); // ArrayList<Integer>
var name = "Raja"; // String
var n = 5; // intIt is still statically typed — the type is fixed at compile time, just not written out. It works only for local variables with an initialiser, never for fields, parameters or return types.
What to take into an interview
- Eight primitives.
longliterals needL,floatliterals needf,charis an unsigned 16-bit number. intoverflow is silent. Cast tolongbefore multiplying; uselo + (hi - lo) / 2.- Autoboxing converts between
intandInteger; unboxing anullthrowsNullPointerException. UsegetOrDefault. ==compares values for primitives and references for objects. Never use it on wrappers.Integer.valueOfcaches −128..127, which is whyInteger 127 == 127is true and128 == 128is false.- Widening is implicit; narrowing needs a cast and truncates.
5 / 2is 2.-5 % 2is −1 in Java, unlike Python.&&short-circuits,&does not.NaN != NaN.