Глава 5. Преобразования и контексты
Оглавление
- 5.1. Виды преобразований
-
- 5.1.1. Тождественное преобразование
- 5.1.2. Расширяющее преобразование примитивных типов
- 5.1.3. Сужающее преобразование примитивных типов
- 5.1.4. Расширяющее и сужающее преобразование примитивных типов
- 5.1.5. Расширяющее преобразование ссылочных типов
- 5.1.6. Сужающее преобразование ссылочных типов
- 5.1.7. Упаковка (boxing)
- 5.1.8. Распаковка (unboxing)
- 5.1.9. Непроверяемое преобразование
- 5.1.10. Преобразование захвата (capture)
- 5.1.11. Преобразование в строку
- 5.1.12. Запрещенные преобразования
- 5.2. Контексты присваивания
- 5.3. Контексты вызова
- 5.4. Контексты строк
- 5.5. Контексты приведения типов
- 5.6. Численные контексты
Каждое выражение, написанное на языке программирования Java, либо не производит результата (§15.1), либо имеет тип, который может быть выведен на этапе компиляции (§15.3). Когда выражение появляется в большинстве контекстов, оно должно быть совместимо с типом, ожидаемым в этом контексте; этот тип называется целевым типом. Для удобства совместимость выражения с окружающим контекстом облегчается двумя способами:
-
Во-первых, для некоторых выражений, называемых поливыражениями (§15.2), выведенный тип может зависеть от целевого типа. Одно и то же выражение может иметь разные типы в разных контекстах.
-
Во-вторых, после того, как тип выражения был выведен, иногда можно выполнить неявное преобразование из типа выражения в целевой тип.
Если ни одна из стратегий не может произвести соответствующий тип, возникает ошибка компиляции.
Правила, определяющие, является ли выражение поливыражением, и если да, то его тип и совместимость в конкретном контексте, зависят от вида контекста и формы выражения. Помимо влияния на тип выражения, целевой тип в некоторых случаях может влиять на поведение выражения во время выполнения, чтобы произвести значение соответствующего типа.
Аналогично, правила, определяющие, позволяет ли целевой тип неявное преобразование, зависят от вида контекста, типа выражения и, в одном особом случае, значения константного выражения (§15.29). Преобразование от типа S к типу T позволяет рассматривать выражение типа S во время компиляции как имеющее тип T вместо этого. В некоторых случаях это потребует соответствующих действий во время выполнения для проверки допустимости преобразования или для преобразования значения выражения во время выполнения в форму, подходящую для нового типа T.
Пример 5.0-1. Преобразования во время компиляции и во время выполнения
-
Преобразование от типа
Objectк типуThreadтребует проверки во время выполнения, чтобы убедиться, что значение во время выполнения является фактически экземпляром классаThreadили одним из его подклассов; если это не так, выбрасывается исключение. -
Преобразование от типа
Threadк типуObjectне требует действий во время выполнения;Threadявляется подклассомObject, поэтому любая ссылка, созданная выражением типаThread, является допустимой ссылкой типаObject. -
Преобразование от типа
intк типуlongтребует расширения знака во время выполнения значения 32-битного целого числа до 64-битного представленияlong. Информация не теряется. -
Преобразование от типа
doubleк типуlongтребует нетривиального преобразования от 64-битного значения с плавающей запятой к 64-битному представлению целого числа. В зависимости от фактического значения во время выполнения, информация может быть потеряна.
Возможные преобразования в языке программирования Java сгруппированы в несколько основных категорий:
-
Тождественные преобразования
-
Расширяющие преобразования примитивных типов
-
Сужающие преобразования примитивных типов
-
Расширяющие преобразования ссылочных типов
-
Сужающие преобразования ссылочных типов
-
Преобразования упаковки (boxing)
-
Преобразования распаковки (unboxing)
-
Непроверяемые преобразования
-
Преобразования захвата (capture)
-
Преобразования в строку
Существует шесть видов контекстов преобразования, в которых поливыражения могут быть подвержены влиянию контекста или могут произойти неявные преобразования. Каждый вид контекста имеет свои правила для типизации поливыражений и допускает преобразования в некоторых категориях выше, но не в других. Контексты:
-
Контексты присваивания (§5.2, §15.26), в которых значение выражения привязывается к именованной переменной. Примитивные и ссылочные типы могут быть подвергнуты расширению, значения могут быть упакованы или распакованы, а некоторые константные выражения примитивных типов могут быть подвергнуты сужению. Также может произойти непроверяемое преобразование.
-
Строгие контексты вызова (§5.3, §15.9, §15.12), в которых аргумент привязывается к формальному параметру конструктора или метода. Могут произойти расширяющие преобразования примитивных типов, расширяющие преобразования ссылочных типов и непроверяемые преобразования.
-
Свободные контексты вызова (§5.3, §15.9, §15.12), в которых, подобно строгим контекстам вызова, аргумент привязывается к формальному параметру. Вызовы методов или конструкторов могут предоставить этот контекст, если не может быть найдена соответствующая декларация, используя только строгие контексты вызова. В дополнение к расширяющим и непроверяемым преобразованиям, в этом контексте могут произойти преобразования упаковки и распаковки.
-
Контексты строк (§5.4, §15.18.1), в которых значение любого типа преобразуется в объект типа
String. -
Контексты приведения типов (§5.5), в которых значение выражения преобразуется в тип, явно указанный оператором приведения типов (§15.16). Контексты приведения типов более обширны, чем контексты присваивания или свободные контексты вызова, позволяя любые конкретные преобразования, кроме преобразования в строку, но некоторые приведения к ссылочному типу проверяются на корректность во время выполнения.
-
Численные контексты (§5.6), в которых операнды числового оператора или некоторые другие выражения, работающие с числами, могут быть расширены до общего типа.
Термин "преобразование" также используется для описания, не уточняя, каких-либо преобразований, разрешенных в конкретном контексте. Например, мы говорим, что выражение, которое является инициализатором локальной переменной, подвергается "преобразованию присваивания", что означает, что для этого выражения будет неявно выбрано определенное преобразование в соответствии с правилами контекста присваивания. В качестве другого примера, мы говорим, что выражение претерпевает "преобразование приведения типов", чтобы обозначить, что тип выражения будет преобразован в соответствии с разрешенными преобразованиями в контексте приведения типов.
Пример 5.0-2. Преобразования в различных контекстах
class Test {
public static void main(String[] args) {
// Casting conversion (5.5) of a float literal to
// type int. Without the cast operator, this would
// be a compile-time error, because this is a
// narrowing conversion (5.1.3):
int i = (int)12.5f;
// String conversion (5.4) of i's int value:
System.out.println("(int)12.5f==" + i);
// Assignment conversion (5.2) of i's value to type
// float. This is a widening conversion (5.1.2):
float f = i;
// String conversion of f's float value:
System.out.println("after float widening: " + f);
// Numeric promotion (5.6) of i's value to type
// float. This is a binary numeric promotion.
// After promotion, the operation is float*float:
System.out.print(f);
f = f * i;
// Two string conversions of i and f:
System.out.println("*" + i + "==" + f);
// Invocation conversion (5.3) of f's value
// to type double, needed because the method Math.sin
// accepts only a double argument:
double d = Math.sin(f);
// Two string conversions of f and d:
System.out.println("Math.sin(" + f + ")==" + d);
}
}
Эта программа выводит:
(int)12.5f==12 after float widening: 12.0 12.0*12==144.0 Math.sin(144.0)==-0.49102159389846934
Конкретные типы преобразований в языке программирования Java делятся на 12 видов.
Преобразование типа в тот же самый тип разрешено для любого типа.
Это может показаться тривиальным, но у этого есть два практических последствия. Во-первых, это всегда разрешено для выражения, чтобы иметь желаемый тип с самого начала, тем самым позволяя просто сформулировать правило, что каждое выражение подвергается преобразованию, если только не идет речь о тривиальном преобразовании тождества. Во-вторых, это подразумевает, что разрешается включение в программу избыточных операторов приведения типов ради ясности.
19 конкретных преобразований примитивных типов называются расширяющими преобразованиями примитивных типов:
-
byteкshort,int,long,floatилиdouble -
shortкint,long,floatилиdouble -
charкint,long,floatилиdouble -
intкlong,floatилиdouble -
longкfloatилиdouble -
floatкdouble
Расширяющее преобразование примитивного типа не приводит к потере информации о величине числового значения в следующих случаях, когда числовое значение сохраняется точно:
-
от целочисленного типа к другому целочисленному типу
-
от
byte,shortилиcharк типу с плавающей запятой -
от
intкdouble -
от
floatкdouble
Расширяющее преобразование примитивного типа от int к float или от long к float или от long к double может привести к потере точности, то есть результат может потерять некоторые из наименее значащих битов значения. В этом случае результирующее значение с плавающей запятой будет правильно округленной версией целочисленного значения, используя политику округления до ближайшего значения (§15.4).
Расширяющее преобразование знакового целочисленного значения в целочисленный тип T просто расширяет представление значения в дополнительном коде до двух для заполнения более широкого формата.
Расширяющее преобразование char в целочисленный тип T дополняет представление значения char нулями для заполнения более широкого формата.
Расширяющее преобразование от int к float, или от long к float, или от int к double, или от long к double происходит в соответствии с правилами IEEE 754 для преобразования из целочисленного формата в двоичный формат с плавающей запятой.
Расширяющее преобразование от float к double происходит в соответствии с правилами IEEE 754 для преобразования между двоичными форматами с плавающей запятой.
Несмотря на то, что может произойти потеря точности, расширяющее преобразование примитивного типа никогда не приводит к исключению во время выполнения (§11.1.1).
Пример 5.1.2-1. Расширяющее преобразование примитивного типа
class Test {
public static void main(String[] args) {
int big = 1234567890;
float approx = big;
System.out.println(big - (int)approx);
}
}
Эта программа выводит:
-46
тем самым показывая, что информация была потеряна во время преобразования из типа int в тип float, потому что значения типа float не точны до девяти значащих цифр.
22 конкретных преобразования примитивных типов называются сужающими примитивными преобразованиями:
-
shortкbyteилиchar -
charкbyteилиshort -
intкbyte,shortилиchar -
longкbyte,short,charилиint -
floatкbyte,short,char,intилиlong -
doubleкbyte,short,char,int,longилиfloat
При сужающем примитивном преобразовании может быть потеряна информация о целом масштабе числового значения, а также может быть потеряна точность и диапазон.
Сужающее преобразование знакового целого числа в целочисленный тип T просто отбрасывает все, кроме n младших битов, где n — количество битов, используемых для представления типа T. В дополнение к возможной потере информации о величине числового значения это может привести к тому, что знак результирующего значения будет отличаться от знака входного значения.
Сужающее преобразование символьного значения в целочисленный тип T также просто отбрасывает все, кроме n младших битов, где n — количество битов, используемых для представления типа T. В дополнение к возможной потере информации о величине числового значения это может привести к тому, что результирующее значение будет отрицательным, даже если символы представляют 16-битовые целые без знака.
Сужающее преобразование числа с плавающей точкой в целочисленный тип T выполняется в два этапа:
-
На первом этапе число с плавающей точкой преобразуется либо в
long, если T являетсяlong, или вint, если T являетсяbyte,short,charилиint, следующим образом:-
Если число с плавающей точкой является NaN (§4.2.3), результатом первого этапа преобразования является
intилиlong0. -
В противном случае, если число с плавающей точкой не является бесконечностью, значение с плавающей точкой округляется до целого значения
Vс использованием политики округления до нуля (§4.2.4). Тогда есть два случая:-
Если T является
long, и это целое значение может быть представлено какlong, то результатом первого шага является значениеlongV. -
В противном случае, если это целое значение может быть представлено как
int, то результатом первого этапа является значениеintV.
-
-
В противном случае должен быть истинным один из следующих двух случаев:
-
Значение слишком мало (отрицательное значение большой величины или отрицательная бесконечность), и результатом первого этапа является наименьшее представимое значение типа
intилиlong. -
Значение слишком велико (положительное значение большой величины или положительная бесконечность), и результатом первого этапа является наибольшее представимое значение типа
intилиlong.
-
-
-
На втором этапе:
-
Если T является
intилиlong, результатом преобразования является результат первого этапа. -
Если T является
byte,charилиshort, результатом преобразования является результат сужающего преобразования в тип T (§5.1.3) результата первого этапа.
-
Сужающее преобразование из double в float происходит в соответствии с правилами IEEE 754 для преобразования между двоичными форматами чисел с плавающей точкой с использованием политики округления до ближайшего значения (§15.4). Это преобразование может потерять точность, но также может потерять диапазон, что приводит к нулю float из ненулевого double и бесконечности float из конечного double. double NaN преобразуется в float NaN, а double бесконечность преобразуется в бесконечность с тем же знаком float.
Несмотря на то, что может произойти переполнение, недополнение или другая потеря информации, сужающее примитивное преобразование никогда не приводит к исключению во время выполнения (§11.1.1).
Пример 5.1.3-1. Сужающее примитивное преобразование
class Test {
public static void main(String[] args) {
float fmin = Float.NEGATIVE_INFINITY;
float fmax = Float.POSITIVE_INFINITY;
System.out.println("long: " + (long)fmin +
".." + (long)fmax);
System.out.println("int: " + (int)fmin +
".." + (int)fmax);
System.out.println("short: " + (short)fmin +
".." + (short)fmax);
System.out.println("char: " + (int)(char)fmin +
".." + (int)(char)fmax);
System.out.println("byte: " + (byte)fmin +
".." + (byte)fmax);
}
}
Эта программа выводит:
long: -9223372036854775808..9223372036854775807 int: -2147483648..2147483647 short: 0..-1 char: 0..65535 byte: 0..-1
Результаты для char, int и long предсказуемы, они производят минимальные и максимальные представимые значения типа.
Результаты для byte и short теряют информацию о знаке и величине числовых значений, а также теряют точность. Результаты можно понять, рассмотрев младшие биты минимальных и максимальных int. Минимальное int в шестнадцатеричном формате равно 0x80000000, а максимальное int равно 0x7fffffff. Это объясняет результаты short, которые являются 16 младшими битами этих значений, а именно 0x0000 и 0xffff; это объясняет результаты char, которые также являются 16 младшими битами этих значений, а именно '\u0000' и '\uffff'; и это объясняет результаты byte, которые являются 8 младшими битами этих значений, а именно 0x00 и 0xff.
Пример 5.1.3-2. Сужающие примитивные преобразования, теряющие информацию
class Test {
public static void main(String[] args) {
// A narrowing of int to short loses high bits:
System.out.println("(short)0x12345678==0x" +
Integer.toHexString((short)0x12345678));
// An int value too big for byte changes sign and magnitude:
System.out.println("(byte)255==" + (byte)255);
// A float value too big to fit gives largest int value:
System.out.println("(int)1e20f==" + (int)1e20f);
// A NaN converted to int yields zero:
System.out.println("(int)NaN==" + (int)Float.NaN);
// A double value too large for float yields infinity:
System.out.println("(float)-1e100==" + (float)-1e100);
// A double value too small for float underflows to zero:
System.out.println("(float)1e-50==" + (float)1e-50);
}
}
Эта программа выводит:
(short)0x12345678==0x5678 (byte)255==-1 (int)1e20f==2147483647 (int)NaN==0 (float)-1e100==-Infinity (float)1e-50==0.0
Следующее преобразование объединяет расширяющие и сужающие примитивные преобразования:
-
byteвchar
Сначала byte преобразуется в int с помощью расширяющего примитивного преобразования (§5.1.2), а затем полученное int преобразуется в char с помощью сужающего примитивного преобразования (§5.1.3).
Расширяющее ссылочное преобразование существует от любого типа ссылки S к любому типу ссылки T, при условии, что S является подтипом T (§4.10).
Расширяющие ссылочные преобразования никогда не требуют специальных действий во время выполнения и поэтому никогда не генерируют исключение во время выполнения. Они заключаются просто в рассматривании ссылки как имеющей другой тип таким образом, который может быть доказан корректным во время компиляции.
Тип null не является типом ссылки (§4.1), и поэтому расширяющее ссылочное преобразование не существует от типа null к типу ссылки. Однако во многих контекстах преобразования явным образом разрешается преобразование типа null в тип ссылки.
Сужение ссылочной конверсии рассматривает выражения ссылочного типа S как выражения другого ссылочного типа T, где S не является подтипом T. Поддерживаемые пары типов определены в §5.1.6.1. В отличие от расширения ссылочной конверсии, типы не обязательно должны быть напрямую связаны. Однако существуют ограничения, которые запрещают преобразование между определенными парами типов, когда можно статически доказать, что ни одно значение не может быть одновременно и того, и другого типа.
Сужение ссылочной конверсии может потребовать проверки во время выполнения, чтобы убедиться, что значение типа S является допустимым значением типа T. Однако из-за отсутствия информации о параметризованном типе во время выполнения некоторые преобразования не могут быть полностью проверены во время выполнения; они отмечаются на этапе компиляции (§5.1.6.2). Для преобразований, которые могут быть полностью проверены во время выполнения, и для определенных преобразований, включающих информацию о параметризованном типе, но всё ещё частично проверяемых во время выполнения, выбрасывается ClassCastException, если проверка завершается неудачно (§5.1.6.3).
Ссылочное сужение существует от ссылочного типа S к ссылочному типу T, если все перечисленные ниже условия выполняются:
-
S не является подтипом T (§4.10)
-
Если существует параметризованный тип X, являющийся надтипом T, и параметризованный тип Y, являющийся надтипом S, такие, что стирания X и Y совпадают, то X и Y не могут быть доказаны как различные (§4.5).
В качестве примера, используя типы из пакета
java.util, сужения ссылочной конверсии не существует отArrayList<String>кArrayList<Object>, и наоборот, потому что типы аргументовStringиObjectмогут быть доказаны как различные. По той же причине сужение ссылочной конверсии не существует отArrayList<String>кList<Object>, и наоборот. Отклонение доказательно различных типов — простая статическая проверка, чтобы предотвратить «глупые» сужения ссылочной конверсии. -
Выполняется одно из следующих условий:
-
S — это классный или интерфейсный тип, T — классный или интерфейсный тип, и S обозначает класс или интерфейс, который не является непересекающимся с классом или интерфейсом, обозначаемым T. («Непересекающийся» определяется ниже.)
-
S — это классный тип
Objectили интерфейсный типjava.io.SerializableилиCloneable(единственные интерфейсы, реализуемые массивами (§10.8)), а T — это массивный тип. -
S — это массивный тип SC
[], то есть массив компонентов типа SC; T — это массивный тип TC[], то есть массив компонентов типа TC; и существует сужение ссылочной конверсии от SC к TC. -
S — это тип переменной, и сужение ссылочной конверсии существует от верхней границы S к T.
-
T — это тип переменной, и существует либо расширение ссылочной конверсии, либо сужение ссылочной конверсии от S к верхней границе T.
-
S — это пересекающийся тип S1
&...&Sn, и для всех i (1 ≤ i ≤ n), либо существует расширение ссылочной конверсии, либо сужение ссылочной конверсии от Si к T. -
T — это пересекающийся тип T1
&...&Tn, и для всех i (1 ≤ i ≤ n), либо существует расширение ссылочной конверсии, либо сужение ссылочной конверсии от S к Ti.
-
Класс или интерфейс непересекающийся с другим классом или интерфейсом, если статически можно определить, что у них нет общих экземпляров (кроме значения null). Правила непересекаемости следующие:
-
Класс с именем C непересекающийся с интерфейсом с именем I, если (i) C не является подтипом I, и (ii) выполняется одно из следующих условий:
-
C является
final. -
C является
sealed, и все разрешенные непосредственные подклассы C непересекающиеся с I. -
C свободно расширяемый (§8.1.1.2), I является
sealed, и C непересекающийся со всеми разрешёнными непосредственными подклассами и подинтерфейсами I.
-
-
Интерфейс с именем I непересекающийся с классом с именем C, если C непересекающийся с I.
-
Класс с именем C непересекающийся с другим классом с именем D, если (i) C не является подтипом D, и (ii) D не является подтипом C.
-
Интерфейс с именем I непересекающийся с другим интерфейсом с именем J, если (i) I не является подтипом J, (ii) J не является подтипом I, и (iii) выполняется одно из следующих условий:
-
I является
sealed, и все разрешённые непосредственные подклассы и подинтерфейсы I непересекающиеся с J. -
J является
sealed, и I непересекающийся со всеми разрешёнными непосредственными подклассами и подинтерфейсами J.
-
Факт, является ли класс final, имеет наибольшее влияние на то, является ли класс непересекающимся с интерфейсами. Рассмотрим следующие объявления:
interface I {}
final class C {}
Так как класс C является final и не реализует I, не может быть экземпляров C, которые также являются экземплярами I, поэтому C и I непересекающиеся. Следовательно, нет сужения ссылочной конверсии от C к I.
В противовес этому, рассмотрим следующие объявления:
interface J {}
class D {}
Даже если класс D не реализует J, всё ещё возможно, что экземпляр D может быть экземпляром J, например, если происходит следующее объявление:
class E extends D implements J {}
По этой причине D не непересекающийся с J, и существует сужение ссылочной конверсии от D к J.
Последнее условие выше подразумевает, что два свободно расширяемых интерфейса (§9.1.1.4) не непересекающиеся.
Преобразование сужающей ссылки может быть проверяемым или непроверяемым. Эти термины относятся к возможности виртуальной машины Java проверить или не проверить корректность типа преобразования.
Если преобразование сужающей ссылки непроверяемое, виртуальная машина Java не сможет полностью проверить корректность типа, что может привести к загрязнению кучи (§4.12.2). Чтобы сообщить об этом программисту, непроверяемое сужающее преобразование ссылок вызывает предупреждение о компиляции unchecked warning, если оно не подавлено с помощью @SuppressWarnings (§9.6.4.5). И наоборот, если преобразование сужающей ссылки не непроверяемое, то оно проверяемое; виртуальная машина Java сможет полностью проверить корректность типа, поэтому предупреждение во время компиляции не выдаётся.
Непроверяемые сужающие преобразования ссылок следующие:
-
Преобразование сужающей ссылки от типа S к параметризованному классу или интерфейсу типа T является непроверяемым, если не выполняется хотя бы одно из следующих условий:
-
Все аргументы типа T являются неограниченными универсальными типами.
-
T
<:S, и S не имеет подтипа X кроме T, где аргументы типа X не содержатся в аргументах типа T.
-
-
Преобразование сужающей ссылки от типа S к переменной типа T является непроверяемым.
-
Преобразование сужающей ссылки от типа S к пересекающемуся типу T1
&...&Tn является непроверяемым, если существует Ti (1 ≤ i ≤ n), такой что S не является подтипом Ti, а преобразование сужающей ссылки от S к Ti является непроверяемым.
Все проверяемые сужающие ссылочные преобразования требуют проверки допустимости во время выполнения. В основном, эти преобразования относятся к типам классов и интерфейсов, которые не являются параметризованными.
Некоторые непроверяемые сужающие ссылочные преобразования требуют проверки допустимости во время выполнения. Это зависит от того, является ли непроверяемое сужающее ссылочное преобразование полностью непроверяемым или частично непроверяемым. Частично непроверяемое сужающее ссылочное преобразование требует проверки допустимости во время выполнения, в то время как полностью непроверяемое сужающее ссылочное преобразование — нет.
Эти термины относятся к совместимости типов, участвующих в преобразовании, при рассмотрении их как типов без параметров. Если преобразование концептуально является «расширением», то преобразование является полностью непроверяемым; проверка во время выполнения не требуется, потому что преобразование является допустимым в системе типов Java Virtual Machine без дженериков. И наоборот, если преобразование концептуально является «сужением», то преобразование является частично непроверяемым; даже в системе типов Java Virtual Machine без дженериков, требуется проверка во время выполнения для проверки совместимости (непараметризованных) типов, участвующих в преобразовании.
В качестве примера, используя типы из пакета java.util, преобразование из ArrayList<String> в Collection<T> является полностью непроверяемым, потому что (непараметризованный) тип ArrayList является подтипом (непараметризованного) типа Collection в Java Virtual Machine. И наоборот, преобразование из Collection<T> в ArrayList<String> является частично непроверяемым, потому что (непараметризованный) тип Collection не является подтипом (непараметризованного) типа ArrayList в Java Virtual Machine.
Категоризация непроверяемого сужающего ссылочного преобразования выглядит следующим образом:
-
Непроверяемое сужающее ссылочное преобразование от S к типу T без пересечений является полностью непроверяемым, если |S|
<:|T|.В противном случае оно является частично непроверяемым.
-
Непроверяемое сужающее ссылочное преобразование от S к пересекающемуся типу T1
&...&Tn является полностью непроверяемым, если для всех i (1 ≤ i ≤ n), либо S<:Ti, либо сужающее ссылочное преобразование от S к Ti является полностью непроверяемым.В противном случае оно является частично непроверяемым.
Проверка допустимости во время выполнения для проверяемого или частично непроверяемого сужающего ссылочного преобразования выполняется следующим образом:
-
Если значение во время выполнения равно
null, то преобразование разрешено. -
В противном случае, пусть R — класс объекта, на который ссылается значение, и пусть T — стёртый тип (§4.6), в который производится преобразование. Тогда:
-
Если R — обычный класс (не класс массива):
-
Если T — тип класса, то R должен быть либо тем же классом, что и T (§4.3.4), либо подклассом T, иначе выбрасывается
ClassCastException. -
Если T — тип интерфейса, то R должен реализовывать интерфейс T (§8.1.5), иначе выбрасывается
ClassCastException. -
Если T — тип массива, то выбрасывается
ClassCastException.
-
-
Если R — интерфейс:
Обратите внимание, что R не может быть интерфейсом, когда эти правила применяются впервые для любого данного преобразования, но R может быть интерфейсом, если правила применяются рекурсивно, потому что значение ссылки во время выполнения может ссылаться на массив, тип элементов которого является типом интерфейса.
-
Если T — тип класса, то T должен быть
Object(§4.3.2), иначе выбрасываетсяClassCastException. -
Если T — тип интерфейса, то R должен быть либо тем же интерфейсом, что и T, либо подинтерфейсом T, иначе выбрасывается
ClassCastException. -
Если T — тип массива, то выбрасывается
ClassCastException.
-
-
Если R — класс, представляющий тип массива RC
[], то есть массив компонентов типа RC:-
Если T — тип класса, то T должен быть
Object(§4.3.2), иначе выбрасываетсяClassCastException. -
Если T — тип интерфейса, то T должен быть типом
java.io.SerializableилиCloneable(единственными интерфейсами, реализуемыми массивами), иначе выбрасываетсяClassCastException. -
Если T — тип массива TC
[], то есть массив компонентов типа TC, то выбрасываетсяClassCastException, если TC и RC не являются одним и тем же примитивным типом, или если TC и RC являются ссылочными типами и разрешены рекурсивным применением этих правил во время выполнения.
-
-
Если преобразование производится к пересекающемуся типу T1 & ... & Tn, то для всех i (1 ≤ i ≤ n), любая проверка во время выполнения, необходимая для преобразования от S к Ti, также необходима для преобразования к пересекающемуся типу.
Преобразование упаковки (boxing) рассматривает выражения примитивного типа как выражения соответствующего ссылочного типа. В частности, следующие девять преобразований называются преобразованиями упаковки:
-
Из типа
booleanв типBoolean -
Из типа
byteв типByte -
Из типа
shortв типShort -
Из типа
charв типCharacter -
Из типа
intв типInteger -
Из типа
longв типLong -
Из типа
floatв типFloat -
Из типа
doubleв типDouble -
Из типа null в тип null
Это правило необходимо, потому что условный оператор (§15.25) применяет преобразование упаковки к типам своих операндов и использует результат в дальнейших вычислениях.
Во время выполнения преобразование упаковки происходит следующим образом:
-
Если
pимеет типboolean, то преобразование упаковки преобразуетpв ссылкуrкласса и типаBoolean, таким образом, чтоr.booleanValue() ==p -
Если
pимеет типbyte, то преобразование упаковки преобразуетpв ссылкуrкласса и типаByte, таким образом, чтоr.byteValue() ==p -
Если
pимеет типchar, то преобразование упаковки преобразуетpв ссылкуrкласса и типаCharacter, таким образом, чтоr.charValue() ==p -
Если
pимеет типshort, то преобразование упаковки преобразуетpв ссылкуrкласса и типаShort, таким образом, чтоr.shortValue() ==p -
Если
pимеет типint, то преобразование упаковки преобразуетpв ссылкуrкласса и типаInteger, таким образом, чтоr.intValue() ==p -
Если
pимеет типlong, то преобразование упаковки преобразуетpв ссылкуrкласса и типаLong, таким образом, чтоr.longValue() ==p -
Если
pимеет типfloat, то:-
Если
pне NaN, то преобразование упаковки преобразуетpв ссылкуrкласса и типаFloat, таким образом, чторавноr.floatValue()p -
В противном случае, преобразование упаковки преобразует
pв ссылкуrкласса и типаFloat, таким образом, чторавноr.isNaN()true
-
-
Если
pимеет типdouble, то:-
Если
pне NaN, преобразование упаковки преобразуетpв ссылкуrкласса и типаDouble, таким образом, чторавноr.doubleValue()p -
В противном случае, преобразование упаковки преобразует
pв ссылкуrкласса и типаDouble, таким образом, чторавноr.isNaN()true
-
-
Если
pимеет любой другой тип, то преобразование упаковки эквивалентно преобразованию тождества (§5.1.1).
Если значение p, которое упаковывается, является результатом вычисления константного выражения (§15.29) типа boolean, byte, char, short, int или long, и результатом является true, false, символ в диапазоне '\u0000' по '\u007f' включительно, или целое число в диапазоне -128 по 127 включительно, то пусть a и b будут результатами двух преобразований упаковки p. Всегда верно, что a == b.
В идеале, упаковка примитивного значения всегда должна возвращать идентичную ссылку. На практике это может быть невозможным с использованием существующих методов реализации. Вышеприведенное правило является прагматическим компромиссом, требующим, чтобы определенные общие значения всегда упаковывались в неразличимые объекты. Реализация может кэшировать их, лениво или активно. Для других значений правило запрещает какие-либо предположения о тождестве упакованных значений со стороны программиста. Это позволяет (но не требует) совместного использования некоторых или всех этих ссылок.
Это гарантирует, что в большинстве распространённых случаев поведение будет желательным, не налагая чрезмерных штрафов на производительность, особенно на небольших устройствах. Реализации с меньшими ограничениями на память могут, например, кэшировать все char и short значения, а также int и long значения в диапазоне от -32K до +32K.
Преобразование упаковки может привести к исключению OutOfMemoryError, если необходимо создать новый экземпляр одного из классов-обёрток (Boolean, Byte, Character, Short, Integer, Long, Float или Double), и памяти недостаточно.
Преобразование распаковки рассматривает выражения ссылочного типа как выражения соответствующего примитивного типа. В частности, следующие восемь преобразований называются преобразованиями распаковки:
-
Из типа
Booleanв типboolean -
Из типа
Byteв типbyte -
Из типа
Shortв типshort -
Из типа
Characterв типchar -
Из типа
Integerв типint -
Из типа
Longв типlong -
Из типа
Floatв типfloat -
Из типа
Doubleв типdouble
Во время выполнения преобразование распаковки выполняется следующим образом:
-
Если
rявляется ссылкой типаBoolean, то преобразование распаковки преобразуетrвr.booleanValue() -
Если
rявляется ссылкой типаByte, то преобразование распаковки преобразуетrвr.byteValue() -
Если
rявляется ссылкой типаCharacter, то преобразование распаковки преобразуетrвr.charValue() -
Если
rявляется ссылкой типаShort, то преобразование распаковки преобразуетrвr.shortValue() -
Если
rявляется ссылкой типаInteger, то преобразование распаковки преобразуетrвr.intValue() -
Если
rявляется ссылкой типаLong, то преобразование распаковки преобразуетrвr.longValue() -
Если
rявляется ссылкой типаFloat, преобразование распаковки преобразуетrвr.floatValue() -
Если
rявляется ссылкой типаDouble, то преобразование распаковки преобразуетrвr.doubleValue() -
Если
rявляетсяnull, преобразование распаковки генерирует исключениеNullPointerException
Говорят, что тип является преобразуемым в числовой тип, если он является числовым типом (§4.2), или это ссылочный тип, который может быть преобразован в числовой тип путем распаковки.
Говорят, что тип является преобразуемым в целочисленный тип, если он является целочисленным типом, или это ссылочный тип, который может быть преобразован в целочисленный тип путем распаковки.
Пусть G обозначает объявление обобщённого типа с n параметрами типа.
Существует преобразование без проверки от исходного класса или интерфейсного типа (§4.8) G к любому параметризованному типу вида G<T1,...,Tn>.
Существует преобразование без проверки от исходного массива типа G[]k к любому массивному типу вида G<T1,...,Tn>[]k. (Обозначение []k указывает тип массива с k измерениями.)
Использование преобразования без проверки вызывает предупреждение о компиляции предупреждение без проверки, если все аргументы типа Ti (1 ≤ i ≤ n) являются неограниченными универсальными подстановками (§4.5.1), или предупреждение подавляется с помощью @SuppressWarnings (§9.6.4.5).
Преобразование без проверки используется для обеспечения плавного взаимодействия устаревшего кода, написанного до появления обобщенных типов, с библиотеками, которые были преобразованы для использования обобщенности (процесс, который мы называем обобщением). В таких ситуациях (прежде всего, клиенты Collections Framework в java.util), устаревший код использует сырые типы (например, Collection вместо Collection<String>). Выражения сырых типов передаются в качестве аргументов методам библиотеки, которые используют параметризованные версии тех же типов как типы соответствующих формальных параметров.
Такие вызовы не могут быть статически безопасными в системе типов с использованием обобщений. Отклонение таких вызовов привело бы к недействительности больших объемов существующего кода и помешало бы им использовать более новые версии библиотек. В свою очередь, это бы отбило у поставщиков библиотек желание использовать обобщенность. Для предотвращения такого нежелательного поворота событий, сырой тип может быть преобразован в произвольное вызывание объявления обобщенного типа, к которому относится сырой тип. Хотя преобразование некорректно, оно допускается в качестве уступки практичности. В таких случаях выдается предупреждение о преобразовании без проверки.
Пусть G обозначает объявление универсального типа (§8.1.2, §9.1.2) с n параметрами типа A1,...,An с соответствующими ограничениями U1,...,Un.
Существует преобразование захвата от параметризованного типа G<T1,...,Tn> (§4.5) к параметризованному типу G<S1,...,Sn>, где для 1 ≤ i ≤ n :
-
Если Ti является аргументом типа-дикого символа (§4.5.1) в виде
?, то Si является свежей переменной типа, верхняя граница которой — Ui[A1:=S1,...,An:=Sn], а нижняя граница — нулевой тип (§4.1). -
Если Ti является аргументом типа-дикого символа в форме
?extendsBi, то Si является свежей переменной типа, верхняя граница которой — glb(Bi, Ui[A1:=S1,...,An:=Sn]), а нижняя граница — нулевой тип.glb(V1,...,Vm) определяется как V1
&...&Vm.Если для любых двух классов (не интерфейсов) Vi и Vj Vi не является подклассом Vj или наоборот, это ошибка времени компиляции.
-
Если Ti является аргументом типа-дикого символа в форме
?superBi, то Si является свежей переменной типа, верхняя граница которой — Ui[A1:=S1,...,An:=Sn], а нижняя граница — Bi. -
В противном случае, Si = Ti.
Преобразование захвата для любого типа, кроме параметризованного типа (§4.5), действует как преобразование тождества (§5.1.1).
Преобразование захвата не применяется рекурсивно.
Преобразование захвата никогда не требует специального действия во время выполнения и поэтому никогда не генерирует исключение во время выполнения.
Преобразование захвата разработано для повышения полезности диких символов. Чтобы понять мотивацию, начнём с рассмотрения метода java.util.Collections.reverse():
public static void reverse(List<?> list);
Метод переворачивает список, переданный в качестве параметра. Он работает для любого типа списка, поэтому использование типа дикого символа List<?> в качестве типа формального параметра вполне уместно.
Теперь рассмотрим, как можно реализовать reverse():
public static void reverse(List<?> list) { rev(list); }
private static <T> void rev(List<T> list) {
List<T> tmp = new ArrayList<T>(list);
for (int i = 0; i < list.size(); i++) {
list.set(i, tmp.get(list.size() - i - 1));
}
}
Для реализации необходимо скопировать список, извлечь элементы из копии и вставить их в исходный список. Для того, чтобы сделать это безопасным с точки зрения типа, нам нужно дать имя, T, типу элемента входного списка. Мы делаем это в частном методе обслуживания rev(). Это требует передачи входного аргумента списка, типа List<?>, в качестве аргумента rev(). В общем случае List<?> является списком неизвестного типа. Он не является подтипом List<T> для любого типа T. Разрешение такого отношения подтипов было бы некорректным. Учитывая метод:
public static <T> void fill(List<T> l, T obj)
следующий код подорвал бы систему типов:
List<String> ls = new ArrayList<String>();
List<?> l = ls;
Collections.fill(l, new Object()); // not legal - but assume it was!
String s = ls.get(0); // ClassCastException - ls contains
// Objects, not Strings.
Таким образом, без каких-либо специальных послаблений, мы видим, что вызов из reverse() в rev() был бы запрещён. Если бы это было так, автору reverse() пришлось бы написать его сигнатуру как:
public static <T> void reverse(List<T> list)
Это нежелательно, так как раскрывает информацию о реализации вызывающему коду. Хуже того, разработчик API мог бы предположить, что сигнатура с использованием дикого символа необходима вызывающим сторонам API и только потом понять, что реализация типа была недоступна.
Вызов из reverse() в rev() на самом деле безобиден, но не может быть обоснован на основе общего отношения подтипов между List<?> и List<T>. Вызов безопасен, потому что входной аргумент, несомненно, представляет собой список определённого типа (хотя и неизвестного). Если мы можем захватить этот неизвестный тип в переменной типа X, мы можем вывести T как X. В этом суть преобразования захвата. Конечно, спецификация должна справляться со сложностями, такими как нетривиальные (и, возможно, рекурсивно определённые) верхние или нижние границы, наличие нескольких аргументов и т. д.
Математически подкованные читатели захотят связать преобразование захвата с устоявшимися понятиями теории типов. Читатели, незнакомые с теорией типов, могут пропустить это обсуждение — или же изучить соответствующий текст, например, Типы и языки программирования Бенджамина Пёрса, а затем вернуться к этому разделу.
Вот краткое изложение взаимосвязи преобразования захвата с устоявшимися понятиями теории типов. Типы диких символов являются ограниченной формой экзистенциальных типов. Преобразование захвата соответствует примерно открытию значения экзистенциального типа. Преобразование захвата выражения e можно рассматривать как раскрытие open e в области, которая включает в себя выражение верхнего уровня, которое включает в себя e.
Классическая операция open на экзистенциалах требует, чтобы захваченная переменная типа не покидала открытое выражение. Преобразование захвата, которое соответствует преобразованию захвата, всегда находится в достаточно большой области, чтобы захваченная переменная типа никогда не была видна вне этой области. Преимущество этой схемы заключается в том, что нет необходимости в операции close, как определено в статье О варьируемости, основанной на подтипах, для параметризованных типов Ацуси Игараси и Мирко Вироли, в материалах 16-й европейской конференции по объектно-ориентированному программированию (ECOOP 2002). Подробное описание диких символов см. в Wild FJ Мадса Торгертсена, Эрика Эрнста и Кристиана Плезнера Хансена в 12-й рабочей группе по основам объектно-ориентированного программирования (FOOL 2005).
Любой тип может быть преобразован в тип String с помощью преобразования в строку.
Значение x примитивного типа T сначала преобразуется в значение ссылки, как если бы оно было аргументом соответствующего выражения создания экземпляра класса (§15.9):
-
Если T —
boolean, используйтеnew Boolean(.x) -
Если T —
char, используйтеnew Character(.x) -
Если T —
byte,shortилиint, используйтеnew Integer(.x) -
Если T —
long, используйтеnew Long(.x) -
Если T —
float, используйтеnew Float(.x) -
Если T —
double, используйтеnew Double(.x)
Это значение ссылки затем преобразуется в тип String с помощью преобразования в строку.
Теперь необходимо рассмотреть только значения ссылок:
-
Если ссылка —
null, она преобразуется в строку "null" (четыре символа ASCIIn,u,l,l). -
В противном случае преобразование выполняется как вызов метода
toStringсохранённого объекта без аргументов; но если результат вызова методаtoString—null, то вместо этого используется строка "null".Метод
toStringопределён первоначальным классомObject(§4.3.2). Многие классы переопределяют его, в частности,Boolean,Character,Integer,Long,Float,DoubleиString.
Контексты присваивания позволяют присвоить значение выражения (§15.26) переменной; тип выражения должен быть преобразован к типу переменной.
В контекстах присваивания разрешено использовать одно из следующего:
-
тождественное преобразование (§5.1.1)
-
расширяющее преобразование примитивного типа (§5.1.2)
-
расширяющее преобразование ссылочного типа (§5.1.5)
-
расширяющее преобразование ссылочного типа, за которым следует преобразование распаковки
-
расширяющее преобразование ссылочного типа, за которым следует преобразование распаковки, а затем расширяющее преобразование примитивного типа
-
преобразование упаковки (§5.1.7)
-
преобразование упаковки, за которым следует расширяющее преобразование ссылочного типа
-
преобразование распаковки (§5.1.8)
-
преобразование распаковки, за которым следует расширяющее преобразование примитивного типа
Если после применения перечисленных выше преобразований результирующий тип является сырым типом (§4.8), то затем может быть применено неявное преобразование (§5.1.9).
Кроме того, если выражение является константным выражением (§15.29) типа byte, short, char или int:
-
Может быть использовано сужающее преобразование примитивного типа, если переменная имеет тип
byte,shortилиchar, а значение константного выражения представимо в типе переменной. -
Может быть использовано сужающее преобразование примитивного типа, за которым следует преобразование упаковки, если переменная имеет тип
Byte,ShortилиCharacter, а значение константного выражения представимо в типеbyte,shortилиcharсоответственно.
Компиляционное сужение константных выражений означает, что код, такой как:
byte theAnswer = 42;
разрешен. Без сужения тот факт, что целочисленная литерал 42 имеет тип int, означал бы, что требуется приведение к типу byte:
byte theAnswer = (byte)42; // cast is permitted but not required
Наконец, значение типа null (единственное такое значение – null-ссылка) может быть присвоено любому ссылочному типу, результатом чего станет null-ссылка соответствующего типа.
Ошибка компиляции, если цепочка преобразований содержит два параметризованных типа, не находящихся в подтиповом отношении (§4.10).
Пример такой недопустимой цепочки:
Integer, Comparable<Integer>, Comparable, Comparable<String>
Первые три элемента цепочки связаны расширяющим преобразованием ссылочного типа, в то время как последний элемент получен из предыдущего с помощью неявного преобразования. Однако это некорректное преобразование присваивания, поскольку цепочка содержит два параметризованных типа, Comparable<Integer> и Comparable<String>, которые не являются подтипами.
Если тип выражения может быть преобразован в тип переменной при помощи преобразования присваивания, то мы говорим, что выражение (или его значение) совместимо по присваиванию с переменной или, что тип выражения совместим с типом переменной по присваиванию.
Единственные исключения, которые могут возникнуть при преобразованиях в контексте присваивания:
-
Ошибка
ClassCastException, если после применения вышеуказанных преобразований результирующее значение является объектом, который не является экземпляром подкласса или подинтерфейса стёртого типа (§4.6) типа переменной.Эта ситуация может возникнуть только в результате загрязнения кучи (§4.12.2). На практике реализации требуется выполнять приведение типов только при доступе к полю или методу объекта параметризованного типа, когда стёртый тип поля или стёртый возвращаемый тип метода отличаются от нестёртого типа.
-
Ошибка
OutOfMemoryErrorв результате преобразования упаковки. -
Ошибка
NullPointerExceptionв результате преобразования распаковки по null-ссылке. -
Ошибка
ArrayStoreExceptionв особых случаях, связанных с элементами массива или доступом к полям (§10.5, §15.26.1).
Пример 5.2-1. Присваивание для примитивных типов
class Test {
public static void main(String[] args) {
short s = 12; // narrow 12 to short
float f = s; // widen short to float
System.out.println("f=" + f);
char c = '\u0123';
long l = c; // widen char to long
System.out.println("l=0x" + Long.toString(l,16));
f = 1.23f;
double d = f; // widen float to double
System.out.println("d=" + d);
}
}
Эта программа выводит:
f=12.0 l=0x123 d=1.2300000190734863
Следующая программа, однако, вызывает ошибки компиляции:
class Test {
public static void main(String[] args) {
short s = 123;
char c = s; // error: would require cast
s = c; // error: would require cast
}
}
поскольку не все значения short являются значениями типа char, и не все значения char являются значениями типа short.
Пример 5.2-2. Присваивание для ссылочных типов
class Point { int x, y; }
class Point3D extends Point { int z; }
interface Colorable { void setColor(int color); }
class ColoredPoint extends Point implements Colorable {
int color;
public void setColor(int color) { this.color = color; }
}
class Test {
public static void main(String[] args) {
// Assignments to variables of class type:
Point p = new Point();
p = new Point3D();
// OK because Point3D is a subclass of Point
Point3D p3d = p;
// Error: will require a cast because a Point
// might not be a Point3D (even though it is,
// dynamically, in this example.)
// Assignments to variables of type Object:
Object o = p; // OK: any object to Object
int[] a = new int[3];
Object o2 = a; // OK: an array to Object
// Assignments to variables of interface type:
ColoredPoint cp = new ColoredPoint();
Colorable c = cp;
// OK: ColoredPoint implements Colorable
// Assignments to variables of array type:
byte[] b = new byte[4];
a = b;
// Error: these are not arrays of the same primitive type
Point3D[] p3da = new Point3D[3];
Point[] pa = p3da;
// OK: since we can assign a Point3D to a Point
p3da = pa;
// Error: (cast needed) since a Point
// can't be assigned to a Point3D
}
}
Следующая тестовая программа иллюстрирует преобразования присваивания для ссылочных значений, но не компилируется, как описано в комментариях. Этот пример следует сравнить с предыдущим.
class Point { int x, y; }
interface Colorable { void setColor(int color); }
class ColoredPoint extends Point implements Colorable {
int color;
public void setColor(int color) { this.color = color; }
}
class Test {
public static void main(String[] args) {
Point p = new Point();
ColoredPoint cp = new ColoredPoint();
// Okay because ColoredPoint is a subclass of Point:
p = cp;
// Okay because ColoredPoint implements Colorable:
Colorable c = cp;
// The following cause compile-time errors because
// we cannot be sure they will succeed, depending on
// the run-time type of p; a run-time check will be
// necessary for the needed narrowing conversion and
// must be indicated by including a cast:
cp = p; // p might be neither a ColoredPoint
// nor a subclass of ColoredPoint
c = p; // p might not implement Colorable
}
}
Пример 5.2-3. Присваивание для типов массивов
class Point { int x, y; }
class ColoredPoint extends Point { int color; }
class Test {
public static void main(String[] args) {
long[] veclong = new long[100];
Object o = veclong; // okay
Long l = veclong; // compile-time error
short[] vecshort = veclong; // compile-time error
Point[] pvec = new Point[100];
ColoredPoint[] cpvec = new ColoredPoint[100];
pvec = cpvec; // okay
pvec[0] = new Point(); // okay at compile time,
// but would throw an
// exception at run time
cpvec = pvec; // compile-time error
}
}
В этом примере:
-
Значение
veclongне может быть присвоено переменной типаLong, посколькуLong— это класс, отличный отObject. Массив может быть присвоен только переменной совместимого типа массива, или переменной типаObject,Cloneableилиjava.io.Serializable. -
Значение
veclongне может быть присвоеноvecshort, так как это массивы примитивных типов, аshortиlong— не один и тот же примитивный тип. -
Значение
cpvecможет быть присвоеноpvec, так как любая ссылка, которая может быть значением выражения типаColoredPoint, может быть значением переменной типаPoint. Последующее присваивание новогоPointэлементу массиваpvecвызовет исключениеArrayStoreException(если программа в остальном была бы исправлена, чтобы её можно было скомпилировать), так как массив типаColoredPointне может иметь экземплярPointв качестве элемента. -
Значение
pvecне может быть присвоеноcpvec, так как не каждая ссылка, которая может быть значением выражения типаPoint, может быть корректно значением переменной типаColoredPoint. Если значениеpvecв момент выполнения будет ссылкой на экземпляр классаPoint[], и присваиваниеcpvecбудет разрешено, простая ссылка на компонентcpvec, скажем,cpvec[0], может вернутьPoint, аPointне являетсяColoredPoint. Таким образом, разрешение такого присваивания позволит нарушить систему типов. Приведение (§5.5, §15.16) может быть использовано для обеспечения того, чтоpvecссылается наColoredPoint[]:cpvec = (ColoredPoint[])pvec; // OK, but may throw an // exception at run time
Контексты вызова позволяют назначить значение аргумента в вызове метода или конструктора (§8.8.7.1, §15.9, §15.12) соответствующему формальному параметру.
Строгие контексты вызова позволяют использовать одно из следующего:
Слабые контексты вызова допускают более широкие преобразования, поскольку они используются только для конкретного вызова, если не найдено соответствующее объявление, используя строгие контексты вызова. Слабые контексты вызова позволяют использовать одно из следующего:
-
преобразование тождества (§5.1.1)
-
расширяющее преобразование примитивных типов (§5.1.2)
-
расширяющее преобразование ссылочных типов (§5.1.5)
-
расширяющее преобразование ссылочного типа, за которым следует преобразование распаковки
-
расширяющее преобразование ссылочного типа, за которым следует преобразование распаковки, а затем расширяющее преобразование примитивного типа
-
преобразование упаковки (§5.1.7)
-
преобразование упаковки, за которым следует расширяющее преобразование ссылочного типа
-
преобразование распаковки (§5.1.8)
-
преобразование распаковки, за которым следует расширяющее преобразование примитивного типа
Если после применения преобразований, перечисленных для контекста вызова, полученный тип является необработанным типом (§4.8), может быть применено преобразование без проверки (§5.1.9).
Значение типа null (единственное такое значение — ссылка null) может быть присвоено любому ссылочному типу.
Ошибка времени компиляции, если цепочка преобразований содержит два параметризованных типа, которые не находятся в отношении подтипа (§4.10).
Единственные исключения, которые могут возникнуть в контексте вызова:
-
ClassCastException, если после применения вышеуказанных преобразований типов полученное значение является объектом, который не является экземпляром подкласса или подинтерфейса стирания (§4.6) соответствующего типа формального параметра. -
OutOfMemoryErrorв результате преобразования упаковки. -
NullPointerExceptionв результате преобразования распаковки по ссылке null.
Ни строгие, ни слабые контексты вызова не включают неявное сужение целых константных выражений, которое допускается в контекстах присваивания. Разработчики языка программирования Java посчитали, что включение этих неявных сужающих преобразований добавит дополнительной сложности правилам разрешения перегрузки (§15.12.2).
Таким образом, программа:
class Test {
static int m(byte a, int b) { return a+b; }
static int m(short a, short b) { return a-b; }
public static void main(String[] args) {
System.out.println(m(12, 2)); // compile-time error
}
}
вызывает ошибку времени компиляции, потому что целочисленные литералы 12 и 2 имеют тип int, поэтому ни один метод m не соответствует правилам разрешения перегрузки. Язык, который включал неявное сужение целочисленных константных выражений, потребовал бы дополнительных правил для разрешения случаев, подобных этому примеру.
Контексты строк применяются только к операнду бинарного оператора +, который не является String, когда другой операнд является String.
Целевой тип в этих контекстах всегда String, и преобразование строки (§5.1.11) не-String операнда всегда происходит. Вычисление оператора + затем продолжается, как указано в §15.18.1.
Контексты приведения типов позволяют преобразовать операнд выражения приведения типов (§15.16) к типу, явно указанному оператором приведения. По сравнению с контекстами присваивания и вызова, контексты приведения типов позволяют использовать больше преобразований, определённых в §5.1, и позволяют использовать больше комбинаций этих преобразований.
Если выражение является примитивного типа, то контекст приведения типов позволяет использовать одно из следующих:
-
тождественное преобразование (§5.1.1)
-
расширяющее преобразование примитивных типов (§5.1.2)
-
сужающее преобразование примитивных типов (§5.1.3)
-
расширяющее и сужающее преобразование примитивных типов (§5.1.4)
-
преобразование в обёртку (§5.1.7)
-
преобразование в обёртку, за которым следует расширяющее преобразование ссылочных типов (§5.1.5)
Если выражение является ссылочного типа, то контекст приведения типов позволяет использовать одно из следующих:
-
тождественное преобразование (§5.1.1)
-
расширяющее преобразование ссылочных типов (§5.1.5)
-
расширяющее преобразование ссылочных типов, за которым следует преобразование из обёртки
-
расширяющее преобразование ссылочных типов, за которым следует преобразование из обёртки, а затем расширяющее преобразование примитивных типов
-
сужающее преобразование ссылочных типов (§5.1.6)
-
сужающее преобразование ссылочных типов, за которым следует преобразование из обёртки
-
преобразование из обёртки (§5.1.8)
-
преобразование из обёртки, за которым следует расширяющее преобразование примитивных типов
Если выражение имеет тип null, то выражение может быть приведено к любому ссылочному типу.
Если контекст приведения типов использует сужающее преобразование ссылочных типов, которое проверяется или частично не проверяется (§5.1.6.2, §5.1.6.3), то будет произведена проверка класса значения выражения во время выполнения, что может привести к ClassCastException. В противном случае проверка во время выполнения не выполняется.
Если выражение может быть преобразовано к ссылочному типу с помощью преобразования приведения (кроме сужающего преобразования ссылочных типов, которое не проверяется), то мы говорим, что выражение (или его значение) совместимо с проверяемым приведением к ссылочному типу.
Если выражение ссылочного типа S совместимо с проверяемым приведением к другому ссылочному типу T, то мы говорим, что тип S преобразуется к типу T с проверяемым приведением.
В следующих таблицах перечислены используемые преобразования в определённых контекстах приведения. Каждое преобразование обозначено символом:
-
- обозначает, что преобразование недопустимо
-
≈ обозначает тождественное преобразование (§5.1.1)
-
ω обозначает расширяющее преобразование примитивных типов (§5.1.2)
-
η обозначает сужающее преобразование примитивных типов (§5.1.3)
-
ωη обозначает расширяющее и сужающее преобразование примитивных типов (§5.1.4)
-
⇑ обозначает расширяющее преобразование ссылочных типов (§5.1.5)
-
⇓ обозначает сужающее преобразование ссылочных типов (§5.1.6)
-
⊕ обозначает преобразование в обёртку (§5.1.7)
-
⊗ обозначает преобразование из обёртки (§5.1.8)
В таблицах запятая между символами означает, что контекст приведения типов использует одно преобразование, за которым следует другое. Тип Object означает любой ссылочный тип, кроме восьми классов-обёрток Boolean, Byte, Short, Character, Integer, Long, Float, Double.
Таблица 5.5-A. Приведение к примитивным типам
| К → | byte | short | char | int | long | float | double | boolean |
|---|---|---|---|---|---|---|---|---|
| От ↓ | ||||||||
byte | ≈ | ω | ωη | ω | ω | ω | ω | - |
short | η | ≈ | η | ω | ω | ω | ω | - |
char | η | η | ≈ | ω | ω | ω | ω | - |
int | η | η | η | ≈ | ω | ω | ω | - |
long | η | η | η | η | ≈ | ω | ω | - |
float | η | η | η | η | η | ≈ | ω | - |
double | η | η | η | η | η | η | ≈ | - |
boolean | - | - | - | - | - | - | - | ≈ |
Byte | ⊗ | ⊗,ω | - | ⊗,ω | ⊗,ω | ⊗,ω | ⊗,ω | - |
Short | - | ⊗ | - | ⊗,ω | ⊗,ω | ⊗,ω | ⊗,ω | - |
Character | - | - | ⊗ | ⊗,ω | ⊗,ω | ⊗,ω | ⊗,ω | - |
Integer | - | - | - | ⊗ | ⊗,ω | ⊗,ω | ⊗,ω | - |
Long | - | - | - | - | ⊗ | ⊗,ω | ⊗,ω | - |
Float | - | - | - | - | - | ⊗ | ⊗,ω | - |
Double | - | - | - | - | - | - | ⊗ | - |
Boolean | - | - | - | - | - | - | - | ⊗ |
Object | ⇓,⊗ | ⇓,⊗ | ⇓,⊗ | ⇓,⊗ | ⇓,⊗ | ⇓,⊗ | ⇓,⊗ | ⇓,⊗ |
Таблица 5.5-B. Приведение к ссылкам на типы
| К → | Byte | Short | Character | Integer | Long | Float | Double | Boolean | Object |
|---|---|---|---|---|---|---|---|---|---|
| От ↓ | |||||||||
byte | ⊕ | - | - | - | - | - | - | - | ⊕,⇑ |
short | - | ⊕ | - | - | - | - | - | - | ⊕,⇑ |
char | - | - | ⊕ | - | - | - | - | - | ⊕,⇑ |
int | - | - | - | ⊕ | - | - | - | - | ⊕,⇑ |
long | - | - | - | - | ⊕ | - | - | - | ⊕,⇑ |
float | - | - | - | - | - | ⊕ | - | - | ⊕,⇑ |
double | - | - | - | - | - | - | ⊕ | - | ⊕,⇑ |
boolean | - | - | - | - | - | - | - | ⊕ | ⊕,⇑ |
Byte | ≈ | - | - | - | - | - | - | - | ⇑ |
Short | - | ≈ | - | - | - | - | - | - | ⇑ |
Character | - | - | ≈ | - | - | - | - | - | ⇑ |
Integer | - | - | - | ≈ | - | - | - | - | ⇑ |
Long | - | - | - | - | ≈ | - | - | - | ⇑ |
Float | - | - | - | - | - | ≈ | - | - | ⇑ |
Double | - | - | - | - | - | - | ≈ | - | ⇑ |
Boolean | - | - | - | - | - | - | - | ≈ | ⇑ |
Object | ⇓ | ⇓ | ⇓ | ⇓ | ⇓ | ⇓ | ⇓ | ⇓ | ≈ |
Пример 5.5-1. Приведение для типов ссылок
class Point { int x, y; }
interface Colorable { void setColor(int color); }
class ColoredPoint extends Point implements Colorable {
int color;
public void setColor(int color) { this.color = color; }
}
final class EndPoint extends Point {}
class Test {
public static void main(String[] args) {
Point p = new Point();
ColoredPoint cp = new ColoredPoint();
Colorable c;
// The following may cause errors at run time because
// we cannot be sure they will succeed; this possibility
// is suggested by the casts:
cp = (ColoredPoint)p; // p might not reference an
// object which is a ColoredPoint
// or a subclass of ColoredPoint
c = (Colorable)p; // p might not be Colorable
// The following are incorrect at compile time because
// they can never succeed as explained in the text:
Long l = (Long)p; // compile-time error #1
EndPoint e = new EndPoint();
c = (Colorable)e; // compile-time error #2
}
}
Здесь первая ошибка компиляции возникает, потому что типы классов Long и Point не связаны (то есть они не одинаковы, и ни один не является подклассом другого), поэтому приведение между ними всегда завершится ошибкой.
Вторая ошибка компиляции возникает, потому что переменная типа EndPoint никогда не может ссылаться на значение, реализующее интерфейс Colorable. Это происходит потому, что EndPoint — это тип final, и переменная типа final всегда содержит значение того же времени выполнения, что и её тип компиляции. Поэтому тип времени выполнения переменной e должен точно соответствовать типу EndPoint, а тип EndPoint не реализует Colorable.
Пример 5.5-2. Приведение для типов массивов
class Point {
int x, y;
Point(int x, int y) { this.x = x; this.y = y; }
public String toString() { return "("+x+","+y+")"; }
}
interface Colorable { void setColor(int color); }
class ColoredPoint extends Point implements Colorable {
int color;
ColoredPoint(int x, int y, int color) {
super(x, y); setColor(color);
}
public void setColor(int color) { this.color = color; }
public String toString() {
return super.toString() + "@" + color;
}
}
class Test {
public static void main(String[] args) {
Point[] pa = new ColoredPoint[4];
pa[0] = new ColoredPoint(2, 2, 12);
pa[1] = new ColoredPoint(4, 5, 24);
ColoredPoint[] cpa = (ColoredPoint[])pa;
System.out.print("cpa: {");
for (int i = 0; i < cpa.length; i++)
System.out.print((i == 0 ? " " : ", ") + cpa[i]);
System.out.println(" }");
}
}
Эта программа компилируется без ошибок и выводит:
cpa: { (2,2)@12, (4,5)@24, null, null }
Пример 5.5-3. Приведение несовместимых типов во время выполнения
class Point { int x, y; }
interface Colorable { void setColor(int color); }
class ColoredPoint extends Point implements Colorable {
int color;
public void setColor(int color) { this.color = color; }
}
class Test {
public static void main(String[] args) {
Point[] pa = new Point[100];
// The following line will throw a ClassCastException:
ColoredPoint[] cpa = (ColoredPoint[])pa;
System.out.println(cpa[0]);
int[] shortvec = new int[2];
Object o = shortvec;
// The following line will throw a ClassCastException:
Colorable c = (Colorable)o;
c.setColor(0);
}
}
Эта программа использует приведения для компиляции, но при выполнении генерирует исключения, потому что типы несовместимы.
Числовые контексты применяются к операндам арифметических операторов, выражениям создания и доступа к массивам, условным выражениям и выражениям результатов switch выражений.
Выражение появляется в числовом арифметическом контексте, если выражение является одним из следующих:
-
Операнд унарного оператора плюс
+, унарного оператора минус-или поразрядного оператора дополнения~(§15.15.3, §15.15.4, §15.15.5) -
Операнд оператора умножения
*,/или%(§15.17) -
Операнд оператора сложения или вычитания для числовых типов
+или-(§15.18.2) -
Операнд оператора сдвига
<<,>>или>>>(§15.19). Операнды этих операторов сдвига обрабатываются отдельно, а не как группа.longвеличина сдвига (правый операнд) не повышает значение, подвергаемое сдвигу (левый операнд), к типуlong. -
Операнд оператора числового сравнения
<,<=,>или>=(§15.20.1) -
Операнд оператора числового равенства
==или!=(§15.21.1) -
Операнд поразрядного целочисленного оператора
&,^или|(§15.22.1)
Выражение появляется в числовом контексте массива, если выражение является одним из следующих:
Выражение появляется в числовом контексте выбора, если выражение является одним из следующих:
Числовое продвижение определяет тип, получаемый после продвижения всех выражений в числовом контексте. Тип, получаемый после продвижения, выбирается таким образом, чтобы каждое выражение могло быть преобразовано к этому типу, и в случае арифметической операции операция была определена для значений этого типа. Порядок выражений в числовом контексте не имеет значения для числового продвижения. Правила следующие:
-
Если какое-либо выражение имеет тип ссылочного типа, оно подвергается преобразованию разворачивания (§5.1.8).
-
Далее, применяются расширяющее преобразование примитивного типа (§5.1.2) и сужающее преобразование примитивного типа (§5.1.3) к некоторым выражениям в соответствии со следующими правилами:
-
Если какое-либо выражение имеет тип
double, то тип, получаемый после продвижения, —double, а другие выражения, которые не имеют типdouble, преобразуются к типуdoubleс помощью расширяющего преобразования примитивного типа. -
В противном случае, если какое-либо выражение имеет тип
float, то тип, получаемый после продвижения, —float, а другие выражения, которые не имеют типfloat, преобразуются к типуfloatс помощью расширяющего преобразования примитивного типа. -
В противном случае, если какое-либо выражение имеет тип
long, то тип, получаемый после продвижения, —long, а другие выражения, которые не имеют типlong, преобразуются к типуlongс помощью расширяющего преобразования примитивного типа. -
В противном случае ни одно из выражений не имеет тип
double,floatилиlong. В этом случае тип, получаемый после продвижения, выбирается в зависимости от вида контекста.В числовом арифметическом контексте или числовом контексте массива тип, получаемый после продвижения, —
int, а все выражения, которые не имеют типint, преобразуются к типуintс помощью расширяющего преобразования примитивного типа.В числовом контексте выбора применяются следующие правила:
-
Если какое-либо выражение имеет тип
intи не является константным выражением (§15.29), то тип, получаемый после продвижения, —int, а другие выражения, которые не имеют типint, преобразуются к типуintс помощью расширяющего преобразования примитивного типа. - (Остальные правила перевода аналогичным образом)
-
-
Унарное числовое продвижение заключается в применении числового продвижения к одному выражению, которое встречается в числовом арифметическом контексте или числовом контексте массива.
Бинарное числовое продвижение заключается в применении числового продвижения к паре выражений, которые встречаются в числовом арифметическом контексте.
Общее числовое продвижение заключается в применении числового продвижения ко всем выражениям, которые встречаются в числовом контексте выбора.
Пример 5.6-1. Унарное числовое продвижение
class Test {
public static void main(String[] args) {
byte b = 2;
int[] a = new int[b]; // dimension expression promotion
char c = '\u0001';
a[c] = 1; // index expression promotion
a[0] = -c; // unary - promotion
System.out.println("a: " + a[0] + "," + a[1]);
b = -1;
int i = ~b; // bitwise complement promotion
System.out.println("~0x" + Integer.toHexString(b)
+ "==0x" + Integer.toHexString(i));
i = b << 4L; // shift promotion (left operand)
System.out.println("0x" + Integer.toHexString(b)
+ "<<4L==0x" + Integer.toHexString(i));
}
}
Эта программа выводит:
a: -1,1 ~0xffffffff==0x0 0xffffffff<<4L==0xfffffff0
Пример 5.6-2. Бинарное числовое продвижение
class Test {
public static void main(String[] args) {
int i = 0;
float f = 1.0f;
double d = 2.0;
// First int*float is promoted to float*float, then
// float==double is promoted to double==double:
if (i * f == d) System.out.println("oops");
// A char&byte is promoted to int&int:
byte b = 0x1f;
char c = 'G';
int control = c & b;
System.out.println(Integer.toHexString(control));
// Here int:float is promoted to float:float:
f = (b==0) ? i : 4.0f;
System.out.println(1.0/f);
}
}
Эта программа выводит:
7 0.25
Пример преобразует ASCII-символ G в ASCII-управляющий символ G (BEL), скрывая все, кроме 5 младших битов символа. 7 — это числовое значение этого управляющего символа.
© Oracle and/or its affiliates. All rights reserved.
Licensed under the Oracle Technology Network License Agreement.