Spec-Zone.ru › Java Language Specification 8

Глава 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.5.1. Приведение типов ссылочных типов
5.5.2. Проверяемые и непроверяемые приведения
5.5.3. Проверяемые приведения во время выполнения
5.6. Численные контексты
5.6.1. Унарное повышение числовых типов
5.6.2. Двойное повышение числовых типов

Каждое выражение, написанное на языке программирования 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

5.1. Виды преобразований

Конкретные типы преобразований в языке программирования Java разделены на 13 категорий.

5.1.1. Преобразование тождества

Преобразование типа в тот же тип разрешено для любого типа.

Это может показаться тривиальным, но у этого есть два практических последствия. Во-первых, всегда разрешается, чтобы выражение имело нужный тип с самого начала, что позволяет просто утверждать, что каждое выражение подвержено преобразованию, если только не требуется тривиальное преобразование тождества. Во-вторых, это подразумевает, что программа может содержать избыточные операторы приведения для большей ясности.

5.1.2. Расширяющее преобразование примитивных типов

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 не точны до девяти значащих цифр.


5.1.3. Сужающее преобразование примитивных типов

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 выполняется в два этапа:

  1. На первом этапе число с плавающей точкой преобразуется в long, если T является long, или в int, если T является byte, short, char или int, следующим образом:

    • Если число с плавающей точкой является NaN (§4.2.3), результатом первого шага преобразования является int или long 0.

    • В противном случае, если число с плавающей точкой не является бесконечностью, значение с плавающей точкой округляется до целого значения V, используя режим округления IEEE 754 к нулю (§4.2.3). Затем следуют два случая:

      1. Если T является long, и это целое значение может быть представлено как long, то результатом первого шага является значение long V.

      2. В противном случае, если это целое значение может быть представлено как int, то результатом первого шага является значение int V.

    • В противном случае должен выполняться один из следующих двух случаев:

      1. Значение слишком мало (отрицательное значение большой величины или отрицательная бесконечность), и результатом первого шага является наименьшее представимое значение типа int или long.

      2. Значение слишком велико (положительное значение большой величины или положительная бесконечность), и результатом первого шага является наибольшее представимое значение типа int или long.

  2. На втором шаге:

    • Если 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

5.1.4. Расширяющее и сужающее преобразование примитивных типов

Следующее преобразование объединяет расширяющее и сужающее преобразования примитивных типов:

  • byte в char

Сначала byte преобразуется в int с помощью расширяющего преобразования примитивного типа (§5.1.2), а затем полученное int преобразуется в char с помощью сужающего преобразования примитивного типа (§5.1.3).

5.1.5. Расширяющее преобразование ссылок

Существует расширяющее преобразование ссылки от любого типа ссылки S к любому типу ссылки T, при условии, что S является подтипом (§4.10) T.

Расширяющие преобразования ссылок никогда не требуют специального действия во время выполнения и, следовательно, никогда не вызывают исключение во время выполнения. Они просто заключаются в рассмотрении ссылки как имеющей другой тип таким образом, что это может быть доказано правильным во время компиляции.

5.1.6. Преобразование сужения ссылок

Шесть видов преобразований называются преобразованиями сужения ссылок:

  • От любого ссылочного типа 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.

5.1.7. Преобразование упаковки

Преобразование упаковки преобразует выражения примитивного типа в соответствующие выражения ссылочного типа. В частности, следующие девять преобразований называются преобразованиями упаковки:

  • От типа boolean к типу Boolean

  • От типа byte к типу Byte

  • От типа short к типу Short

  • От типа char к типу Character

  • От типа int к типу Integer

  • От типа long к типу Long

  • От типа float к типу Float

  • От типа double к типу Double

  • От нулевого типа к нулевому типу

    Это правило необходимо, потому что условный оператор (§15.25) применяет преобразование упаковки к типам своих операндов и использует результат в дальнейших вычислениях.

Во время выполнения преобразование упаковки выполняется следующим образом:

  • Если p является значением типа boolean, то преобразование упаковки преобразует p в ссылку r класса и типа Boolean, таким образом, что r.booleanValue() == p

  • Если p является значением типа byte, то преобразование упаковки преобразует p в ссылку r класса и типа Byte, таким образом, что r.byteValue() == p

  • Если p является значением типа char, то преобразование упаковки преобразует p в ссылку r класса и типа Character, таким образом, что r.charValue() == p

  • Если p является значением типа short, то преобразование упаковки преобразует p в ссылку r класса и типа Short, таким образом, что r.shortValue() == p

  • Если p является значением типа int, то преобразование упаковки преобразует p в ссылку r класса и типа Integer, таким образом, что r.intValue() == p

  • Если p является значением типа long, то преобразование упаковки преобразует p в ссылку r класса и типа Long, таким образом, что r.longValue() == p

  • Если p является значением типа float, то:

    • Если p не является NaN, то преобразование упаковки преобразует p в ссылку r класса и типа Float, таким образом, что r.floatValue() вычисляется как p

    • В противном случае преобразование упаковки преобразует p в ссылку r класса и типа Float, таким образом, что r.isNaN() вычисляется как true

  • Если p является значением типа double, то:

    • Если p не является NaN, преобразование упаковки преобразует p в ссылку r класса и типа Double, таким образом, что r.doubleValue() вычисляется как p

    • В противном случае преобразование упаковки преобразует p в ссылку r класса и типа Double, таким образом, что r.isNaN() вычисляется как true

  • Если p является значением любого другого типа, преобразование упаковки эквивалентно преобразованию идентичности (§5.1.1).

