8 июня 2023 761
Этой публикацией мы начинаем серию небольших статей с разбором “сложных” заданий из сертификации для Java-разработчиков, чтобы помочь вам подготовиться к сдаче сертификационного экзамена и помочь вам лучше понять, как работает Java и как эффективнее использовать его в своих проектах.
Предположим, нам дали два целых числа, но не примитивы, а Integer-объекты…

Итак, один из подвопросов, традиционно выносимых на экзамен – будь это сертификация Oracle или сертификации для Java-разработчиков от Учебного центра IBS – интересуется разработкой кода с использованием классов-оболочек, в частности, Boolean, Double или, скажем, Integer. Такая задача может быть сформулирована в следующей форме:

photo_2023-06-05_10-27-43.jpg

Как мы видим, в вопросе задействованы два класса-оболочки, а именно Boolean (в лице его статического фабричного метода) и Integer. Следует отметить, что для нашего экзамена характерен подход, сочетающий в себе сразу несколько тестируемых аспектов. Поэтому не всегда очевидно, чем именно задача интересуется: тем, как работают "фабрики", механизмом автоупаковки/распаковки или оператором сравнения и т. д.

Обсудим все по-порядку. Практически в каждом классе-оболочке есть два популярных статических метода для создания объекта: valueOf(), который принимает подстилающий примитив, и parseXXX(), который – как и всякий парсер – принимает стринг (здесь вместо XXX надо подставить, например, Double, Int и т.д.). Исключением является класс Character: в нем парсера нет. Встречаются и перегруженные парсеры, где можно указать систему счисления, но наш экзамен таких тонкостей не касается.

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

Мы знаем, что при вызове конструктора через ключевое слово new возможны два результата: мы получим совершенно новый объект данного типа, либо вылетит исключение. Из-за такой ограниченности функционала конструкторов предпочтение отдается фабричным методам, которые – в отличие от любого конструктора – способны вернуть ссылку на уже существующий объект.

