Зачем нужны классы обертки в java
Перейти к содержимому

Зачем нужны классы обертки в java

  • автор:

Классы оболочки

Очень часто необходимо создать класс, основное назначение которого содержать в себе какое-то примитивное значение. Например, как мы увидим в следующих занятиях, обобщенные классы и в частности коллекции работают только с объектами. Поэтому, чтобы каждый разработчик не изобретал велосипед, в Java SE уже добавлены такие классы, которые называются оболочки типов (или классы обертки, Wrappers).

К оболочкам типов относятся классы Double , Float , Long , Integer , Short , Byte , Character , Boolean , Void . Для каждого примитивного значения и ключевого слова void есть свой класс двойник. Имя класса, как вы видите, совпадает с именем примитивного значения. Исключения составляют класс Integer (примитивный тип int ) и класс Character (примитивный тип char ). Кроме содержания в себе значения, классы оболочки предоставляют обширный ряд методов, которые мы рассмотрим в этом уроке.

Объекты классов оболочек неизменяемые (immutable). Это значит, что объект не может быть изменен.

Все классы обертки числовых типов имеют переопределенный метод equals(Object) , сравнивающий примитивные значения объектов.

2. Конструкторы оболочек

В следующей таблицы для каждого класса оболочки указан соответствующий примитивный тип и варианты конструкторов. Как вы видите каждый класс имеет два конструктора: один на вход принимает значение соответствующего примитивного значения, а второй — значение типа String. Исключения: класс Character , у которого только один конструктор с аргументом char и класс Float , объявляющий три конструктора — для значения float , String и еще double .

Примитивный тип

Оболочка

Аргументы конструктора

Зачем нужны классы обертки в java

В этом руководстве мы рассмотрим плюсы и минусы использования примитивов в Java и соответствующих им классов-оберток.

1. Типы переменных в Java

В языке Java существует два типа переменных: примитивные, например int и boolean, а также ссылочные типы вроде Integer и Boolean (классы-обертки). Для каждого примитивного типа существует, соответствующий ему ссылочный тип.

Классы-обертки являются неизменяемыми: это означает, что после создания объекта его состояние (значение поля value) не может быть изменено; и задекларированы, как final (от этих классов невозможно наследоваться).

Java автоматически производит преобразования между примитивными типами и их обертками:

 Integer j = 1; // autoboxing int i = new Integer(1); // unboxing 

Процесс преобразования примитивных типов в ссылочные называется автоупаковкой (autoboxing), а обратный ему — автораспаковкой (unboxing).

2. Плюсы и минусы

Решение, переменные каких типов использовать, основывается на необходимой нам производительности приложения, объеме доступной памяти и значений по умолчанию с которыми мы собираемся работать.

2.1 Использование памяти единичными объектами

В зависимости от реализации виртуальной машины, эти значения могут изменяться. Например, в виртуальной машине Oracle значения типа boolean сопоставляются со значениями 0 и 1 типа int (это связано с тем, что в VM нет инструкций для работы с булевыми значениями) и, как результат, занимают в памяти 32 бита.

Переменные примитивных типов хранятся в стеке, позволяя иметь к ним быстрый доступ.

Ссылочные переменные ссылаются на объекты, которые хранятся в куче, к ним мы имеем более медленный доступ (рекомендуем ознакомиться с понятиями Стек и Куча, для лучшего усвоения материала). Ссылочные типы имеют определенные накладные расходы относительно их примитивных аналогов.

Накладные расходы зависят от реализации конкретной JVM. Здесь мы приведем результаты для 64-х битной виртуальной машины со следующими параметрами:

В результате, один экземпляр ссылочного типа на данной JVM занимает 128 бит. За исключением Long и Double, которые занимают 192 бита:

  • Boolean — 128 бит
  • Byte — 128 бит
  • Short, Character — 128 бит
  • Integer, Float — 128 бит
  • Long, Double — 192 бита

Обратите внимание, переменная типа Boolean занимает в 128 раз больше места чем соответствующий ей примитив, тогда как Integer занимает памяти как 4 int переменные.