Если значение p, которое упаковывается, является целочисленным литералом типа int между -128 и 127 включительно (§3.10.1), или логическим литералом true или false (§3.10.3), или символьным литералом между '\u0000' и '\u007f' включительно (§3.10.4), то пусть a и b будут результатами любых двух преобразований упаковки p. Всегда верно, что a == b.

В идеале упаковка примитивного значения всегда должна давать идентичную ссылку. На практике это может быть невыполнимо с использованием существующих методов реализации. Приведенное выше правило представляет собой прагматичный компромисс, требующий, чтобы определенные общие значения всегда упаковывались в неразличимые объекты. Реализация может кэшировать их, лениво или усердно. Для других значений правило не допускает каких-либо предположений об идентичности упакованных значений со стороны программиста. Это допускает (но не требует) совместное использование некоторых или всех этих ссылок. Обратите внимание, что целочисленные литералы типа long разрешены, но не требуются, для совместного использования.

Это гарантирует, что в большинстве общих случаев поведение будет желаемым, не налагая чрезмерного снижения производительности, особенно на небольших устройствах. Реализации с меньшими ограничениями по памяти могут, например, кэшировать все char и short значения, а также int и long значения в диапазоне от -32K до +32K.

Преобразование упаковки может привести к OutOfMemoryError, если необходимо выделить новый экземпляр одного из классов-оберток (Boolean, Byte, Character, Short, Integer, Long, Float или Double) и недостаточно памяти.

5.1.8. Преобразование разboxing

Преобразование разboxing преобразует выражения ссылочного типа в соответствующие выражения примитивного типа. В частности, следующие восемь преобразований называются преобразованиями разboxing:

  • Из типа Boolean в тип boolean

  • Из типа Byte в тип byte

  • Из типа Short в тип short

  • Из типа Character в тип char

  • Из типа Integer в тип int

  • Из типа Long в тип long

  • Из типа Float в тип float

  • Из типа Double в тип double

Во время выполнения преобразование разboxing происходит следующим образом:

  • Если r является ссылкой типа Boolean, то преобразование разboxing преобразует r в r.booleanValue()

  • Если r является ссылкой типа Byte, то преобразование разboxing преобразует r в r.byteValue()

  • Если r является ссылкой типа Character, то преобразование разboxing преобразует r в r.charValue()

  • Если r является ссылкой типа Short, то преобразование разboxing преобразует r в r.shortValue()

  • Если r является ссылкой типа Integer, то преобразование разboxing преобразует r в r.intValue()

  • Если r является ссылкой типа Long, то преобразование разboxing преобразует r в r.longValue()

  • Если r является ссылкой типа Float, то преобразование разboxing преобразует r в r.floatValue()

  • Если r является ссылкой типа Double, то преобразование разboxing преобразует r в r.doubleValue()

  • Если r является null, преобразование разboxing вызывает исключение NullPointerException

Тип считается преобразуемым в числовой тип, если это числовой тип (§4.2), или это ссылочный тип, который может быть преобразован в числовой тип с помощью преобразования разboxing.

Тип считается преобразуемым в целочисленный тип, если это целочисленный тип, или это ссылочный тип, который может быть преобразован в целочисленный тип с помощью преобразования разboxing.

5.1.9. Непроверенное преобразование

Пусть G обозначает объявление обобщенного типа с n параметрами типа.

Существует непроверенное преобразование от исходного класса или интерфейса типа (§4.8) G к любому параметризованному типу вида G<T1,...,Tn>.

Существует непроверенное преобразование от исходного массива типа G[]k к любому массиву типа вида G<T1,...,Tn>[]k. (Обозначение []k указывает тип массива с k измерениями.)

Использование непроверенного преобразования вызывает предупреждение о компиляции непроверенного преобразования, если все аргументы типа Ti (1 ≤ i ≤ n) являются неопределенными универсальными параметрами (§4.5.1), или предупреждение о непроверенном преобразовании подавляется аннотацией SuppressWarnings (§9.6.4.5).

Непроверенное преобразование используется для обеспечения плавной работы устаревшего кода, написанного до появления обобщенных типов, с библиотеками, которые были переведены на использование обобщенности (процесс, который мы называем обобщением). В таких случаях (в основном, клиенты фреймворка Collections в java.util), устаревший код использует сырые типы (например, Collection вместо Collection<String>). Выражения сырых типов передаются как аргументы методам библиотек, которые используют параметризованные версии тех же типов в качестве типов соответствующих формальных параметров.

Такие вызовы не могут быть доказаны статически безопасными в системе типов, использующей обобщения. Отклонение таких вызовов приведет к недействительности большого объема существующего кода и предотвратит их использование в новых версиях библиотек. В свою очередь, это отговорит поставщиков библиотек от использования обобщений. Чтобы предотвратить такой нежелательный результат, сырой тип может быть преобразован в любое вызов объявления обобщенного типа, на который ссылается сырой тип. Хотя преобразование некорректно, оно допускается как уступка практичности. В таких случаях выдается предупреждение о непроверенном преобразовании.

5.1.10. Преобразование захвата

Пусть 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 является аргументом типа-подстановки в форме ? extends Bi, то Si — это свежая переменная типа, верхняя граница которой равна glb(Bi, Ui[A1:=S1,...,An:=Sn]), а нижняя граница — нулевой тип.

    glb(V1,...,Vm) определяется как V1 & ... & Vm.

    Если для любых двух классов (не интерфейсов) Vi и Vj Vi не является подклассом Vj или наоборот, это ошибка на этапе компиляции.

  • Если Ti является аргументом типа-подстановки в форме ? super Bi, то 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).

