Can you use primitive types as generic parameters
Generics use type erasure — after compilation, T is replaced with Object. Primitives do not inherit from Object, so they cannot be type parameters.
🟢 Junior Level
No, you cannot. Generics in Java work only with reference types, not with primitives.
// ❌ Cannot use primitives
List<int> ints = new ArrayList<>(); // compilation error
Map<String, double> map = new HashMap<>(); // compilation error
Box<char> box = new Box<>(); // compilation error
// ✅ Use wrapper classes
List<Integer> ints = new ArrayList<>(); // OK
Map<String, Double> map = new HashMap<>(); // OK
Box<Character> box = new Box<>(); // OK
All primitives and their wrapper classes:
| Primitive | Wrapper |
|---|---|
byte |
Byte |
short |
Short |
int |
Integer |
long |
Long |
float |
Float |
double |
Double |
boolean |
Boolean |
char |
Character |
🟡 Middle Level
Why is it this way?
Generics use type erasure — after compilation, T is replaced with Object. Primitives do not inherit from Object, so they cannot be type parameters.
public class Box<T> {
private T value;
}
// After type erasure:
public class Box {
private Object value; // T -> Object
}
// int cannot be Object — that's why Box<int> is not allowed
Autoboxing and Unboxing
Java automatically converts primitives ↔ wrapper classes:
List<Integer> list = new ArrayList<>();
// Autoboxing: int -> Integer
list.add(42); // compiler: list.add(Integer.valueOf(42))
// Unboxing: Integer -> int
int value = list.get(0); // compiler: list.get(0).intValue()
Autoboxing overhead:
// Primitive — 4 bytes, on stack
int primitive = 42;
// Wrapper — ~24 bytes, on heap
Integer wrapper = Integer.valueOf(42);
// - Object header: 12-16 bytes
// - int value: 4 bytes
// - Padding: 4 bytes
Common mistakes
- Autoboxing null:
Integer wrapper = null; int primitive = wrapper; // ❌ NullPointerException! - Autoboxing performance:
```java
// ❌ Slow — Integer created for every iteration
List
list = new ArrayList<>(); for (int i = 0; i < 1_000_000; i++) { list.add(i); // autoboxing every time }
// ✅ Faster — primitive collection int[] array = new int[1_000_000]; for (int i = 0; i < 1_000_000; i++) { array[i] = i; }
---
## 🔴 Senior Level
### Internal Implementation
**Autoboxing cache:**
```java
// Integer caches values from -128 to 127
Integer a = 100;
Integer b = 100;
System.out.println(a == b); // true (same object from cache)
Integer c = 200;
Integer d = 200;
System.out.println(c == d); // false (different objects)
// Integer.valueOf(int) checks cache:
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
Memory overhead:
Type | Size | Location
-------------|-----------|-------------
int | 4 bytes | Stack
Integer | ~24 bytes | Heap
int[] | 4N bytes | Heap
Integer[] | ~24N bytes| Heap
ArrayList<Integer> | ~32N bytes | Heap
Architectural Trade-offs
Wrapper vs primitives:
| Aspect | Wrapper | Primitives |
|---|---|---|
| Memory | 24 bytes | 4 bytes |
| Speed | Slower | Faster |
| Null | Allowed | Not allowed |
| Generics | ✅ | ❌ |
| Collections | ✅ | ❌ |
Edge Cases
1. Autoboxing in conditions:
Integer count = 0;
for (int i = 0; i < 1000; i++) {
count = count + i; // unboxing + boxing every time
}
// 2000 autoboxing/unboxing operations!
// ✅ Better
int count = 0;
for (int i = 0; i < 1000; i++) {
count += i;
}
2. Ternary operator with autoboxing:
Integer a = 1;
Integer b = null;
// ⚠️ Dangerous — unboxing null
Integer result = condition ? a : b; // OK
int primitive = condition ? a : b; // ❌ NPE if a OR b == null when unboxing to int.
Performance
Benchmark (JMH, Java 21):
Operation | Time
-------------------------|--------
int addition | 0.3 ns
Integer addition | 1.5 ns (5x slower)
// Approximate values (JMH). Depend on JVM, CPU, warmup.
int[] access | 1 ns
ArrayList<Integer> get | 3 ns (3x slower)
Autoboxing int->Integer | 5 ns
Unboxing Integer->int | 1 ns
Highload consequences:
// ❌ Problem — 1M operations with autoboxing
List<Integer> numbers = new ArrayList<>();
for (int i = 0; i < 1_000_000; i++) {
numbers.add(i); // 1M autoboxing = ~5ms + 24MB memory
}
// ✅ Solution — primitive collections
// Eclipse Collections, fastutil, or arrays
int[] numbers = new int[1_000_000]; // 4MB memory, zero autoboxing
Production Experience
Primitive collections:
// fastutil — library for primitive collections
IntList numbers = new IntArrayList();
numbers.add(42); // no autoboxing
int value = numbers.getInt(0); // no unboxing
// Eclipse Collections
MutableIntList list = IntLists.mutable.empty();
list.add(1);
list.add(2);
// Or just arrays
int[] array = new int[100];
Stream API with primitives:
// ❌ Stream<Integer> — autoboxing
List<Integer> list = List.of(1, 2, 3);
int sum = list.stream()
.mapToInt(Integer::intValue) // unboxing
.sum();
// ✅ IntStream — no autoboxing
int[] array = {1, 2, 3};
int sum = Arrays.stream(array).sum();
// ✅ IntStream range
int sum = IntStream.rangeClosed(1, 100).sum();
Best Practices
// ✅ Wrapper for collections and generics
List<Integer> list = new ArrayList<>();
Map<String, Double> map = new HashMap<>();
// ✅ Primitives for computations
int sum = 0;
for (int i = 0; i < 1000; i++) {
sum += i;
}
// ✅ Primitive streams
int sum = IntStream.range(0, 100).sum();
// ✅ Primitive collections for highload
int[] array = new int[1_000_000];
// ❌ Wrapper for computations
// ❌ Ignoring autoboxing overhead
// ❌ Autoboxing null
🎯 Interview Cheat Sheet
Must know:
- Primitives cannot be used as type parameters:
List<int>— compilation error - Reason: type erasure replaces T with Object, primitives do not inherit Object
- Autoboxing: primitive -> wrapper (
int->Integer), unboxing: the reverse - Integer caches values -128..127 (IntegerCache), other wrappers have caches too
- Autoboxing overhead: ~24 bytes per Integer vs 4 bytes int, ~5 ns per boxing
- For highload: primitive collections (fastutil, Eclipse Collections) or arrays
Frequent follow-up questions:
- Why can’t primitives be used in generics? — Type erasure: T -> Object, primitives are not Object
- What is the overhead of autoboxing? — Memory: 24 bytes vs 4 bytes, time: ~5 ns per boxing
- What happens when unboxing null? — NullPointerException:
Integer x = null; int y = x; - When to use primitive collections? — Highload, large data volumes (1M+ elements)
Red flags (DO NOT say):
- ❌ “List
works in new Java" — Forbidden forever, no plans to add it - ❌ “Autoboxing is free” — 6x memory + time for boxing/unboxing
- ❌ “Integer a = 100; Integer b = 100; a == b is always true” — Only for cache (-128..127)
- ❌ “Stream
is as fast as IntStream" — IntStream without autoboxing is significantly faster
Related topics:
- [[11. What are Generics in Java]]
- [[13. What is type erasure]]
- [[14. Can you create an array of generic type]]
- [[22. Can you overload methods that differ only in generic parameters]]