2.2 Использование памяти массивами
Более интересно обстоят дела с объемом памяти который занимают массивы, рассматриваемых нами типов.
При создании массивов с различным количеством элементов для каждого типа, мы получаем график:

который демонстрирует как массивы различных типов потребляют память (m) в зависимости от количества содержащихся элементов (e):

  • long, double: m = 128 + 64e
  • short, char: m = 128 + 64(e/4)
  • byte, boolean: m) = 128 + 64(e/8)
  • the rest: m = 128 + 64(e/2)

где значения в скобках округляются до ближайшего меньшего.

Это может показаться неожиданным, но массивы примитивных типов long и double занимают больше памяти чем их классы-обертки Long и Double соответственно.

2.3 Производительность

Производительность Java кода, довольно тонкий вопрос, сильно зависящий от аппаратной части устройства на котором он исполняется, от различных оптимизационных процессов, выполняемых компилятором, от конкретной JVM, а также от других процессов, происходящих в операционной системе.

Мы уже упоминали ранее, что переменные примитивных типов живут в стеке, тогда как ссылочные переменные хранят ссылки на объекты расположенные в куче. Это важное свойство, определяющее скорость доступа к данным.

Чтобы продемонстрировать насколько операции над примитивными типами быстрее чем над их классами-обертками, создадим массив из миллиона элементов в котором все элементы одинаковы, кроме последнего. Затем мы попытаемся найти этот элемент и сравним результаты производительности работы массива переменных примитивных типов с массивом ссылочных переменных.

Мы используем известный инструмент тестирования производительности JMH, а результаты операции суммируем в диаграмме:

Как мы видим, даже выполнение такой простой операции требует значительно больше времени для классов-оберток, чем для их примитивных аналогов.

В случае более трудоемких операций, таких как сложение, умножение или деление разница в скорости может значительно увеличиться.

Классы обертки — Java: Введение в ООП

В Java у каждого примитивного типа есть соответствующий «объектный» тип. Например, для int существует пара в виде Integer . Последний представлен классом и называется классом-оберткой.

// Примитивный тип // Можно так var value = 42; // int // Или так int value = 42; // int // Ссылочный тип // Можно так Integer value = 42; // Integer Integer value = new Integer(42); // Integer var value = new Integer(42); // Integer // Так написать нельзя, // потому что невозможно вывести тип value // null может быть присвоено любому ссылочному типу // var value = null; 

То же самое и для всех остальных примитивных типов:

Примитивный тип Класс-обертка
byte Byte
short Short
int Integer
long Long
float Float
double Double
char Character
boolean Boolean

Классы-обертки нужны для нескольких целей, которых по разным причинам невозможно решить с помощью примитивных типов.

Совместимость и единообразие

Многие возможности Java завязаны на то, что типы представлены классами, только в этом случае они смогут быть использованы как параметры методов или их возвращаемое значение. К ним, например, относятся обобщенные типы и как их следствие коллекции. Все это мы рассмотрим в следующих курсах.

// Так написать нельзя // var items = new ArrayList; var items = new ArrayListInteger>; 

null

Примитивные типы всегда содержат какое-то осмысленное значение. На практике же, значение может быть не определено. Особенно часто такое происходит, когда данные поступают из внешних источников. Для представления таких данных нужно специальное обозначение и возможность использовать это значение. В Java, как и в большинстве других языков, таким значением является null . Его можно использовать только с классами-обертками.

// Ошибка! int value = null; // Работает Integer value = null; 

По этой причине, множество методов ожидает на вход классы-обертки, а не примитивные типы.

Утилиты и значения

Классы-обертки содержат в себе полезные значения и методы, для своих типов. Например, в Integer есть данные о максимальном и минимальном значениях для типа, там же можно найти методы, которые преобразуют другие типы в числа.

var max = Integer.MAX_VALUE; var min = Integer.MIN_VALUE; // Преобразование в число // Возвращает Integer var value = Integer.valueOf("42"); // 42 // Возвращает int var value = Integer.parseInt("42"); // 42 

Почему так устроено?

Зачем тогда нужны примитивные типы если есть классы-обертки? Ответ кроется в производительности. Работа с примитивными типами как объектами значительно ухудшает производительность программы. Поэтому по умолчанию создаются объекты именно примитивных типов.