5.1.11. Преобразование в строку

Любой тип может быть преобразован в тип 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" (четыре символа ASCII n, u, l, l).

  • В противном случае преобразование выполняется как вызов метода toString сохраненного объекта без аргументов; но если результатом вызова метода toString является null, то вместо этого используется строка "null".

    Метод toString определен базовым классом Object (§4.3.2). Многие классы переопределяют его, в частности, Boolean, Character, Integer, Long, Float, Double и String.

Подробности контекста строк см. в §5.4.

5.1.12. Запрещенные преобразования

Любое преобразование, которое не разрешено явно, запрещено.

5.1.13. Преобразование множества значений

Преобразование множества значений — это процесс сопоставления значения с плавающей запятой из одного множества значений в другое без изменения его типа.

В выражении, которое не является FP-строгим (§15.4), преобразование множества значений предоставляет варианты для реализации языка программирования Java:

  • Если значение является элементом множества значений с расширенным порядком показателя плавающей запятой, то реализация может по своему усмотрению сопоставить значение с ближайшим элементом множества значений с плавающей запятой. Это преобразование может привести к переполнению (в этом случае значение заменяется бесконечностью того же знака) или потере точности из-за недолива (в этом случае значение может потерять точность, так как оно заменяется денормализованным числом или нулем того же знака).

  • Если значение является элементом множества значений с расширенным порядком показателя двойной точности, то реализация может по своему усмотрению сопоставить значение с ближайшим элементом множества значений двойной точности. Это преобразование может привести к переполнению (в этом случае значение заменяется бесконечностью того же знака) или потере точности из-за недолива (в этом случае значение может потерять точность, так как оно заменяется денормализованным числом или нулем того же знака).

В FP-строгом выражении (§15.4) преобразование множества значений не предоставляет вариантов; каждая реализация должна вести себя одинаково:

  • Если значение типа float и не является элементом множества значений с плавающей запятой, то реализация должна сопоставить значение с ближайшим элементом множества значений с плавающей запятой. Это преобразование может привести к переполнению или недоливу.

  • Если значение типа double и не является элементом множества значений двойной точности, то реализация должна сопоставить значение с ближайшим элементом множества значений двойной точности. Это преобразование может привести к переполнению или недоливу.

В FP-строгом выражении сопоставление значений из множества значений с расширенным порядком показателя плавающей запятой или множества значений с расширенным порядком показателя двойной точности необходимо только тогда, когда вызывается метод, объявление которого не является FP-строгим, и реализация выбрала представить результат вызова метода как элемент множества значений с расширенным порядком показателя.

Как в FP-строгом коде, так и в коде, не являющемся FP-строгим, преобразование множества значений всегда оставляет неизменным любое значение, тип которого не является ни float, ни double.

5.2. Контексты присваивания

Контексты присваивания позволяют присвоить (§15.26) значение выражения переменной; тип выражения должен быть преобразован к типу переменной.

Контексты присваивания позволяют использовать следующие преобразования:

  • тождественное преобразование (§5.1.1)

  • расширяющее преобразование примитивного типа (§5.1.2)

  • расширяющее преобразование ссылочного типа (§5.1.5)

  • преобразование упаковки (§5.1.7) необязательно сопровождаемое расширяющим преобразованием ссылочного типа

  • преобразование распаковки (§5.1.8) необязательно сопровождаемое расширяющим преобразованием примитивного типа.

Если после применения указанных преобразований получившийся тип является сырым типом (§4.8), то затем может быть применено неявное преобразование (§5.1.9).

Кроме того, если выражение является константным выражением (§15.28) типа byte, short, char или int:

  • Может быть применено сужающее преобразование примитивного типа, если тип переменной является byte, short или char, и значение константного выражения представимо в типе переменной.

  • Может быть применено сужающее преобразование примитивного типа, за которым следует преобразование упаковки, если тип переменной:

    • Byte и значение константного выражения представимо в типе byte.

    • Short и значение константного выражения представимо в типе short.

    • Character и значение константного выражения представимо в типе char.

Временное сужение константных выражений означает, что код, такой как:

byte theAnswer = 42;

разрешается. Без сужения тот факт, что целочисленная константа 42 имеет тип int, означал бы, что требуется приведение к типу byte:

byte theAnswer = (byte)42;  // cast is permitted but not required

Наконец, значение типа null (единственное такое значение) может быть присвоено любому типу ссылки, что приводит к null-ссылке соответствующего типа.

Ошибка компиляции возникает, если цепочка преобразований содержит два параметризованных типа, не находящихся в отношении подтипов (§4.10).

Пример такой недопустимой цепочки:

Integer, Comparable<Integer>, Comparable, Comparable<String>

Первые три элемента цепочки связаны расширяющим преобразованием ссылочного типа, тогда как последний элемент получен из предыдущего неявным преобразованием. Однако это не является допустимым преобразованием присваивания, поскольку цепочка содержит два параметризованных типа, Comparable<Integer> и Comparable<String>, не являющихся подтипами.

Если тип выражения не может быть преобразован в тип переменной с помощью преобразования, разрешенного в контексте присваивания, возникает ошибка компиляции.

Если тип выражения может быть преобразован в тип переменной посредством преобразования присваивания, то говорят, что выражение (или его значение) совместимо с переменной или, что тип выражения совместим с типом переменной.

