Wrapper классы ·
‹ Предыдущий Следующий ›
⏱ 5 минут чтения Обновлено: 2026-10-15

Классы обертки в Java (оболочки типов)

Почему List<int> не компилируется, а List<Integer> работает? Почему Integer.parseInt("4.2") падает с исключением, а Boolean.parseBoolean("yes") молча возвращает false? Ответы на оба вопроса — в том, как устроены классы обертки.

Классы обертки (оболочки типов, wrapper-классы) в Java — это классы из пакета java.lang, объект которых хранит одно значение примитивного типа: Integer для int, Double для double, Boolean для boolean и т.д. Они нужны там, где требуется объект, а не примитив: в коллекциях и дженериках, для значения null, а также как набор полезных статических методов для преобразования и сравнения значений.

Что такое классы обертки и зачем они нужны

Примитивные типы (int, double, boolean и другие) быстрые и компактные, но это не объекты: у них нет методов, они не могут быть null и их нельзя положить в коллекцию. Чтобы каждый разработчик не писал свой класс-контейнер для числа, в Java SE уже есть готовые классы обертки.

Класс-обертка (класс оболочка) нужен, чтобы:

  • хранить примитивы в коллекциях и обобщенных классах: List<Integer>, Map<String, Double> — дженерики работают только с объектами;
  • обозначать отсутствие значения через null (например, поле Integer age, которое еще не заполнено, или результат из базы данных);
  • преобразовывать строки в числа и обратно: Integer.parseInt("42"), Integer.toString(42);
  • пользоваться константами и утилитами: Integer.MAX_VALUE, Integer.compare(), Character.isDigit(), Double.isNaN().

Имя класса-обертки совпадает с именем примитивного типа, но пишется с заглавной буквы. Исключения — Integer (примитив int) и Character (примитивный тип char).

Объекты классов оберток неизменяемые (immutable): значение, переданное при создании, изменить нельзя. Что при этом происходит в памяти при i++, разобрано в уроке Объекты классов оболочек неизменяемые.

Во всех классах-обертках переопределен метод equals(Object): он сравнивает хранимые значения, а не ссылки. Но учитывается и тип: Integer.valueOf(1).equals(1L) вернет false, потому что 1L упаковывается в Long.

Таблица: примитивные типы и их обертки

В Java восемь примитивных типов, и у каждого есть свой класс-обертка. Отдельно стоит класс Void: он соответствует ключевому слову void, не хранит никакого значения и не может быть создан. Его используют в дженериках, когда тип результата формально нужен, но возвращать нечего, например Callable<Void> или CompletableFuture<Void> (единственное допустимое значение — null).

Примитивный тип Класс-обертка Наследует Number
byte Byte да
short Short да
int Integer да
long Long да
float Float да
double Double да
char Character нет
boolean Boolean нет
void Void нет (объекты не создаются)

Автоупаковка и распаковка (autoboxing и unboxing)

Автоупаковка (autoboxing) — автоматическое преобразование примитива в объект класса-обертки (Integer boxed = 42;), распаковка (unboxing) — обратное преобразование (int n = boxed;). Начиная с Java 5 оба преобразования выполняет компилятор: для упаковки он вызывает valueOf(), для распаковки — intValue(), doubleValue() и т.п. Подробно, с примерами и правилами, эта тема разобрана в отдельном уроке Autoboxing и unboxing в Java.

NullPointerException при распаковке

Распаковка — это вызов метода вроде intValue(). Если ссылка равна null, получите NullPointerException в строке, где на первый взгляд нет ни одного вызова метода: Integer count = null; int n = count;. Особенно коварен тернарный оператор: Integer x = flag ? map.get(key) : 0; — из-за операнда 0 результат map.get(key) распаковывается и при отсутствии ключа падает с NPE.

Создание объектов: valueOf() вместо конструкторов

Раньше объекты оберток создавали конструкторами: new Integer(42), new Integer("42"), new Boolean("true"). Все эти конструкторы объявлены deprecated начиная с Java 9, а с Java 16 помечены @Deprecated(forRemoval = true) (JEP 390) — то есть могут быть удалены в будущих версиях. Компилятор выдает предупреждение при их использовании.

Как не надо писать в современном коде:

// Устаревший способ - конструкторы deprecated for removal
Integer i1 = new Integer(42);
Integer i2 = new Integer("42");
Boolean b1 = new Boolean("true");

Как надо:

Integer i1 = 42;                     // автоупаковка
Integer i2 = Integer.valueOf("42");  // из строки
Float f1 = Float.valueOf(3.14f);
Float f2 = Float.valueOf("3.14");
Character c1 = 'c';                  // или Character.valueOf('c')
Boolean b1 = Boolean.valueOf("true");

