Skip to main content

9.3 Wrapper Classes

Java has 8 primitive types (int, char, double, boolean, etc.) that are not objects. They are used frequently because they are lightweight and fast, but when doing object-oriented programming, the moment inevitably comes when you need to treat these simple numbers or characters as objects. For example, ArrayList<int> is not possible, but ArrayList<Integer> is. The classes that wrap primitive variables in an object container are called Wrapper classes.

1. The 8 Wrapper Classes

The mapping between primitive types and their wrapper classes is shown below. Most follow the pattern of capitalizing the first letter, but int and char have different names.

PrimitiveWrapper ClassSizeRange
byteByte1 byte-128 ~ 127
shortShort2 bytes-32,768 ~ 32,767
intInteger4 bytes-2,147,483,648 ~ 2,147,483,647
longLong8 bytes-9.2 * 10^18 ~ 9.2 * 10^18
floatFloat4 bytesapprox. ±3.4 * 10^38
doubleDouble8 bytesapprox. ±1.8 * 10^308
charCharacter2 bytes0 ~ 65,535 (Unicode)
booleanBoolean1 bittrue / false

2. Autoboxing and Unboxing

Since JDK 5, the Java compiler automatically handles conversions between primitives and wrapper objects.

  • Boxing: primitive → wrapper object (wrapping)
  • Unboxing: wrapper object → primitive (unwrapping)
  • Autoboxing/Auto-unboxing: the compiler handles both processes automatically
public class AutoboxingDemo {
public static void main(String[] args) {
// === Manual boxing (deprecated since Java 9) ===
// Integer deprecated = new Integer(10); // not recommended

// === Autoboxing: primitive → wrapper object (automatic) ===
Integer num1 = 42; // int → Integer automatic conversion
Double d1 = 3.14; // double → Double automatic conversion
Boolean b1 = true; // boolean → Boolean automatic conversion
Character c1 = 'A'; // char → Character automatic conversion

// === Auto-unboxing: wrapper object → primitive (automatic) ===
int i1 = num1; // Integer → int automatic conversion
double dval = d1; // Double → double automatic conversion

// === Mixed arithmetic (auto-unboxing before operation) ===
Integer a = 10;
Integer b = 20;
int sum = a + b; // internally: a.intValue() + b.intValue()
System.out.println("Sum: " + sum); // 30

// Naturally mix Integer and int in operations
Integer counter = 0;
for (int i = 0; i < 5; i++) {
counter++; // auto-unbox → increment → autobox
}
System.out.println("Counter: " + counter); // 5
}
}
Autoboxing Performance Caution

Using wrapper types like Integer inside loops causes repeated autoboxing/unboxing, which degrades performance. Use the primitive type int in performance-critical loops.

public class AutoboxingPerformance {
public static void main(String[] args) {
int N = 10_000_000;

// Bad: using Integer inside a loop (N autoboxing operations!)
long start1 = System.currentTimeMillis();
Integer sum1 = 0;
for (int i = 0; i < N; i++) {
sum1 += i; // sum1 = sum1 + i → unbox + add + autobox
}
long time1 = System.currentTimeMillis() - start1;

// Good: using int (no autoboxing)
long start2 = System.currentTimeMillis();
int sum2 = 0;
for (int i = 0; i < N; i++) {
sum2 += i;
}
long time2 = System.currentTimeMillis() - start2;

System.out.println("Using Integer: " + time1 + "ms, result: " + sum1);
System.out.println("Using int: " + time2 + "ms, result: " + sum2);
// Integer is significantly slower (approx. 5~10x difference)
}
}

3. Integer Cache: The == Comparison Trap

Wrapper classes like Integer cache values in the range -128 ~ 127. Within this range, == returns true, but outside this range it returns false.