Если тип переменной — float или double, то к значению v, которое является результатом преобразования(ий), применяется преобразование множества значений (§5.1.13):

  • Если v имеет тип float и является элементом множества значений с расширенным экспоненциальным представлением чисел с плавающей точкой, то реализация должна отобразить v на ближайший элемент множества значений чисел с плавающей точкой. Это преобразование может привести к переполнению или потере точности.

  • Если v имеет тип double и является элементом множества значений с расширенным экспоненциальным представлением чисел с двойной точностью, то реализация должна отобразить v на ближайший элемент множества значений чисел с двойной точностью. Это преобразование может привести к переполнению или потере точности.

Исключения, которые могут возникнуть при преобразованиях в контексте присваивания:

  • Ошибка компиляции, если после применения вышеперечисленных преобразований полученное значение представляет собой объект, который не является экземпляром подкласса или подинтерфейса стёртого типа (§4.6) типа переменной.

    Эта ситуация может возникнуть только в результате загрязнения кучи (§4.12.2). На практике реализациям необходимо выполнять приведение типов только при доступе к полю или методу объекта параметризованного типа, когда стёртый тип поля или стёртый возвращаемый тип метода отличаются от нестёртого типа.

  • Ошибка компиляции в результате преобразования упаковки.

  • Ошибка компиляции в результате преобразования распаковки на null-ссылке.

  • Ошибка компиляции в особых случаях, связанных с элементами массива или доступом к полю (§10.5, §15.26.1).

Пример 5.2-1. Преобразование присваивания для примитивных типов

class Test {
    public static void main(String[] args) {
        short s = 12;      // narrow 12 to short
        float f = s;       // widen short to float
        System.out.println("f=" + f);
        char c = '\u0123';
        long l = c;        // widen char to long
        System.out.println("l=0x" + Long.toString(l,16));
        f = 1.23f;
        double d = f;      // widen float to double
        System.out.println("d=" + d);
    }
}

Эта программа выводит:

f=12.0
l=0x123
d=1.2300000190734863

Однако следующая программа выводит ошибки компиляции:

class Test {
    public static void main(String[] args) {
        short s = 123;
        char c = s;    // error: would require cast
        s = c;         // error: would require cast
    }
}

потому что не все short значения являются char значениями, и не все char значения являются short значениями.


Пример 5.2-2. Преобразование присваивания для ссылочных типов

class Point { int x, y; }
class Point3D extends Point { int z; }
interface Colorable { void setColor(int color); }

class ColoredPoint extends Point implements Colorable {
    int color;
    public void setColor(int color) { this.color = color; }
}

class Test {
    public static void main(String[] args) {
        // Assignments to variables of class type:
        Point p = new Point();
        p = new Point3D(); 
          // OK because Point3D is a subclass of Point
        Point3D p3d = p; 
          // Error: will require a cast because a Point
          // might not be a Point3D (even though it is,
          // dynamically, in this example.)

        // Assignments to variables of type Object:
        Object o  = p;          // OK: any object to Object
        int[] a   = new int[3];
        Object o2 = a;          // OK: an array to Object

        // Assignments to variables of interface type:
        ColoredPoint cp = new ColoredPoint();
        Colorable c = cp;
          // OK: ColoredPoint implements Colorable

        // Assignments to variables of array type:
        byte[] b = new byte[4];
        a = b;
          // Error: these are not arrays of the same primitive type
        Point3D[] p3da = new Point3D[3];
        Point[] pa = p3da; 
          // OK: since we can assign a Point3D to a Point
        p3da = pa;
          // Error: (cast needed) since a Point
          // can't be assigned to a Point3D
    }
}

Следующая программа демонстрирует преобразования присваивания для ссылочных значений, но не компилируется, как описано в комментариях. Этот пример следует сравнить с предыдущим.

class Point { int x, y; }
interface Colorable { void setColor(int color); }
class ColoredPoint extends Point implements Colorable {
    int color;
    public void setColor(int color) { this.color = color; }
}

class Test {
    public static void main(String[] args) {
        Point p = new Point();
        ColoredPoint cp = new ColoredPoint();
        // Okay because ColoredPoint is a subclass of Point:
        p = cp;
        // Okay because ColoredPoint implements Colorable:
        Colorable c = cp;
        // The following cause compile-time errors because
        // we cannot be sure they will succeed, depending on
        // the run-time type of p; a run-time check will be
        // necessary for the needed narrowing conversion and
        // must be indicated by including a cast:
        cp = p;    // p might be neither a ColoredPoint
                   // nor a subclass of ColoredPoint
        c = p;     // p might not implement Colorable
    }
}

Пример 5.2-3. Преобразование присваивания для массивов

class Point { int x, y; }
class ColoredPoint extends Point { int color; }

class Test {
    public static void main(String[] args) {
        long[] veclong = new long[100];
        Object o = veclong;          // okay
        Long l = veclong;            // compile-time error
        short[] vecshort = veclong;  // compile-time error
        Point[] pvec = new Point[100];
        ColoredPoint[] cpvec = new ColoredPoint[100];
        pvec = cpvec;                // okay
        pvec[0] = new Point();       // okay at compile time,
                                     // but would throw an
                                     // exception at run time
        cpvec = pvec;                // compile-time error
    }
}

