Глава 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, имеет тип, который можно вывести из структуры выражения и типов литералов, переменных и методов, упомянутых в выражении. Однако возможно написать выражение в контексте, где тип выражения не подходит. В некоторых случаях это приводит к ошибке во время компиляции. В других случаях контекст может принять тип, связанный с типом выражения; для удобства, вместо того, чтобы требовать от программиста явно указать преобразование типа, язык программирования Java выполняет неявное преобразование из типа выражения в тип, приемлемый для его окружающего контекста.
Конкретное преобразование из типа 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.1), включая специальные преобразования в String, разрешенные для оператора конкатенации строк + (§15.18.1). Затем описываются пять контекстов преобразований:
-
Преобразование при присваивании (§5.2, §15.26) преобразует тип выражения в тип указанной переменной.
Преобразование при присваивании может привести к возникновению
OutOfMemoryError(в результате преобразования в обёртку (§5.1.7)),NullPointerException(в результате преобразования из обёртки (§5.1.8)) илиClassCastException(в результате непроверяемого преобразования (§5.1.9)) для выбрасывания во время выполнения. -
Преобразование при вызове метода (§5.3, §15.9, §15.12) применяется к каждому аргументу в вызове метода или конструктора и, за исключением одного случая, выполняет те же преобразования, что и преобразование при присваивании.
Преобразование при вызове метода может привести к возникновению
OutOfMemoryError(в результате преобразования в обёртку (§5.1.7)),NullPointerException(в результате преобразования из обёртки (§5.1.8)) илиClassCastException(в результате непроверяемого преобразования (§5.1.9)) для выбрасывания во время выполнения. -
Преобразование при приведении типов (§5.5) преобразует тип выражения в тип, явно указанный оператором приведения (§15.16).
Он более всеобъемлющий, чем преобразование при присваивании или при вызове метода, позволяя любое конкретное преобразование, кроме преобразования в строку, но некоторые приведения к ссылочному типу могут вызвать исключение во время выполнения.
-
Преобразование в строку (§5.4) применяется только к операнду бинарного оператора
+, который не являетсяString, когда другой операнд являетсяString.Преобразование в строку может вызвать
OutOfMemoryError(в результате создания экземпляра класса (§12.5)) для выбрасывания во время выполнения. -
Повышение числовых типов (§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);
// Method 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
Расширяющее преобразование примитивных типов не теряет информации о величине числового значения.
Расширяющее преобразование примитивного типа от целочисленного типа к другому целочисленному типу или от float к double в выражении strictfp (§15.4) не теряет никакой информации; числовое значение сохраняется точно.
Расширяющее преобразование примитивного типа от float к double, которое не является strictfp, может потерять информацию о величине преобразованного значения.
Расширяющее преобразование значения int или 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, то результат первого шага — этоlongзначениеV. -
В противном случае, если это целое значение может быть представлено как
int, то результат первого шага — этоintзначениеV.
-
-
В противном случае должен быть истинным один из следующих двух случаев:
-
Значение должно быть слишком маленьким (отрицательное значение большой величины или отрицательная бесконечность), и результат первого шага — это наименьшее представимое значение типа
intилиlong. -
Значение должно быть слишком большим (положительное значение большой величины или положительная бесконечность), и результат первого шага — это наибольшее представимое значение типа
intилиlong.
-
-
-
На втором шаге:
-
Если T является
intилиlong, результат преобразования — результат первого шага. -
Если T является
byte,charилиshort, результат преобразования — результат преобразования сужения к типу T (§5.1.3) результата первого шага.
-
Пример 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.
Несмотря на то, что может произойти переполнение, недополнение или другая потеря информации, преобразование примитивных типов сужения никогда не приводит к исключению во время выполнения (§11.1.1).
Пример 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, которое заключается в оболочку, является true, false, byte, или char в диапазоне \u0000 к \u007f, или int или short число между -128 и 127 (включительно), то пусть r1 и r2 будут результатами любых двух преобразований в оболочку p. Всегда имеет место то, что r1 == r2.
В идеале, упаковка заданного примитивного значения p всегда должна давать идентичную ссылку. На практике это может быть невыполнимо с использованием существующих методов реализации. Приведенные выше правила являются прагматичным компромиссом. Последняя фраза выше требует, чтобы некоторые общие значения всегда заключались в неразличимые объекты. Реализация может кэшировать их, лениво или усердно. Для других значений эта формулировка не допускает каких-либо предположений об идентичности упакованных значений со стороны программиста. Это позволит (но не потребует) совместного использования некоторых или всех этих ссылок.
Это гарантирует, что в большинстве распространенных случаев поведение будет желаемым, не налагая чрезмерных штрафов за производительность, особенно на небольших устройствах. Менее ограниченные по памяти реализации могут, например, кэшировать все 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[] к любому массиву типа вида G<T1,...,Tn>[].
Использование непроверенного преобразования вызывает предупреждение о непроверенном преобразовании во время компиляции, если G<...> — это параметризованный тип, в котором все аргументы типа являются не ограниченными универсальными символами (§4.5.1), или предупреждение о непроверенном преобразовании подавляется аннотацией SuppressWarnings (§9.6.3.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 над экзистенциалами требует, чтобы захваченная переменная типа не выходила за пределы открытого выражения. Преобразование захвата, соответствующее 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).
Ошибка компиляции возникает, если цепочка преобразований содержит два параметризованных типа, не находящихся в отношении подтипа.
Пример такой недопустимой цепочки:
Integer, Comparable<Integer>, Comparable, Comparable<String>
Первые три элемента цепочки связаны расширяющим преобразованием ссылочных типов, а последний элемент получен из предыдущего преобразованием без проверки. Однако это не допустимое преобразование присваивания, поскольку цепочка содержит два параметризованных типа, Comparable<Integer> и Comparable<String>, которые не являются подтипами.
Кроме того, если выражение является константным выражением (§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-ссылка) может быть присвоено любому ссылочному типу, в результате чего получается null-ссылка того типа.
Если тип выражения не может быть преобразован в тип переменной с помощью преобразования, разрешенного в контексте присваивания, возникает ошибка компиляции.
Если тип выражения может быть преобразован в тип переменной посредством преобразования присваивания, то говорят, что выражение (или его значение) совместимо с переменной или, что тип выражения совместим с типом переменной.
Если тип переменной равен float или double, то к значению v, являющемуся результатом преобразования типа, применяется преобразование множества значений (§5.1.13):
-
Если v имеет тип
floatи является элементом множества значений с расширенной экспонентой для чисел с плавающей запятой, то реализация должна сопоставить v с ближайшим элементом множества значений для чисел с плавающей запятой. Это преобразование может привести к переполнению или потере точности. -
Если v имеет тип
doubleи является элементом множества значений с расширенной экспонентой для чисел с двойной точностью, то реализация должна сопоставить v с ближайшим элементом множества значений для чисел с двойной точностью. Это преобразование может привести к переполнению или потере точности.
Единственные исключения, которые может вызвать преобразование присваивания, это:
-
Ошибка
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, потому что не каждая ссылка, которая может быть значением выражения типа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 (единственное такое значение) может быть преобразовано в любой ссылочный тип.
Если тип выражения не может быть преобразован к типу параметра с помощью преобразования, допустимого в контексте вызова метода, то возникает ошибка компиляции.
Если тип выражения-аргумента равен либо float, либо double, то после преобразования типа применяется преобразование набора значений (§5.1.13):
-
Если значение аргумента типа
floatявляется элементом набора значений с расширенным порядком экспоненты для чисел с плавающей точкой, то реализация должна сопоставить значение с ближайшим элементом набора значений для чисел с плавающей точкой. Это преобразование может привести к переполнению или потере точности. -
Если значение аргумента типа
doubleявляется элементом набора значений с расширенным порядком экспоненты для чисел с двойной точностью, то реализация должна сопоставить значение с ближайшим элементом набора значений для чисел с двойной точностью. Это преобразование может привести к переполнению или потере точности.
Единственные исключения, которые может вызвать преобразование вызова метода, это:
-
ClassCastException, если после применения вышеперечисленных преобразований полученное значение является объектом, который не является экземпляром подкласса или подинтерфейса стёртого (§4.6) типа соответствующего формального параметра.Эта ситуация может возникнуть только в результате загрязнения кучи (§4.12.2).
-
OutOfMemoryErrorв результате преобразования в объект. -
NullPointerExceptionв результате преобразования из объекта в примитивный тип по значению null.
Преобразования вызова метода специально не включают неявное сужение целочисленных констант, которое является частью преобразования присваивания (§5.2). Разработчики языка 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 не соответствует правилам (§15.12.2). Язык, который включал неявное сужение целочисленных констант, потребовал бы дополнительных правил для разрешения таких случаев, как этот пример.
Преобразование в строку применяется только к операнду бинарного оператора +, который не является String, когда другой операнд является String.
В этом единственном специальном случае операнд, не являющийся String, для оператора + преобразуется в String (§5.1.11), и вычисление оператора + продолжается в соответствии с §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.1. Преобразования при приведении к примитивным типам
| К → | 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.2. Преобразования при приведении типов к ссылкам
| К → | 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, в противном случае возникает ошибка на этапе компиляции.
Если 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 являются доказуемо различными параметризованными типами, то возникает ошибка на этапе компиляции.
-
Если S является переменной типа, то этот алгоритм применяется рекурсивно, используя верхнюю границу S вместо S.
Если S является пересечением типов A1 & ... & An, то это ошибка на этапе компиляции, если существует Ai (1 ≤ i ≤ n), такой что тип S не может быть приведён к типу Ai по этому алгоритму. То есть, успех приведения определяется наиболее ограниченным компонентом типа пересечения.
Если S является типом массива SC[], то есть массивом компонентов типа SC:
-
Если T является типом класса, то если T не
Object, то возникает ошибка на этапе компиляции (потому чтоObject— единственный тип класса, которому могут быть присвоены массивы). -
Если T является типом интерфейса, то возникает ошибка на этапе компиляции, если T не является типом
java.io.Serializableили типомCloneable(единственными интерфейсами, реализуемыми массивами). -
Если T является переменной типа, то:
-
Если верхней границей T является
Objectилиjava.io.SerializableилиCloneable, или переменная типа, к которой S мог бы быть приведён, то приведение разрешено (хотя и непроверяемо). -
Если верхней границей T является тип массива TC
[], то ошибка на этапе компиляции возникает, если тип SC[]не может быть приведён к типу TC[]. -
В противном случае возникает ошибка на этапе компиляции.
-
-
Если T является типом массива TC
[], то есть массивом компонентов типа TC, то ошибка на этапе компиляции возникает, если не выполняется одно из следующих условий:-
TC и SC — один и тот же примитивный тип.
-
TC и SC — ссылочные типы, и тип SC может быть приведён к типу TC.
-
Пример 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 к T является полностью непроверенным, если приведение от |S| к |T| статически известно как правильное. В противном случае оно является частично непроверенным.
Непроверенное приведение вызывает предупреждение компилятора о непроверенном приведении, если не подавлена аннотация SuppressWarnings (§9.6.3.5).
Приведение считается проверенным, если оно не статически известно как правильное и не является непроверенным.
Если приведение к ссылочному типу не является ошибкой на этапе компиляции, существуют несколько случаев:
-
Приведение статически известно как правильное.
Для такого приведения не выполняется никаких действий во время выполнения.
-
Приведение является полностью непроверенным приведением.
Для такого приведения не выполняется никаких действий во время выполнения.
-
Приведение является частично непроверенным приведением.
Такое приведение требует проверки корректности во время выполнения. Проверка выполняется так, как если бы приведение было проверенным приведением между |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);
}
}
Эта программа использует приведения для компиляции, но она генерирует исключения во время выполнения, потому что типы несовместимы.
Числовое преобразование применяется к операндам арифметического оператора.
Контексты числового преобразования позволяют использовать:
Числовые преобразования используются для преобразования операндов числового оператора к общему типу, чтобы операцию можно было выполнить. Существуют два вида числовых преобразований: унарное числовое преобразование (§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)
-
Выражение индекса в выражении доступа к элементу массива (§15.13)
-
Операнд унарного оператора плюс
+(§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.