public class IntegerCacheDemo {
public static void main(String[] args) {
// Range -128 ~ 127: cached objects are reused
Integer a = 100;
Integer b = 100;
System.out.println(a == b); // true (same cached object)
System.out.println(a.equals(b)); // true

// Above 127: a new object is created each time
Integer c = 200;
Integer d = 200;
System.out.println(c == d); // false (different objects!)
System.out.println(c.equals(d)); // true (same content)

// Conclusion: always use equals() to compare Integer values!
Integer x = 1000;
Integer y = 1000;
System.out.println(x.equals(y)); // true (correct comparison)
}
}
Integer Comparison Rule

Always use equals() to compare wrapper class objects. == compares object addresses, so it produces unexpected results outside the cached range.

4. Wrapper Class Utility Methods

Wrapper classes provide powerful static utility methods beyond simple wrapping.

Integer Utilities

public class IntegerUtils {
public static void main(String[] args) {
// String ↔ number conversion
int parsed = Integer.parseInt("42"); // "42" → 42
int parsedHex = Integer.parseInt("FF", 16); // "FF" (hex) → 255
int parsedBin = Integer.parseInt("1010", 2); // "1010" (binary) → 10
System.out.printf("decimal: %d, hex: %d, binary: %d%n", parsed, parsedHex, parsedBin);

// Number → various radix strings
System.out.println(Integer.toBinaryString(255)); // 11111111
System.out.println(Integer.toHexString(255)); // ff
System.out.println(Integer.toOctalString(255)); // 377

// Min/max constants
System.out.println(Integer.MAX_VALUE); // 2147483647
System.out.println(Integer.MIN_VALUE); // -2147483648
System.out.println(Long.MAX_VALUE); // 9223372036854775807

// Bit manipulation
System.out.println(Integer.bitCount(255)); // 8 (number of set bits)
System.out.println(Integer.highestOneBit(100)); // 64
System.out.println(Integer.numberOfLeadingZeros(1)); // 31
System.out.println(Integer.reverse(1)); // bit reversal

// Comparison
System.out.println(Integer.compare(10, 20)); // -1 (10 < 20)
System.out.println(Integer.max(10, 20)); // 20
System.out.println(Integer.min(10, 20)); // 10
System.out.println(Integer.sum(10, 20)); // 30
}
}

Double and Character Utilities

public class WrapperUtils {
public static void main(String[] args) {
// Double
System.out.println(Double.parseDouble("3.14")); // 3.14
System.out.println(Double.isNaN(0.0 / 0.0)); // true (Not a Number)
System.out.println(Double.isInfinite(1.0 / 0)); // true (infinity)
System.out.println(Double.MAX_VALUE); // 1.7976931348623157E308
System.out.println(Double.MIN_VALUE); // 4.9E-324 (smallest positive value)

// Character
System.out.println(Character.isLetter('A')); // true
System.out.println(Character.isDigit('5')); // true
System.out.println(Character.isWhitespace(' ')); // true
System.out.println(Character.isUpperCase('A')); // true
System.out.println(Character.isLowerCase('a')); // true
System.out.println(Character.toUpperCase('a')); // A
System.out.println(Character.toLowerCase('Z')); // z
System.out.println(Character.isLetterOrDigit('9')); // true

// Boolean
System.out.println(Boolean.parseBoolean("true")); // true
System.out.println(Boolean.parseBoolean("TRUE")); // true
System.out.println(Boolean.parseBoolean("yes")); // false (only "true" is recognized)
System.out.println(Boolean.toString(false)); // "false"
}
}

5. The Number Class Hierarchy

All numeric wrapper classes (Integer, Long, Double, Float, Short, Byte) inherit from the abstract class Number.

public class NumberHierarchy {
public static void printNumber(Number n) {
// Accepts Number type and can extract various primitives
System.out.printf("intValue: %d, doubleValue: %.2f, longValue: %d%n",
n.intValue(), n.doubleValue(), n.longValue());
}

public static void main(String[] args) {
printNumber(42); // int → Integer → Number
printNumber(3.14); // double → Double → Number
printNumber(100L); // long → Long → Number
printNumber((short)5); // short → Short → Number
}
}

6. Null-Safe Handling with Optional