В этом примере:

  • Значение veclong не может быть присвоено переменной типа Long, потому что Long — тип класса, отличный от Object. Массив может быть присвоен только переменной совместимого типа массива, или переменной типа Object, Cloneable или java.io.Serializable.

  • Значение veclong не может быть присвоено переменной vecshort, потому что это массивы примитивных типов, а short и long не являются одним и тем же примитивным типом.

  • Значение cpvec может быть присвоено переменной pvec, потому что любая ссылка, которая может быть значением выражения типа ColoredPoint, может быть значением переменной типа Point. Последующее присвоение нового Point элементу pvec вызовет исключение ArrayStoreException (если программа будет исправлена для компиляции), потому что массив типа ColoredPoint не может содержать экземпляр Point в качестве элемента.

  • Значение pvec не может быть присвоено переменной cpvec, потому что не каждая ссылка, которая может быть значением выражения типа ColoredPoint, может быть значением переменной типа Point. Если значение pvec во время выполнения было ссылкой на экземпляр Point[], и присвоение переменной cpvec было разрешено, простая ссылка на элемент cpvec, например, cpvec[0], могла бы вернуть Point, а Point не является ColoredPoint. Таким образом, разрешение такого присваивания позволило бы нарушить систему типов. Приведение типов может использоваться (§5.5, §15.16) для обеспечения того, что pvec ссылается на ColoredPoint[]:

          cpvec = (ColoredPoint[])pvec;  // OK, but may throw an 
                                   // exception at run time
        

5.3. Контексты вызова

Контексты вызова позволяют присваивать значение аргумента в вызове метода или конструктора (§8.8.7.1, §15.9, §15.12) соответствующему формальному параметру.

Жесткие контексты вызова допускают использование одного из следующих преобразований:

  • тождественное преобразование (§5.1.1)

  • расширяющее преобразование примитивных типов (§5.1.2)

  • расширяющее преобразование ссылок (§5.1.5)

Гибкие контексты вызова допускают более широкий набор преобразований, поскольку они используются только для конкретного вызова, если не найдено соответствующее объявление, используя жесткие контексты вызова. Гибкие контексты вызова допускают использование одного из следующих:

  • тождественное преобразование (§5.1.1)

  • расширяющее преобразование примитивных типов (§5.1.2)

  • расширяющее преобразование ссылок (§5.1.5)

  • преобразование упаковки (§5.1.7), необязательно с последующим расширяющим преобразованием ссылки

  • преобразование распаковки (§5.1.8), необязательно с последующим расширяющим преобразованием примитивных типов

Если после применения преобразований, перечисленных для контекста вызова, результирующий тип является сырым типом (§4.8), тогда может быть применено преобразование без проверки (§5.1.9).

Значение типа null (единственное такое значение) может быть присвоено любому типу ссылки.

Ошибка компиляции возникает, если цепочка преобразований содержит два параметризованных типа, которые не находятся в подтиповом отношении (§4.10).

Если тип выражения не может быть преобразован в тип параметра посредством преобразования, разрешенного в гибком контексте вызова, возникает ошибка компиляции.

Если тип выражения-аргумента равен float или double, то преобразование множества значений (§5.1.13) применяется после преобразования(ий):

  • Если значение аргумента типа float является элементом множества значений с расширенным показателем мантиссы чисел с плавающей точкой, то реализация должна сопоставить значение с ближайшим элементом множества значений чисел с плавающей точкой. Это преобразование может привести к переполнению или недополнению.

  • Если значение аргумента типа double является элементом множества значений с расширенным показателем мантиссы чисел с двойной точностью, то реализация должна сопоставить значение с ближайшим элементом множества значений чисел с двойной точностью. Это преобразование может привести к переполнению или недополнению.

Исключения, которые могут возникнуть в контексте вызова:

  • ClassCastException, если после применения вышеперечисленных преобразований типов результирующее значение является объектом, который не является экземпляром подкласса или подинтерфейса стирания (§4.6) соответствующего типа формального параметра.

  • OutOfMemoryError в результате преобразования упаковки.

  • NullPointerException в результате преобразования распаковки на значение null.

Ни жесткие, ни гибкие контексты вызова не включают неявное сужение целых константных выражений, которое разрешено в контекстах присваивания. Разработчики языка программирования Java посчитали, что включение этих неявных сужающих преобразований добавит дополнительную сложность к правилам разрешения перегрузки (§15.12.2).

Таким образом, программа:

class Test {
    static int m(byte a, int b) { return a+b; }
    static int m(short a, short b) { return a-b; }
    public static void main(String[] args) {
        System.out.println(m(12, 2));  // compile-time error
    }
}

вызывает ошибку компиляции, потому что целочисленные литералы 12 и 2 имеют тип int, поэтому ни один метод m не соответствует правилам разрешения перегрузки. Язык, который включал неявное сужение целых константных выражений, потребовал бы дополнительных правил для разрешения случаев, подобных этому примеру.

5.4. Контексты строк

Контексты строк применяются только к операнду бинарного оператора +, который не является String, когда другой операнд является String.

Целевой тип в этих контекстах всегда String, и преобразование строки (§5.1.11) не-String операнда всегда происходит. Оценка оператора + затем выполняется в соответствии с указанным в §15.18.1.

5.5. Контексты приведения типов

Контексты приведения типов позволяют преобразовать операнд оператора приведения типов (§15.16) к типу, явно указанному оператором приведения.

