В этом руководстве покажем плюсы и минусы использования примитивных типов Java и их обернутых аналогов.
Система типов Java
Java имеет двойную систему типов, состоящую из примитивов, таких как int, boolean, и ссылочных типов, таких как Integer, Boolean. Каждый примитивный тип соответствует ссылочному типу.
Каждый объект содержит одно значение соответствующего примитивного типа. Классы-оболочки являются неизменяемыми (immutable) (поэтому их состояние не может измениться после создания объекта) и окончательными (final) (поэтому нельзя наследоваться от них).
Под капотом Java выполняет преобразование между примитивными и ссылочными типами, если фактический тип отличается от объявленного:
Integer j = 1; // автоупаковка
int i = new Integer(1); // распаковка
Процесс преобразования примитивного типа в эталонный называется автоупаковкой (autoboxing), противоположный процесс называется распаковкой (unboxing).
Плюсы и минусы
Решение о том, какой объект следует использовать, основано на том, какую производительность приложения пытаемся достичь, сколько доступной памяти есть, объем доступной памяти и какие значения по умолчанию должны обрабатывать.
Если не столкнемся ни с одним из них, можно игнорировать эти соображения, хотя знать их стоит.
Объем памяти для одного элемента
Переменные примитивного типа имеют следующее влияние на память:
- boolean – 1 бит;
- byte – 8 бит;
- short, char – 16 бит;
- int, float – 32 бита;
- long, double – 64 бита.
На практике эти значения могут различаться в зависимости от реализации виртуальной машины. Например, в виртуальной машине Oracle boolean сопоставляется со значениями int 0 и 1, поэтому он занимает 32 бита, как описано здесь: Примитивные типы и значения.
Переменные этих типов находятся в стеке и поэтому доступны быстро. Для получения подробной информации смотри здесь.
Ссылочные типы – это объекты, они живут в куче и относительно медленны для доступа. У них есть определенные накладные расходы по сравнению с их примитивными аналогами.
Конкретные значения накладных расходов обычно зависят от JVM. Здесь представлены результаты для 64-битной виртуальной машины со следующими параметрами:
java 10.0.1 2018-04-17
Java(TM) SE Runtime Environment 18.3 (build 10.0.1+10)
Java HotSpot(TM) 64-Bit Server VM 18.3 (build 10.0.1+10, mixed mode)
Чтобы получить внутреннюю структуру объекта, можно использовать инструмент Java Object Layout (см. Как получить размер объекта в Java).
Получается, что один экземпляр ссылочного типа на этой JVM занимает 128 бит, кроме Long и Double, которые занимают 192 бита:
- Boolean – 128 бит;
- Byte – 128 бит;
- Short, Character – 128 бит;
- Integer, Float – 128 бит;
- Long, Double – 192 бита.
Одна переменная типа Boolean занимает столько же места, сколько 128 примитивных, а одна переменная Integer занимает столько же места, сколько четыре int.
Объем памяти для массивов
Ситуация становится более интересной, если сравнить, сколько памяти занимают массивы рассматриваемых типов. Когда создаем массивы с различным количеством элементов для каждого типа, получаем такой график:

График демонстрирует, что типы сгруппированы в четыре семейства в зависимости от того, как память m(s) зависит от количества элементов s массива:
- long, double: m(s) = 128 + 64 s
- short, char: m(s) = 128 + 64 [s/4]
- byte, boolean: m(s) = 128 + 64 [s/8]
- the rest: m(s) = 128 + 64 [s/2]
Квадратные скобки обозначают стандартную функцию предела.
Удивительно, но массивы примитивных типов long и double потребляют больше памяти, чем их классы-оболочки Long и Double.
Одноэлементные массивы примитивных типов почти всегда дороже (кроме long и double), чем соответствующий ссылочный тип.
Производительность
Производительность Java-кода – довольно тонкая проблема, она во многом зависит от аппаратного обеспечения, на котором выполняется код, от компилятора, который может выполнять определенные оптимизации, от состояния виртуальной машины, от активности других процессов в операционной системе.
Примитивные типы живут в стеке, а ссылочные типы – в куче. Это доминирующий фактор, определяющий скорость доступа к объектам.
Чтобы продемонстрировать, насколько операции для примитивных типов быстрее операций для классов-оболочек, создадим массив из пяти миллионов элементов, в котором все элементы равны, кроме последнего, затем выполним поиск этого элемента:
while (!pivot.equals(elements[index])) {
index++;
}
Теперь сравним производительность этой операции для случая, когда массив содержит переменные примитивных типов и для случая, когда он содержит объекты ссылочных типов.
Для этого используем известный инструмент сравнительного анализа JMH (см. руководство о том, как его использовать). Результаты операции поиска можно обобщить на этой диаграмме:

Даже для такой простой операции требуется больше времени для выполнения операции для классов-оболочек.
В случае более сложных операций, таких как суммирование, умножение или деление, разница в скорости может резко возрасти.
Значения по умолчанию
Значения по умолчанию примитивных типов 0 (в соответствующем представлении, т.е. 0, 0.0d и т. д.) для числовых типов, false для boolean, \u0000 для типа char. Для классов-оболочек значение по умолчанию равно null.
Это означает, что примитивные типы могут получать значения только из своих доменов, тогда как ссылочные типы могут получать значение (null), которое в каком-то смысле не принадлежит их доменам.
Хотя не рекомендуется оставлять переменные неинициализированными, иногда можно присвоить значение после его создания.
В такой ситуации, когда переменная примитивного типа имеет значение, равное ее типу по умолчанию, необходимо выяснить, действительно ли была инициализирована переменная. С переменными класса-оболочки такой проблемы нет, поскольку значение null является вполне очевидным признаком того, что переменная не была инициализирована.
Использование
Примитивные типы намного быстрее и требуют гораздо меньше памяти. Поэтому можно предпочесть их использование. С другой стороны, текущая спецификация языка Java не позволяет использовать примитивные типы в параметризованных типах (дженериках), в коллекциях Java или Reflection API.
Если приложению нужны коллекции с большим количеством элементов, необходимо рассмотреть возможность использования массивов как можно более «экономного» типа, как это показано на графике выше.
Заключение
В этом руководстве было показано, что объекты в Java работают медленнее и имеют большее влияние на память, чем их примитивные аналоги.
Фрагменты кода можно найти на GitHub.