Тип переменной в Java определяет, что лежит в самой переменной: у примитивного типа — само значение (число, символ, true/false), у ссылочного типа — ссылка на объект в памяти или null. Короткий ответ на частый тестовый вопрос: из вариантов «целочисленные типы, типы с плавающей запятой, массивы, строки» к ссылочным относятся массивы и строки, а целочисленные типы и типы с плавающей запятой — примитивные. Если в вашем варианте теста есть и другие пункты: символьный char и логический boolean — примитивные, классы, интерфейсы и обертки вроде Integer — ссылочные.
Содержание
| Вариант из теста | Какие это типы в Java | Примитивный или ссылочный |
|---|---|---|
| Целочисленные типы | byte, short, int, long |
примитивные |
| Типы с плавающей запятой | float, double |
примитивные |
| Массивы | int[], String[] и любые другие |
ссылочные (массив — объект) |
| Строки | String |
ссылочный (класс из java.lang) |
| Символьный тип | char |
примитивный |
| Логический тип | boolean |
примитивный |
| Классы, интерфейсы, обертки | Object, List, Integer |
ссылочные |
Ниже разберем, чем эти две группы отличаются на практике, какие у примитивов диапазоны, зачем нужны обертки вроде Integer и на каких ошибках спотыкаются новички. Примеры проверены на Java 25 (Temurin 25.0.4), запуск одной командой java Имя.java.
Мини-словарь
- Переменная — именованная ячейка, у которой есть тип и значение.
- Значение — то, что хранится в переменной: число у примитива или ссылка у ссылочного типа.
- Объект — данные в куче (heap), созданные через
new, литерал строки или литерал массива. - Ссылка — значение ссылочной переменной, по которому программа находит объект. Сама ссылка — это не объект.
Упрощение: фраза «примитив хранится в стеке, объект в куче» верна только для локальных переменных. Поле int внутри объекта лежит в куче вместе с объектом, а JIT-компилятор может вообще не создавать объект, если докажет, что он не выходит за пределы метода.
Главное отличие на одном примере
Разница проявляется при присваивании: примитив копируется целиком, а у ссылочного типа копируется только ссылка.
public class CopyDemo {
public static void main(String[] args) {
// Примитив: в переменной лежит само значение
int a = 10;
int b = a; // копируется число
b = 99;
System.out.println("a = " + a + ", b = " + b);
// Массив: в переменной лежит ссылка на объект
int[] x = {1, 2, 3};
int[] y = x; // копируется ссылка, объект один
y[0] = 99;
System.out.println("x[0] = " + x[0] + ", y[0] = " + y[0]);
// Строка: тоже ссылка, но объект String неизменяемый
String s = "Java";
String t = s;
t = t + " 25"; // создается новый объект, s не меняется
System.out.println("s = " + s + ", t = " + t);
}
}
a = 10, b = 99
x[0] = 99, y[0] = 99
s = Java, t = Java 25
Разбор по строкам. После b = a у переменных два независимых числа, поэтому a осталось 10. После y = x обе переменные указывают на один массив, и изменение через y видно через x. Строка тоже ссылочный тип, но методы и конкатенация String не меняют объект, а возвращают новый — поэтому s не изменилась.
То же правило действует при вызове метода: Java всегда передает аргументы по значению, но для ссылочного типа этим значением является ссылка. Метод может изменить содержимое переданного массива, но не может заставить переменную вызывающего кода указывать на другой массив.
Восемь примитивных типов
Примитивов в Java ровно восемь, их набор задан спецификацией языка (JLS) и не расширяется. Размеры и диапазоны числовых типов одинаковы на любой платформе.
| Тип | Размер | Диапазон | Значение поля по умолчанию | Литерал |
|---|---|---|---|---|
byte |
8 бит | -128 … 127 | 0 |
(byte) 10 или 10 в присваивании |
short |
16 бит | -32 768 … 32 767 | 0 |
1000 |
int |
32 бита | -2 147 483 648 … 2 147 483 647 | 0 |
42, 1_000_000 |
long |
64 бита | примерно ±9,22 * 10^18 | 0L |
42L |
float |
32 бита | примерно ±3,4 * 10^38, 6-7 значащих цифр | 0.0f |
3.14f |
double |
64 бита | примерно ±1,8 * 10^308, 15-17 значащих цифр | 0.0d |
3.14 |
char |
16 бит | '\u0000' … '\uffff' (0 … 65 535) |
'\u0000' |
'A' |
boolean |
не зафиксирован JLS | true / false |
false |
true |
Две оговорки к таблице. Значение по умолчанию получают только поля и элементы массива, а локальную переменную нужно инициализировать явно. char хранит одну кодовую единицу UTF-16, а не «любой символ»: эмодзи и часть редких иероглифов занимают две такие единицы, то есть не помещаются в один char.
Проверка правила про инициализацию:
public class NoInit {
static int counter; // поле: по умолчанию 0
static String name; // поле-ссылка: по умолчанию null
public static void main(String[] args) {
System.out.println(counter + " " + name);
int local;
System.out.println(local);
}
}
NoInit.java:8: error: variable local might not have been initialized
System.out.println(local);
^
1 error
error: compilation failed
Исправление — присвоить значение до чтения: int local = 0;. После этого программа напечатает 0 null.
Особенности чисел: переполнение, дроби, бесконечность
У числовых примитивов есть поведение, которое не похоже на школьную математику. Все строки ниже взяты из одного прогона.
public class Numbers {
public static void main(String[] args) {
int max = Integer.MAX_VALUE;
System.out.println(max + 1); // переполнение int
System.out.println((long) max + 1); // расширили до long заранее
System.out.println(0.1 + 0.2); // двоичная дробь
System.out.println(1.0 / 0); // бесконечность
System.out.println(-1.0 / 0);
double nan = 0.0 / 0;
System.out.println(nan + " " + (nan == nan) + " " + Double.isNaN(nan));
byte small = (byte) 200; // явное сужение
System.out.println(small);
char c = 'A';
System.out.println(c + 1); // char участвует в арифметике как число
System.out.println((char) (c + 1));
System.out.println(7 / 2 + " " + 7 / 2.0);
System.out.println(Math.addExact(max, 1));
}
}
-2147483648
2147483648
0.30000000000000004
Infinity
-Infinity
NaN false true
-56
66
B
3 3.5
Exception in thread "main" java.lang.ArithmeticException: integer overflow
at java.base/java.lang.Math.addExact(Math.java:915)
at Numbers.main(Numbers.java:17)
Что из этого следует:
- Целые типы при переполнении молча «заворачиваются» в минимальное значение. Если это недопустимо, используйте
Math.addExactи аналоги — они бросаютArithmeticException. floatиdoubleхранят двоичную дробь, поэтому 0.1 + 0.2 не равно 0.3 точно. Для денег берутBigDecimalили целое число копеек вlong.- Деление вещественного числа на ноль дает
Infinity, а0.0 / 0—NaN.NaNне равен даже самому себе, проверять его нужно черезDouble.isNaN. Целочисленное деление на ноль (1 / 0) бросаетArithmeticException. 7 / 2при двухintдает 3: дробная часть отбрасывается. Чтобы получить 3.5, хотя бы один операнд должен быть вещественным.
Ссылочные типы: что к ним относится
Ссылочные типы — это все, что не входит в восемь примитивов: классы (включая String и обертки), интерфейсы, массивы, перечисления enum и записи record. У всех ссылочных переменных общие свойства: они могут содержать null, сравнение == проверяет, один ли это объект, а сравнение содержимого делается методом equals.
Типичная ошибка новичка — сравнивать строки через ==:
public class Strings {
public static void main(String[] args) {
String a = "otus";
String b = "otus";
String c = new String("otus");
System.out.println(a == b); // оба литерала - один объект из пула
System.out.println(a == c); // разные объекты
System.out.println(a.equals(c)); // сравнение содержимого
String input = new java.util.Scanner("otus").next();
System.out.println(input == a);
System.out.println(input.equals(a));
}
}
true
false
true
false
true
Одинаковые строковые литералы спецификация объединяет в один объект, поэтому a == b дает true, но это свойство литералов, а не сравнение текста. Но строка, полученная во время работы программы (из ввода, файла, сети), — отдельный объект, и == возвращает false при одинаковом тексте. Правило простое: строки и любые объекты сравниваем через equals.
Обертки и автоупаковка
Для каждого примитива есть класс-обертка: Byte, Short, Integer, Long, Float, Double, Character, Boolean. Обертка — обычный ссылочный тип, и нужна она там, где язык требует объект. Главный пример — коллекции и дженерики: параметр типа может быть только ссылочным.
import java.util.List;
public class Generic {
public static void main(String[] args) {
List<int> numbers = List.of(1, 2, 3);
}
}
Generic.java:5: error: unexpected type
List<int> numbers = List.of(1, 2, 3);
^
required: reference
found: int
1 error
error: compilation failed
Исправление — List<Integer>. Переводить int в Integer и обратно вручную не нужно: компилятор делает это сам. Это называется автоупаковка (autoboxing, int -> Integer) и автораспаковка (unboxing, Integer -> int). У этого удобства две ловушки, обе видны в примере.
import java.util.ArrayList;
import java.util.List;
public class Boxing {
public static void main(String[] args) {
List<Integer> scores = new ArrayList<>();
scores.add(5); // автоупаковка int -> Integer
int first = scores.get(0); // автораспаковка Integer -> int
System.out.println(first * 2);
Integer x1 = 127, x2 = 127;
Integer y1 = 128, y2 = 128;
System.out.println(x1 == x2); // кэш -128..127
System.out.println(y1 == y2); // разные объекты
System.out.println(y1.equals(y2)); // сравнение значений
Integer missing = null;
int value = missing; // распаковка null
System.out.println(value);
}
}
10
true
false
true
Exception in thread "main" java.lang.NullPointerException: Cannot invoke "java.lang.Integer.intValue()" because "<local7>" is null
at Boxing.main(Boxing.java:18)
Первая ловушка — сравнение оберток через ==. Значения от -128 до 127 при автоупаковке берутся из кэша, поэтому 127 == 127 дает true, а 128 == 128 — false. Верхнюю границу кэша можно поднять настройкой JVM, так что полагаться на == нельзя в любом случае: сравнивайте через equals или распакуйте значения в int.
Вторая ловушка — распаковка null. Обертка может быть null (например, Map.get не нашел ключ), и присваивание в int бросает NullPointerException. Вместо имени переменной в сообщении стоит <local7>, потому что при запуске java Boxing.java класс компилируется без отладочной информации; после javac -g в сообщении будет "missing". Исправление — проверить на null до распаковки или задать значение по умолчанию, например map.getOrDefault(key, 0).
Как выбрать тип переменной
| Задача | Что взять | Когда иначе |
|---|---|---|
| Счетчик, индекс, обычное целое | int |
значения выходят за 2,1 млрд — long |
| Время в миллисекундах, идентификаторы | long |
— |
| Дробные измерения, физика, графика | double |
огромные массивы при нехватке памяти — float |
| Деньги | BigDecimal или long в копейках |
— |
| Флаг да/нет | boolean |
нужно третье состояние «неизвестно» — Boolean с явной обработкой null |
| Текст | String |
много склеек в цикле — StringBuilder |
| Элементы коллекции | обертки Integer, Long, Double |
— |
Ключевое слово var (с Java 10) не создает особый тип: компилятор выводит обычный статический тип из выражения справа. var n = 5; — это int, var list = new ArrayList<String>(); — это ArrayList<String>. Про символьный тип подробнее — в статье о типе char в Java, а про переменные без привязки к языку — в материале о данных и переменных в программировании.
Выводы
- В Java восемь примитивных типов:
byte,short,int,long,float,double,char,boolean; все остальное, включая массивы иString, — ссылочные типы. - Примитив хранит значение и копируется целиком; ссылочная переменная хранит ссылку, и после присваивания две переменные могут указывать на один объект.
- Целые типы при переполнении заворачиваются без ошибки, а
doubleне подходит для точных денежных расчетов. - Строки и обертки сравнивают через
equals, а не==; распаковкаnullв примитив бросаетNullPointerException. - Коллекции и дженерики работают только со ссылочными типами, поэтому
intв них превращается вIntegerчерез автоупаковку.
Где применяется / связь с практикой
Освойте тему на практике
Выбор между примитивом и ссылочным типом встречается в каждой задаче на Java: от счетчика цикла до модели данных, которую сохраняют в базу через JPA, где Integer вместо int означает «значение может отсутствовать». Систематически освоить типы, коллекции, ООП и работу с памятью с проверкой домашних заданий можно на курсе «Java разработчик. Базовый уровень». Разборы отдельных тем с преподавателями проходят на открытых уроках Otus.
FAQ
Является ли String примитивным типом?
Нет. String — класс, строковая переменная хранит ссылку на объект. Особенность только в том, что у строк есть литералы в двойных кавычках и оператор + для склейки.
Сколько памяти занимает boolean?
Спецификация языка размер не фиксирует. В массиве boolean[] HotSpot обычно тратит 1 байт на элемент, а точный размер поля объекта зависит от JVM и выравнивания.
Можно ли присвоить null переменной типа int?
Нет, это ошибка компиляции: null допустим только для ссылочных типов. Если значение может отсутствовать, используют Integer или OptionalInt.