Контексты приведения типов допускают использование одного из следующих типов:

  • преобразование тождества (§5.1.1)

  • расширяющее преобразование примитивных типов (§5.1.2)

  • сужающее преобразование примитивных типов (§5.1.3)

  • расширяющее и сужающее преобразование примитивных типов (§5.1.4)

  • расширяющее преобразование ссылочных типов (§5.1.5), за которым необязательно следует преобразование распаковки (§5.1.8) или неявное преобразование (§5.1.9)

  • сужающее преобразование ссылочных типов (§5.1.6), за которым необязательно следует преобразование распаковки (§5.1.8) или неявное преобразование (§5.1.9)

  • преобразование упаковки (§5.1.7), за которым необязательно следует расширяющее преобразование ссылочных типов (§5.1.5)

  • преобразование распаковки (§5.1.8), за которым необязательно следует расширяющее преобразование примитивных типов (§5.1.2).

Преобразование множества значений (§5.1.13) применяется после преобразования типа.

Правомерность преобразования приведения типов на этапе компиляции определяется следующим образом:

  • Выражение примитивного типа может быть преобразовано к другому примитивному типу с помощью преобразования тождества (если типы совпадают), или расширяющего преобразования примитивных типов, или сужающего преобразования примитивных типов, или расширяющего и сужающего преобразования примитивных типов.

  • Выражение примитивного типа может быть преобразовано к ссылочному типу без ошибки с помощью преобразования упаковки.

  • Выражение ссылочного типа может быть преобразовано к примитивному типу без ошибки с помощью преобразования распаковки.

  • Выражение ссылочного типа может быть преобразовано к другому ссылочному типу, если на этапе компиляции не возникает ошибки в соответствии с правилами в §5.5.1.

Следующие таблицы перечисляют, какие преобразования используются в определенных преобразованиях приведения типов. Каждое преобразование обозначается символом:

  • - означает, что преобразование приведения типов не разрешено

  • ≈ означает преобразование тождества (§5.1.1)

  • ω означает расширяющее преобразование примитивных типов (§5.1.2)

  • η означает сужающее преобразование примитивных типов (§5.1.3)

  • ωη означает расширяющее и сужающее преобразование примитивных типов (§5.1.4)

  • ⇑ означает расширяющее преобразование ссылочных типов (§5.1.5)

  • ⇓ означает сужающее преобразование ссылочных типов (§5.1.6)

  • ⊕ означает преобразование упаковки (§5.1.7)

  • ⊗ означает преобразование распаковки (§5.1.8)

В таблицах запятая между символами указывает, что преобразование приведения типов использует одно преобразование, за которым следует другое. Тип Object означает любой ссылочный тип, кроме восьми оберток классов Boolean, Byte, Short, Character, Integer, Long, Float, Double.

Таблица 5.5-A. Преобразования при приведении типов к примитивным типам

К → byte short char int long float double boolean
Из ↓
byte ≈ ω ωη ω ω ω ω -
short η ≈ η ω ω ω ω -
char η η ≈ ω ω ω ω -
int η η η ≈ ω ω ω -
long η η η η ≈ ω ω -
float η η η η η ≈ ω -
double η η η η η η ≈ -
boolean - - - - - - - ≈
Byte ⊗ ⊗,ω - ⊗,ω ⊗,ω ⊗,ω ⊗,ω -
Short - ⊗ - ⊗,ω ⊗,ω ⊗,ω ⊗,ω -
Character - - ⊗ ⊗,ω ⊗,ω ⊗,ω ⊗,ω -
Integer - - - ⊗ ⊗,ω ⊗,ω ⊗,ω -
Long - - - - ⊗ ⊗,ω ⊗,ω -
Float - - - - - ⊗ ⊗,ω -
Double - - - - - - ⊗ -
Boolean - - - - - - - ⊗
Object ⇓,⊗ ⇓,⊗ ⇓,⊗ ⇓,⊗ ⇓,⊗ ⇓,⊗ ⇓,⊗ ⇓,⊗

Таблица 5.5-B. Преобразования при приведении типов к ссылкам

К → Byte Short Character Integer Long Float Double Boolean Object
Из ↓
byte ⊕ - - - - - - - ⊕,⇑
short - ⊕ - - - - - - ⊕,⇑
char - - ⊕ - - - - - ⊕,⇑
int - - - ⊕ - - - - ⊕,⇑
long - - - - ⊕ - - - ⊕,⇑
float - - - - - ⊕ - - ⊕,⇑
double - - - - - - ⊕ - ⊕,⇑
boolean - - - - - - - ⊕ ⊕,⇑
Byte ≈ - - - - - - - ⇑
Short - ≈ - - - - - - ⇑
Character - - ≈ - - - - - ⇑
Integer - - - ≈ - - - - ⇑
Long - - - - ≈ - - - ⇑
Float - - - - - ≈ - - ⇑
Double - - - - - - ≈ - ⇑
Boolean - - - - - - - ≈ ⇑
Object ⇓ ⇓ ⇓ ⇓ ⇓ ⇓ ⇓ ⇓ ≈

5.5.1. Преобразование ссылочных типов

Учитывая тип ссылки на этапе компиляции S (источник) и тип ссылки на этапе компиляции T (цель), преобразование типа существует от S к T, если не возникает ошибок компиляции из-за следующих правил.

