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.
| Primitive | Wrapper Class | Size | Range |
|---|---|---|---|
byte | Byte | 1 byte | -128 ~ 127 |
short | Short | 2 bytes | -32,768 ~ 32,767 |
int | Integer | 4 bytes | -2,147,483,648 ~ 2,147,483,647 |
long | Long | 8 bytes | -9.2 * 10^18 ~ 9.2 * 10^18 |
float | Float | 4 bytes | approx. ±3.4 * 10^38 |
double | Double | 8 bytes | approx. ±1.8 * 10^308 |
char | Character | 2 bytes | 0 ~ 65,535 (Unicode) |
boolean | Boolean | 1 bit | true / 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
}
}
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)
}
}
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
- Collections, generics: Use where only objects are allowed, such as
List<Integer>,Map<String, Boolean> - Allow null: Declare fields as
Integertype when null can come from a database - Utility methods: Use static methods like
Integer.parseInt(),Double.isNaN() - Performance matters: Use primitives
int,doublefor repeated computations - 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.