Классы-обертки в Java (Wrapper Classes). Practical Tasks
Строка int count = stock.get("banana"); не содержит ни одного явного вызова intValue(), но падает с NullPointerException, если ключа "banana" в Map<String, Integer> нет. Такие места проще научиться замечать на практике, чем запомнить по теории.
На этой странице — задачи по классам-оберткам Java (Integer, Long, Double, Boolean) и автоупаковке: методы valueOf(), parseXxx() и xxxValue(), кэш Integer и сравнение через == и equals(), распаковка null, автоупаковка в циклах и выбор перегруженного метода при boxing и varargs. Перед решением прочитайте уроки модуля: Классы обертки в Java, Autoboxing и unboxing и Перегрузка с дополнительными факторами.
Методы Double: valueOf(), parseDouble() и xxxValue()
Напишите программу, которая работает с классом Double и выводит на консоль результат каждого шага.
- Создайте объекты
Doubleс помощью обоих вариантов методаvalueOf(): из примитиваdoubleи из строки. - Преобразуйте строку
"300.99"в примитивdoubleметодомDouble.parseDouble(). - Преобразуйте объект
Doubleсо значением 300.99 ко всем числовым примитивным типам методамиbyteValue(),shortValue(),intValue(),longValue(),floatValue()иdoubleValue(). Объясните, почемуbyteValue()возвращает не 300. - Преобразуйте литерал типа
doubleв строку:String d = Double.toString(3.14); - Попробуйте разобрать строки
"3,14"и" 3.14 "методомDouble.parseDouble(). Ошибку разбора перехватите и выведите сообщение «Не число: ...» вместо аварийного завершения программы.
Нюанс
Double.parseDouble() понимает только точку как разделитель дробной части, независимо от локали, поэтому "3,14" приводит к NumberFormatException. А пробелы по краям строки он, в отличие от Integer.parseInt(), обрезает сам: " 3.14 " разбирается в 3.14.
Таблица методов классов-оберток
Заполните таблицу: на пересечении строки и столбца поставьте «x», если метод есть у соответствующего класса-обертки. В последнем столбце отметьте, статический это метод или экземплярный.
| Метод | Boolean | Byte | Character | Double | Float | Integer | Long | Short | Статический? |
|---|---|---|---|---|---|---|---|---|---|
| byteValue() | |||||||||
| doubleValue() | |||||||||
| floatValue() | |||||||||
| intValue() | |||||||||
| longValue() | |||||||||
| shortValue() | |||||||||
| parseXxx(String) | |||||||||
| parseXxx(String, int radix) | |||||||||
| valueOf(String, int radix) | |||||||||
| toString() | |||||||||
| toString(примитив) | |||||||||
| toString(примитив, int radix) |
Как проверить себя
Сигнатуры всех методов класса выводит команда javap java.lang.Byte (и так для каждой обертки); то же покажет автодополнение в IDE после Byte.. Помните, что Character и Boolean не наследуют класс Number, а значит, и его методов xxxValue().
Сравнение Integer и Long: == или equals()
Напишите программу, которая сравнивает обертки разными способами. Перед запуском запишите ожидаемый результат каждой строки, затем сверьте его с фактическим выводом.
- Объявите
Integer a = 127, b = 127;иInteger c = 128, d = 128;. Выведите результаты выраженийa == b,c == d,c.equals(d)иc <= d && c >= d. - Объявите
Long f = 128L;и выведитеf == 128,f.equals(128)иf.equals(128L). - Напишите статический метод
boolean sameValue(Integer x, Integer y), который сравнивает значения и не выбрасывает исключений, когда один или оба аргумента равныnull(дваnullсчитаются равными). Проверьте его на парах (1000, 1000), (null, 5) и (null, null). - Запустите программу с параметром JVM
-XX:AutoBoxCacheMax=1000. Какая строка вывода изменилась и почему не изменились остальные?
Подсказка
Две обертки через == сравниваются по ссылкам, а valueOf(), который вызывает автоупаковка, возвращает один и тот же объект только для значений из кэша. Для метода sameValue() пригодится Objects.equals().
Long или long: автоупаковка в цикле
Сравните на практике, во что обходится автоупаковка в «горячем» цикле.
- Напишите метод
static Long sumBoxed(int n), который суммирует числа от 0 до n - 1 в переменную типаLong, и методstatic long sumPrimitive(int n)с накопителем типаlong. - Вызовите оба метода для
n = 10_000_000и замерьте время каждого вызова черезSystem.nanoTime(). Выведите сумму и время в миллисекундах. Обе версии должны вывести сумму49999995000000. - Повторите замер 5 раз в цикле и сравните время первого и последнего повтора.
- Посчитайте, сколько новых объектов
Longсоздает вызовsumBoxed(10_000_000), еслиLong.valueOf()кэширует значения от -128 до 127. Ответ запишите комментарием в коде.
Нюанс
Время зависит от компьютера и версии JVM, поэтому важны не точные миллисекунды, а разница в разы. Первые повторы медленнее из-за прогрева JIT-компилятора; для точных измерений используют библиотеку JMH. Чтобы посчитать объекты, найдите первую итерацию, на которой сумма превысит 127.
Какой перегруженный метод вызовется: расширение, boxing, varargs
Дан класс с четырьмя перегруженными методами print():
public class OverloadQuiz {
static void print(long x) { System.out.print("long "); }
static void print(Integer x) { System.out.print("Integer "); }
static void print(Object x) { System.out.print("Object "); }
static void print(int... x) { System.out.print("int... "); }
public static void main(String[] args) {
byte b = 1;
char ch = 'a';
Integer boxed = 3;
Character c = 'a';
print(b);
print(5);
print(boxed);
print(ch);
print(c);
print(3.0);
print(1, 2);
print();
// print(null);
}
} - Не запуская программу, запишите, что выведет каждый вызов, и укажите фазу выбора метода (1, 2 или 3). Затем запустите программу и сверьте ответы.
- Добавьте метод
static void print(double x). Предскажите, какие слова в выводе изменятся, и проверьте. - Раскомментируйте вызов
print(null). Скомпилируется ли программа? Объясните сообщение компилятора.
Подсказка
Для каждого вызова проходите фазы по порядку: 1 — точное совпадение и расширение (примитивов и ссылок), 2 — упаковка и распаковка, 3 — раскрытие varargs. Как только на фазе нашелся хотя бы один подходящий метод, следующие фазы не рассматриваются. На фазах 1 и 2 метод print(int... x) виден как метод с параметром int[].
Какие темы закрепляет каждая задача
Первые две задачи проверяют знание методов классов-оберток, следующие три — автоупаковку и распаковку, последняя — выбор перегруженного метода. Если задача не получается, вернитесь к разделу урока из таблицы: в нем есть правило и пример с выводом.
| Задача | Что проверяет | Что повторить |
|---|---|---|
| Методы Double | valueOf() против parseDouble(), xxxValue(), NumberFormatException | Методы классов оберток |
| Таблица методов | какие методы есть у каждой обертки, статические и экземплярные | Класс Number |
| Сравнение Integer и Long | кэш -128..127, == против equals(), сравнение с null | Сравнение оберток: == или equals() |
| NullPointerException при распаковке | Map.get(), тернарный оператор, Boolean в if | NullPointerException при распаковке null |
| Long или long в цикле | цена автоупаковки, LongStream | Производительность: автоупаковка в циклах |
| Какой метод вызовется | фазы выбора метода, remove(int) против remove(Object) | Три фазы выбора перегруженного метода |
Типичные промахи в решениях
Большинство ошибок в этих задачах возникает не из-за незнания синтаксиса, а из-за проверки на «удобных» данных: маленьких числах, заполненных ключах, одном запуске. Перед сдачей решения проверьте его на граничных значениях.
- Сравнение двух Integer через ==. На значениях до 127 тест проходит, на реальных данных — нет.
- try-catch вместо исправления. Перехват
NullPointerExceptionпрячет ошибку; правильнее подставить значение по умолчанию до распаковки (getOrDefault(),Boolean.TRUE.equals()). - Один замер времени. Первый запуск цикла включает работу JIT-компилятора, поэтому сравнивайте результаты после нескольких повторов.
- Выбор метода «по самому похожему типу». Компилятор останавливается на первой фазе, где нашелся подходящий метод, даже если на следующей фазе есть метод с более похожим типом.
Проверь свой код
Перед тем как сверяться с разбором, пройдитесь по рекомендациям по оформлению кода: понятные имена переменных, отсутствие устаревших конструкторов new Integer(...), примитивы там, где значение не может отсутствовать.
Часто задаваемые вопросы
В каком порядке решать задачи по классам-оберткам?
Начните с задач на методы Double и таблицу методов: они проверяют базовый API. Задачи на сравнение Integer, распаковку null и автоупаковку в цикле независимы, их можно решать в любом порядке. Задачу о перегруженных методах оставьте на конец: без понимания автоупаковки ее не решить.
Можно ли в решениях создавать обертки через new Integer() или new Double()?
Лучше не надо: эти конструкторы устарели в Java 9 и с Java 16 помечены forRemoval, компилятор выдает предупреждение. Используйте valueOf(), parseXxx() или автоупаковку. Подробнее — в разделе valueOf() вместо конструкторов.
Как проверить решение, если к задаче нет разбора?
В задачах на автоупаковку и перегрузку есть контрольный вывод или прямое указание запустить код. Сначала запишите предсказание, потом запустите программу и объясните каждое расхождение по правилам из урока. Если не можете объяснить строку вывода — вернитесь к разделу из таблицы выше.
Какие вопросы по классам-оберткам чаще всего задают на собеседовании?
Почему Integer 127 == 127 дает true, а 128 == 128 — false; где возникает NullPointerException при распаковке; почему Long.valueOf(5).equals(5) возвращает false; что удалит list.remove(1) из List<Integer>. Все четыре темы есть в задачах о сравнении Integer, о распаковке null и о перегрузке.
Comments