Since Java 8, using Optional<T> provides safer handling than working directly with null.

import java.util.Optional;

public class OptionalExample {
// Method that may return null (unsafe)
static Integer findScoreUnsafe(String name) {
if ("Alice".equals(name)) return 95;
return null; // returns null if not found → risk of NullPointerException!
}

// Method returning Optional (safe)
static Optional<Integer> findScore(String name) {
if ("Alice".equals(name)) return Optional.of(95);
return Optional.empty(); // return empty Optional instead of null
}

public static void main(String[] args) {
// Basic Optional usage
Optional<Integer> result = findScore("Alice");
if (result.isPresent()) {
System.out.println("Score: " + result.get()); // Score: 95
}

// orElse: return default value if absent
int score = findScore("Bob").orElse(0);
System.out.println("Bob's score: " + score); // 0

// orElseGet: execute lambda if absent
int score2 = findScore("Charlie").orElseGet(() -> -1);
System.out.println("Charlie's score: " + score2); // -1

// orElseThrow: throw exception if absent
try {
int score3 = findScore("Dave").orElseThrow(
() -> new RuntimeException("Score not found")
);
} catch (RuntimeException e) {
System.out.println("Error: " + e.getMessage()); // Error: Score not found
}

// ifPresent: execute only if value is present
findScore("Alice").ifPresent(s -> System.out.println("Alice's score: " + s));

// map: transform value if present
Optional<String> grade = findScore("Alice").map(s -> s >= 90 ? "A" : "B");
System.out.println("Grade: " + grade.orElse("none")); // Grade: A
}
}

7. Practical Example: Safely Converting a String Array to Integers

import java.util.ArrayList;
import java.util.List;

public class SafeIntegerConversion {

// Safe string → integer conversion (returns null on failure)
static Integer safeParseInt(String s) {
if (s == null || s.isBlank()) return null;
try {
return Integer.parseInt(s.trim());
} catch (NumberFormatException e) {
return null;
}
}

// Convert string array to list of valid integers
static List<Integer> parseIntegers(String[] strings) {
List<Integer> result = new ArrayList<>();
for (String s : strings) {
Integer value = safeParseInt(s);
if (value != null) {
result.add(value);
} else {
System.out.println("Conversion failed (skipped): '" + s + "'");
}
}
return result;
}

public static void main(String[] args) {
String[] inputs = {"10", "25", "abc", null, " 30 ", "", "15", "xyz", "100"};

System.out.println("=== String Array → Integer Conversion ===");
List<Integer> numbers = parseIntegers(inputs);

System.out.println("\nSuccessfully converted: " + numbers);
System.out.println("Count: " + numbers.size());

// Compute statistics
int sum = 0;
int max = Integer.MIN_VALUE;
int min = Integer.MAX_VALUE;

for (Integer n : numbers) {
sum += n; // auto-unboxing
if (n > max) max = n; // auto-unboxing
if (n < min) min = n; // auto-unboxing
}

double avg = (double) sum / numbers.size();
System.out.printf("Sum: %d, Average: %.1f, Max: %d, Min: %d%n",
sum, avg, max, min);
}
}

Output:

=== String Array → Integer Conversion ===
Conversion failed (skipped): 'abc'
Conversion failed (skipped): 'null'
Conversion failed (skipped): ''
Conversion failed (skipped): 'xyz'

Successfully converted: [10, 25, 30, 15, 100]
Count: 5
Sum: 180, Average: 36.0, Max: 100, Min: 10
Pro Tip: Wrapper Class Usage Guidelines
  1. Collections, generics: Use where only objects are allowed, such as List<Integer>, Map<String, Boolean>
  2. Allow null: Declare fields as Integer type when null can come from a database
  3. Utility methods: Use static methods like Integer.parseInt(), Double.isNaN()
  4. Performance matters: Use primitives int, double for repeated computations
  5. Comparison: Always use equals() to compare wrapper objects; never use ==

In the next chapter we will learn about the Object class, the ultimate parent of all classes.