Q5 of 40 · Core Java

Explain Java's primitive types and their wrapper classes.

Core JavaJuniorjava-primitiveswrapper-classesautoboxinginteger-cache

Short answer

Short answer: Java has 8 primitives (byte, short, int, long, float, double, char, boolean) stored directly on the stack or inline in objects. Each has a wrapper class (Integer, Double, Boolean, etc.) that is a full object, enabling use in collections, generics, and APIs that require objects.

Detail

Java distinguishes between primitive types and reference types. The 8 primitives — byte, short, int, long, float, double, char, boolean — are stored by value. A local int x = 5 lives on the call stack; there is no object header, no GC involvement. This makes primitives fast and memory-efficient.

Wrapper classes (Integer, Long, Double, Boolean, Character, Byte, Short, Float) are regular objects in the heap. They exist because Java's type system requires objects in many places: generic type parameters (List<int> is illegal, List<Integer> works), null values, and utility methods like Integer.parseInt(), Integer.toBinaryString(), Double.isNaN().

Integer cache: the JVM caches Integer objects from -128 to 127. Integer.valueOf(100) == Integer.valueOf(100) is true because they're the same cached instance, but Integer.valueOf(200) == Integer.valueOf(200) is false. This is a well-known interview trap — always use .equals() on wrapper types.

Memory footprint: a primitive int is 4 bytes; an Integer object is ~16 bytes of header + 4 bytes of value. For large arrays of numbers, using int[] instead of Integer[] cuts memory by ~4× and is significantly faster to iterate due to cache locality. In test data builders, prefer primitive arrays for large datasets.

// EXAMPLE

PrimitivesAndWrappers.java

// Primitive vs wrapper
int primitive = 42;          // 4 bytes, on stack
Integer wrapped = 42;        // ~20 bytes, on heap

// Wrapper required for generics and null
List<Integer> ids = new ArrayList<>();
ids.add(primitive);          // autoboxed to Integer automatically
Integer nullable = null;     // legal
// int nullable = null;      // ❌ compile error

// Integer cache trap — only -128 to 127 are cached
Integer a = 127;
Integer b = 127;
System.out.println(a == b);  // true  (same cached instance)

Integer c = 128;
Integer d = 128;
System.out.println(c == d);  // false (different heap objects!)
System.out.println(c.equals(d)); // true — always use equals()

// Utility methods live on wrapper classes
int parsed = Integer.parseInt("42");
String binary = Integer.toBinaryString(255); // "11111111"
boolean isNaN = Double.isNaN(0.0 / 0.0);    // true

// WHAT INTERVIEWERS LOOK FOR

The 8 primitives by name, why wrappers exist (generics, null, utility methods), the Integer cache trap (== on boxed values), and the memory/performance tradeoff. Strong answers mention the cache range (-128 to 127) and always recommending .equals() on wrapper types.

// COMMON PITFALL

Forgetting about the Integer cache and saying '== always fails on objects'. It fails for values outside the cache range but passes for -128 to 127, making it an intermittent bug that only surfaces with larger IDs or counters.