Приведение типов
Вопрос 1
Как завершит работу следующий фрагмент кода?
Number n = 1_000_000;
int i = (int) n;
double d = (double) n;
System.out.println("int: " + i + " double: " + d);
а. На инструкции Number n = 1_000_000; выполнение программы прервётся из-за исключения.
б. На инструкции int i = (int) n; выполнение программы прервётся из-за исключения.
в. На инструкции double d = (double) n; выполнение программы прервётся из-за исключения.
г. Программа отработает без ошибок и исключительных ситуаций.
в. На инструкции double d = (double) n; выполнение программы прервётся из-за исключения.
Number n = 1_000_000;
Первая строка выполнится без проблем. Литерал 1_000_000 уже имеет тип int. Поэтому в первой строке он будет автоматически упакован в Integer. Присвоение переменной n типа Number величины типа Integer произойдёт без проблем, так как Integer extends Number, а переменные предков нормально ссылаются на объекты потомков, по принципу is-a.
int i = (int) n;
Это присвоение выполнится без ошибок. n фактически содержит Integer, поэтому явное приведение фактического Integer к int отработает.
double d = (double) n;
Попытка привести n к типу double обернётся тем, что машина выбросит исключение:
Exception in thread "main" java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.Double
Фактически переменная n ссылается на объект типа Integer. Машина будет пытаться привести Integer к Double, что невозможно сделать напрямую, так как это объектные типы и Double не является наследником Integer.
Числовые преобразования из целочисленных типов в вещественные возможны только у примитивов. Например, int легко приводится к double даже без необходимости явно приводить тип.
Вопрос 2
Будет ли удачным такое приведение типов?
Number n = 1_000_000;
double d = (Integer) n;
а. да
б. нет
а. да
Код отработает без проблем и переменная n будет содержать double значение миллион.
Первая строка отработает без проблем, потому что литерал 1_000_000 имеет тип int, который будет автоматически упакован в Integer. Переменная типа Number может ссылаться на объекты типа Integer, потому что Integer наследует от Number.
Во второй строке n будет приведена (явным образом) к Integer. Преобразование пройдёт успешно, так как объект фактически является Integer'ом.
Затем машина (неявно) распакует Integer в int. Затем int будет сохранён в переменную типа double, что не требует явного приведения, так как double считается шире, чем int.
Весь трюк этого примера в том, что после приведения Number к Integer машина сделает автораспаковку Integer в int. А правила приведения примитивов отличаются от правил приведения объектных типов.
Вопрос 3
Что выведет в консоль следующий фрагмент кода?
char ch = 'a';
System.out.println(+ch);
а. a
б. +а
в. 97
г. +97
в. 97
Унарный оператор "+" возвращает приведённое к типу int значение переменных "узких" типов (byte, short, char). Поэтому этот код выведет в консоль целочисленное значение номера символа 'a' в таблице символов.
Вопрос 4
Что будет, если попытаться откомпилировать и запустить следующий код?
Object[] objectArr = {new String("Hello!")};
String[] stringArr = (String[]) objectArr;
System.out.println(stringArr[0]);
а. Код не откомпилируется.
б. Код нормально откомпилируется, но во время выполнения программы вылетит исключение.
в. Код нормально откомпилируется, программа нормально отработает и выведет в консоль "Hello!"
б. Код нормально откомпилируется, но во время выполнения программы вылетит исключение.
Во время выполнения строки
String[] stringArr = (String[]) objectArr;
вылетит исключение:
java.lang.ClassCastException: class [Ljava.lang.Object; cannot be cast to class [Ljava.lang.String;
Ясно, что это немного неожиданное поведение машины, но формально машина ведёт себя правильно.
И Object[], и String[] являются объектными типами. Для объектных типов действует правило, согласно которому приведение работает только для типов, находящихся в иерархических отношениях. И хотя java.lang.String, как и все остальные классы, считается наследником java.lang.Object, нигде не сказано, что String[] наследует от Object[].
Формально никакие массивы не состоят между собой в иерархических отношениях и поэтому подобные преобразования считаются недопустимыми. Но компилятор, как бы странно это ни было, такой код пропускает.
Вопрос 5
Будет ли удачным такое приведение типов?
Number i = null;
Integer j = (Integer) i;
а. да
б. нет
а. да
Этот откомпилируется и отработает без ошибок. i является ссылкой на объект типа Number, j — на объект типа Integer. Приводя тип, мы говорим машине, что она может обращаться с объектом на который ссылается i, а начиная с второй строки ещё и j, как с Integer'ом.
Поскольку нигде в программе мы объекта как такового не касаемся, то есть не пытаемся читать значения его полей и/или выполнять привязанные к объекту методы, то тот факт, что обе переменные ссылаются на null, никак себя не проявит. И уж тем более не помешает приведению типов переменных.
Вопрос 6
Будет ли удачным такое приведение типов?
Number i = null;
int j = (Integer) i;
а. да
б. нет
а. да
Это вопрос с подвохом. Приведение типов происходит во второй строке справа от знака присваивания, то есть в (Integer) i. И в этой точке машина будет смотреть на i как на Integer, а не Number.
Код выпадет в ошибку, но не из-за приведения, а из-за того, что, чтобы присвоить переменной int j значение переменной i, нужно провести автораспаковку i в примитивный тип. И собственно автораспаковка null'а и приведёт к выбрасыванию java.lang.NullPointerException.
Так что формально само приведение Number к Integer произойдёт без проблем.
Вопрос 7
Что выведет в консоль следующий код:
int[] nums = {1, 2, 3, 4, 5};
System.out.println(Arrays.asList(nums).contains(3));
а. Код не откомпилируется
б. true
в. false
в. false
Код откомпилируется и отработает без ошибок. Загвоздка в том, что метод Arrays.asList(T... a) принимает в качестве аргумента массив объектов, которые, благодаря синтаксису varArgs можно передать и в виде просто значений, разделённых запятой.
Переменная nums не является массивом объектов. Это массив примитивов. Но она сама по себе — представитель ссылочного типа. Таким образом её содержимое (ссылка на int[]) станет единственным элементом нового списка. То есть метод asList в данном случае вернёт такой список: List<int[]>.
Очевидно, что ни один элемент такого списка, ни при каких обстоятельствах не может при сравнении с целым числом (3, 4, -1000 — неважно) вернуть true, так как сопоставление разных типов всегда возвращает false.
Вопрос 8
Будет ли удачной такая передача параметра в метод:
public static void main(String[] args) {
testNullCast((String) null);
}
public static void testNullCast(String nullStr) {
System.out.println(nullStr);
}
а. Да
б. Нет
а. Да
Код откомпилируется, нормально отработает и выведет на консоль слово null.
Ошибки (в частности NullPointerException) возникают при попытки привести ссылающиеся на null переменные или даже сам литерал null к примитивным типам. Такие преобразования недопустимы, так как примитивы по определению не могут быть null.
Объектные типы могут быть null'ами. И пока ссылка на null не используется (то есть на ней не вызывается метод, или из неё не пытаются читать значение поля), она вполне может существовать, как это происходит с параметром nullStr метода testNullStr.
Вопрос 9
Будет ли удачной такая передача параметра в метод:
public static void main(String[] args) {
testLongCast(0);
}
public static void testLongCast(Long lValue) {
System.out.println(lValue);
}
а. Код не откомпилируется.
б. Код откомпилируется, но во время выполнения выпадет в ошибку.
в. Код откомпилируется, нормально отработает и выведет на консоль 0.
а. Код не откомпилируется.
Возникнет следующая ошибка компиляции:
error: incompatible types: int cannot be converted to Long
Причина ошибки проста. Примитивный тип int даже без явного приведения легко приводится к примитивному типу long. Но в данном случае метод принимает в качестве параметра объектный тип Long.
И хотя мы привыкли к автоупаковке/автораспаковке, нужно иметь в виду, что при передаче параметров и других случаях присваивания Java не выполняет две операции одновременно. Она может произвести либо автоприведение типа, либо автоупаковку в объектный тип, но не два действия сразу.