Если S — тип класса:

  • Если T — тип класса, то либо |S| <: |T|, или |T| <: |S|. В противном случае возникает ошибка компиляции.

    Кроме того, если существует супертип X типа T и супертип Y типа S, такие что оба X и Y являются доказуемо различными параметризованными типами (§4.5), и что стёртые представления X и Y совпадают, возникает ошибка компиляции.

  • Если T — тип интерфейса:

    • Если S не является final классом (§8.1.1), то, если существует супертип X типа T и супертип Y типа S, такие что оба X и Y являются доказуемо различными параметризованными типами, и что стёртые представления X и Y совпадают, возникает ошибка компиляции.

      В противном случае преобразование всегда является допустимым на этапе компиляции (потому что даже если S не реализует T, подкласс S может).

    • Если S является final классом (§8.1.1), то S должен реализовывать T, в противном случае возникает ошибка компиляции.

  • Если T — переменная типа, то этот алгоритм применяется рекурсивно, используя верхнюю границу T вместо T.

  • Если T — массив типов, то S должен быть классом Object, в противном случае возникает ошибка компиляции.

  • Если T — пересечение типов, T1 & ... & Tn, то это ошибка компиляции, если существует Ti (1 ≤ i ≤ n), такое что S не может быть преобразовано к Ti по этому алгоритму. То есть, успех преобразования определяется самым ограниченным компонентом типа пересечения.

Если S — тип интерфейса:

  • Если T — тип массива, то S должен быть типом java.io.Serializable или Cloneable (единственные интерфейсы, реализуемые массивами), в противном случае возникает ошибка компиляции.

  • Если T — тип класса или интерфейса, который не является final (§8.1.1), то если существует супертип X типа T и супертип Y типа S, такие что оба X и Y являются доказуемо различными параметризованными типами, и что стёртые представления X и Y совпадают, возникает ошибка компиляции.

    В противном случае преобразование всегда является допустимым на этапе компиляции (потому что даже если T не реализует S, подкласс T может).

  • Если T — тип класса, который является final, то:

    • Если S не является параметризованным типом или сырым типом, то T должен реализовывать S, в противном случае возникает ошибка компиляции.

    • В противном случае S — либо параметризованный тип, являющийся вызовом некоторого объявления обобщённого типа G, либо сырой тип, соответствующий обобщённому типу объявления G. Тогда должен существовать супертип X типа T, такой что X является вызовом G, в противном случае возникает ошибка компиляции.

      Кроме того, если S и X являются доказуемо различными параметризованными типами, возникает ошибка компиляции.

  • Если T — переменная типа, то этот алгоритм применяется рекурсивно, используя верхнюю границу T вместо T.

  • Если T — пересечение типов, T1 & ... & Tn, то это ошибка компиляции, если существует Ti (1 ≤ i ≤ n), такое что S не может быть преобразовано к Ti по этому алгоритму.

Если S — переменная типа, то этот алгоритм применяется рекурсивно, используя верхнюю границу S вместо S.

Если S — пересечение типов A1 & ... & An, то это ошибка компиляции, если существует Ai (1 ≤ i ≤ n), такое что Ai не может быть преобразовано к T по этому алгоритму. То есть, успех преобразования определяется самым ограниченным компонентом типа пересечения.

Если S — тип массива SC[], то есть массив компонентов типа SC:

  • Если T — тип класса, то если T не является Object, то возникает ошибка компиляции (потому что Object — единственный тип класса, которому могут быть присвоены массивы).

  • Если T — тип интерфейса, то возникает ошибка компиляции, если T не является типом java.io.Serializable или типом Cloneable (единственные интерфейсы, реализуемые массивами).

  • Если T — переменная типа, то этот алгоритм применяется рекурсивно, используя верхнюю границу T вместо T.

  • Если T — тип массива TC[], то есть массив компонентов типа TC, то возникает ошибка компиляции, если не выполняется одно из следующих условий:

    • TC и SC — один и тот же примитивный тип.

    • TC и SC — ссылочные типы, и тип SC может быть преобразован к TC.

  • Если T — пересечение типов, T1 & ... & Tn, то это ошибка компиляции, если существует Ti (1 ≤ i ≤ n), такое что S не может быть преобразовано к Ti по этому алгоритму.

Пример 5.5.1-1. Преобразование ссылочных типов

class Point { int x, y; }
interface Colorable { void setColor(int color); }
class ColoredPoint extends Point implements Colorable {
    int color;
    public void setColor(int color) { this.color = color; }
}
final class EndPoint extends Point {}

class Test {
    public static void main(String[] args) {
        Point p = new Point();
        ColoredPoint cp = new ColoredPoint();
        Colorable c;
        // The following may cause errors at run time because
        // we cannot be sure they will succeed; this possibility
        // is suggested by the casts:
        cp = (ColoredPoint)p;  // p might not reference an
                               // object which is a ColoredPoint
                               // or a subclass of ColoredPoint
        c = (Colorable)p;      // p might not be Colorable
        // The following are incorrect at compile time because
        // they can never succeed as explained in the text:
        Long l = (Long)p;            // compile-time error #1
        EndPoint e = new EndPoint();
        c = (Colorable)e;            // compile-time error #2
    }
}

Здесь первая ошибка компиляции возникает, потому что типы классов Long и Point не связаны (то есть они не совпадают, и ни один не является подклассом другого), поэтому преобразование между ними всегда завершится неудачей.

Вторая ошибка компиляции возникает, потому что переменная типа EndPoint никогда не может ссылаться на значение, реализующее интерфейс Colorable. Это потому, что EndPoint — это final тип, и переменная final типа всегда содержит значение того же времени выполнения, что и её тип компиляции. Следовательно, тип времени выполнения переменной e должен быть точно типом EndPoint, а тип EndPoint не реализует Colorable.


Пример 5.5.1-2. Приведение типов массивов

