Целые числа — byte, short, int, long: диапазоны, литералы и переполнение
Знакомимся с четырьмя типами целых чисел в Java, узнаем, сколько памяти они занимают, как записывать числовые литералы и что такое переполнение
Зачем нужны разные типы чисел?
Представьте, что вы арендуете ячейки в банке. Маленькая ячейка стоит дешевле, но в неё поместится только украшение. Большая ячейка дороже, но туда можно поставить сейф. Если вам нужно хранить только кольцо, было бы глупо арендовать огромный сейф.
С памятью компьютера происходит то же самое. Java предоставляет четыре размера «коробок» для хранения целых чисел: byte, short, int и long. Чем больше коробка, тем больше числа в неё помещаются, но тем больше оперативной памяти она занимает.
Почему бы просто не использовать всегда самую большую коробку? В реальных программах вы можете создавать миллионы переменных одновременно — например, массив из миллиона байт займёт всего 1 мегабайт, а массив из миллионов long-чисел — уже 8 мегабайт. В мобильной разработке или на серверах с ограниченной памятью это имеет огромное значение.
Тип byte — младенец среди чисел
Тип byte — самый маленький целочисленный тип в Java. Он занимает ровно 8 бит (1 байт) памяти. Один бит — это крошечная ячейка, которая может хранить только одно из двух значений: 0 или 1. Восемь таких ячеек вместе могут составить 2⁸ = 256 различных комбинаций.
Поскольку один бит отводится на знак (положительное или отрицательное число), диапазон byte составляет:
- От -128 до 127
Это как однозначные числа: от -9 до 9 (но побольше). Byte подходит для situations, где вы точно знаете, что значения будут небольшими — например, возраст человека (вряд ли кому-то больше 127 лет), коды ошибок, или когда вы читаете файл побайтово.
public class ByteExample {
public static void main(String[] args) {
byte temperature = -10;
byte age = 25;
byte score = 127;
System.out.println("Температура: " + temperature);
System.out.println("Возраст: " + age);
System.out.println("Рекорд: " + score);
byte wrong = 128;
}
}
Последняя строка вызовет ошибку компиляции! Число 128 уже не помещается в byte — оно на единицу больше максимального значения 127. Компилятор Java не позволит вам сделать такой «обман».
Почему диапазон симметричен вокруг нуля, но не совсем? Число 128 «съедается» знаковым битом. В двоичном представлении 127 — это 01111111 (все восемь битов, кроме знакового, единицы). Добавить ещё единицу невозможно без использования знакового бита, что превратило бы число в отрицательное.
Тип short — подросток
Тип short занимает 16 бит (2 байта) памяти. Это вдвое больше, чем byte, а значит, и «коробка» вдвое вместительнее:
- От -32 768 до 32 767
Short хорош для situations, когда значение больше 127, но гарантированно не превышает 32 тысячи. Например, максимальная высота в метрах для географических данных, номера некоторых сущностей в базе данных.
public class ShortExample {
public static void main(String[] args) {
short maxHeight = 8848;
short population = 32000;
short year = 2024;
System.out.println("Высота Эвереста: " + maxHeight + " м");
System.out.println("Население города: " + population);
System.out.println("Текущий год: " + year);
}
}
Заметьте, что на практике short используется довольно редко. Программисты обычно выбирают между byte и int, а short как бы «между ними» — не такой крошечный, но и не такой удобный. Тем не менее, он встречается в некоторых API и форматах данных.
Тип int — основной рабочий тип
Тип int (от слова integer — целое число) занимает 32 бита (4 байта) памяти. Это самый часто используемый тип для целых чисел в Java. Его диапазон впечатляет:
- От -2 147 483 648 до 2 147 483 647
Это более 4 миллиардов различных значений (для беззнакового диапазона) или более 2 миллиардов в каждую сторону от нуля. Для подавляющего большинства задач этого более чем достаточно: количество жителей Земли, расстояние в миллиметрах, количество секунд в месяце — всё это помещается в int.
public class IntExample {
public static void main(String[] args) {
int worldPopulation = 8_000_000_000;
int secondsInDay = 86_400;
int distanceToMoon = 384_400_000;
System.out.println("Население мира: " + worldPopulation);
System.out.println("Секунд в дне: " + secondsInDay);
System.out.println("Расстояние до Луцы (км): " + distanceToMoon);
}
}
Обратите внимание на подчёркивания в числах: 8_000_000_000. Это не переменная с именем — Java разрешает ставить подчёркивания в числовые литералы для удобства чтения. Компилятор их просто игнорирует. Подчёркивание можно ставить где угодно: 1_00, 1_000_000, 10_0_0 — всё это валидные числа. Но ставить их в начало или конец нельзя: _100 и 100_ — ошибка.
Важная деталь: когда вы пишете число без суффикса, Java автоматически считает его типом int. То есть запись 42 — это литерал типа int, а не byte, не short, не long.
Тип long — тяжеловес
Тип long (от слова long — длинный) занимает 64 бита (8 байт) памяти. Это самый большой целочисленный тип в Java. Его диапазон поражает воображение:
- От -9 223 372 036 854 775 808 до 9 223 372 036 854 775 807
Это более 9 квинтиллионов! Long необходим, когда int не хватает: количество миллисекунд от начала эпохи Unix (1 января 1970 года), номера транзакций в банке, хэши, идентификаторы и другие большие числа.
Чтобы создать литерал типа long, нужно добавить суффикс L (или l, но рекомендуется заглавная L, потому что строчная l легко перепутать с цифрой 1):
public class LongExample {
public static void main(String[] args) {
long worldPopulation = 8_000_000_000L;
long secondsInYear = 31_536_000L;
long starsInGalaxy = 100_000_000_000L;
long distanceInLightYears = 9_461_000_000_000_000L;
System.out.println("Население мира: " + worldPopulation);
System.out.println("Секунд в году: " + secondsInYear);
System.out.println("Звёзд в галактике: " + starsInGalaxy);
System.out.println("Расстояние в световых годах: " + distanceInLightYears);
}
}
Почему 8_000_000_000L, а не просто 8000000000? Потому что без суффикса L компилятор попытается интерпретировать число как int, а 8 миллиардов не помещаются в int (максимум ~2,14 миллиарда). Это вызовет ошибку компиляции: «integer number too large». Суффикс L говорит компилятору: «это long, используй 64 бита».
Сравнение типов в таблице
Давайте обобщим информацию в удобной таблице:
| Тип | Размер | Минимум | Максимум |
|---|---|---|---|
| byte | 8 бит (1 байт) | -128 | 127 |
| short | 16 бит (2 байта) | -32 768 | 32 767 |
| int | 32 бита (4 байта) | -2 147 483 648 | 2 147 483 647 |
| long | 64 бита (8 байт) | -9 223 372 036 854 775 808 | 9 223 372 036 854 775 807 |
Числовые литералы: способы записи
Литерал — это число, записанное прямо в коде. Java поддерживает несколько способов записи числовых литералов:
Десятичная запись (обычная)
Это запись, привычная нам с детства: 42, -100, 0, 123456789. Никаких специальных префиксов.
Шестнадцатеричная запись (hexadecimal)
Начинается с префикса 0x. Использует цифры 0-9 и буквы A-F (или a-f). Каждая цифра代表ляет 4 бита. Очень удобна для работы с битовыми операциями и цветами.
public class HexExample {
public static void main(String[] args) {
int hex1 = 0xFF;
int hex2 = 0x1A;
int hex3 = 0xDEAD_BEEF;
System.out.println("0xFF = " + hex1);
System.out.println("0x1A = " + hex2);
System.out.println("0xDEADBEEF = " + hex3);
}
}
0xFF = 255 в десятичной, 0x1A = 26, а 0xDEADBEEF = 3735928559. Шестнадцатеричная запись особенно полезна, когда вы работаете с цветами: 0xFF0000 — красный, 0x00FF00 — зелёный, 0x0000FF — синий.
Восьмеричная запись (octal)
Начинается с префикса 0. Использует цифры 0-7. Встречается реже, но иногда используется в UNIX-системах для прав доступа к файлам.
public class OctalExample {
public static void main(String[] args) {
int oct1 = 010;
int oct2 = 077;
int oct3 = 0100;
System.out.println("010 = " + oct1);
System.out.println("077 = " + oct2);
System.out.println("0100 = " + oct3);
}
}
010 в восьмеричной = 8 в десятичной, 077 = 63, 0100 = 64. Будьте осторожны: обычное число 010 (десять) и восьмеричное 010 (восемь) — это разные числа!
Двоичная запись (binary)
Начинается с префикса 0b. Использует только цифры 0 и 1. Сама «родная» система счисления для компьютера.
public class BinaryExample {
public static void main(String[] args) {
int bin1 = 0b1010;
int bin2 = 0b1111_1111;
int bin3 = 0b1000_0000;
System.out.println("0b1010 = " + bin1);
System.out.println("0b11111111 = " + bin2);
System.out.println("0b10000000 = " + bin3);
}
}
Двоичная запись идеальна, когда вы изучаете битовые операции или работаете с битовыми масками. С помощью подчёркиваний можно сделать читаемым любое двоичное число: 0b1101_0101_1111_1100.
Суффиксы литералов
Как мы уже видели, по умолчанию Java считает все целочисленные литералы типа int. Чтобы указать другой тип, добавляется суффикс:
| Суффикс | Тип | Пример |
|---|---|---|
| (нет) | int | 42 |
| L или l | long | 42L |
Рекомендация: Всегда используйте заглавную L вместо строчной l. Строчная l в некоторых шрифтах выглядит почти так же, как цифра 1, что может привести к ошибкам при чтении кода.
Разделители разрядов — читаем большие числа
В Java 7 и новее можно использовать подчёркивания (_) в числовых литералах для повышения читаемости. Компилятор просто игнорирует эти подчёркивания.
Представьте, что вы читаете число 1000000000. Трудно сразу понять, сколько это — миллиард или миллион? А если записать 1_000_000_000 — сразу ясно!
public class SeparatorExample {
public static void main(String[] args) {
int million = 1_000_000;
long billion = 1_000_000_000L;
int hexColor = 0xFF_DD_00;
long binary = 0b1010_1100_1111_0000;
System.out.println("Миллион: " + million);
System.out.println("Миллиард: " + billion);
System.out.println("Цвет: " + hexColor);
System.out.println("Двоичное: " + binary);
}
}
Правила для подчёркиваний: Нельзя ставить подчёркивание в начале или конце числа (_42, 42_ — ошибка). Нельзя ставить два подчёркивания подряд (1__000 — ошибка). Нельзя ставить перед суффиксом (42_L — ошибка). Разрешено ставить между любыми цифрами и системами счисления.
Переполнение — когда число «не влезает»
Переполнение (overflow) — это ситуация, когда результат операции выходит за пределы диапазона типа. Это одна из самых коварных особенностей Java.
Представьте термометр, который показывает温度 от -128 до +127 градусов. Если сейчас 127 градусов и добавить ещё один градус, термометр не покажет 128 — он «перекинется» на -128. Это и есть переполнение: значение циклически «перескакивает» с одного конца диапазона на другой.
public class OverflowExample {
public static void main(String[] args) {
int maxInt = 2_147_483_647;
System.out.println("Максимум int: " + maxInt);
System.out.println("Максимум + 1: " + (maxInt + 1));
int minInt = -2_147_483_648;
System.out.println("Минимум int: " + minInt);
System.out.println("Минимум - 1: " + (minInt - 1));
byte maxByte = 127;
System.out.println("Максимум byte: " + maxByte);
System.out.println("Максимум + 1: " + (maxByte + 1));
}
}
Вы увидите, что максимальный int + 1 даёт минимальный int, а минимальный int - 1 даёт максимальный int. Это происходит потому, что целые числа в компьютере хранятся в «кольцевом» представлении.
Для byte результат ещё интереснее: maxByte + 1 возвращает -128, потому что в выражении maxByte + 1 byte автоматически «промоутируется» (расширяется) до int, вычисляется результат, а затем при выводе вы видите -128 (результат переполнения byte).
Важно: Java не выбрасывает исключение при переполнении! Программа просто молча «перекинется» на другое значение. Это может привести к серьёзным багам, которые трудно найти. Будьте осторожны с арифметикой на больших числах.
Классический пример переполнения — попытка вычислить, когда произойдёт 1 января 3000 года:
public class Year2038Problem {
public static void main(String[] args) {
int maxUnixTimestamp = 2_147_483_647;
System.out.println("Максимальная дата Unix (int): " + maxUnixTimestamp);
long safeTimestamp = 2_147_483_647L + 1;
System.out.println("Следующая секунда (long): " + safeTimestamp);
}
}
Проблема 2038 года — это реальная проблема, связанная с переполнением 32-битного int в системах, которые хранят время в секундах с 1 января 1970 года.
Неявное расширение и явное сужение
Java автоматически расширяет (расширяет) smaller типы в larger без потери данных. Это называется неявным расширением (implicit widening):
public class WideningExample {
public static void main(String[] args) {
byte b = 42;
short s = b;
int i = s;
long l = i;
System.out.println("byte: " + b);
System.out.println("short: " + s);
System.out.println("int: " + i);
System.out.println("long: " + l);
long bigNumber = 100L;
int smallNumber = (int) bigNumber;
System.out.println("Приведение long к int: " + smallNumber);
}
}
Обратите внимание на последнюю часть: присваивание long-значения int-переменной требует явного приведения типа (int). Это называется явным сужением (explicit narrowing). Компилятор требует явного приведения, потому что данные могут быть потеряны — bigNumber может быть больше, чем max int.
Правило неявного расширения: byte → short → int → long. Каждый следующий тип может безопасно вместить любое значение предыдущего. Но в обратную сторону (long → int → short → byte) данные могут быть потеряны, поэтому Java требует явного приведения.
Автопродвижение (binary promotion)
Когда вы выполняете арифметическую операцию с двумя operands разного типа, Java автоматически «промоутирует» (продвигает) меньший тип до большего:
public class PromotionExample {
public static void main(String[] args) {
byte a = 10;
int b = 20;
long c = 30L;
int result1 = a + b;
long result2 = a + c;
long result3 = a + b + c;
System.out.println("byte + int = " + result1);
System.out.println("byte + long = " + result2);
System.out.println("byte + int + long = " + result3);
}
}
В выражении a + b byte «a» автоматически превращается в int перед сложением. Результат — int. В выражении a + c byte «a» превращается в long. В сложном выражении a + b + c всё вычисляется слева направо: сначала a+b (оба int), результат int, затем int + long (int превращается в long).
Это важно знать, потому что новички часто думают, что если сложить два byte, результат будет byte. Но нет — результат всегда минимум int!
Практический пример: калькулятор возраста
Давайте напишем программу, которая вычисляет возраст человека в днях и секундах:
public class AgeCalculator {
public static void main(String[] args) {
byte ageYears = 25;
short daysInYear = 365;
int totalDays = ageYears * daysInYear;
long totalSeconds = totalDays * 24L * 60 * 60;
System.out.println("Возраст: " + ageYears + " лет");
System.out.println("Примерно " + totalDays + " дней");
System.out.println("Примерно " + totalSeconds + " секунд жизни");
}
}
Здесь мы использовали разные типы для разных целей: byte для возраста (хватит), short для дней в году (хватит), int для общего количества дней (нужно больше), и long для секунд (нужно очень много). Это хороший пример осознанного выбора типа.
Типы и локальные переменные
В Java существуют два способа объявления переменных:
Явное указание типа
int age = 25;
long population = 8_000_000_000L;
byte temperature = -10;
Вывод типа (local variable type inference)
Начиная с Java 10, можно использовать ключевое слово var вместо типа. Компилятор сам определит тип по значению:
public class VarExample {
public static void main(String[] args) {
var age = 25;
var population = 8_000_000_000L;
var temperature = -10;
System.out.println(age);
System.out.println(population);
System.out.println(temperature);
}
}
Переменная age станет типом int (потому что 25 — это int-литерал), population станет long (потому что суффикс L), а temperature станет int (потому что -10 — тоже int-литерал).
Когда использовать var, а когда указывать тип? Если тип очевиден из контекста (инициализация литералом, вызов метода с понятным возвращаемым типом) — можно var. Если тип неочевиден или важен для документации кода — лучше указать явно. В начале обучения рекомендуется всегда указывать тип явно для лучшего понимания.
Частые ошибки новичков
Давайте рассмотрим типичные ловушки, в которые попадаются начинающие:
Ошибка 1: Забытый суффикс L
long x = 8000000000; // ОШИБКА компиляции!
long y = 8000000000L; // Правильно
Ошибка 2: Переполнение без предупреждения
int a = 2_000_000_000;
int b = 2_000_000_000;
int sum = a + b;
System.out.println(sum); // -294967296! Не 4000000000!
Ошибка 3: Деление большего на меньший
long large = 9_223_372_036_854_775_807L;
int small = (int) large;
System.out.println(small); // -1! Данные потеряны
Ошибка 4: Путаница систем счисления
int hex = 0x10; // 16 в десятичной!
int octal = 010; // 8 в десятичной!
int decimal = 10; // 10 в десятичной
System.out.println(hex); // 16
System.out.println(octal); // 8
System.out.println(decimal); // 10
Полный пример: работа со всеми типами
Давайте создадим программу, которая демонстрирует все основные концепции:
public class IntegerTypesOverview {
public static void main(String[] args) {
byte smallNumber = 42;
short mediumNumber = 1_000;
int bigNumber = 1_000_000;
long hugeNumber = 1_000_000_000_000L;
System.out.println("=== Все типы ===");
System.out.println("byte: " + smallNumber + " (от -128 до 127)");
System.out.println("short: " + mediumNumber + " (от -32768 до 32767)");
System.out.println("int: " + bigNumber + " (от -2.1 млрд до 2.1 млрд)");
System.out.println("long: " + hugeNumber + " (огромный диапазон)");
System.out.println();
System.out.println("=== Системы счисления ===");
int decimal = 255;
int hex = 0xFF;
int octal = 0377;
int binary = 0b1111_1111;
System.out.println("Десятичная: " + decimal);
System.out.println("Шестнадцатеричная: " + hex);
System.out.println("Восьмеричная: " + octal);
System.out.println("Двоичная: " + binary);
System.out.println();
System.out.println("=== Разделители разрядов ===");
int withSeparator = 1_234_567_890;
long bigWithSeparator = 9_223_372_036_854_775_807L;
System.out.println("С разделителем: " + withSeparator);
System.out.println("Большое число: " + bigWithSeparator);
System.out.println();
System.out.println("=== Автоматическое расширение ===");
byte b = 10;
short s = b;
int i = s;
long l = i;
System.out.println("byte → short → int → long: " + l);
System.out.println();
System.out.println("=== Переполнение ===");
int max = Integer.MAX_VALUE;
System.out.println("MAX_VALUE: " + max);
System.out.println("MAX_VALUE + 1: " + (max + 1));
}
}
Методы классов-обёрток
Для каждого примитивного типа существует соответствующий класс-обёртка (wrapper class): Byte, Short, Integer, Long. Эти классы提供了 полезные методы:
public class WrapperExample {
public static void main(String[] args) {
System.out.println("Максимум int: " + Integer.MAX_VALUE);
System.out.println("Минимум int: " + Integer.MIN_VALUE);
System.out.println("Максимум long: " + Long.MAX_VALUE);
int parsed = Integer.parseInt("12345");
System.out.println("Из строки: " + parsed);
String hexString = Integer.toHexString(255);
System.out.println("255 в hex: " + hexString);
String binaryString = Integer.toBinaryString(42);
System.out.println("42 в двоичной: " + binaryString);
int compared = Integer.compare(10, 20);
System.out.println("Сравнение 10 и 20: " + compared);
}
}
Эти методы очень полезны на практике. Integer.MAX_VALUE и Integer.MIN_VALUE — константы, содержащие максимальное и минимальное значения int. Integer.parseInt() преобразует строку в число. Integer.toHexString() возвращает шестнадцатеричное представление.
Когда какой тип использовать?
Вот практические рекомендации:
- int — используйте по умолчанию для целых чисел. Это «рабочая лошадка» Java
- long — когда int не хватает: большие ID, временные метки в миллисекундах, большие вычисления
- byte — когда экономия памяти критична: обработка бинарных данных, изображений, сети
- short — почти никогда (особенно в начале карьеры). Используется в некоторых legacy API
Золотое правило: если сомневаетесь — используйте int. В современных компьютерах экономия几个 байтов редко оправдана, а читаемость и простота кода важнее.
Итоги урока
- Java предоставляет 4 типа целых чисел: byte (8 бит), short (16 бит), int (32 бита), long (64 бита)
- По умолчанию литералы без суффикса считаются типом int
- Для long-литералов обязательный суффикс L
- Используйте подчёркивания для читаемости больших чисел
- Поддерживается запись в десятичной, шестнадцатеричной (0x), восьмеричной (0) и двоичной (0b) системах
- Переполнение происходит молча — без исключений!
- Меньшие типы автоматически расширяются до больших (неявное расширение)
- Для сужения типа требуется явное приведение (casting)
- В арифметических операциях byte/short автоматически промоутируются до int
- По умолчанию используйте int; используйте long когда нужен больший диапазон
Тест: Целые числа — byte, short, int, long
10 вопросов