Java Notes
Complete guide to wrapper classes in Java — autoboxing, unboxing, primitive to object conversion, number parsing, caching, and when to use primitives vs wrappers.
Wrapper classes provide an object representation for each of Java's 8 primitive types. They "wrap" a primitive value in an object, enabling primitives to participate in the object-oriented world — collections, generics, null values, and utility methods.
Primitive to Wrapper Mapping
| Primitive | Wrapper Class | Size |
|---|---|---|
byte | Byte | 8 bits |
short | Short | 16 bits |
int | Integer | 32 bits |
long | Long | 64 bits |
float | Float | 32 bits |
double | Double | 64 bits |
char | Character | 16 bits |
boolean | Boolean | 1 bit |
Why Wrapper Classes Exist
1. Collections Require Objects
2. Generics Require Objects
// ❌ Cannot use primitives as type parameters
// Map<int, String> map = new HashMap<int, String>();
// ✅ Must use wrappers
Map<Integer, String> map = new HashMap<>();
map.put(1, "One");
map.put(2, "Two");3. Null Values
// Primitive cannot be null
// int value = null; // ❌ COMPILE ERROR
// Wrapper CAN be null — useful for "no value" / database nulls
Integer value = null; // ✅ Valid — represents absence of value4. Utility Methods
// Wrappers provide useful conversion and inspection methods
int max = Integer.MAX_VALUE; // 2147483647
int min = Integer.MIN_VALUE; // -2147483648
int parsed = Integer.parseInt("42"); // String to int
String binary = Integer.toBinaryString(255); // "11111111"
String hex = Integer.toHexString(255); // "ff"
int bits = Integer.bitCount(255); // 8 (number of 1-bits)
System.out.println("Max int: " + max);
System.out.println("Parsed: " + parsed);
System.out.println("Binary of 255: " + binary);
System.out.println("Hex of 255: " + hex);
System.out.println("Bit count of 255: " + bits);Max int: 2147483647 Parsed: 42 Binary of 255: 11111111 Hex of 255: ff Bit count of 255: 8
Autoboxing and Unboxing
Autoboxing: Automatic conversion from primitive to wrapper (compiler inserts valueOf()). Unboxing: Automatic conversion from wrapper to primitive (compiler inserts xxxValue()).
public class AutoboxingDemo {
public static void main(String[] args) {
// Autoboxing: int → Integer (compiler adds Integer.valueOf(42))
Integer boxed = 42;
System.out.println("Autoboxed: " + boxed);
// Unboxing: Integer → int (compiler adds .intValue())
int unboxed = boxed;
System.out.println("Unboxed: " + unboxed);
// Autoboxing in expressions
Integer a = 10;
Integer b = 20;
int sum = a + b; // Both unboxed, added, result assigned to int
System.out.println("Sum: " + sum);
// Autoboxing in collections
List<Double> prices = new ArrayList<>();
prices.add(9.99); // double autoboxed to Double
prices.add(19.99);
prices.add(29.99);
double total = 0;
for (double price : prices) { // Double unboxed to double
total += price;
}
System.out.println("Total: $" + total);
}
}Autoboxed: 42 Unboxed: 42 Sum: 30 Total: $59.97
Creating Wrapper Objects
public class CreationDemo {
public static void main(String[] args) {
// Method 1: valueOf() — PREFERRED (uses cache)
Integer i1 = Integer.valueOf(42);
Integer i2 = Integer.valueOf("42");
Double d1 = Double.valueOf(3.14);
Boolean b1 = Boolean.valueOf("true");
// Method 2: Autoboxing — same as valueOf()
Integer i3 = 42; // Compiler converts to Integer.valueOf(42)
// Method 3: new — DEPRECATED since Java 9
// Integer i4 = new Integer(42); // ❌ Deprecated! Don't use.
// Parsing strings
int parsed = Integer.parseInt("123"); // Returns primitive int
double dParsed = Double.parseDouble("3.14"); // Returns primitive double
boolean bParsed = Boolean.parseBoolean("true"); // Returns primitive boolean
System.out.println(i1 + ", " + i2 + ", " + d1 + ", " + b1);
System.out.println(parsed + ", " + dParsed + ", " + bParsed);
}
}42, 42, 3.14, true 123, 3.14, true
Integer Cache — The -128 to 127 Trap
Java caches Integer objects from -128 to 127. This creates a subtle equality trap:
public class CacheDemo {
public static void main(String[] args) {
// Values within cache range (-128 to 127)
Integer a = 100;
Integer b = 100;
System.out.println("a == b (100): " + (a == b)); // true — same cached object!
System.out.println("a.equals(b): " + a.equals(b)); // true
// Values OUTSIDE cache range
Integer c = 200;
Integer d = 200;
System.out.println("c == d (200): " + (c == d)); // FALSE! Different objects!
System.out.println("c.equals(d): " + c.equals(d)); // true
// Same applies to: Byte, Short, Long (-128 to 127)
// Character (0 to 127)
// Boolean (always cached: TRUE and FALSE)
Long l1 = 50L;
Long l2 = 50L;
System.out.println("Long 50 ==: " + (l1 == l2)); // true (cached)
Long l3 = 200L;
Long l4 = 200L;
System.out.println("Long 200 ==: " + (l3 == l4)); // false (not cached)
}
}a == b (100): true a.equals(b): true c == d (200): false c.equals(d): true Long 50 ==: true Long 200 ==: false
Rule: ALWAYS use .equals() to compare wrapper objects, never ==!
NullPointerException with Unboxing
The most dangerous wrapper trap — unboxing null:
public class NullDanger {
public static void main(String[] args) {
Integer value = null;
// ❌ NullPointerException! Unboxing null → crash
try {
int primitive = value; // Compiler adds value.intValue() — but value is null!
} catch (NullPointerException e) {
System.out.println("NPE! Cannot unbox null Integer");
}
// ✅ Always check for null before unboxing
if (value != null) {
int safe = value;
}
// ❌ Also dangerous in conditions
Boolean flag = null;
try {
if (flag) { // Unboxing null Boolean → NPE!
System.out.println("Never reaches here");
}
} catch (NullPointerException e) {
System.out.println("NPE! Cannot unbox null Boolean");
}
}
}NPE! Cannot unbox null Integer NPE! Cannot unbox null Boolean
Conversion Methods
public class ConversionDemo {
public static void main(String[] args) {
// String → Primitive
int i = Integer.parseInt("42");
double d = Double.parseDouble("3.14");
long l = Long.parseLong("1000000");
boolean b = Boolean.parseBoolean("true");
// String → Wrapper (returns object)
Integer iObj = Integer.valueOf("42");
Double dObj = Double.valueOf("3.14");
// Primitive → String
String s1 = Integer.toString(42);
String s2 = String.valueOf(42);
String s3 = 42 + ""; // Concatenation (less efficient)
// Wrapper → different primitive type
Integer num = 42;
double asDouble = num.doubleValue();
long asLong = num.longValue();
float asFloat = num.floatValue();
// Number base conversions
System.out.println(Integer.toBinaryString(42)); // 101010
System.out.println(Integer.toOctalString(42)); // 52
System.out.println(Integer.toHexString(42)); // 2a
System.out.println(Integer.parseInt("2a", 16)); // 42 (hex to int)
System.out.println(Integer.parseInt("101010", 2)); // 42 (binary to int)
}
}101010 52 2a 42 42
Primitive vs Wrapper — Performance
Primitive: 5 ms Wrapper: 45 ms Wrapper is ~9.0x slower
When to Use Primitives vs Wrappers
| Use Primitive When | Use Wrapper When |
|---|---|
| Performance matters (loops, calculations) | Collections/Generics require objects |
| Value can never be null | Value might be null (e.g., database fields) |
| Simple data fields | Need utility methods (parsing, comparing) |
| Array elements | Need to store in Map, List, Set |
| Method parameters (usually) | Optional values / nullable results |
Real-World Example: Data Processing
public class DataProcessor {
// Using wrappers for nullable database fields
public static class DatabaseRow {
private Integer id;
private String name;
private Double salary; // May be null (new employees)
private Integer managerId; // May be null (CEO has no manager)
private Boolean active;
public DatabaseRow(Integer id, String name, Double salary,
Integer managerId, Boolean active) {
this.id = id;
this.name = name;
this.salary = salary;
this.managerId = managerId;
this.active = active;
}
public void printInfo() {
System.out.printf("ID: %d | Name: %s | Salary: %s | Manager: %s | Active: %s%n",
id,
name,
salary != null ? String.format("$%.2f", salary) : "TBD",
managerId != null ? managerId.toString() : "None (top level)",
active != null ? active : "Unknown");
}
}
public static void main(String[] args) {
List<DatabaseRow> employees = List.of(
new DatabaseRow(1, "Alice", 95000.0, null, true),
new DatabaseRow(2, "Bob", 75000.0, 1, true),
new DatabaseRow(3, "Charlie", null, 1, null), // New hire, no salary yet
new DatabaseRow(4, "Diana", 85000.0, 2, false)
);
for (DatabaseRow row : employees) {
row.printInfo();
}
// Calculate average salary (skip nulls)
double avgSalary = employees.stream()
.map(r -> r.salary)
.filter(s -> s != null)
.mapToDouble(Double::doubleValue)
.average()
.orElse(0);
System.out.printf("\nAverage salary: $%.2f%n", avgSalary);
}
}ID: 1 | Name: Alice | Salary: $95000.00 | Manager: None (top level) | Active: true ID: 2 | Name: Bob | Salary: $75000.00 | Manager: 1 | Active: true ID: 3 | Name: Charlie | Salary: TBD | Manager: 1 | Active: Unknown ID: 4 | Name: Diana | Salary: $85000.00 | Manager: 2 | Active: false Average salary: $85000.00
Common Mistakes
- Using == to compare wrappers:
``java Integer a = 200; Integer b = 200; if (a == b) { } // ❌ False! Compares references outside cache range if (a.equals(b)) { } // ✅ True! Compares values ``
- Unboxing null:
``java Integer x = null; int y = x; // ❌ NullPointerException! ``
- Using wrappers in tight loops:
``java Integer sum = 0; for (int i = 0; i < 1000000; i++) { sum += i; // ❌ Creates ~1 million Integer objects! } int sum = 0; // ✅ Use primitive ``
- Assuming Integer cache size:
``java // Cache range is -128 to 127 by default // Can be extended with -XX:AutoBoxCacheMax=N JVM flag // But don't rely on this behavior! ``
- Confusing parseInt() vs valueOf():
``java int x = Integer.parseInt("42"); // Returns primitive int Integer y = Integer.valueOf("42"); // Returns Integer object (may use cache) ``
Best Practices
- Use primitives for local variables, loop counters, and computation
- Use wrappers for collections, nullable fields, and generic types
- Always use
.equals()for wrapper comparison - Check for null before unboxing
- Prefer
valueOf()overnew Integer()(deprecated) - Use
OptionalInt,OptionalDoubleinstead of nullable wrappers where appropriate - Be aware of autoboxing in performance-critical loops
Interview Questions
Q1: What is autoboxing and unboxing? Autoboxing: automatic conversion of primitive to wrapper (int → Integer). Unboxing: automatic conversion of wrapper to primitive (Integer → int). Done by the compiler.
Q2: Why does Integer a = 127; Integer b = 127; (a == b) return true? Java caches Integer objects from -128 to 127. Both a and b point to the same cached object. Outside this range, new objects are created.
Q3: What happens when you unbox a null wrapper? NullPointerException. The compiler inserts .intValue() (or similar), and calling any method on null throws NPE.
Q4: Can we create our own wrapper class? Yes, but there's rarely a need. The standard wrappers handle all 8 primitives. You might create a wrapper for domain types (e.g., Money, Percentage).
Q5: Why are wrapper classes immutable? For thread safety, cache correctness, and use as HashMap keys. If Integer were mutable, cached instances could be corrupted, and hash codes would change.
Q6: What is the difference between parseInt() and valueOf()? parseInt() returns the primitive int. valueOf() returns an Integer object (potentially from cache). Use parseInt when you need a primitive.
Q7: Why not use wrappers everywhere? Performance. Wrapper objects consume more memory (~16 bytes vs 4 for int), require heap allocation, and create garbage collection pressure. Use primitives when possible.
Q8: What is Integer caching in Java? What range is cached? Java caches Integer objects for values -128 to 127. Integer a = 127; Integer b = 127; gives a == b as true (same cached object). But Integer a = 128; Integer b = 128; gives a == b as false (different objects). Always use .equals() for wrapper comparison.
Q9: Why can autoboxing cause NullPointerException? When you unbox a null wrapper to a primitive: Integer num = null; int x = num; throws NPE because null can't be converted to a primitive value. This is common in Map operations: map.get(key) returns null if key doesn't exist.
Q10: What is the performance impact of autoboxing in loops? Autoboxing creates new objects. In tight loops: Long sum = 0L; for (...) sum += i; creates thousands of Long objects (box, unbox, add, re-box). Use primitive long sum instead. This can be 5-10x slower with wrapper types in computation-heavy code.
Q11: Why are wrapper classes immutable? Immutability ensures: (1) thread safety without synchronization, (2) safe use as HashMap keys, (3) caching works (cached values can't be modified), (4) predictable behavior. Once created, Integer(5) always represents 5. Need a different value? Create a new object.
Q12: What is the valueOf() vs new Integer() difference? Integer.valueOf(5) uses the cache — returns existing object for -128 to 127. new Integer(5) always creates a new object (deprecated since Java 9). Always prefer valueOf() or autoboxing for better performance and correct behavior with ==.
Exam Focus
Revise definitions, diagrams, examples, and short-answer points for Wrapper Class in Java.
Interview Use
Prepare one clear explanation, one practical example, and one common mistake for this Java Master Course topic.
Search Terms
java-master-course, java master course, java, master, course, oops, wrapper, class
Related Java Master Course Topics