<h2>Вступ</h2>Мова програмування, як і мова якою говорять люди, живе і змінюється, в ньому з'являються нові явища, щоб мовою було користуватися зручніше. А як ми знаємо мову має зручно висловлювати наші думки.
Отже, Java SE 5 був представлений механізм упаковки/розпакування (boxing/unboxing). І особливості цього засобу виразів думок присвячений окремому tutorial від Oracle: Autoboxing and Unboxing . <h2>Автоупаковка Boxing</h2>Давайте, розглянемо приклад автоупаковки Boxing. Спочатку подивимося, як воно працює. Скористаємося сайтом compilejava.net та створимо клас:
Цей той самий горезвісний «байткод». Але важливе нам зараз те, що ми бачимо. Спочатку в стек виконання методу поміщається примітив 8080, а далі виконується Integer.valueOf . Це і є та магія boxing. А всередині магія виглядає так:
Тобто по суті буде взято новий
Як видно, жодної магії. Все в рамках Java. Просто воно працює «само». Для нашої зручності. <h2>Граблі</h2>
Будь-який інструмент при неправильному використанні стає грізною зброєю проти себе. Та механізм автопакування та розпакування boxing/unboxing у Java не виняток. Перше, очевидне, порівняння через
public class App {
public static void main(String[] args) {
Integer portNumber = 8080;
if (args.length != 0) {
portNumber = Integer.valueOf(args[0]);
}
System.out.println("Port number is: " + portNumber);
}
}
Простенький код. Можемо вказати вхідний параметр та змінити значення порту. Як бачимо, т.к. ми зчитуємо значення порту з String
параметрів, ми отримуємо Integer
шляхом отримання через Integer.valueOf
. Тому ми змушені вказувати його не примітивним типом, а як об'єктний тип Integer
. І тут отримуємо з одного боку, у нас змінна об'єктна, а значення за умовчанням – примітив. І вона працює. Але ж ми не віримо в магію? Давайте заглянемо "під капот", що називається. Скачаємо з compilejava.net вихідний код, натиснувши "Download ZIP". Після цього витягнемо завантажений архів у каталог і перейдемо до нього. Тепер виконаємо: javap -c -p App.class
де App.class – скомпільований class файл для вашого класу. Ми побачимо вміст на кшталт такого:
Integer
або буде отримано Integer
з кешу (кеш ніщо інше як просто масив Integer) залежно від величини значення числа. Звичайно, Integer
не одному так пощастило. Є цілий список примітивних типів, що співвідносяться, і їх обгорток (класів, що представляють примітиви у світі ОВП). Даний список наведений у самому низу Tutorial від Oracle: Autoboxing and Unboxing . Варто відразу зазначити, що масиви з примітивів не мають «обгортки» без підключення будь-яких сторонніх бібліотек. Тобто. Arrays.asList
не зробить з int[]
нас List
з Integer
'ів. <h2>Розпакування Unboxing</h2>Зворотний процес до boxing називається unboxing розпакування. Розглянемо приклад розпакування:
public class App {
public static void main(String[] args) {
if (args.length == 0) {
System.out.println("Please, enter params");
return;
}
int value = Math.abs(Integer.valueOf(args[0]));
System.out.println("Absolute value is: " + value);
}
}
Math.abs
приймає лише примітиви. Що ж робити? У класу обгортки є на цей випадок спеціальний метод, який повертає примітив. Наприклад, для Integer
цього метод intValue . Якщо ми подивимось у байткод, то так і є:
==
. Думаю, це зрозуміло, але розберемо ще раз:
public static void main(String[] args) {
Integer inCacheValue = 127;
Integer inCacheValue2 = 127;
Integer notInCache = 128; // new Integer(129)
Integer notInCache2 = 128; // new Integer(129)
System.out.println(inCacheValue == inCacheValue2); //true
System.out.println(notInCache == notInCache2); //false
}
У першому випадку значення береться з кеша Integer
значень (див. пояснення Boxing вище), а в другому буде створюватися щоразу новий об'єкт. Але тут варто обмовитися. Ця поведінка залежить від верхньої межі кешу ( java.lang.Integer.IntegerCache.high ). Крім того, цей кордон може змінитися і через інші налаштування. На цю тему можна ознайомитися з обговоренням на stackoverflow: How large is the Integer cache? Звичайно, об'єкти потрібно порівнювати через equals: System.out.println(notInCache.equals(notInCache2));
Друга проблема, пов'язана з тим самим механізмом – продуктивність. Будь-яка упаковка в Java дорівнює створення нового об'єкта. Якщо число не входить у значення з кешу (тобто -128 до 127), то щоразу створюватиметься новий об'єкт. Якщо раптом упаковка (тобто boxing) вироблятиметься у циклі, це викличе величезний приріст непотрібних об'єктів і споживання ресурсів працювати збирача сміття. Тому, не варто надто безрозсудно ставитися до цього. Треті не менш хворі граблі випливають із того ж механізму:
public static void check(Integer value) {
if (value <= 0) {
throw new IllegalStateException("Value is too small");
}
}
У цьому коді людина явно намагалася не пройти повз помилку. Але тут немає перевірки на null
. Якщо на вхід прийде null
, то замість зрозумілої помилки ми отримаємо невиразний NullPointerException
. Тому що для порівняння Java спробує виконати value.intValue
та впаде, т.к. value
буде null
. <h2>Висновок</h2>Механізм boxing/unboxing дозволяє програмісту писати менше коду і навіть часом не думати про перетворення з примітивів на об'єкти і назад. Але це не означає, що слід забувати, як це працює. Інакше можна припуститися помилки, яка може спливти не відразу. Не варто покладатися на частини системи, які не повністю під нашим контролем (наприклад кордон integer). Але й не варто забувати про всі переваги класів-оберток (начебтоInteger
). Часто ці класи-обертки мають набір додаткових статичних методів, які зроблять ваше життя кращим, а код виразнішим. Ось приклад у наздогін:
public static void main(String[] args) {
int first = 1;
int second = 5;
System.out.println(Integer.max(first, second));
System.out.println(Character.toLowerCase('S'));
}
Так само правильний висновок із усього – немає магії, є якась реалізація. І не скрізь завжди буде те, що ми очікуємо. Наприклад, тут немає упаковки: System.out.println("The number is " + 8);
Приклад вище компілятором буде соптимизирован в один рядок. Тобто наче ви написали «The number is 8». І в прикладі нижче також не буде упаковки:
public static void main(String[] args) {
System.out.println("The number is " + Math.abs(-2));
}
Як же так, коли у нас println
приймає на вхід об'єкт і потрібно з'єднати рядки. Рядки… так, саме тому немає упаковки як такої. Є Integer
статичні методи, але з них рівня package
. Тобто ми їх не можемо використовувати, а в самій Java вони можуть активно використовуватися. Ось тут саме такий випадок. Буде викликаний метод getChars, який у складі робить масив символів. Знову ж таки, ніякої магії, тільки Java ). Тож у будь-якій незрозумілій ситуації варто просто подивитися реалізацію і хоч щось та й стане на свої місця. #Viacheslav
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