Главное отличие: конструктор всегда создает новый объект, а valueOf() может вернуть уже существующий объект из кэша. Какие значения кэшируются и почему из-за этого обертки нельзя сравнивать через ==, читайте в уроке Кэширование объектов классов оболочек.

Устаревший материал

Во многих старых книгах и тестах по Java (включая вопросы сертификации для Java 6–8) объекты оберток создаются через new Integer(...). Знать такой синтаксис нужно, чтобы читать старый код, но в новом коде используйте автоупаковку или valueOf().

Если строка не содержит корректного числа, valueOf(String) выбрасывает NumberFormatException. У класса Boolean поведение другое: исключение не выбрасывается никогда. Строка "true" в любом регистре дает true, любая другая строка (и даже null) — false:

public class WrapperExample1 {
    public static void main(String[] args) {
        Boolean boolean1 = Boolean.valueOf(true);
        Boolean boolean2 = Boolean.valueOf("Some String");
        boolean boolean3 = Boolean.parseBoolean("TRUE");

        System.out.println(boolean1); // true
        System.out.println(boolean2); // false
        System.out.println(boolean3); // true
    }
}

Методы классов оберток

Кроме хранения значения, классы-обертки предоставляют много статических и экземплярных методов. Сначала краткая шпаргалка по методам преобразования, которые чаще всего путают:

Метод Принимает Возвращает Пример
Integer.valueOf() int или String объект Integer Integer.valueOf("6")
Integer.parseInt() String примитив int Integer.parseInt("6")
intValue(), doubleValue() и др. ничего (экземплярный) примитив boxed.intValue()
Integer.toString() int (и основание) String Integer.toString(254, 16)
toString() ничего (экземплярный) String boxed.toString()

Методы valueOf()

Метод valueOf() — рекомендуемый способ получить объект-обертку. Метод перегружен: один вариант принимает значение соответствующего примитивного типа, второй — строку (String). Исключение — класс Character: у него есть только valueOf(char).

В целочисленных классах Byte, Short, Integer и Long есть третий вариант: он принимает строку с числом в произвольной системе счисления, а вторым параметром — основание этой системы (radix):

public class WrapperValueOf {
    public static void main(String[] args) {
        Integer integer1 = Integer.valueOf("6");
        Integer integer2 = Integer.valueOf(6);
        // преобразует двоичное 101011 в десятичное 43
        Integer integer3 = Integer.valueOf("101011", 2);

        System.out.println(integer1); // 6
        System.out.println(integer2); // 6
        System.out.println(integer3); // 43
    }
}

Методы parseXxx()

В каждом числовом классе-обертке (и в Boolean) есть статический метод, который преобразует строку в примитивное значение: Integer.parseInt(), Long.parseLong(), Double.parseDouble(), Boolean.parseBoolean() и т.д. Разница с valueOf(): valueOf() возвращает объект, а parseXxx() — примитив. Если нужен int, вызывайте parseInt(), чтобы не создавать лишний объект.

У Byte, Short, Integer и Long тоже есть вариант с системой счисления:

public class WrapperExample3 {
    public static void main(String[] args) {
        Long long1 = Long.valueOf("45");
        long long2 = Long.parseLong("67");
        long long3 = Long.parseLong("101010", 2);

        System.out.println("long1 = " + long1); // long1 = 45
        System.out.println("long2 = " + long2); // long2 = 67
        System.out.println("long3 = " + long3); // long3 = 42
    }
}

NumberFormatException при разборе строки

Если строка не является корректным числом, parseXxx() и valueOf(String) выбрасывают NumberFormatException. Это непроверяемое исключение, поэтому компилятор не заставит его обработать — при разборе пользовательского ввода оборачивайте вызов в try-catch:

public class ParseExample {
    public static void main(String[] args) {
        String[] inputs = {"42", "+42", " 42", "4.2", "abc"};
        for (String s : inputs) {
            try {
                System.out.println(Integer.parseInt(s));
            } catch (NumberFormatException e) {
                System.out.println("Не число: \"" + s + "\"");
            }
        }
    }
}

Результат выполнения:

42
42
Не число: " 42"
Не число: "4.2"
Не число: "abc"

Обратите внимание: пробелы не обрезаются автоматически (используйте s.trim() или s.strip()), а дробное число нельзя разобрать как int. Вызов Integer.parseInt(null) тоже бросает NumberFormatException, а не NullPointerException.

Методы toString()

  • Все классы-обертки переопределяют экземплярный метод toString(). Он возвращает строковое представление значения, поэтому объект-обертку можно сразу передать в println():
    Double double1 = Double.valueOf("4.6");
    System.out.println(double1); // 4.6
  • Все числовые обертки предоставляют статический метод toString(), принимающий примитивное значение и возвращающий String:
    String string1 = Double.toString(3.14); // "3.14"
  • Integer и Long предоставляют третий вариант toString() — для представления числа в любой системе счисления. Метод статический: первый аргумент — число, второй — основание системы счисления:
    String string2 = Long.toString(254, 16); // string2 = "fe"

Методы toHexString(), toOctalString(), toBinaryString()

Integer и Long позволяют перевести число из десятичной системы счисления в шестнадцатеричную, восьмеричную и двоичную:

public class WrapperToXString {
    public static void main(String[] args) {
        String string1 = Integer.toHexString(254);
        System.out.println("254 в 16-ой системе = " + string1);

        String string2 = Long.toOctalString(254);
        System.out.println("254 в 8-ой системе = " + string2);

        String string3 = Long.toBinaryString(254);
        System.out.println("254 в 2-ой системе = " + string3);
    }
}

Результат выполнения:

254 в 16-ой системе = fe
254 в 8-ой системе = 376
254 в 2-ой системе = 11111110

В классах Double и Float из этой группы есть только метод toHexString().

Сравнение и арифметика: compare(), sum(), max(), min()

Начиная с Java 7–8 в числовых обертках появились статические методы, которые удобно передавать как ссылки на методы в Stream API и компараторы:

System.out.println(Integer.compare(5, 10));  // -1 (отрицательное: 5 < 10)
System.out.println(Integer.sum(5, 10));      // 15
System.out.println(Integer.max(5, 10));      // 10
System.out.println(Integer.min(5, 10));      // 5
System.out.println(Double.compare(0.0, -0.0)); // 1

// ссылка на метод вместо лямбды
int total = List.of(1, 2, 3).stream().reduce(0, Integer::sum); // 6

Integer.compare(x, y) безопаснее, чем вычитание x - y в компараторе: вычитание может переполниться для больших по модулю значений.

Полезные методы Character

Класс Character не наследует Number, зато содержит набор статических методов для анализа символов:

System.out.println(Character.isDigit('7'));          // true
System.out.println(Character.isLetter('Ж'));         // true
System.out.println(Character.isLetterOrDigit('_'));  // false
System.out.println(Character.isWhitespace(' '));     // true
System.out.println(Character.isUpperCase('a'));      // false
System.out.println(Character.toUpperCase('a'));      // A
System.out.println(Character.getNumericValue('7'));  // 7

Класс Number

Все числовые классы-обертки (Byte, Short, Integer, Long, Float, Double) наследуют абстрактный класс Number. От него же наследуются BigInteger, BigDecimal и атомарные AtomicInteger, AtomicLong. Character и Boolean наследниками Number не являются.

Number объявляет методы, которые возвращают значение объекта в каждом из числовых форматов: byteValue(), shortValue(), intValue(), longValue(), floatValue(), doubleValue().

Иерархия класса Number и его наследников в Java

Пример приведения типов через методы Number

public class WrapperExample2 {
    public static void main(String[] args) {
        Integer iOb = 1000;
        System.out.println(iOb.byteValue());
        System.out.println(iOb.shortValue());
        System.out.println(iOb.intValue());
        System.out.println(iOb.longValue());
        System.out.println(iOb.floatValue());
        System.out.println(iOb.doubleValue());
    }
}

Результат выполнения:

-24
1000
1000
1000
1000.0
1000.0

Методы xxxValue() работают как обычное приведение примитивов: byteValue() для 1000 возвращает -24, потому что при сужении до 8 бит старшие биты отбрасываются.

Константы: MIN_VALUE, MAX_VALUE, бесконечность и NaN

Каждый числовой класс-обертка (а также Character) содержит статические константы с минимальным и максимальным значением типа. Например, Integer.MIN_VALUE — минимальное значение int (-2147483648), Integer.MAX_VALUE — максимальное (2147483647). Также есть константы SIZE (размер в битах) и BYTES (размер в байтах).

Обертки типов с плавающей точкой Float и Double дополнительно содержат константы:

  • NEGATIVE_INFINITY — отрицательная бесконечность;
  • POSITIVE_INFINITY — положительная бесконечность;
  • NaN — «не число» (Not a Number).

Обратите внимание: у Double константа MIN_VALUE — это не самое маленькое отрицательное число, а наименьшее положительное значение (около 4.9E-324). Самое маленькое отрицательное — -Double.MAX_VALUE.

Целочисленное деление на ноль выбрасывает ArithmeticException. А деление на ноль с плавающей точкой по стандарту IEEE 754 ошибкой не является: результат равен положительной или отрицательной бесконечности. Умножение бесконечности на ноль дает NaN:

public class InfinityExample {
    public static void main(String[] args) {
        int a = 7;
        double b = 0.0;
        double c = -0.0;
        double g = Double.NEGATIVE_INFINITY;
        System.out.println("7 / 0.0 = " + a / b);
        System.out.println("7 / -0.0 = " + a / c);
        System.out.println("0.0 == -0.0 = " + (b == c));
        System.out.println("-Infinity * 0 = " + g * 0);
    }
}