И вот что интересно: поскольку класс-оболочка Integer является немутирующим, два объекта этого класса с одним и тем же значением способны взаимозаменять друг друга. Вот почему нет никакой необходимости иметь два разных объекта с одинаковым подстилающим значением. Мало того, помимо экономии памяти мы получаем также возможность сравнивать объекты через == вместо вызова метода equals(Object o). В частности, для класса Integer такой подход популярен с объектами, чьи значения лежат в пределах диапазона байта, т.е. от -128 до 127. (И если тут возникает ощущение дежа-вю, то все правильно: со стрингами аналогичная история, мы предпочитаем пользоваться строковыми константами в двойных кавычках вместе вызова, скажем, new String("5").

Фабричные методы обладают и другими преимуществами. Например, мы можем иметь несколько аналогичных методов с разными именами, но одинаковыми списками формальных параметров. А вот с конструкторами такой фокус не пройдет: их можно только перегрузить.

В книге Джошуа Блока, "Effective Java", приведен полный список преимуществ статических фабричных методов над конструкторами. Еще одна выгода в том, что конструктор способен вернуть объект одного типа, а вот фабричный метод может вернуть что угодно, если это не противоречит задекларированному типу возвращаемого значения. Одно из наиболее наглядных и понятных проявленией – это имплементация интерфейсов, где можно аккуратно скрыть специфические детали реализации.

Метод Boolean.valueOf()может вернуть одно из двух значений, причем это константные объекты, а именно, Boolean.TRUE и Boolean.FALSE. Именно эти объекты будут использоваться раз за разом, не требуя дополнительного выделения памяти, что не представляется возможным с ключевым словом new.

Далее, большинство оболочечных классов бросают исключение в случае null-аргумента или стринга, чей формат нарушает требования к подстилающему примитиву (например, если бы мы вызвали Integer.valueOf() с аргументом "five", а не "5"). Но здесь следует отметить, что парсер класса java.lang.Boolean всегда тестирует аргумент: во-первых, существует ли он и, во-вторых, содержит ли значение "true" при любом сочетании букв в верхнем и нижнем регистрах. Если да, булевый парсер вернет Boolean.TRUE, а в противном случае – Boolean.FALSE. Другими словами, парсеру можно скормить хоть "что угодно", хоть даже null – и он просто вернет Boolean.FALSE без вылета какого-либо исключения.

Вот почему строка Х в нашем примере не бросает исключение и присваивает переменной boo значение Boolean.FALSE. Следовательно, вариант A не верный.

Теперь давайте займемся поведением оператора if. Мы знаем, что тестируемое выражение обязано иметь тип boolean. Привыкнув к механизму автоупаковки и распаковки, мы ожидаем, что Boolean-объект будет транспарентно распакован в подстилающий примитив. Так оно и случится, поэтому код скомпилируется – но из-за того, что наш экзамен рассчитан на Java11; до появления автоупаковки в Java5 эта же строчка вызвала бы синтаксическую ошибку. Впрочем, в нашей задаче вообще нет такого варианта.

После распаковки булевый тест получит значение false и, стало быть, оператор печати не исполнится. Что ж, выходит, вариант D – единственный правильный.

А теперь давайте задумаемся над тем, что было бы, если бы булевый тест дал значение true.

Как мы знаем, Java предоставляет две идиомы сравнения. Одна из них является конструкцией, встроенной непосредственно в лексику языка: это оператор ==. Вторая идиома – а именно, метод equals(Object o) – предоставлена стандартной библиотекой и, будучи частью функционала класса java.lang.Object, доступна любому объекту. Она не делает ничего полезного и своим поведением не отличается от оператора "double equals", поэтому классам-наследникам предписывается по-своему решать, каким образом переопределить такое поведение. Метод equals() имеет интересный контракт из пяти пунктов, но в нашем примере он не используется, поэтому мы не будем обсуждать его; вместо этого давайте займемся оператором ==.

Мы знаем, что "double equals" принимает два операнда, точнее два выражения. Нюанс в том, что выражения могут иметь разные типы – и это различие сказывается на результате сравнения самым радикальным образом.

Что за разные типы? Это наши старые знакомые: примитивы (коих в Java восемь: boolean, byte, short, char, int, long, float и, наконец, double), либо то, что мы именуем термином ссылка (reference). Напомним, что ссылка в Java напоминает указатель (pointer), который показывает, где именно в памяти расположен тот или иной объект.

Если выражение выражение имеет примитивный тип, то это бинарная репрезентация некоего числа, например, 0b101010, то есть, десятичное 42. Это число будет записано в стеке и ему будет присвоена некая метка, которую мы привыкли называть словом "переменная". Но вот если речь идет не про примитив 42, а про Integer-объект, который инкапсулирует подстилающий примитив 42, то в стеке будет сидеть уже не 0b101010, а нечто куда более "вихрастое", например, 0xffff5637. Эта комбинация шестнадцатиричных литералов по сути является значением ссылки на объект, который в языке Java живет не в стеке, а в т.н. динамически аллоцируемой памяти, в знаменитой "куче" (heap). К примеру, в языке C/С++, прородителе Java, значением пойнтера является адрес объекта. В Java ситуация несколько сложнее, но в первом приближении мы тоже можем сказать, что значением ссылки (которая живет в стеке) является адрес объекта (который, напомним, живет на heap'е). Что в итоге? Пусть оператор "double equals" сравнивает значения двух переменных. Если это примитивы, JVM сравнит их бинарные значения, то есть – числа. Если это ссылки, то JVM сравнит их значения, но ведь значением ссылки является адрес того объекта, на который данная ссылка указывает. Вот почему принято говорить, что оператор == проверяет эквивалентность примитивных значений, а в случае ссылок он сравнивает уже идентичность объектов. Если у двух ссылочных переменных (например, класса Integer) одно и то же значение, мы имеем дело с одним и тем же объектом (один и тот же адрес!), но если объектов два, то они расположены по разным адресам. В этой ситуации оператор == вернет false.

Сейчас не должно вызывать удивление, что данный код:

photo_2023-06-05_13-58-36.jpg

обязательно покажет false: ведь успешный вызов конструктора через new, как мы уже упоминали, приведет к рождению очередного объекта. Простое правило на нашем экзамене: сколько видим слов new, как минимум столько же будет объектов. Стало быть, переменные v1 и v2 по необходимости ссылаются на разные объекты, и сравнение их адресов оператором == даст нам false.

А вот небольшая вариация на эту же тему: пусть у нас такой код:

уцкуц.jpg

Опять мы видим new и это означает, что у нас вновь два разных объекта: один родился благодаря стараниям конструктора, а второй прилетел к нам из статического фабричного метода, который был вызван имплицитно и полностью для нас транспарентно был вызван механизмом автоупаковки.

В заключение следует отметить, что фабрики для немутирующих объектов часто пишутся так, чтобы возвращать все тот же объект, если в метод были переданы те же аргументы. В частности, в документации на java.lang.Integer API мы видим следующую ремарку о методе valueOf(int):

“This method will always cache values in the range -128 to 127, inclusive, and may cache other values outside of this range.”

Другими словами, следующий кодовый сниппет:

Integer v1 = Integer.valueOf(1);

Integer v2 = Integer.valueOf(1);

System.out.print(v1 == v2);

даст нам true.

Официально задокументированная гарантия присутствует лишь для метода valueOf(int), хотя на практике valueOf(String)также демонстрирует это же поведение: Integer-объекты в пределах диапазона байта помещаются в константный пул.

Подведем итоги: поскольку код пользуется механизмом автоупаковки (в форме вызова Integer.valueOf(int)для создания одного объекта, а для второго применяет вызов конструктора, это означает, что если бы отработал оператор печати, мы бы увидели false. Поэтому правильным вариантом ответа остается D, о чем мы уже упоминали.

Последние статьи в блоге

Выгодный май — на курсы залетай!

Друзья, спешим поделиться отличной новостью — вы можете получить скидки до 40% на наши популярные курсы. Это отличная возможность улучшить навыки и инвестировать в профессиональное развитие по более выгодной цене. Выбирайте направление и подавайте заявку прямо сейчас!

05 мая 2025

Кейс: кастомизация курса по Jira

Кейс по проведению кастомизированного курса «Основы Jira» для крупной российской компании, занимающейся производством цифровой техники.

05 мая 2025

Зачем специалистам по 1С изучать системный анализ и архитектуру ПО

Как системный анализ и архитектура ПО помогают эффективнее работать в 1С.

29 апреля 2025

Банка Nutella, IT, ESG — что общего?

Когда вы читали этикетку на продукте не из-за состава, а из-за ESG-маркировки?

25 апреля 2025

Каковы плюсы и минусы монолитной и микросервисной архитектуры при разработке ИТ-продуктов?

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

25 апреля 2025

Станьте архитектором ПО с выгодой! Только в апреле сэкономьте 20 000 ₽ и получите новый модуль по микросервисам в подарок

24 апреля стартует обучение на комплексной программе «Архитектор ПО. Путь к мастерству в проектировании систем»*.

14 апреля 2025

Архитектурные ошибки в корпоративных системах, которые могут создать проблемы в долгосрочной перспективе

В основе любой информационной системы — сложная структура. Спроектировать ее не легче, чем построить дом. Какие ошибки можно допустить при планировании ИТ-архитектуры, и в чем их основные причины? К чему они могут привести в долгосрочной перспективе и как этого избежать? Рассказывает Михаил Рощин, заместитель директора отделения управления проектами и архитектуры IBS.

Новости
10 апреля 2025

Кейс: Интенсив по управлению проектами для промышленной компании

Мы адаптировали курс по управлению проектами под запрос команды крупной промышленной компании и провели обучение. Вот что из этого вышло.

27 марта 2025

Кейс: Обучение сотрудников крупной компании работе с ClickHouse

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

19 марта 2025

Платформа сертификации IBS получила аккредитацию АПКИТ

Ассоциация предприятий компьютерных и информационных технологий (АПКИТ) приняла новый регламент сертификации ИТ-специалистов.

Новости
10 марта 2025

Специальные акции на учебные программы

У нас отличная новость для всех, кто стремится развивать свои навыки в мире ИТ.

06 марта 2025

Как остановить спам-атаку

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

06 марта 2025

Учебный центр IBS подписал партнерское соглашение с ООО «РусБИТех-Астра», разработчиком российской операционной системы Astra Linux.

Теперь мы можем проводить авторизованное обучение по работе с Astra Linux для специалистов в области информационной безопасности.

17 февраля 2025

Двойная выгода: покупай один курс — получай второй за 50% стоимости!

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

29 января 2025

Сертификация преподавателя Java-разработки для крупного провайдера ИТ-обучения

Подтверждение квалификации значимо не только для ИТ-специалистов, работающих в сфере коммерческой разработки, но и для преподавателей, обучающих будущих экспертов в этой области. Делимся историей одного из преподавателей крупного провайдера ИТ-курсов, который успешно прошел Java-сертификацию.

Новости
21 января 2025

Системный аналитик 100 lvl — дорожная карта развития

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

23 декабря 2024

Платформа сертификации IBS признана лучшим digital-решением для корпоративного обучения

Центр сертификации IBS стал обладателем Гран-при премии «Смарт пирамида» — одной из самых престижных российских премий за достижения в области обучения и развития человеческого капитала.

20 декабря 2024

Учебный центр IBS получил сертификат ГОСТ Р ИСО 9001-2015

В октябре 2024 года Учебный центр IBS получил сертификат соответствия ГОСТ Р ИСО 9001-2015. Это важное достижение подтверждает, что мы придерживаемся высоких стандартов качества и результативно управляем образовательными процессами организации.

19 декабря 2024

9 курсов со скидкой до 50%

Друзья, в январе стартует 9 курсов, обучение на которых можно купить со скидкой до 50%*! 

15 декабря 2024

8 заблуждений про тестирование

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

15 декабря 2024

Не нашли, что искали? — Просто напишите, и мы поможем

Корпоративное обучение Оценка персонала Сертификация О нас Стань тренером Блог
Пользователь только что записался на курс ""
Спасибо!
Форма отправлена успешно.