Lesson 02 · 10 min read · Java for placements

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 only

Three 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;     // fine

float 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 int

That 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);          // -2147483648

No 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 multiplying

Note 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;         // safe

If you need to know, Math.addExact throws rather than wrapping:

Math.addExact(Integer.MAX_VALUE, 1);  // ArithmeticException: integer overflow

Wrappers 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 automatically

Two 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 way

This 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));      // true

Why 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..127

The rule to take away is not "remember 127". It is:

Never compare wrapper objects with ==. Use equals, 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:

byteshortintlongfloatdouble
       charint
int i = 100;
long l = i;                   // automatic
double d = l;                 // automatic

Narrowing 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 infinity

Integer 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 Python

And 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 itself

Operators 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 even

x & 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;                                // int

It 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. long literals need L, float literals need f, char is an unsigned 16-bit number.
  • int overflow is silent. Cast to long before multiplying; use lo + (hi - lo) / 2.
  • Autoboxing converts between int and Integer; unboxing a null throws NullPointerException. Use getOrDefault.
  • == compares values for primitives and references for objects. Never use it on wrappers.
  • Integer.valueOf caches −128..127, which is why Integer 127 == 127 is true and 128 == 128 is false.
  • Widening is implicit; narrowing needs a cast and truncates.
  • 5 / 2 is 2. -5 % 2 is −1 in Java, unlike Python.
  • && short-circuits, & does not. NaN != NaN.
Lesson 2 of 12 · all 12 are free to read, no account. Course contents