// Тип: boolean var hasErrors = true; 

Боксинг и анбоксинг (Boxing and Unboxing)

По логике, такая система должна приводить к постоянному конвертированию типов в коде, так как где-то создаются примитивные типы, где-то нужны классы-обертки и наоборот. К счастью, этого почти никогда не происходит благодаря механизму автобоксинга (Autoboxing). Тип преобразуется автоматически в тот момент, когда это нужно.

int value = 42; // int -> Integer Integer wrappedInt = value; // Autoboxing // Integer -> int unboxedInt = wrappedInt; // Auto-unboxing 

Открыть доступ

Курсы программирования для новичков и опытных разработчиков. Начните обучение бесплатно

  • 130 курсов, 2000+ часов теории
  • 1000 практических заданий в браузере
  • 360 000 студентов

Наши выпускники работают в компаниях:

Классы-обертки

У многих мог возникнуть вопрос: зачем задавать целочисленную переменную не int , а Integer ? Часто классы-обертки используют для того, чтобы сохранять данные в коллекции. Все дело в том, что коллекции — это набор объектов, и для того, чтобы оперировать примитивными типами как объектами, и были придуманы классы обертки.

Рассмотрим классы-обертки для примитивных типов данных.

Данные классы имеют название, которое происходит от названия примитивного типа данных, который они представляют: Double, Float, Long, Integer, Short, Byte, Character, Boolean .

Данные классы очень напоминают класс String . Объект обертку для примитивного типа можно создать как явно (используя конструктор), так и не явно (прямым присвоением примитива классу обертке) с помощью оператора = либо при передаче примитива в параметры метода (типа «класса-обертки»). Последнее еще называют автоупаковка (autoboxing).

public class AutoBoxing < public static void main(String[] args)< Integer a = new Integer(777); //явное создание переменной обертки int b = a; //неявное создание. > > 

Как и все объекты, переменные, созданные с помощью классов-оберток, будут храниться в куче ( heap ). Но, есть одно важное замечание, о котором часто любят спрашивать в тестах или на собеседованиях. Оно касается целочисленных классов оберток. Неявное создание таких переменных со значением в диапазоне -128 +127 будут храниться в пуле int-ов (В Java есть пул ( pool ) целых чисел в промежутке [-128;127]. Т.е. если мы создаем Integer в этом промежутке, то вместо того, чтобы каждый раз создавать новый объект, JVM берет их из пула). Потому такие обертки с одинаковыми значениями будут являться ссылками на один объект.

Автоупаковка переменных примитивных типов требует точного соответствия типа исходного примитива типу «класса-обертки». Попытка автоупаковать переменную типа byte в Short , без предварительного явного приведения byte->short вызовет ошибку компиляции.

public class AutoBoxing < public static void main(String[] args)< Integer integerA = new Integer(23); Integer integerB = new Integer(777); integerB = new Integer(888); System.out.println("integerA = " + integerA); Integer integerC = new Integer(666); Integer integerD = new Integer(2412); integerD = new Integer(1991); System.out.println("integerD = " + integerD); > > 

Результат работы программы:

integerA = 23 integerD = 1991 

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

Давайте посмотрим на самые популярные:

public class AutoBoxing < public static void main(String[] args)< String a = "45"; double b = Double.parseDouble(a); // наверное, самый популярный метод перевод из строки в целочисленный или дробный тип int c = Integer.parseInt(a); System.out.println(b); System.out.println(c); System.out.println(Integer.MAX_VALUE); // константа максимального значения System.out.println(Integer.bitCount(78)); // представляет число в двоичном виде System.out.println(Float.valueOf("80")); // возвращает целочисленный объект, содержащий значение указанного типа System.out.println(Integer.valueOf("444", 16)); // возвращает целочисленный объект, содержащий целое значение указанного строкового представления в 16-ричной системе счисления > > 

Вот и все, что касается классов оберток для примитивных типов данных. Используя их, Вы сможете оперировать примитивными типами, как объектами.

results matching » «

No results matching » «

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *