Глава 5. Преобразования и контексты
Содержание
- 5.1. Типы преобразований
-
- 5.1.1. Тождественное преобразование
- 5.1.2. Расширяющее преобразование примитивных типов
- 5.1.3. Сужающее преобразование примитивных типов
- 5.1.4. Расширяющее и сужающее преобразование примитивных типов
- 5.1.5. Расширяющее преобразование ссылочных типов
- 5.1.6. Сужающее преобразование ссылочных типов
- 5.1.7. Преобразование в обёртку
- 5.1.8. Преобразование из обёртки
- 5.1.9. Преобразование без проверки
- 5.1.10. Преобразование захвата
- 5.1.11. Преобразование в строку
- 5.1.12. Запрещённые преобразования
- 5.1.13. Преобразование множества значений
- 5.2. Контексты присваивания
- 5.3. Контексты вызова
- 5.4. Контексты строк
- 5.5. Контексты приведения типов
- 5.6. Численные контексты
Каждое выражение, написанное на языке программирования Java, либо не производит результата (§15.1), либо имеет тип, который может быть выведен на этапе компиляции (§15.3). Когда выражение используется в большинстве контекстов, оно должно быть совместимо с типом, ожидаемым в этом контексте; этот тип называется целевым типом. Для удобства совместимость выражения с окружающим контекстом обеспечивается двумя способами:
-
Во-первых, для некоторых выражений, называемых поли выражениями (§15.2), выведенный тип может быть подвержен влиянию целевого типа. Одно и то же выражение может иметь разные типы в разных контекстах.
-
Во-вторых, после того, как тип выражения был выведен, иногда может быть выполнено неявное преобразование из типа выражения в целевой тип.
Если ни один из стратегий не может произвести соответствующий тип, возникает ошибка компиляции.
Правила, определяющие, является ли выражение поли выражением, и если да, то его тип и совместимость в конкретном контексте, зависят от типа контекста и формы выражения. Кроме влияния на тип выражения, целевой тип в некоторых случаях может влиять на поведение выражения во время выполнения, чтобы произвести значение соответствующего типа.
Аналогично, правила, определяющие, позволяет ли целевой тип неявное преобразование, зависят от типа контекста, типа выражения и, в одном особом случае, от значения константного выражения (§15.28). Преобразование из типа 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 сгруппированы в несколько основных категорий:
-
Тождественные преобразования
-
Расширяющие преобразования примитивных типов
-
Сужающие преобразования примитивных типов
-
Расширяющие преобразования ссылочных типов
-
Сужающие преобразования ссылочных типов
-
Преобразования в обёртку
-
Преобразования из обёртки
-
Преобразования без проверки
-
Преобразования захвата
-
Преобразования в строку
-
Преобразования множества значений
Существует шесть типов контекстов преобразования, в которых поли выражения могут быть подвержены влиянию контекста или могут произойти неявные преобразования. Каждый тип контекста имеет свои правила для типизации поли выражений и допускает преобразования в некоторых из вышеперечисленных категорий, но не в других. Эти контексты:
-
Контексты присваивания (§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.4) 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 разделены на 13 категорий.
Преобразование типа в тот же тип разрешено для любого типа.
Это может показаться тривиальным, но у этого есть два практических последствия. Во-первых, всегда разрешается, чтобы выражение имело нужный тип с самого начала, что позволяет просто утверждать, что каждое выражение подвержено преобразованию, если только не требуется тривиальное преобразование тождества. Во-вторых, это подразумевает, что программа может содержать избыточные операторы приведения для большей ясности.
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в выраженииstrictfp(§15.4)
Расширяющее преобразование примитивного типа от float к double, которое не является strictfp, может потерять информацию о величине преобразованного значения.
Расширяющее преобразование примитивного типа от int к float или от long к float или от long к double может привести к потере точности — то есть результат может потерять некоторые из наименее значимых битов значения. В этом случае полученное значение с плавающей точкой будет правильно округленной версией целочисленного значения с использованием режима округления IEEE 754 до ближайшего значения (§4.2.4).
Расширяющее преобразование знака целочисленного значения в целочисленный тип T просто расширяет представление значения в дополнительном коде до двух разрядов для заполнения более широкого формата.
Расширяющее преобразование char в целочисленный тип T расширяет представление значения char до нуля, чтобы заполнить более широкий формат.
Несмотря на то, что потеря точности может произойти, расширяющее преобразование примитивного типа никогда не приводит к исключению во время выполнения (§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
Сужающее преобразование примитивного типа может потерять информацию о величине числового значения, а также может потерять точность и диапазон.
Сужающее преобразование примитивного типа из double в float регулируется правилами округления IEEE 754 (§4.2.4). Это преобразование может потерять точность, а также диапазон, что приводит к нулю float из ненулевого double и бесконечности float из конечного double. double NaN преобразуется в float NaN, а double бесконечность преобразуется в бесконечность с тем же знаком float.
Сужающее преобразование знакового целого числа в целочисленный тип T просто отбрасывает все, кроме n младших разрядов, где n — количество разрядов, используемых для представления типа T. Помимо возможной потери информации о величине числового значения, это может привести к изменению знака результирующего значения по сравнению со знаком входного значения.
Сужающее преобразование char в целочисленный тип T аналогичным образом просто отбрасывает все, кроме n младших разрядов, где n — количество разрядов, используемых для представления типа T. Помимо возможной потери информации о величине числового значения, это может привести к тому, что результирующее значение будет отрицательным, даже если символы представляют 16-битные беззнаковые целые значения.
Сужающее преобразование числа с плавающей точкой в целочисленный тип T выполняется в два этапа:
-
На первом этапе число с плавающей точкой преобразуется в
long, если T являетсяlong, или вint, если T являетсяbyte,short,charилиint, следующим образом:-
Если число с плавающей точкой является NaN (§4.2.3), результатом первого шага преобразования является
intилиlong0. -
В противном случае, если число с плавающей точкой не является бесконечностью, значение с плавающей точкой округляется до целого значения
V, используя режим округления IEEE 754 к нулю (§4.2.3). Затем следуют два случая:-
Если T является
long, и это целое значение может быть представлено какlong, то результатом первого шага является значениеlongV. -
В противном случае, если это целое значение может быть представлено как
int, то результатом первого шага является значениеintV.
-
-
В противном случае должен выполняться один из следующих двух случаев:
-
Значение слишком мало (отрицательное значение большой величины или отрицательная бесконечность), и результатом первого шага является наименьшее представимое значение типа
intилиlong. -
Значение слишком велико (положительное значение большой величины или положительная бесконечность), и результатом первого шага является наибольшее представимое значение типа
intилиlong.
-
-
-
На втором шаге:
-
Если T является
intилиlong, результатом преобразования является результат первого шага. -
Если T является
byte,charилиshort, результатом преобразования является результат сужающего преобразования в тип T (§5.1.3) результата первого шага.
-
Несмотря на то, что могут возникнуть переполнение, недополнение или другие потери информации, сужающее преобразование примитивного типа никогда не приводит к исключению во время выполнения (§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 является подтипом (§4.10) T.
Расширяющие преобразования ссылок никогда не требуют специального действия во время выполнения и, следовательно, никогда не вызывают исключение во время выполнения. Они просто заключаются в рассмотрении ссылки как имеющей другой тип таким образом, что это может быть доказано правильным во время компиляции.
Шесть видов преобразований называются преобразованиями сужения ссылок:
-
От любого ссылочного типа S к любому ссылочному типу T, при условии, что S является надтипом для T (§4.10).
Важным частным случаем является наличие преобразования сужения ссылок от типа класса
Objectк любому другому ссылочному типу (§4.12.4). -
От любого типа класса C к любому непараметризованному типу интерфейса K, при условии, что C не
finalи не реализует K. -
От любого типа интерфейса J к любому непараметризованному типу класса C, который не является
final. -
От любого типа интерфейса J к любому непараметризованному типу интерфейса K, при условии, что J не является под-интерфейсом K.
-
От типов интерфейсов
Cloneableиjava.io.Serializableк любому типу массива T[]. -
От любого типа массива SC
[]к любому типу массива TC[], при условии, что SC и TC являются ссылочными типами и существует преобразование сужения ссылок от SC к TC.
Такие преобразования требуют проверки во время выполнения, чтобы выяснить, является ли фактическое ссылочное значение допустимым значением нового типа. Если нет, то выбрасывается ClassCastException.
Преобразование упаковки преобразует выражения примитивного типа в соответствующие выражения ссылочного типа. В частности, следующие девять преобразований называются преобразованиями упаковки:
-
От типа
booleanк типуBoolean -
От типа
byteк типуByte -
От типа
shortк типуShort -
От типа
charк типуCharacter -
От типа
intк типуInteger -
От типа
longк типуLong -
От типа
floatк типуFloat -
От типа
doubleк типуDouble -
От нулевого типа к нулевому типу
Это правило необходимо, потому что условный оператор (§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, которое упаковывается, является целочисленным литералом типа int между -128 и 127 включительно (§3.10.1), или логическим литералом true или false (§3.10.3), или символьным литералом между '\u0000' и '\u007f' включительно (§3.10.4), то пусть a и b будут результатами любых двух преобразований упаковки p. Всегда верно, что a == b.
В идеале упаковка примитивного значения всегда должна давать идентичную ссылку. На практике это может быть невыполнимо с использованием существующих методов реализации. Приведенное выше правило представляет собой прагматичный компромисс, требующий, чтобы определенные общие значения всегда упаковывались в неразличимые объекты. Реализация может кэшировать их, лениво или усердно. Для других значений правило не допускает каких-либо предположений об идентичности упакованных значений со стороны программиста. Это допускает (но не требует) совместное использование некоторых или всех этих ссылок. Обратите внимание, что целочисленные литералы типа long разрешены, но не требуются, для совместного использования.
Это гарантирует, что в большинстве общих случаев поведение будет желаемым, не налагая чрезмерного снижения производительности, особенно на небольших устройствах. Реализации с меньшими ограничениями по памяти могут, например, кэшировать все char и short значения, а также int и long значения в диапазоне от -32K до +32K.
Преобразование упаковки может привести к OutOfMemoryError, если необходимо выделить новый экземпляр одного из классов-оберток (Boolean, Byte, Character, Short, Integer, Long, Float или Double) и недостаточно памяти.
Преобразование разboxing преобразует выражения ссылочного типа в соответствующие выражения примитивного типа. В частности, следующие восемь преобразований называются преобразованиями разboxing:
-
Из типа
Booleanв типboolean -
Из типа
Byteв типbyte -
Из типа
Shortв типshort -
Из типа
Characterв типchar -
Из типа
Integerв типint -
Из типа
Longв типlong -
Из типа
Floatв типfloat -
Из типа
Doubleв типdouble
Во время выполнения преобразование разboxing происходит следующим образом:
-
Если
rявляется ссылкой типаBoolean, то преобразование разboxing преобразуетrвr.booleanValue() -
Если
rявляется ссылкой типаByte, то преобразование разboxing преобразуетrвr.byteValue() -
Если
rявляется ссылкой типаCharacter, то преобразование разboxing преобразуетrвr.charValue() -
Если
rявляется ссылкой типаShort, то преобразование разboxing преобразуетrвr.shortValue() -
Если
rявляется ссылкой типаInteger, то преобразование разboxing преобразуетrвr.intValue() -
Если
rявляется ссылкой типаLong, то преобразование разboxing преобразуетrвr.longValue() -
Если
rявляется ссылкой типаFloat, то преобразование разboxing преобразуетrвr.floatValue() -
Если
rявляется ссылкой типаDouble, то преобразование разboxing преобразуетrвr.doubleValue() -
Если
rявляетсяnull, преобразование разboxing вызывает исключениеNullPointerException
Тип считается преобразуемым в числовой тип, если это числовой тип (§4.2), или это ссылочный тип, который может быть преобразован в числовой тип с помощью преобразования разboxing.
Тип считается преобразуемым в целочисленный тип, если это целочисленный тип, или это ссылочный тип, который может быть преобразован в целочисленный тип с помощью преобразования разboxing.
Пусть 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 в 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 на экзистенциальных типах требует, чтобы захваченная переменная типа не покидала открытое выражение. Операция open, соответствующая преобразованию захвата, всегда находится в достаточно большой области видимости, чтобы захваченная переменная типа никогда не была видна вне этой области. Преимущество этой схемы заключается в том, что нет необходимости в операции close, как определено в статье О базировании на вариации подтипа для параметризованных типов Ацуши Игараси и Мирко Вироли в материалах 16-й Европейской конференции по объектно-ориентированному программированию (ECOOP 2002). Для формального описания подстановочных типов см. Дикий 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.
Подробности контекста строк см. в §5.4.
Преобразование множества значений — это процесс сопоставления значения с плавающей запятой из одного множества значений в другое без изменения его типа.
В выражении, которое не является FP-строгим (§15.4), преобразование множества значений предоставляет варианты для реализации языка программирования Java:
-
Если значение является элементом множества значений с расширенным порядком показателя плавающей запятой, то реализация может по своему усмотрению сопоставить значение с ближайшим элементом множества значений с плавающей запятой. Это преобразование может привести к переполнению (в этом случае значение заменяется бесконечностью того же знака) или потере точности из-за недолива (в этом случае значение может потерять точность, так как оно заменяется денормализованным числом или нулем того же знака).
-
Если значение является элементом множества значений с расширенным порядком показателя двойной точности, то реализация может по своему усмотрению сопоставить значение с ближайшим элементом множества значений двойной точности. Это преобразование может привести к переполнению (в этом случае значение заменяется бесконечностью того же знака) или потере точности из-за недолива (в этом случае значение может потерять точность, так как оно заменяется денормализованным числом или нулем того же знака).
В FP-строгом выражении (§15.4) преобразование множества значений не предоставляет вариантов; каждая реализация должна вести себя одинаково:
-
Если значение типа
floatи не является элементом множества значений с плавающей запятой, то реализация должна сопоставить значение с ближайшим элементом множества значений с плавающей запятой. Это преобразование может привести к переполнению или недоливу. -
Если значение типа
doubleи не является элементом множества значений двойной точности, то реализация должна сопоставить значение с ближайшим элементом множества значений двойной точности. Это преобразование может привести к переполнению или недоливу.
В FP-строгом выражении сопоставление значений из множества значений с расширенным порядком показателя плавающей запятой или множества значений с расширенным порядком показателя двойной точности необходимо только тогда, когда вызывается метод, объявление которого не является FP-строгим, и реализация выбрала представить результат вызова метода как элемент множества значений с расширенным порядком показателя.
Как в FP-строгом коде, так и в коде, не являющемся FP-строгим, преобразование множества значений всегда оставляет неизменным любое значение, тип которого не является ни float, ни double.
Контексты присваивания позволяют присвоить (§15.26) значение выражения переменной; тип выражения должен быть преобразован к типу переменной.
Контексты присваивания позволяют использовать следующие преобразования:
-
тождественное преобразование (§5.1.1)
-
расширяющее преобразование примитивного типа (§5.1.2)
-
расширяющее преобразование ссылочного типа (§5.1.5)
-
преобразование упаковки (§5.1.7) необязательно сопровождаемое расширяющим преобразованием ссылочного типа
-
преобразование распаковки (§5.1.8) необязательно сопровождаемое расширяющим преобразованием примитивного типа.
Если после применения указанных преобразований получившийся тип является сырым типом (§4.8), то затем может быть применено неявное преобразование (§5.1.9).
Кроме того, если выражение является константным выражением (§15.28) типа byte, short, char или int:
-
Может быть применено сужающее преобразование примитивного типа, если тип переменной является
byte,shortилиchar, и значение константного выражения представимо в типе переменной. -
Может быть применено сужающее преобразование примитивного типа, за которым следует преобразование упаковки, если тип переменной:
-
Byteи значение константного выражения представимо в типеbyte. -
Shortи значение константного выражения представимо в типеshort. -
Characterи значение константного выражения представимо в типеchar.
-
Временное сужение константных выражений означает, что код, такой как:
byte theAnswer = 42;
разрешается. Без сужения тот факт, что целочисленная константа 42 имеет тип int, означал бы, что требуется приведение к типу byte:
byte theAnswer = (byte)42; // cast is permitted but not required
Наконец, значение типа null (единственное такое значение) может быть присвоено любому типу ссылки, что приводит к null-ссылке соответствующего типа.
Ошибка компиляции возникает, если цепочка преобразований содержит два параметризованных типа, не находящихся в отношении подтипов (§4.10).
Пример такой недопустимой цепочки:
Integer, Comparable<Integer>, Comparable, Comparable<String>
Первые три элемента цепочки связаны расширяющим преобразованием ссылочного типа, тогда как последний элемент получен из предыдущего неявным преобразованием. Однако это не является допустимым преобразованием присваивания, поскольку цепочка содержит два параметризованных типа, Comparable<Integer> и Comparable<String>, не являющихся подтипами.
Если тип выражения не может быть преобразован в тип переменной с помощью преобразования, разрешенного в контексте присваивания, возникает ошибка компиляции.
Если тип выражения может быть преобразован в тип переменной посредством преобразования присваивания, то говорят, что выражение (или его значение) совместимо с переменной или, что тип выражения совместим с типом переменной.
Если тип переменной — float или double, то к значению v, которое является результатом преобразования(ий), применяется преобразование множества значений (§5.1.13):
-
Если
vимеет типfloatи является элементом множества значений с расширенным экспоненциальным представлением чисел с плавающей точкой, то реализация должна отобразитьvна ближайший элемент множества значений чисел с плавающей точкой. Это преобразование может привести к переполнению или потере точности. -
Если
vимеет типdoubleи является элементом множества значений с расширенным экспоненциальным представлением чисел с двойной точностью, то реализация должна отобразитьvна ближайший элемент множества значений чисел с двойной точностью. Это преобразование может привести к переполнению или потере точности.
Исключения, которые могут возникнуть при преобразованиях в контексте присваивания:
-
Ошибка компиляции, если после применения вышеперечисленных преобразований полученное значение представляет собой объект, который не является экземпляром подкласса или подинтерфейса стёртого типа (§4.6) типа переменной.
Эта ситуация может возникнуть только в результате загрязнения кучи (§4.12.2). На практике реализациям необходимо выполнять приведение типов только при доступе к полю или методу объекта параметризованного типа, когда стёртый тип поля или стёртый возвращаемый тип метода отличаются от нестёртого типа.
-
Ошибка компиляции в результате преобразования упаковки.
-
Ошибка компиляции в результате преобразования распаковки на null-ссылке.
-
Ошибка компиляции в особых случаях, связанных с элементами массива или доступом к полю (§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, потому что не каждая ссылка, которая может быть значением выражения типаColoredPoint, может быть значением переменной типаPoint. Если значение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 (единственное такое значение) может быть присвоено любому типу ссылки.
Ошибка компиляции возникает, если цепочка преобразований содержит два параметризованных типа, которые не находятся в подтиповом отношении (§4.10).
Если тип выражения не может быть преобразован в тип параметра посредством преобразования, разрешенного в гибком контексте вызова, возникает ошибка компиляции.
Если тип выражения-аргумента равен float или double, то преобразование множества значений (§5.1.13) применяется после преобразования(ий):
-
Если значение аргумента типа
floatявляется элементом множества значений с расширенным показателем мантиссы чисел с плавающей точкой, то реализация должна сопоставить значение с ближайшим элементом множества значений чисел с плавающей точкой. Это преобразование может привести к переполнению или недополнению. -
Если значение аргумента типа
doubleявляется элементом множества значений с расширенным показателем мантиссы чисел с двойной точностью, то реализация должна сопоставить значение с ближайшим элементом множества значений чисел с двойной точностью. Это преобразование может привести к переполнению или недополнению.
Исключения, которые могут возникнуть в контексте вызова:
-
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.1)
-
расширяющее преобразование примитивных типов (§5.1.2)
-
сужающее преобразование примитивных типов (§5.1.3)
-
расширяющее и сужающее преобразование примитивных типов (§5.1.4)
-
расширяющее преобразование ссылочных типов (§5.1.5), за которым необязательно следует преобразование распаковки (§5.1.8) или неявное преобразование (§5.1.9)
-
сужающее преобразование ссылочных типов (§5.1.6), за которым необязательно следует преобразование распаковки (§5.1.8) или неявное преобразование (§5.1.9)
-
преобразование упаковки (§5.1.7), за которым необязательно следует расширяющее преобразование ссылочных типов (§5.1.5)
-
преобразование распаковки (§5.1.8), за которым необязательно следует расширяющее преобразование примитивных типов (§5.1.2).
Преобразование множества значений (§5.1.13) применяется после преобразования типа.
Правомерность преобразования приведения типов на этапе компиляции определяется следующим образом:
-
Выражение примитивного типа может быть преобразовано к другому примитивному типу с помощью преобразования тождества (если типы совпадают), или расширяющего преобразования примитивных типов, или сужающего преобразования примитивных типов, или расширяющего и сужающего преобразования примитивных типов.
-
Выражение примитивного типа может быть преобразовано к ссылочному типу без ошибки с помощью преобразования упаковки.
-
Выражение ссылочного типа может быть преобразовано к примитивному типу без ошибки с помощью преобразования распаковки.
-
Выражение ссылочного типа может быть преобразовано к другому ссылочному типу, если на этапе компиляции не возникает ошибки в соответствии с правилами в §5.5.1.
Следующие таблицы перечисляют, какие преобразования используются в определенных преобразованиях приведения типов. Каждое преобразование обозначается символом:
-
- означает, что преобразование приведения типов не разрешено
-
≈ означает преобразование тождества (§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 | ⇓ | ⇓ | ⇓ | ⇓ | ⇓ | ⇓ | ⇓ | ⇓ | ≈ |
Учитывая тип ссылки на этапе компиляции S (источник) и тип ссылки на этапе компиляции T (цель), преобразование типа существует от S к T, если не возникает ошибок компиляции из-за следующих правил.
Если S — тип класса:
-
Если T — тип класса, то либо |S|
<:|T|, или |T|<:|S|. В противном случае возникает ошибка компиляции.Кроме того, если существует супертип X типа T и супертип Y типа S, такие что оба X и Y являются доказуемо различными параметризованными типами (§4.5), и что стёртые представления X и Y совпадают, возникает ошибка компиляции.
-
Если T — тип интерфейса:
-
Если S не является
finalклассом (§8.1.1), то, если существует супертип X типа T и супертип Y типа S, такие что оба X и Y являются доказуемо различными параметризованными типами, и что стёртые представления X и Y совпадают, возникает ошибка компиляции.В противном случае преобразование всегда является допустимым на этапе компиляции (потому что даже если S не реализует T, подкласс S может).
-
Если S является
finalклассом (§8.1.1), то S должен реализовывать T, в противном случае возникает ошибка компиляции.
-
-
Если T — переменная типа, то этот алгоритм применяется рекурсивно, используя верхнюю границу T вместо T.
-
Если T — массив типов, то S должен быть классом
Object, в противном случае возникает ошибка компиляции. -
Если T — пересечение типов, T1
&...&Tn, то это ошибка компиляции, если существует Ti (1 ≤ i ≤ n), такое что S не может быть преобразовано к Ti по этому алгоритму. То есть, успех преобразования определяется самым ограниченным компонентом типа пересечения.
Если S — тип интерфейса:
-
Если T — тип массива, то S должен быть типом
java.io.SerializableилиCloneable(единственные интерфейсы, реализуемые массивами), в противном случае возникает ошибка компиляции. -
Если T — тип класса или интерфейса, который не является
final(§8.1.1), то если существует супертип X типа T и супертип Y типа S, такие что оба X и Y являются доказуемо различными параметризованными типами, и что стёртые представления X и Y совпадают, возникает ошибка компиляции.В противном случае преобразование всегда является допустимым на этапе компиляции (потому что даже если T не реализует S, подкласс T может).
-
Если T — тип класса, который является
final, то:-
Если S не является параметризованным типом или сырым типом, то T должен реализовывать S, в противном случае возникает ошибка компиляции.
-
В противном случае S — либо параметризованный тип, являющийся вызовом некоторого объявления обобщённого типа G, либо сырой тип, соответствующий обобщённому типу объявления G. Тогда должен существовать супертип X типа T, такой что X является вызовом G, в противном случае возникает ошибка компиляции.
Кроме того, если S и X являются доказуемо различными параметризованными типами, возникает ошибка компиляции.
-
-
Если T — переменная типа, то этот алгоритм применяется рекурсивно, используя верхнюю границу T вместо T.
-
Если T — пересечение типов, T1
&...&Tn, то это ошибка компиляции, если существует Ti (1 ≤ i ≤ n), такое что S не может быть преобразовано к Ti по этому алгоритму.
Если S — переменная типа, то этот алгоритм применяется рекурсивно, используя верхнюю границу S вместо S.
Если S — пересечение типов A1 & ... & An, то это ошибка компиляции, если существует Ai (1 ≤ i ≤ n), такое что Ai не может быть преобразовано к T по этому алгоритму. То есть, успех преобразования определяется самым ограниченным компонентом типа пересечения.
Если S — тип массива SC[], то есть массив компонентов типа SC:
-
Если T — тип класса, то если T не является
Object, то возникает ошибка компиляции (потому чтоObject— единственный тип класса, которому могут быть присвоены массивы). -
Если T — тип интерфейса, то возникает ошибка компиляции, если T не является типом
java.io.Serializableили типомCloneable(единственные интерфейсы, реализуемые массивами). -
Если T — переменная типа, то этот алгоритм применяется рекурсивно, используя верхнюю границу T вместо T.
-
Если T — тип массива TC
[], то есть массив компонентов типа TC, то возникает ошибка компиляции, если не выполняется одно из следующих условий:-
TC и SC — один и тот же примитивный тип.
-
TC и SC — ссылочные типы, и тип SC может быть преобразован к TC.
-
-
Если T — пересечение типов, T1
&...&Tn, то это ошибка компиляции, если существует Ti (1 ≤ i ≤ n), такое что S не может быть преобразовано к Ti по этому алгоритму.
Пример 5.5.1-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.1-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 }
Приведение типа S к типу T статически известно как корректное, если и только если S <: T (§4.10).
Приведение типа S к параметризованному типу (§4.5) T является непроверяемым, если не выполняется хотя бы одно из следующих условий:
-
S
<:T -
Все аргументы типа (§4.5.1) T являются неограниченными джойлвайдами
-
T
<:S и S не имеет подтипа X помимо T, где аргументы типа X не содержатся в аргументах типа T.
Приведение типа S к переменной типа T является непроверяемым, если S <: T.
Приведение типа S к пересечению типов T1 & ... & Tn является непроверяемым, если существует Ti (1 ≤ i ≤ n), при котором приведение S к Ti является непроверяемым.
Непроверяемое приведение типа S к типу T (не пересечению) является полностью непроверяемым, если приведение |S| к |T| статически известно как корректное. В противном случае оно является частично непроверяемым.
Непроверяемое приведение типа S к пересечению типов T1 & ... & Tn является полностью непроверяемым, если для всех i (1 ≤ i ≤ n) приведение S к Ti либо статически известно как корректное, либо полностью непроверяемое. В противном случае оно является частично непроверяемым.
Непроверяемое приведение вызывает предупреждение о непроверяемом приведении во время компиляции, если оно не подавлено с помощью аннотации SuppressWarnings (§9.6.4.5).
Приведение является проверяемым, если оно не статически известно как корректное и не является непроверяемым.
Если приведение к ссылочному типу не является ошибкой во время компиляции, существует несколько случаев:
-
Приведение статически известно как корректное.
Для такого приведения не выполняется никаких действий во время выполнения.
-
Приведение является полностью непроверяемым.
Для такого приведения не выполняется никаких действий во время выполнения.
-
Приведение является частично непроверяемым или проверяемым к пересечению типов.
Если тип пересечения T1
&...&Tn, то для всех i (1 ≤ i ≤ n) любая проверка во время выполнения, необходимая для приведения S к Ti, также необходима для приведения к типу пересечения. -
Приведение является частично непроверяемым к типу, не являющемуся пересечением.
Такое приведение требует проверки корректности во время выполнения. Проверка выполняется так, как если бы приведение было проверяемым приведением между |S| и |T|, как описано ниже.
-
Приведение является проверяемым к типу, не являющемуся пересечением.
Такое приведение требует проверки корректности во время выполнения. Если значение во время выполнения
null, то приведение разрешается. В противном случае, пусть R — класс объекта, на который ссылается значение ссылки во время выполнения, и пусть T — стирание (§4.6) типа, указанного в операторе приведения. Приведение должно проверить во время выполнения, что класс R совместим с типом T в соответствии с алгоритмом в §5.5.3.Обратите внимание, что R не может быть интерфейсом, когда эти правила впервые применяются для данного приведения, но R может быть интерфейсом, если правила применяются рекурсивно, так как значение ссылки во время выполнения может ссылаться на массив, тип элемента которого является типом интерфейса.
Вот алгоритм проверки того, является ли тип R во время выполнения объекта совместимым с типом T, который является стиранием (§4.6) типа, указанного в операторе приведения. Если генерируется исключение во время выполнения, это ClassCastException.
Если R — обычный класс (не класс массива):
-
Если T — класс, то R должен быть тем же классом (§4.3.4) что и T или подклассом T, в противном случае генерируется исключение во время выполнения.
-
Если T — тип интерфейса, то R должен реализовывать (§8.1.5) интерфейс T, в противном случае генерируется исключение во время выполнения.
-
Если T — тип массива, то генерируется исключение во время выполнения.
Если R — интерфейс:
-
Если T — класс, то T должен быть
Object(§4.3.2), в противном случае генерируется исключение во время выполнения. -
Если T — тип интерфейса, то R должен быть либо тем же интерфейсом, что и T, либо подинтерфейсом T, в противном случае генерируется исключение во время выполнения.
-
Если T — тип массива, то генерируется исключение во время выполнения.
Если R — класс, представляющий тип массива RC[], то есть массив компонентов типа RC:
-
Если T — класс, то T должен быть
Object(§4.3.2), в противном случае генерируется исключение во время выполнения. -
Если T — тип интерфейса, то генерируется исключение во время выполнения, если T не является типом
java.io.Serializableили типомCloneable(единственные интерфейсы, реализуемые массивами).Этот случай может пройти мимо проверки во время компиляции, если, например, ссылка на массив хранится в переменной типа
Object. -
Если T — тип массива TC
[], то есть массив компонентов типа TC, то генерируется исключение во время выполнения, если не выполняется одно из следующих условий:-
TC и RC — один и тот же примитивный тип.
-
TC и RC — ссылочные типы, и тип RC может быть приведён к TC путём рекурсивного применения этих правил приведения во время выполнения.
-
Пример 5.5.3-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; }
}
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);
}
}
Эта программа использует приведения для компиляции, но генерирует исключения во время выполнения, потому что типы несовместимы.
Числовые контексты применяются к операндам арифметического оператора.
Числовые контексты позволяют использовать:
Числовое продвижение — это процесс, в котором, исходя из арифметического оператора и выражений его аргументов, аргументы преобразуются к выведенному целевому типу T. T выбирается во время продвижения таким образом, чтобы каждое выражение аргумента могло быть преобразовано в T, и арифметическая операция была определена для значений типа T.
Существует два вида числового продвижения: унарное числовое продвижение (§5.6.1) и бинарное числовое продвижение (§5.6.2).
Некоторые операторы применяют унарное числовое продвижение к одному операнду, который должен представлять значение числового типа:
-
Если операнд имеет тип времени компиляции
Byte,Short,CharacterилиInteger, к нему применяется преобразование распаковки (§5.1.8). Результат затем преобразуется в значение типаintс помощью расширяющего примитивного преобразования (§5.1.2) или преобразования тождества (§5.1.1). -
В противном случае, если операнд имеет тип времени компиляции
Long,FloatилиDouble, к нему применяется преобразование распаковки (§5.1.8). -
В противном случае, если операнд имеет тип времени компиляции
byte,shortилиchar, он преобразуется в значение типаintс помощью расширяющего примитивного преобразования (§5.1.2). -
В противном случае, унарный числовой операнд остается без изменений и не преобразуется.
После преобразования (если необходимо), затем применяется преобразование множества значений (§5.1.13).
Унарное числовое продвижение выполняется для выражений в следующих ситуациях:
-
Каждое выражение размерности в выражении создания массива (§15.10.1)
-
Выражение индекса в выражении доступа к массиву (§15.10.3)
-
Операнд унарного оператора «плюс»
+(§15.15.3) -
Операнд унарного оператора «минус»
-(§15.15.4) -
Операнд побитового оператора дополнения
~(§15.15.5) -
Каждый операнд (отдельно) оператора сдвига
<<,>>или>>>(§15.19).Расстояние сдвига
long(правый операнд) не преобразует значение, сдвигаемое (левый операнд) вlong.
Пример 5.6.1-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.1.8).
-
Расширяющее примитивное преобразование (§5.1.2) применяется для преобразования одного или обоих операндов в соответствии со следующими правилами:
-
Если какой-либо операнд имеет тип
double, другой преобразуется вdouble. -
В противном случае, если какой-либо операнд имеет тип
float, другой преобразуется вfloat. -
В противном случае, если какой-либо операнд имеет тип
long, другой преобразуется вlong. -
В противном случае, оба операнда преобразуются в тип
int.
-
После преобразования (если необходимо), затем применяется преобразование множества значений (§5.1.13) к каждому операнду.
Бинарное числовое продвижение выполняется для операндов следующих операторов:
-
Мультипликативные операторы
*,/и%(§15.17) -
Операторы сложения и вычитания для числовых типов
+и-(§15.18.2) -
Числовые операторы сравнения
<,<=,>и>=(§15.20.1) -
Числовые операторы равенства
==и!=(§15.21.1) -
Побитовые целочисленные операторы
&,^и|(§15.22.1) -
В определенных случаях условный оператор
? :(§15.25)
Пример 5.6.2-1. Бинарное числовое продвижение
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.