Результат выполнения кода:

7 / 0.0 = Infinity
7 / -0.0 = -Infinity
0.0 == -0.0 = true
-Infinity * 0 = NaN

NaN не равен ничему, даже самому себе: Double.NaN == Double.NaN дает false. Для проверки используйте Double.isNaN(x), а для бесконечности — Double.isInfinite(x). Метод equals() у Double ведет себя иначе, чем ==: Double.valueOf(Double.NaN).equals(Double.NaN) возвращает true, а Double.valueOf(0.0).equals(-0.0) — false.

Обертки в коллекциях и дженериках

Параметр типа в дженериках может быть только ссылочным типом, поэтому List<int> не скомпилируется — нужно писать List<Integer>. Благодаря автоупаковке работа с такой коллекцией выглядит почти как работа с примитивами:

// List<int> list = new ArrayList<>(); // ошибка компиляции
List<Integer> list = new ArrayList<>();
list.add(10);                 // автоупаковка
list.add(20);
int sum = 0;
for (int n : list) {          // распаковка каждого элемента
    sum += n;
}

Map<String, Integer> ages = new HashMap<>();
ages.put("Анна", 30);
Integer age = ages.get("Иван"); // null - ключа нет, распаковывать в int опасно

Если важна производительность при большом объеме чисел, используйте массивы примитивов (int[]) или специализированные потоки IntStream, LongStream, DoubleStream — они не создают объект на каждое значение.

remove() в List<Integer>

У List есть два метода: remove(int index) и remove(Object o). Вызов list.remove(10) удалит элемент по индексу 10 (автоупаковки не будет, так как подходит метод с примитивом). Чтобы удалить значение 10, пишите list.remove(Integer.valueOf(10)). Как компилятор выбирает перегруженный метод при упаковке, расширении и var-args, разобрано в уроке Перегрузка с дополнительными факторами.

На чем чаще всего ошибаются

  • Распаковка null. int x = map.get(key); при отсутствии ключа дает NullPointerException.
  • equals() с разными типами. Long.valueOf(5).equals(5) возвращает false: 5 упаковывается в Integer, а не в Long.
  • Deprecated-конструкторы. new Integer(5) дает предупреждение компилятора и всегда создает новый объект; используйте Integer.valueOf(5) или автоупаковку.
  • Ожидание исключения от Boolean. Boolean.parseBoolean("yes") и Boolean.valueOf("Some String") возвращают false без исключения.
  • Упаковка в цикле. Long sum = 0L; for (...) sum += i; создает миллионы лишних объектов; объявите long sum.
  • Синхронизация на обертке. synchronized (count) над Integer опасна: после count++ переменная указывает на другой объект, а кэшированные объекты общие для всего приложения.

Часто задаваемые вопросы

Чем int отличается от Integer?

int — примитивный тип: хранит само значение, не может быть null, у поля класса значение по умолчанию 0. Integer — класс-обертка: переменная хранит ссылку на объект, может быть null (значение по умолчанию у поля — null), объект занимает больше памяти, зато его можно использовать в коллекциях и дженериках.

Является ли String классом-оберткой?

Нет. String тоже неизменяемый класс из java.lang, но он не оборачивает никакой примитивный тип. Классов-оберток восемь: Byte, Short, Integer, Long, Float, Double, Character, Boolean; к ним иногда относят и Void.

Можно ли изменить значение Integer, переданного в метод?

Нет. Объекты оберток неизменяемы, а ссылка передается в метод по значению. Присваивание param = param + 1 внутри метода создает новый объект и меняет только локальную копию ссылки — переменная вызывающего кода останется прежней. Чтобы изменить значение, верните результат из метода или используйте изменяемый контейнер, например AtomicInteger или массив.

Что будет при вызове Integer.parseInt(null)?

Будет выброшено NumberFormatException с сообщением «Cannot parse null string». При этом Double.parseDouble(null) выбрасывает NullPointerException, а Boolean.parseBoolean(null) просто возвращает false. Перед разбором внешних данных проверяйте строку на null и пустоту.

Что использовать в полях и параметрах: int или Integer?

По умолчанию выбирайте примитив: он быстрее, экономнее и не дает NullPointerException. Integer нужен, когда значение может отсутствовать (например, необязательное поле сущности JPA или DTO), и там, где требуется объект: в коллекциях, дженериках, Optional.

Практика по теме

Закрепите урок на задачах — у каждой есть уровень и оценка времени.

Все задачи модуля →

Видео объяснение

Предпочитаете видеоформат? Посмотрите этот урок с примерами и объяснениями.

Комментарии

Зарегистрируйтесь или войдите, чтобы иметь возможность оставить комментарий.