Глава 5. Преобразования и контексты
Содержание
- 5.1. Виды преобразований
-
- 5.1.1. Тождественное преобразование
- 5.1.2. Расширяющее преобразование примитивных типов
- 5.1.3. Сужающее преобразование примитивных типов
- 5.1.4. Расширяющее и сужающее преобразование примитивных типов
- 5.1.5. Расширяющее преобразование ссылочных типов
- 5.1.6. Сужающее преобразование ссылочных типов
- 5.1.7. Преобразование в обёртку (boxing)
- 5.1.8. Преобразование из обёртки (unboxing)
- 5.1.9. Непроверяемое преобразование
- 5.1.10. Преобразование захвата (capture)
- 5.1.11. Преобразование в строку
- 5.1.12. Запрещенные преобразования
- 5.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 является подтипом T (§4.10).
Расширяющие преобразования ссылочных типов никогда не требуют специальных действий во время выполнения и, следовательно, никогда не выбрасывают исключение во время выполнения. Они состоят просто в том, что ссылка рассматривается как имеющая какой-то другой тип, что может быть доказано правильным на этапе компиляции.
Тип null не является ссылочным типом (§4.1), поэтому расширяющее преобразование ссылочного типа не существует от типа null к ссылочному типу. Однако многие контексты преобразования явно позволяют преобразовать тип null в ссылочный тип.
Сужение преобразования ссылок рассматривает выражения ссылочного типа S как выражения другого ссылочного типа T, где S не является подтипом T. Поддерживаемые пары типов определены в §5.1.6.1. В отличие от расширения преобразования ссылок, типы не обязательно должны быть напрямую связаны. Однако существуют ограничения, запрещающие преобразование между определенными парами типов, когда статически доказано, что ни одно значение не может иметь оба типа.
Сужение преобразования ссылок может потребовать проверки во время выполнения, чтобы убедиться, что значение типа S является законным значением типа T. Однако из-за отсутствия информации о параметризованном типе во время выполнения некоторые преобразования не могут быть полностью проверены тестом во время выполнения; они помечаются во время компиляции (§5.1.6.2). Для преобразований, которые могут быть полностью проверены тестом во время выполнения, и для определенных преобразований, которые включают информацию о параметризованном типе, но все еще могут быть частично проверены во время выполнения, выбрасывается ClassCastException, если проверка завершается неудачей (§5.1.6.3).
Сужение преобразования ссылок существует из ссылочного типа S в ссылочный тип T, если выполняются все следующие условия:
-
S не является подтипом T (§4.10)
-
Если существует параметризованный тип X, который является супертипом T, и параметризованный тип Y, который является супертипом S, такой, что стирания X и Y одинаковы, то X и Y не являются доказуемо различными (§4.5).
Используя типы из пакета
java.utilв качестве примера, не существует сужения преобразования ссылок изArrayList<String> вArrayList<Object> или наоборот, потому что аргументы типаStringиObjectдоказуемо различны. По той же причине не существует сужения преобразования ссылок изArrayList<String> вList<Object> или наоборот. Отклонение доказуемо различных типов — это простой статический барьер для предотвращения "глупых" сужений преобразования ссылок. -
Один из следующих случаев применяется:
-
S и T являются типами классов, и либо |S|
<:|T| или |T|<:|S|. -
S и T являются типами интерфейсов.
-
S — это тип класса, T — тип интерфейса, и S не называет
finalкласс (§8.1.1). -
S — это тип класса, T — тип интерфейса, и S называет
finalкласс, который реализует интерфейс, названный T. -
S — это тип интерфейса, T — тип класса, и T не называет
finalкласс. -
S — это тип интерфейса, T — тип класса, и T называет
finalкласс, который реализует интерфейс, названный S. -
S — это тип класса
Objectили тип интерфейсаjava.io.SerializableилиCloneable(единственные интерфейсы, реализованные массивами (§10.8)), а T — тип массива. -
S — это тип массива SC
[], то есть массив компонентов типа SC; T — тип массива TC[], то есть массив компонентов типа TC; и существует сужение преобразования ссылок из SC в TC. -
S — это переменная типа, и существует сужение преобразования ссылок из верхней границы S в T.
-
T — это переменная типа, и либо расширение преобразования ссылок, либо сужение преобразования ссылок существует из S в верхнюю границу T.
-
S — это пересекающийся тип S1
&...&Sn, и для всех i (1 ≤ i ≤ n), либо расширение преобразования ссылок, либо сужение преобразования ссылок существует из Si в T. -
T — это пересекающийся тип T1
&...&Tn, и для всех i (1 ≤ i ≤ n), либо расширение преобразования ссылок, либо сужение преобразования ссылок существует из S в Ti.
-
Сужение преобразования ссылок является либо проверенным, либо непроверенным. Эти термины относятся к возможности Java Virtual Machine проверять или не проверять правильность типа преобразования.
Если сужение преобразования ссылок является непроверенным, то Java Virtual Machine не сможет полностью проверить его правильность типа, что может привести к загрязнению кучи (§4.12.2). Чтобы сообщить об этом программисту, непроверенное сужение преобразования ссылок вызывает предупреждение во время компиляции непроверенное предупреждение, если не подавлено @SuppressWarnings (§9.6.4.5). Напротив, если сужение преобразования ссылок не является непроверенным, то оно проверяется; Java Virtual Machine сможет полностью проверить его правильность типа, поэтому предупреждение во время компиляции не выдается.
Непроверенные сужения преобразования ссылок следующие:
-
Сужение преобразования ссылок из типа S в параметризованный класс или тип интерфейса T является непроверенным, если не выполняется хотя бы одно из следующих условий:
-
Все аргументы типа T являются неограниченными wildcards.
-
T
<:S, и S не имеет подтипа X, кроме T, где аргументы типа X не содержатся в аргументах типа T.
-
-
Сужение преобразования ссылок из типа S в переменную типа T является непроверенным.
-
Сужение преобразования ссылок из типа S в пересекающийся тип T1
&...&Tn является непроверенным, если существует Ti (1 ≤ i ≤ n) такой, что S не является подтипом Ti и сужение преобразования ссылок из S в Ti является непроверенным.
Все проверяемые преобразования с сужением ссылки требуют проверки на допустимость во время выполнения. В основном, эти преобразования осуществляются к типам классов и интерфейсов, которые не являются параметризованными.
Некоторые непроверяемые преобразования с сужением ссылки требуют проверки на допустимость во время выполнения. Это зависит от того, является ли непроверяемое преобразование с сужением ссылки полностью непроверяемым или частично непроверяемым. Частично непроверяемое преобразование с сужением ссылки требует проверки на допустимость во время выполнения, в то время как полностью непроверяемое преобразование с сужением ссылки – нет.
Эти термины относятся к совместимости типов, участвующих в преобразовании, если рассматривать их как примитивные типы. Если преобразование концептуально является "расширением", то преобразование является полностью непроверяемым; проверка во время выполнения не нужна, поскольку преобразование является допустимым в системе типов Java Virtual Machine без дженериков. В противоположность этому, если преобразование концептуально является "сужением", то преобразование является частично непроверяемым; даже в системе типов Java Virtual Machine без дженериков необходима проверка во время выполнения для проверки совместимости (примитивных) типов, участвующих в преобразовании.
В качестве примера, используя типы из пакета java.util, преобразование от ArrayList<String> к Collection<T> является полностью непроверяемым, поскольку (примитивный) тип ArrayList является подтипом (примитивного) типа Collection в Java Virtual Machine. В отличие от этого, преобразование от Collection<T> к ArrayList<String> является частично непроверяемым, поскольку (примитивный) тип Collection не является подтипом (примитивного) типа ArrayList в Java Virtual Machine.
Категоризация непроверяемого преобразования с сужением ссылки следующая:
-
Непроверяемое преобразование с сужением ссылки от S к типу T (не пересекающемуся) является полностью непроверяемым, если |S|
<:|T|.В противном случае оно является частично непроверяемым.
-
Непроверяемое преобразование с сужением ссылки от S к пересекающемуся типу T1
&...&Tn является полностью непроверяемым, если для всех i (1 ≤ i ≤ n), либо S<:Ti, либо преобразование с сужением ссылки от S к Ti является полностью непроверяемым.В противном случае оно является частично непроверяемым.
Проверка допустимости во время выполнения для проверяемого или частично непроверяемого преобразования с сужением ссылки выглядит следующим образом:
-
Если значение во время выполнения равно
null, то преобразование разрешено. -
В противном случае, пусть R – класс объекта, на который ссылается значение, а T – стёртая форма (§4.6) типа, к которому осуществляется преобразование. Тогда:
-
Если R – обычный класс (не класс массива):
-
Если T – тип класса, то R должен быть либо тем же классом, что и T (§4.3.4), либо подклассом T, иначе будет брошено исключение
ClassCastException. -
Если T – тип интерфейса, то R должен реализовывать интерфейс T (§8.1.5), иначе будет брошено исключение
ClassCastException. -
Если T – тип массива, то будет брошено исключение
ClassCastException.
-
-
Если R – интерфейс:
Обратите внимание, что R не может быть интерфейсом, когда эти правила применяются впервые для данного преобразования, но R может быть интерфейсом, если правила применяются рекурсивно, потому что значение ссылки во время выполнения может ссылаться на массив, тип элемента которого является типом интерфейса.
-
Если T – тип класса, то T должен быть
Object(§4.3.2), иначе будет брошено исключениеClassCastException. -
Если T – тип интерфейса, то R должен быть либо тем же интерфейсом, что и T, либо подинтерфейсом T, иначе будет брошено исключение
ClassCastException. -
Если T – тип массива, то будет брошено исключение
ClassCastException.
-
-
Если R – класс, представляющий тип массива RC
[], то есть массив компонентов типа RC:-
Если T – тип класса, то T должен быть
Object(§4.3.2), иначе будет брошено исключениеClassCastException. -
Если T – тип интерфейса, то T должен быть типом
java.io.SerializableилиCloneable(единственными интерфейсами, реализуемыми массивами), иначе будет брошено исключениеClassCastException. -
Если T – тип массива TC
[], то есть массив компонентов типа TC, то будет брошено исключениеClassCastException, если TC и RC не являются одним и тем же примитивным типом или если TC и RC – ссылочные типы, и это разрешается рекурсивным применением этих правил во время выполнения.
-
-
Если преобразование осуществляется к пересекающемуся типу T1 & ... & Tn, то для всех i (1 ≤ i ≤ n) любая проверка во время выполнения, необходимая для преобразования от S к Ti, также необходима для преобразования к пересекающемуся типу.
Преобразование в обертку рассматривает выражения примитивного типа как выражения соответствующего ссылочного типа. В частности, следующие девять преобразований называются преобразованиями в обертку:
-
Из типа
booleanв типBoolean -
Из типа
byteв типByte -
Из типа
shortв типShort -
Из типа
charв типCharacter -
Из типа
intв типInteger -
Из типа
longв типLong -
Из типа
floatв типFloat -
Из типа
doubleв типDouble -
Из типа null в тип null
Это правило необходимо, потому что условный оператор (§15.25) применяет преобразование в обертку к типам своих операндов и использует результат в дальнейших вычислениях.
Во время выполнения преобразование в обертку происходит следующим образом:
-
Если
pимеет типboolean, то преобразование в обертку преобразуетpв ссылкуrкласса и типаBoolean, такой чтоr.booleanValue() ==p -
Если
pимеет типbyte, то преобразование в обертку преобразуетpв ссылкуrкласса и типаByte, такой чтоr.byteValue() ==p -
Если
pимеет типchar, то преобразование в обертку преобразуетpв ссылкуrкласса и типаCharacter, такой чтоr.charValue() ==p -
Если
pимеет типshort, то преобразование в обертку преобразуетpв ссылкуrкласса и типаShort, такой чтоr.shortValue() ==p -
Если
pимеет типint, то преобразование в обертку преобразуетpв ссылкуrкласса и типаInteger, такой чтоr.intValue() ==p -
Если
pимеет типlong, то преобразование в обертку преобразуетpв ссылкуrкласса и типаLong, такой чтоr.longValue() ==p -
Если
pимеет типfloat, то:-
Если
pне равно NaN, то преобразование в обертку преобразуетpв ссылкуrкласса и типаFloat, такой чтовычисляется какr.floatValue()p -
В противном случае, преобразование в обертку преобразует
pв ссылкуrкласса и типаFloat, такой чтовычисляется какr.isNaN()true
-
-
Если
pимеет типdouble, то:-
Если
pне равно NaN, то преобразование в обертку преобразуетpв ссылкуrкласса и типаDouble, такой чтовычисляется какr.doubleValue()p -
В противном случае, преобразование в обертку преобразует
pв ссылкуrкласса и типаDouble, такой чтовычисляется какr.isNaN()true
-
-
Если
pимеет любой другой тип, то преобразование в обертку эквивалентно преобразованию тождества (§5.1.1).
Если значение p, которое нужно преобразовать в обертку, является результатом вычисления константного выражения (§15.28) типа boolean, char, short, int или long, и результат равен true, false, символу в диапазоне '\u0000' до '\u007f' включительно или целому числу в диапазоне -128 до 127 включительно, то пусть a и b будут результатами любых двух преобразований в обертку p. Всегда выполняется a == b.
В идеале преобразование в обертку примитивного значения всегда должно возвращать идентичную ссылку. На практике это может быть невозможным при использовании существующих методов реализации. Приведенное выше правило является прагматическим компромиссом, требующим, чтобы определенные общие значения всегда были упакованы в неразличимые объекты. Реализация может кешировать их, лениво или сразу. Для других значений это правило запрещает разработчику делать какие-либо предположения об идентичности упакованных значений. Это позволяет (но не требует) совместного использования некоторых или всех этих ссылок.
Это гарантирует, что в большинстве распространенных случаев поведение будет желаемым, без чрезмерной потери производительности, особенно на небольших устройствах. Реализации с меньшим ограничением по памяти могут, например, кэшировать все значения char и short, а также значения int и long в диапазоне от -32K до +32K.
Преобразование в обертку может привести к OutOfMemoryError, если необходимо создать новый экземпляр одного из классов-оберток (Boolean, Byte, Character, Short, Integer, Long, Float или Double), а доступной памяти недостаточно.
Преобразование распаковки рассматривает выражения ссылочного типа как выражения соответствующего примитивного типа. В частности, следующие восемь преобразований называются преобразованиями распаковки:
-
Из типа
Booleanв типboolean -
Из типа
Byteв типbyte -
Из типа
Shortв типshort -
Из типа
Characterв типchar -
Из типа
Integerв типint -
Из типа
Longв типlong -
Из типа
Floatв типfloat -
Из типа
Doubleв типdouble
Во время выполнения преобразование распаковки происходит следующим образом:
-
Если
rявляется ссылкой типаBoolean, то преобразование распаковки преобразуетrвr.booleanValue() -
Если
rявляется ссылкой типаByte, то преобразование распаковки преобразуетrвr.byteValue() -
Если
rявляется ссылкой типаCharacter, то преобразование распаковки преобразуетrвr.charValue() -
Если
rявляется ссылкой типаShort, то преобразование распаковки преобразуетrвr.shortValue() -
Если
rявляется ссылкой типаInteger, то преобразование распаковки преобразуетrвr.intValue() -
Если
rявляется ссылкой типаLong, то преобразование распаковки преобразуетrвr.longValue() -
Если
rявляется ссылкой типаFloat, то преобразование распаковки преобразуетrвr.floatValue() -
Если
rявляется ссылкой типаDouble, то преобразование распаковки преобразуетrвr.doubleValue() -
Если
rявляетсяnull, преобразование распаковки выбрасываетNullPointerException
Говорят, что тип преобразуется в числовой тип, если это числовой тип (§4.2) или это ссылочный тип, который может быть преобразован в числовой тип преобразованием распаковки.
Говорят, что тип преобразуется в целочисленный тип, если это целочисленный тип или это ссылочный тип, который может быть преобразован в целочисленный тип преобразованием распаковки.
Пусть G обозначает объявление обобщённого типа с n параметрами типа.
Существует преобразование без проверки от исходного класса или интерфейсного типа (§4.8) G к любому параметризованному типу вида G<T1,...,Tn>.
Существует преобразование без проверки от исходного массива типа G[]k к любому массивному типу вида G<T1,...,Tn>[]k. (Запись []k обозначает массив типа с k измерениями.)
Использование преобразования без проверки вызывает предупреждение о компиляции без проверки, если все аргументы типа Ti (1 ≤ i ≤ n) являются неограниченными подстановочными знаками (§4.5.1), или предупреждение подавлено с помощью @SuppressWarnings (§9.6.4.5).
Преобразование без проверки используется для обеспечения бесшовного взаимодействия устаревшего кода, написанного до появления обобщённых типов, с библиотеками, которые были преобразованы для использования обобщённости (процесс, который мы называем обобщением). В таких обстоятельствах (прежде всего, клиенты Collections Framework в java.util), устаревший код использует сырые типы (например, Collection вместо Collection<String>). Выражения сырых типов передаются в качестве аргументов методам библиотек, которые используют параметризованные версии тех же типов как типы соответствующих формальных параметров.
Такие вызовы не могут быть доказаны статически безопасными в системе типов с использованием обобщений. Отклонение таких вызовов сделало бы недействительными большие объёмы существующего кода и помешало бы им использовать более новые версии библиотек. Это, в свою очередь, отбило бы у поставщиков библиотек желание использовать обобщения. Чтобы предотвратить такой нежелательный поворот событий, сырой тип может быть преобразован в любое вызов объявления обобщённого типа, к которому относится сырой тип. Хотя преобразование некорректно, оно допускается как уступка практичности. В таких случаях выдаётся предупреждение о преобразовании без проверки.
Пусть G обозначает объявление универсального типа (§8.1.2, §9.1.2) с n параметрами типа A1,...,An с соответствующими ограничениями U1,...,Un.
Существует преобразование захвата от параметризованного типа G<T1,...,Tn> (§4.5) к параметризованному типу G<S1,...,Sn>, где для 1 ≤ i ≤ n :
-
Если Ti является аргументом типа-подстановки (§4.5.1) вида
?, то Si является свежим переменной типа, верхняя граница которой равна Ui[A1:=S1,...,An:=Sn], а нижняя граница — нулевой тип (§4.1). -
Если Ti является аргументом типа-подстановки вида
?extendsBi, то Si — это свежая переменная типа, верхняя граница которой равна glb(Bi, Ui[A1:=S1,...,An:=Sn]), а нижняя граница — нулевой тип.glb(V1,...,Vm) определяется как V1
&...&Vm.Если для любых двух классов (не интерфейсов) Vi и Vj Vi не является подклассом Vj или наоборот, это ошибка времени компиляции.
-
Если Ti является аргументом типа-подстановки вида
?superBi, то Si — это свежая переменная типа, верхняя граница которой равна Ui[A1:=S1,...,An:=Sn], а нижняя граница — Bi. -
В противном случае Si = Ti.
Преобразование захвата для любого типа, кроме параметризованного типа (§4.5), действует как преобразование тождества (§5.1.1).
Преобразование захвата не применяется рекурсивно.
Преобразование захвата никогда не требует специальных действий во время выполнения и, следовательно, никогда не генерирует исключение во время выполнения.
Преобразование захвата разработано для повышения полезности подстановочных типов. Для понимания мотивации давайте начнем с рассмотрения метода java.util.Collections.reverse():
public static void reverse(List<?> list);
Метод инвертирует переданный в качестве параметра список. Он работает для любого типа списка, и поэтому использование подстановочного типа List<?> в качестве типа формального параметра полностью оправдано.
Теперь рассмотрим, как бы реализовали reverse():
public static void reverse(List<?> list) { rev(list); }
private static <T> void rev(List<T> list) {
List<T> tmp = new ArrayList<T>(list);
for (int i = 0; i < list.size(); i++) {
list.set(i, tmp.get(list.size() - i - 1));
}
}
Реализация требует копирования списка, извлечения элементов из копии и их вставки в оригинал. Для выполнения этой задачи безопасным способом с точки зрения типов нам необходимо присвоить имя, T, типу элементов входного списка. Мы делаем это в частном служебном методе rev(). Это требует передачи входного списка аргументов, имеющего тип List<?>, в качестве аргумента методу rev(). В общем случае List<?> — это список неизвестного типа. Он не является подтипом List<T> для любого типа T. Разрешение такого отношения подтипизации было бы некорректным. Учитывая метод:
public static <T> void fill(List<T> l, T obj)
следующий код нарушил бы систему типов:
List<String> ls = new ArrayList<String>();
List<?> l = ls;
Collections.fill(l, new Object()); // not legal - but assume it was!
String s = ls.get(0); // ClassCastException - ls contains
// Objects, not Strings.
Таким образом, без каких-либо особых исключений мы видим, что вызов из reverse() в rev() будет запрещен. В этом случае автору reverse() пришлось бы записать его сигнатуру как:
public static <T> void reverse(List<T> list)
Это нежелательно, так как оно раскрывает информацию о реализации вызывающей стороне. Хуже того, разработчик API может сделать вывод, что сигнатура с использованием подстановочного типа — то, что требуют вызывающие стороны API, и только позже осознает, что безопасная реализация типа была недоступна.
Вызов из reverse() в rev() на самом деле безвреден, но его нельзя оправдать на основе общего отношения подтипизации между List<?> и List<T>. Вызов безвреден, потому что входной аргумент, безусловно, является списком определенного типа (хотя и неизвестным). Если мы можем захватить этот неизвестный тип в переменной типа X, мы можем сделать вывод, что T является X. В этом суть преобразования захвата. Спецификация, конечно, должна справиться с усложнениями, такими как нетривиальные (и, возможно, рекурсивно определенные) верхние или нижние границы, наличие нескольких аргументов и т. д.
Математически продвинутые читатели захотят сопоставить преобразование захвата с известными понятиями теории типов. Читатели, незнакомые с теорией типов, могут пропустить эту дискуссию — или же изучить соответствующий текст, например, Типы и языки программирования Бенджамина Пирса, а затем вернуться к этому разделу.
Вот краткий обзор взаимоотношения преобразования захвата с известными понятиями теории типов. Типы-подстановки являются ограниченной формой экзистенциальных типов. Преобразование захвата в некотором роде соответствует открытию значения экзистенциального типа. Преобразование захвата выражения e можно рассматривать как открытие open e в области, которая включает в себя выражение верхнего уровня, которое охватывает e.
Классическая операция open над экзистенциальными значениями требует, чтобы захваченная переменная типа не выходила за пределы открытого выражения. Преобразование 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.
Преобразование набора значений — это процесс сопоставления значения с плавающей точкой из одного набора значений в другой без изменения его типа.
В выражении, которое не является 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,ShortилиCharacter, и значение константного выражения представимо в типеbyte,shortилиcharсоответственно.
Время компиляции сужает константные выражения, что означает, что код, такой как:
byte theAnswer = 42;
допускается. Без сужения тот факт, что целочисленная литерал 42 имеет тип int, означал бы, что требуется приведение к типу byte:
byte theAnswer = (byte)42; // cast is permitted but not required
Наконец, значение типа null (единственное такое значение — null-ссылка) может быть присвоено любому ссылочному типу, в результате чего получится null-ссылка соответствующего типа.
Ошибка времени компиляции возникает, если цепочка преобразований содержит два параметризованных типа, которые не находятся в подтиповом отношении (§4.10).
Пример такой недопустимой цепочки:
Integer, Comparable<Integer>, Comparable, Comparable<String>
Первые три элемента цепочки связаны расширяющим ссылочным преобразованием, а последний элемент получен из предыдущего неконтролируемым преобразованием. Однако это не является допустимым преобразованием присваивания, потому что цепочка содержит два параметризованных типа, Comparable<Integer> и Comparable<String>, которые не являются подтипами.
Если тип выражения может быть преобразован в тип переменной с помощью преобразования присваивания, мы говорим, что выражение (или его значение) присваиваемо переменной или, что эквивалентно, что тип выражения совместим по присваиванию с типом переменной.
Если тип переменной — float или double, то к значению v, которое является результатом преобразования(ий), применяется преобразование множества значений (§5.1.13):
-
Если
vимеет типfloatи является элементом множества значений с плавающей точкой с расширенным порядком, то реализация должна сопоставитьvс ближайшим элементом множества значений с плавающей точкой. Это преобразование может привести к переполнению или недополнению. -
Если
vимеет типdoubleи является элементом множества значений с двойной точкой с расширенным порядком, то реализация должна сопоставитьvс ближайшим элементом множества значений с двойной точкой. Это преобразование может привести к переполнению или недополнению.
Единственные исключения, которые могут возникнуть из преобразований в контексте присваивания, это:
-
ClassCastException, если после применения вышеперечисленных преобразований результирующее значение является объектом, который не является экземпляром подкласса или подинтерфейса стирания (§4.6) типа переменной.Эта ситуация может возникнуть только в результате загрязнения кучи (§4.12.2). На практике реализации требуется выполнять приведение типов только при обращении к полю или методу объекта параметризованного типа, когда стёртый тип поля или стёртый возвращаемый тип метода отличаются от нестёртого типа.
-
OutOfMemoryErrorв результате преобразования упаковки. -
NullPointerExceptionв результате преобразования распаковки нулевой ссылки. -
ArrayStoreExceptionв особых случаях, связанных с элементами массива или обращением к полям (§10.5, §15.26.1).
Пример 5.2-1. Присваивание для примитивных типов
class Test {
public static void main(String[] args) {
short s = 12; // narrow 12 to short
float f = s; // widen short to float
System.out.println("f=" + f);
char c = '\u0123';
long l = c; // widen char to long
System.out.println("l=0x" + Long.toString(l,16));
f = 1.23f;
double d = f; // widen float to double
System.out.println("d=" + d);
}
}
Эта программа выводит:
f=12.0 l=0x123 d=1.2300000190734863
Однако следующая программа выводит ошибки времени компиляции:
class Test {
public static void main(String[] args) {
short s = 123;
char c = s; // error: would require cast
s = c; // error: would require cast
}
}
потому что не все значения short являются значениями char, и ни все значения char являются значениями short.
Пример 5.2-2. Присваивание для ссылочных типов
class Point { int x, y; }
class Point3D extends Point { int z; }
interface Colorable { void setColor(int color); }
class ColoredPoint extends Point implements Colorable {
int color;
public void setColor(int color) { this.color = color; }
}
class Test {
public static void main(String[] args) {
// Assignments to variables of class type:
Point p = new Point();
p = new Point3D();
// OK because Point3D is a subclass of Point
Point3D p3d = p;
// Error: will require a cast because a Point
// might not be a Point3D (even though it is,
// dynamically, in this example.)
// Assignments to variables of type Object:
Object o = p; // OK: any object to Object
int[] a = new int[3];
Object o2 = a; // OK: an array to Object
// Assignments to variables of interface type:
ColoredPoint cp = new ColoredPoint();
Colorable c = cp;
// OK: ColoredPoint implements Colorable
// Assignments to variables of array type:
byte[] b = new byte[4];
a = b;
// Error: these are not arrays of the same primitive type
Point3D[] p3da = new Point3D[3];
Point[] pa = p3da;
// OK: since we can assign a Point3D to a Point
p3da = pa;
// Error: (cast needed) since a Point
// can't be assigned to a Point3D
}
}
Следующая тестовая программа иллюстрирует преобразования присваивания для ссылочных значений, но не компилируется, как описано в её комментариях. Этот пример следует сравнить с предыдущим.
class Point { int x, y; }
interface Colorable { void setColor(int color); }
class ColoredPoint extends Point implements Colorable {
int color;
public void setColor(int color) { this.color = color; }
}
class Test {
public static void main(String[] args) {
Point p = new Point();
ColoredPoint cp = new ColoredPoint();
// Okay because ColoredPoint is a subclass of Point:
p = cp;
// Okay because ColoredPoint implements Colorable:
Colorable c = cp;
// The following cause compile-time errors because
// we cannot be sure they will succeed, depending on
// the run-time type of p; a run-time check will be
// necessary for the needed narrowing conversion and
// must be indicated by including a cast:
cp = p; // p might be neither a ColoredPoint
// nor a subclass of ColoredPoint
c = p; // p might not implement Colorable
}
}
Пример 5.2-3. Присваивание для массивов
class Point { int x, y; }
class ColoredPoint extends Point { int color; }
class Test {
public static void main(String[] args) {
long[] veclong = new long[100];
Object o = veclong; // okay
Long l = veclong; // compile-time error
short[] vecshort = veclong; // compile-time error
Point[] pvec = new Point[100];
ColoredPoint[] cpvec = new ColoredPoint[100];
pvec = cpvec; // okay
pvec[0] = new Point(); // okay at compile time,
// but would throw an
// exception at run time
cpvec = pvec; // compile-time error
}
}
В этом примере:
-
Значение
veclongне может быть присвоено переменной типаLong, так какLong— это тип класса, отличный отObject. Массив может быть присвоен только переменной совместимого типа массива или переменной типаObject,Cloneableилиjava.io.Serializable. -
Значение
veclongне может быть присвоено переменнойvecshort, так как это массивы примитивного типа, а типыshortиlongне являются одним и тем же примитивным типом. -
Значение
cpvecможет быть присвоено переменнойpvec, так как любая ссылка, которая может быть значением выражения типаColoredPoint, может быть значением переменной типаPoint. Следующее присваивание новогоPointэлементу массиваpvecбросит исключениеArrayStoreException(если программа будет исправлена для компиляции), так как массив типаColoredPointне может иметь значение элемента типаPoint. -
Значение
pvecне может быть присвоено переменнойcpvec, так как не каждая ссылка, которая может быть значением выражения типаPoint, может быть правильно значением переменной типаColoredPoint. Если значениеpvecв ходе выполнения является ссылкой на экземплярPoint[], и присваивание переменнойcpvecбудет разрешено, то простая ссылка на компонентcpvec, например,cpvec[0], может вернутьPoint, аPointне являетсяColoredPoint. Поэтому разрешение такого присваивания позволит нарушить систему типов. Приведение (§5.5, §15.16) может быть использовано для обеспечения того, чтоpvecссылается наColoredPoint[]:cpvec = (ColoredPoint[])pvec; // OK, but may throw an // exception at run time
Контексты вызова позволяют присваивать значение аргумента в вызове метода или конструктора (§8.8.7.1, §15.9, §15.12) соответствующему формальному параметру.
Жесткие контексты вызова позволяют использовать одно из следующих преобразований:
Гибкие контексты вызова допускают более широкий набор преобразований, поскольку они используются только для конкретного вызова, если не удалось найти соответствующее объявление с использованием жестких контекстов вызова. Гибкие контексты вызова позволяют использовать одно из следующих преобразований:
-
тождественное преобразование (§5.1.1)
-
расширяющее преобразование примитивных типов (§5.1.2)
-
расширяющее преобразование ссылочных типов (§5.1.5)
-
расширяющее преобразование ссылочных типов, за которым следует преобразование распаковки
-
расширяющее преобразование ссылочных типов, за которым следует преобразование распаковки, а затем расширяющее преобразование примитивных типов
-
преобразование упаковки (§5.1.7)
-
преобразование упаковки, за которым следует расширяющее преобразование ссылочных типов
-
преобразование распаковки (§5.1.8)
-
преобразование распаковки, за которым следует расширяющее преобразование примитивных типов
Если после применения преобразований, указанных для контекста вызова, результирующий тип является сырым типом (§4.8), может быть применено преобразование без проверки (§5.1.9).
Значение типа null (единственное такое значение — ссылка null) может быть присвоено любому типу ссылки.
Ошибка компиляции, если цепочка преобразований содержит два параметризованных типа, не находящихся в отношении подтипа (§4.10).
Если тип выражения аргумента равен 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, а также большее их сочетание.
Если выражение имеет примитивный тип, то контекст приведения типов допускает использование одного из следующих:
-
тождественное преобразование (§5.1.1)
-
расширяющее примитивное преобразование (§5.1.2)
-
сужающее примитивное преобразование (§5.1.3)
-
расширяющее и сужающее примитивное преобразование (§5.1.4)
-
преобразование в обёртку (§5.1.7)
-
преобразование в обёртку, за которым следует расширяющее ссылочное преобразование (§5.1.5)
Если выражение имеет ссылочный тип, то контекст приведения типов допускает использование одного из следующих:
-
тождественное преобразование (§5.1.1)
-
расширяющее ссылочное преобразование (§5.1.5)
-
расширяющее ссылочное преобразование, за которым следует преобразование разворачивания
-
расширяющее ссылочное преобразование, за которым следует преобразование разворачивания, затем расширяющее примитивное преобразование
-
сужающее ссылочное преобразование (§5.1.6)
-
сужающее ссылочное преобразование, за которым следует преобразование разворачивания
-
преобразование разворачивания (§5.1.8)
-
преобразование разворачивания, за которым следует расширяющее примитивное преобразование
Если выражение имеет тип null, то выражение можно привести к любому ссылочному типу.
Если в контексте приведения типов используется сужающее ссылочное преобразование, которое проверяется или частично не проверяется (§5.1.6.2, §5.1.6.3), то будет выполнена проверка класса значения выражения во время выполнения, что может вызвать ClassCastException. В противном случае проверка во время выполнения не выполняется.
Преобразование множества значений (§5.1.13) применяется после преобразования типа.
В следующих таблицах перечислены преобразования, используемые в определенных контекстах приведения типов. Каждое преобразование обозначается символом:
-
- означает, что преобразование не разрешено
-
≈ означает тождественное преобразование (§5.1.1)
-
ω означает расширяющее примитивное преобразование (§5.1.2)
-
η означает сужающее примитивное преобразование (§5.1.3)
-
ωη означает расширяющее и сужающее примитивное преобразование (§5.1.4)
-
⇑ означает расширяющее ссылочное преобразование (§5.1.5)
-
⇓ означает сужающее ссылочное преобразование (§5.1.6)
-
⊕ означает преобразование в обёртку (§5.1.7)
-
⊗ означает преобразование разворачивания (§5.1.8)
В таблицах запятая между символами означает, что контекст приведения типов использует одно преобразование, за которым следует другое. Тип Object означает любой ссылочный тип, кроме восьми классов-обёрток Boolean, Byte, Short, Character, Integer, Long, Float, Double.
Таблица 5.5-A. Преобразование в примитивные типы
| В → | byte | short | char | int | long | float | double | boolean |
|---|---|---|---|---|---|---|---|---|
| Из ↓ | ||||||||
byte | ≈ | ω | ωη | ω | ω | ω | ω | - |
short | η | ≈ | η | ω | ω | ω | ω | - |
char | η | η | ≈ | ω | ω | ω | ω | - |
int | η | η | η | ≈ | ω | ω | ω | - |
long | η | η | η | η | ≈ | ω | ω | - |
float | η | η | η | η | η | ≈ | ω | - |
double | η | η | η | η | η | η | ≈ | - |
boolean | - | - | - | - | - | - | - | ≈ |
Byte | ⊗ | ⊗,ω | - | ⊗,ω | ⊗,ω | ⊗,ω | ⊗,ω | - |
Short | - | ⊗ | - | ⊗,ω | ⊗,ω | ⊗,ω | ⊗,ω | - |
Character | - | - | ⊗ | ⊗,ω | ⊗,ω | ⊗,ω | ⊗,ω | - |
Integer | - | - | - | ⊗ | ⊗,ω | ⊗,ω | ⊗,ω | - |
Long | - | - | - | - | ⊗ | ⊗,ω | ⊗,ω | - |
Float | - | - | - | - | - | ⊗ | ⊗,ω | - |
Double | - | - | - | - | - | - | ⊗ | - |
Boolean | - | - | - | - | - | - | - | ⊗ |
Object | ⇓,⊗ | ⇓,⊗ | ⇓,⊗ | ⇓,⊗ | ⇓,⊗ | ⇓,⊗ | ⇓,⊗ | ⇓,⊗ |
Таблица 5.5-B. Преобразование в типы ссылок
| В → | Byte | Short | Character | Integer | Long | Float | Double | Boolean | Object |
|---|---|---|---|---|---|---|---|---|---|
| Из ↓ | |||||||||
byte | ⊕ | - | - | - | - | - | - | - | ⊕,⇑ |
short | - | ⊕ | - | - | - | - | - | - | ⊕,⇑ |
char | - | - | ⊕ | - | - | - | - | - | ⊕,⇑ |
int | - | - | - | ⊕ | - | - | - | - | ⊕,⇑ |
long | - | - | - | - | ⊕ | - | - | - | ⊕,⇑ |
float | - | - | - | - | - | ⊕ | - | - | ⊕,⇑ |
double | - | - | - | - | - | - | ⊕ | - | ⊕,⇑ |
boolean | - | - | - | - | - | - | - | ⊕ | ⊕,⇑ |
Byte | ≈ | - | - | - | - | - | - | - | ⇑ |
Short | - | ≈ | - | - | - | - | - | - | ⇑ |
Character | - | - | ≈ | - | - | - | - | - | ⇑ |
Integer | - | - | - | ≈ | - | - | - | - | ⇑ |
Long | - | - | - | - | ≈ | - | - | - | ⇑ |
Float | - | - | - | - | - | ≈ | - | - | ⇑ |
Double | - | - | - | - | - | - | ≈ | - | ⇑ |
Boolean | - | - | - | - | - | - | - | ≈ | ⇑ |
Object | ⇓ | ⇓ | ⇓ | ⇓ | ⇓ | ⇓ | ⇓ | ⇓ | ≈ |
Пример 5.5-1. Преобразование для типов ссылок
class Point { int x, y; }
interface Colorable { void setColor(int color); }
class ColoredPoint extends Point implements Colorable {
int color;
public void setColor(int color) { this.color = color; }
}
final class EndPoint extends Point {}
class Test {
public static void main(String[] args) {
Point p = new Point();
ColoredPoint cp = new ColoredPoint();
Colorable c;
// The following may cause errors at run time because
// we cannot be sure they will succeed; this possibility
// is suggested by the casts:
cp = (ColoredPoint)p; // p might not reference an
// object which is a ColoredPoint
// or a subclass of ColoredPoint
c = (Colorable)p; // p might not be Colorable
// The following are incorrect at compile time because
// they can never succeed as explained in the text:
Long l = (Long)p; // compile-time error #1
EndPoint e = new EndPoint();
c = (Colorable)e; // compile-time error #2
}
}
Здесь возникает первая ошибка на этапе компиляции, потому что типы классов Long и Point не связаны (то есть они не одинаковы, и ни один не является подклассом другого), поэтому преобразование между ними всегда завершится неудачей.
Вторая ошибка на этапе компиляции возникает, потому что переменная типа EndPoint никогда не может ссылаться на значение, реализующее интерфейс Colorable. Это происходит потому, что EndPoint является типом final, а переменная типа final всегда содержит значение того же времени выполнения, что и ее тип компиляции. Следовательно, тип времени выполнения переменной e должен точно соответствовать типу EndPoint, а тип EndPoint не реализует Colorable.
Пример 5.5-2. Преобразование для типов массивов
class Point {
int x, y;
Point(int x, int y) { this.x = x; this.y = y; }
public String toString() { return "("+x+","+y+")"; }
}
interface Colorable { void setColor(int color); }
class ColoredPoint extends Point implements Colorable {
int color;
ColoredPoint(int x, int y, int color) {
super(x, y); setColor(color);
}
public void setColor(int color) { this.color = color; }
public String toString() {
return super.toString() + "@" + color;
}
}
class Test {
public static void main(String[] args) {
Point[] pa = new ColoredPoint[4];
pa[0] = new ColoredPoint(2, 2, 12);
pa[1] = new ColoredPoint(4, 5, 24);
ColoredPoint[] cpa = (ColoredPoint[])pa;
System.out.print("cpa: {");
for (int i = 0; i < cpa.length; i++)
System.out.print((i == 0 ? " " : ", ") + cpa[i]);
System.out.println(" }");
}
}
Эта программа компилируется без ошибок и выводит:
cpa: { (2,2)@12, (4,5)@24, null, null }
Пример 5.5-3. Преобразование несовместимых типов во время выполнения
class Point { int x, y; }
interface Colorable { void setColor(int color); }
class ColoredPoint extends Point implements Colorable {
int color;
public void setColor(int color) { this.color = color; }
}
class Test {
public static void main(String[] args) {
Point[] pa = new Point[100];
// The following line will throw a ClassCastException:
ColoredPoint[] cpa = (ColoredPoint[])pa;
System.out.println(cpa[0]);
int[] shortvec = new int[2];
Object o = shortvec;
// The following line will throw a ClassCastException:
Colorable c = (Colorable)o;
c.setColor(0);
}
}
Эта программа использует преобразования для компиляции, но во время выполнения она генерирует исключения, потому что типы несовместимы.
Числовые контексты применяются к операндам арифметического оператора.
Числовые контексты позволяют использовать:
-
преобразование тождества (§5.1.1)
-
расширяющее преобразование примитивного типа (§5.1.2)
-
расширяющее преобразование ссылочного типа (§5.1.5) , за которым следует преобразование распаковки
-
расширяющее преобразование ссылочного типа, за которым следует преобразование распаковки, а затем расширяющее преобразование примитивного типа
-
преобразование распаковки (§5.1.8)
-
преобразование распаковки, за которым следует расширяющее преобразование примитивного типа
Числовое повышение — это процесс, в котором, задав арифметический оператор и его выражения-аргументы, аргументы преобразуются в предположительный целевой тип 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.