Приведение типов

Вопрос 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 не выполняет две операции одновременно. Она может произвести либо автоприведение типа, либо автоупаковку в объектный тип, но не два действия сразу.