class Point {
    int x, y;
    Point(int x, int y) { this.x = x; this.y = y; }
    public String toString() { return "("+x+","+y+")"; }
}
interface Colorable { void setColor(int color); }
class ColoredPoint extends Point implements Colorable {
    int color;
    ColoredPoint(int x, int y, int color) {
        super(x, y); setColor(color);
    }
    public void setColor(int color) { this.color = color; }
    public String toString() {
        return super.toString() + "@" + color;
    }
}

class Test {
    public static void main(String[] args) {
        Point[] pa = new ColoredPoint[4];
        pa[0] = new ColoredPoint(2, 2, 12);
        pa[1] = new ColoredPoint(4, 5, 24);
        ColoredPoint[] cpa = (ColoredPoint[])pa;
        System.out.print("cpa: {");
        for (int i = 0; i < cpa.length; i++)
            System.out.print((i == 0 ? " " : ", ") + cpa[i]);
        System.out.println(" }");
    }
}

Эта программа компилируется без ошибок и выводит:

cpa: { (2,2)@12, (4,5)@24, null, null }

5.5.2. Проверяемые и непроверяемые приведения типов

Приведение типа S к типу T статически известно как корректное, если и только если S <: T (§4.10).

Приведение типа S к параметризованному типу (§4.5) T является непроверяемым, если не выполняется хотя бы одно из следующих условий:

  • S <: T

  • Все аргументы типа (§4.5.1) T являются неограниченными джойлвайдами

  • T <: S и S не имеет подтипа X помимо T, где аргументы типа X не содержатся в аргументах типа T.

Приведение типа S к переменной типа T является непроверяемым, если S <: T.

Приведение типа S к пересечению типов T1 & ... & Tn является непроверяемым, если существует Ti (1 ≤ i ≤ n), при котором приведение S к Ti является непроверяемым.

Непроверяемое приведение типа S к типу T (не пересечению) является полностью непроверяемым, если приведение |S| к |T| статически известно как корректное. В противном случае оно является частично непроверяемым.

Непроверяемое приведение типа S к пересечению типов T1 & ... & Tn является полностью непроверяемым, если для всех i (1 ≤ i ≤ n) приведение S к Ti либо статически известно как корректное, либо полностью непроверяемое. В противном случае оно является частично непроверяемым.

Непроверяемое приведение вызывает предупреждение о непроверяемом приведении во время компиляции, если оно не подавлено с помощью аннотации SuppressWarnings (§9.6.4.5).

Приведение является проверяемым, если оно не статически известно как корректное и не является непроверяемым.

Если приведение к ссылочному типу не является ошибкой во время компиляции, существует несколько случаев:

  • Приведение статически известно как корректное.

    Для такого приведения не выполняется никаких действий во время выполнения.

  • Приведение является полностью непроверяемым.

    Для такого приведения не выполняется никаких действий во время выполнения.

  • Приведение является частично непроверяемым или проверяемым к пересечению типов.

    Если тип пересечения T1 & ... & Tn, то для всех i (1 ≤ i ≤ n) любая проверка во время выполнения, необходимая для приведения S к Ti, также необходима для приведения к типу пересечения.

  • Приведение является частично непроверяемым к типу, не являющемуся пересечением.

    Такое приведение требует проверки корректности во время выполнения. Проверка выполняется так, как если бы приведение было проверяемым приведением между |S| и |T|, как описано ниже.

  • Приведение является проверяемым к типу, не являющемуся пересечением.

    Такое приведение требует проверки корректности во время выполнения. Если значение во время выполнения null, то приведение разрешается. В противном случае, пусть R — класс объекта, на который ссылается значение ссылки во время выполнения, и пусть T — стирание (§4.6) типа, указанного в операторе приведения. Приведение должно проверить во время выполнения, что класс R совместим с типом T в соответствии с алгоритмом в §5.5.3.

    Обратите внимание, что R не может быть интерфейсом, когда эти правила впервые применяются для данного приведения, но R может быть интерфейсом, если правила применяются рекурсивно, так как значение ссылки во время выполнения может ссылаться на массив, тип элемента которого является типом интерфейса.

5.5.3. Проверяемые приведения во время выполнения

Вот алгоритм проверки того, является ли тип 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. Числовые контексты

Числовые контексты применяются к операндам арифметического оператора.

Числовые контексты позволяют использовать:

  • преобразование тождества (§5.1.1)

  • расширяющее примитивное преобразование (§5.1.2)

  • преобразование распаковки (§5.1.8) с последующим расширяющим примитивным преобразованием (необязательно)

Числовое продвижение — это процесс, в котором, исходя из арифметического оператора и выражений его аргументов, аргументы преобразуются к выведенному целевому типу T. T выбирается во время продвижения таким образом, чтобы каждое выражение аргумента могло быть преобразовано в T, и арифметическая операция была определена для значений типа T.

Существует два вида числового продвижения: унарное числовое продвижение (§5.6.1) и бинарное числовое продвижение (§5.6.2).

5.6.1. Унарное числовое продвижение

Некоторые операторы применяют унарное числовое продвижение к одному операнду, который должен представлять значение числового типа:

  • Если операнд имеет тип времени компиляции 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.6.2. Бинарное числовое продвижение

Когда оператор применяет бинарное числовое продвижение к паре операндов, каждый из которых должен обозначать значение, преобразуемое в числовой тип, применяются следующие правила, в порядке их следования:

  1. Если какой-либо операнд имеет тип ссылки, к нему применяется преобразование распаковки (§5.1.8).

  2. Расширяющее примитивное преобразование (§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.

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API