Можно ли использовать примитивные типы как параметры дженериков
Дженерики используют type erasure — после компиляции T заменяется на Object. Примитивы не наследуются от Object, поэтому не могут быть type parameters.
🟢 Junior Level
Нет, нельзя. Дженерики в Java работают только с объектными типами (reference types), а не с примитивами.
// ❌ Нельзя использовать примитивы
List<int> ints = new ArrayList<>(); // compilation error
Map<String, double> map = new HashMap<>(); // compilation error
Box<char> box = new Box<>(); // compilation error
// ✅ Используйте wrapper-классы
List<Integer> ints = new ArrayList<>(); // OK
Map<String, Double> map = new HashMap<>(); // OK
Box<Character> box = new Box<>(); // OK
Все примитивы и их wrapper-классы:
| Примитив | Wrapper |
|---|---|
byte |
Byte |
short |
Short |
int |
Integer |
long |
Long |
float |
Float |
double |
Double |
boolean |
Boolean |
char |
Character |
🟡 Middle Level
Почему так?
Дженерики используют type erasure — после компиляции T заменяется на Object. Примитивы не наследуются от Object, поэтому не могут быть type parameters.
public class Box<T> {
private T value;
}
// После type erasure:
public class Box {
private Object value; // T -> Object
}
// int не может быть Object — поэтому нельзя Box<int>
Autoboxing и Unboxing
Java автоматически конвертирует примитивы ↔ wrapper-классы:
List<Integer> list = new ArrayList<>();
// Autoboxing: int -> Integer
list.add(42); // компилятор: list.add(Integer.valueOf(42))
// Unboxing: Integer -> int
int value = list.get(0); // компилятор: list.get(0).intValue()
Оверhead autoboxing:
// Примитив — 4 байта, на стеке
int primitive = 42;
// Wrapper — ~24 байта, в куче
Integer wrapper = Integer.valueOf(42);
// - Заголовок объекта: 12-16 байт
// - Значение int: 4 байта
// - Выравнивание: 4 байта
Типичные ошибки
- Autoboxing null:
Integer wrapper = null; int primitive = wrapper; // ❌ NullPointerException! - Autoboxing performance:
```java
// ❌ Медленно — создаётся Integer для каждой итерации
List
list = new ArrayList<>(); for (int i = 0; i < 1_000_000; i++) { list.add(i); // autoboxing каждый раз }
// ✅ Быстрее — примитивная коллекция 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 кэширует значения от -128 до 127
Integer a = 100;
Integer b = 100;
System.out.println(a == b); // true (один объект из кеша)
Integer c = 200;
Integer d = 200;
System.out.println(c == d); // false (разные объекты)
// Integer.valueOf(int) проверяет кеш:
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:
Тип | Размер | Расположение
-------------|-----------|-------------
int | 4 bytes | Stack
Integer | ~24 bytes | Heap
int[] | 4N bytes | Heap
Integer[] | ~24N bytes| Heap
ArrayList<Integer> | ~32N bytes | Heap
Архитектурные Trade-offs
Wrapper vs примитивы:
| Аспект | Wrapper | Примитивы |
|---|---|---|
| Память | 24 байта | 4 байта |
| Скорость | Медленнее | Быстрее |
| Null | Можно | Нельзя |
| Дженерики | ✅ | ❌ |
| Коллекции | ✅ | ❌ |
Edge Cases
1. Autoboxing в условиях:
Integer count = 0;
for (int i = 0; i < 1000; i++) {
count = count + i; // unboxing + boxing каждый раз
}
// 2000 операций autoboxing/unboxing!
// ✅ Лучше
int count = 0;
for (int i = 0; i < 1000; i++) {
count += i;
}
2. Ternary operator с autoboxing:
Integer a = 1;
Integer b = null;
// ⚠️ Опасно — unboxing null
Integer result = condition ? a : b; // OK
int primitive = condition ? a : b; // ❌ NPE если a ИЛИ b == null при unboxing в int.
Производительность
Бенчмарк (JMH, Java 21):
Операция | Время
-------------------------|--------
int addition | 0.3 ns
Integer addition | 1.5 ns (5x медленнее)
// Примерные значения (JMH). Зависят от JVM, CPU, warmup.
int[] access | 1 ns
ArrayList<Integer> get | 3 ns (3x медленнее)
Autoboxing int->Integer | 5 ns
Unboxing Integer->int | 1 ns
Highload последствия:
// ❌ Проблема — 1M операций с autoboxing
List<Integer> numbers = new ArrayList<>();
for (int i = 0; i < 1_000_000; i++) {
numbers.add(i); // 1M autoboxing = ~5ms + 24MB памяти
}
// ✅ Решение — примитивные коллекции
// Eclipse Collections, fastutil, или массивы
int[] numbers = new int[1_000_000]; // 4MB памяти, zero autoboxing
Production Experience
Примитивные коллекции:
// fastutil — библиотека для примитивных коллекций
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);
// Или просто массивы
int[] array = new int[100];
Stream API с примитивами:
// ❌ Stream<Integer> — autoboxing
List<Integer> list = List.of(1, 2, 3);
int sum = list.stream()
.mapToInt(Integer::intValue) // unboxing
.sum();
// ✅ IntStream — без autoboxing
int[] array = {1, 2, 3};
int sum = Arrays.stream(array).sum();
// ✅ IntStream range
int sum = IntStream.rangeClosed(1, 100).sum();
Best Practices
// ✅ Wrapper для коллекций и дженериков
List<Integer> list = new ArrayList<>();
Map<String, Double> map = new HashMap<>();
// ✅ Примитивы для вычислений
int sum = 0;
for (int i = 0; i < 1000; i++) {
sum += i;
}
// ✅ Примитивные стримы
int sum = IntStream.range(0, 100).sum();
// ✅ Примитивные коллекции для highload
int[] array = new int[1_000_000];
// ❌ Wrapper для вычислений
// ❌ Игнорирование autoboxing overhead
// ❌ Autoboxing null
🎯 Шпаргалка для интервью
Обязательно знать:
- Примитивы нельзя использовать как type parameters:
List<int>— compilation error - Причина: type erasure заменяет T на Object, примитивы не наследуют Object
- Autoboxing: примитив -> wrapper (
int->Integer), unboxing: обратно - Integer кэширует значения -128..127 (IntegerCache), другие wrapper-ы тоже имеют кеш
- Autoboxing overhead: ~24 байта на Integer vs 4 байта int, ~5 ns на boxing
- Для highload: примитивные коллекции (fastutil, Eclipse Collections) или массивы
Частые уточняющие вопросы:
- Почему примитивы нельзя в дженерики? — Type erasure: T -> Object, примитивы не Object
- Какой overhead у autoboxing? — Память: 24 байта vs 4 байта, время: ~5 ns на boxing
- Что будет при unboxing null? — NullPointerException:
Integer x = null; int y = x; - Когда использовать примитивные коллекции? — Highload, большие объёмы данных (1M+ элементов)
Красные флаги (НЕ говорить):
- ❌ “List
работает в новой Java" — Запрещено навсегда, нет планов добавить - ❌ “Autoboxing бесплатный” — 6x памяти + время на boxing/unboxing
- ❌ “Integer a = 100; Integer b = 100; a == b всегда true” — Только для кеша (-128..127)
- ❌ “Stream
так же быстр как IntStream" — IntStream без autoboxing значительно быстрее
Связанные темы:
- [[11. Что такое дженерики (Generics) в Java]]
- [[13. Что такое type erasure (стирание типов)]]
- [[14. Можно ли создать массив дженерик-типа]]
- [[22. Можно ли перегружать методы, отличающиеся только дженерик-параметрами]]