Spec-Zone.ru › Java Language Specification 17

Глава 5. Преобразования и контексты

Оглавление

5.1. Виды преобразований
5.1.1. Тождественное преобразование
5.1.2. Расширяющее преобразование примитивных типов
5.1.3. Сужающее преобразование примитивных типов
5.1.4. Расширяющее и сужающее преобразование примитивных типов
5.1.5. Расширяющее преобразование ссылочных типов
5.1.6. Сужающее преобразование ссылочных типов
5.1.6.1. Разрешенные сужающие преобразования ссылочных типов
5.1.6.2. Проверяемые и непроверяемые сужающие преобразования ссылочных типов
5.1.6.3. Сужающие преобразования ссылочных типов во время выполнения
5.1.7. Упаковка (boxing)
5.1.8. Распаковка (unboxing)
5.1.9. Непроверяемое преобразование
5.1.10. Преобразование захвата (capture)
5.1.11. Преобразование в строку
5.1.12. Запрещенные преобразования
5.2. Контексты присваивания
5.3. Контексты вызова
5.4. Контексты строк
5.5. Контексты приведения типов
5.6. Численные контексты

Каждое выражение, написанное на языке программирования Java, либо не производит результата (§15.1), либо имеет тип, который может быть выведен на этапе компиляции (§15.3). Когда выражение появляется в большинстве контекстов, оно должно быть совместимо с типом, ожидаемым в этом контексте; этот тип называется целевым типом. Для удобства совместимость выражения с окружающим контекстом облегчается двумя способами:

  • Во-первых, для некоторых выражений, называемых поливыражениями (§15.2), выведенный тип может зависеть от целевого типа. Одно и то же выражение может иметь разные типы в разных контекстах.

  • Во-вторых, после того, как тип выражения был выведен, иногда можно выполнить неявное преобразование из типа выражения в целевой тип.

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

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

Аналогично, правила, определяющие, позволяет ли целевой тип неявное преобразование, зависят от вида контекста, типа выражения и, в одном особом случае, значения константного выражения (§15.29). Преобразование от типа S к типу T позволяет рассматривать выражение типа S во время компиляции как имеющее тип T вместо этого. В некоторых случаях это потребует соответствующих действий во время выполнения для проверки допустимости преобразования или для преобразования значения выражения во время выполнения в форму, подходящую для нового типа T.

Пример 5.0-1. Преобразования во время компиляции и во время выполнения

  • Преобразование от типа Object к типу Thread требует проверки во время выполнения, чтобы убедиться, что значение во время выполнения является фактически экземпляром класса Thread или одним из его подклассов; если это не так, выбрасывается исключение.

  • Преобразование от типа Thread к типу Object не требует действий во время выполнения; Thread является подклассом Object, поэтому любая ссылка, созданная выражением типа Thread, является допустимой ссылкой типа Object.

  • Преобразование от типа int к типу long требует расширения знака во время выполнения значения 32-битного целого числа до 64-битного представления long. Информация не теряется.

  • Преобразование от типа double к типу long требует нетривиального преобразования от 64-битного значения с плавающей запятой к 64-битному представлению целого числа. В зависимости от фактического значения во время выполнения, информация может быть потеряна.


Возможные преобразования в языке программирования Java сгруппированы в несколько основных категорий:

  • Тождественные преобразования

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

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

  • Расширяющие преобразования ссылочных типов

  • Сужающие преобразования ссылочных типов

  • Преобразования упаковки (boxing)

  • Преобразования распаковки (unboxing)

  • Непроверяемые преобразования

  • Преобразования захвата (capture)

  • Преобразования в строку

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

  • Контексты присваивания (§5.2, §15.26), в которых значение выражения привязывается к именованной переменной. Примитивные и ссылочные типы могут быть подвергнуты расширению, значения могут быть упакованы или распакованы, а некоторые константные выражения примитивных типов могут быть подвергнуты сужению. Также может произойти непроверяемое преобразование.

  • Строгие контексты вызова (§5.3, §15.9, §15.12), в которых аргумент привязывается к формальному параметру конструктора или метода. Могут произойти расширяющие преобразования примитивных типов, расширяющие преобразования ссылочных типов и непроверяемые преобразования.

  • Свободные контексты вызова (§5.3, §15.9, §15.12), в которых, подобно строгим контекстам вызова, аргумент привязывается к формальному параметру. Вызовы методов или конструкторов могут предоставить этот контекст, если не может быть найдена соответствующая декларация, используя только строгие контексты вызова. В дополнение к расширяющим и непроверяемым преобразованиям, в этом контексте могут произойти преобразования упаковки и распаковки.

  • Контексты строк (§5.4, §15.18.1), в которых значение любого типа преобразуется в объект типа String.

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

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

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

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

class Test {			
    public static void main(String[] args) {
        // Casting conversion (5.5) of a float literal to
        // type int. Without the cast operator, this would
        // be a compile-time error, because this is a
        // narrowing conversion (5.1.3):
        int i = (int)12.5f;

        // String conversion (5.4) of i's int value:
        System.out.println("(int)12.5f==" + i);

        // Assignment conversion (5.2) of i's value to type
        // float. This is a widening conversion (5.1.2):
        float f = i;

        // String conversion of f's float value:
        System.out.println("after float widening: " + f);

        // Numeric promotion (5.6) of i's value to type
        // float. This is a binary numeric promotion.
        // After promotion, the operation is float*float:
        System.out.print(f);
        f = f * i;

        // Two string conversions of i and f:
        System.out.println("*" + i + "==" + f);

        // Invocation conversion (5.3) of f's value
        // to type double, needed because the method Math.sin
        // accepts only a double argument:
        double d = Math.sin(f);

        // Two string conversions of f and d:
        System.out.println("Math.sin(" + f + ")==" + d);
    }
}

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

(int)12.5f==12
after float widening: 12.0
12.0*12==144.0
Math.sin(144.0)==-0.49102159389846934

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

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

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

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

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

Расширяющее преобразование char в целочисленный тип T дополняет представление значения char нулями для заполнения более широкого формата.

Расширяющее преобразование от int к float, или от long к float, или от int к double, или от long к double происходит в соответствии с правилами IEEE 754 для преобразования из целочисленного формата в двоичный формат с плавающей запятой.

Расширяющее преобразование от float к double происходит в соответствии с правилами IEEE 754 для преобразования между двоичными форматами с плавающей запятой.

Несмотря на то, что может произойти потеря точности, расширяющее преобразование примитивного типа никогда не приводит к исключению во время выполнения (§11.1.1).

Пример 5.1.2-1. Расширяющее преобразование примитивного типа

class Test {
    public static void main(String[] args) {
        int big = 1234567890;
        float approx = big;
        System.out.println(big - (int)approx);
    }
}

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

-46

тем самым показывая, что информация была потеряна во время преобразования из типа int в тип float, потому что значения типа float не точны до девяти значащих цифр.


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

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

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

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

Сужающее преобразование числа с плавающей точкой в целочисленный тип T выполняется в два этапа:

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

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

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

      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) результата первого этапа.

Сужающее преобразование из double в float происходит в соответствии с правилами IEEE 754 для преобразования между двоичными форматами чисел с плавающей точкой с использованием политики округления до ближайшего значения (§15.4). Это преобразование может потерять точность, но также может потерять диапазон, что приводит к нулю float из ненулевого double и бесконечности float из конечного double. double NaN преобразуется в float NaN, а double бесконечность преобразуется в бесконечность с тем же знаком float.

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

Пример 5.1.3-1. Сужающее примитивное преобразование

class Test {
    public static void main(String[] args) {
        float fmin = Float.NEGATIVE_INFINITY;
        float fmax = Float.POSITIVE_INFINITY;
        System.out.println("long: " + (long)fmin +
                           ".." + (long)fmax);
        System.out.println("int: " + (int)fmin +
                           ".." + (int)fmax);
        System.out.println("short: " + (short)fmin +
                           ".." + (short)fmax);
        System.out.println("char: " + (int)(char)fmin +
                           ".." + (int)(char)fmax);
        System.out.println("byte: " + (byte)fmin +
                           ".." + (byte)fmax);
    }
}

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

long: -9223372036854775808..9223372036854775807
int: -2147483648..2147483647
short: 0..-1
char: 0..65535
byte: 0..-1

Результаты для char, int и long предсказуемы, они производят минимальные и максимальные представимые значения типа.

Результаты для byte и short теряют информацию о знаке и величине числовых значений, а также теряют точность. Результаты можно понять, рассмотрев младшие биты минимальных и максимальных int. Минимальное int в шестнадцатеричном формате равно 0x80000000, а максимальное int равно 0x7fffffff. Это объясняет результаты short, которые являются 16 младшими битами этих значений, а именно 0x0000 и 0xffff; это объясняет результаты char, которые также являются 16 младшими битами этих значений, а именно '\u0000' и '\uffff'; и это объясняет результаты byte, которые являются 8 младшими битами этих значений, а именно 0x00 и 0xff.


Пример 5.1.3-2. Сужающие примитивные преобразования, теряющие информацию

class Test {
    public static void main(String[] args) {
        // A narrowing of int to short loses high bits:
        System.out.println("(short)0x12345678==0x" +
                           Integer.toHexString((short)0x12345678));
        // An int value too big for byte changes sign and magnitude:
        System.out.println("(byte)255==" + (byte)255);
        // A float value too big to fit gives largest int value:
        System.out.println("(int)1e20f==" + (int)1e20f);
        // A NaN converted to int yields zero:
        System.out.println("(int)NaN==" + (int)Float.NaN);
        // A double value too large for float yields infinity:
        System.out.println("(float)-1e100==" + (float)-1e100);
        // A double value too small for float underflows to zero:
        System.out.println("(float)1e-50==" + (float)1e-50);
    }
}

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

(short)0x12345678==0x5678
(byte)255==-1
(int)1e20f==2147483647
(int)NaN==0
(float)-1e100==-Infinity
(float)1e-50==0.0

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

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

  • byte в char

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

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

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

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

Тип null не является типом ссылки (§4.1), и поэтому расширяющее ссылочное преобразование не существует от типа null к типу ссылки. Однако во многих контекстах преобразования явным образом разрешается преобразование типа null в тип ссылки.

5.1.6. Сужение ссылочной конверсии

Сужение ссылочной конверсии рассматривает выражения ссылочного типа S как выражения другого ссылочного типа T, где S не является подтипом T. Поддерживаемые пары типов определены в §5.1.6.1. В отличие от расширения ссылочной конверсии, типы не обязательно должны быть напрямую связаны. Однако существуют ограничения, которые запрещают преобразование между определенными парами типов, когда можно статически доказать, что ни одно значение не может быть одновременно и того, и другого типа.

Сужение ссылочной конверсии может потребовать проверки во время выполнения, чтобы убедиться, что значение типа S является допустимым значением типа T. Однако из-за отсутствия информации о параметризованном типе во время выполнения некоторые преобразования не могут быть полностью проверены во время выполнения; они отмечаются на этапе компиляции (§5.1.6.2). Для преобразований, которые могут быть полностью проверены во время выполнения, и для определенных преобразований, включающих информацию о параметризованном типе, но всё ещё частично проверяемых во время выполнения, выбрасывается ClassCastException, если проверка завершается неудачно (§5.1.6.3).

5.1.6.1. Разрешённое сужение ссылочной конверсии

Ссылочное сужение существует от ссылочного типа 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>, и наоборот. Отклонение доказательно различных типов — простая статическая проверка, чтобы предотвратить «глупые» сужения ссылочной конверсии.

  • Выполняется одно из следующих условий:

    1. S — это классный или интерфейсный тип, T — классный или интерфейсный тип, и S обозначает класс или интерфейс, который не является непересекающимся с классом или интерфейсом, обозначаемым T. («Непересекающийся» определяется ниже.)

    2. S — это классный тип Object или интерфейсный тип java.io.Serializable или Cloneable (единственные интерфейсы, реализуемые массивами (§10.8)), а T — это массивный тип.

    3. S — это массивный тип SC[], то есть массив компонентов типа SC; T — это массивный тип TC[], то есть массив компонентов типа TC; и существует сужение ссылочной конверсии от SC к TC.

    4. S — это тип переменной, и сужение ссылочной конверсии существует от верхней границы S к T.

    5. T — это тип переменной, и существует либо расширение ссылочной конверсии, либо сужение ссылочной конверсии от S к верхней границе T.

    6. S — это пересекающийся тип S1 & ... & Sn, и для всех i (1 ≤ i ≤ n), либо существует расширение ссылочной конверсии, либо сужение ссылочной конверсии от Si к T.

    7. T — это пересекающийся тип T1 & ... & Tn, и для всех i (1 ≤ i ≤ n), либо существует расширение ссылочной конверсии, либо сужение ссылочной конверсии от S к Ti.

Класс или интерфейс непересекающийся с другим классом или интерфейсом, если статически можно определить, что у них нет общих экземпляров (кроме значения null). Правила непересекаемости следующие:

  • Класс с именем C непересекающийся с интерфейсом с именем I, если (i) C не является подтипом I, и (ii) выполняется одно из следующих условий:

    • C является final.

    • C является sealed, и все разрешенные непосредственные подклассы C непересекающиеся с I.

    • C свободно расширяемый (§8.1.1.2), I является sealed, и C непересекающийся со всеми разрешёнными непосредственными подклассами и подинтерфейсами I.

  • Интерфейс с именем I непересекающийся с классом с именем C, если C непересекающийся с I.

  • Класс с именем C непересекающийся с другим классом с именем D, если (i) C не является подтипом D, и (ii) D не является подтипом C.

  • Интерфейс с именем I непересекающийся с другим интерфейсом с именем J, если (i) I не является подтипом J, (ii) J не является подтипом I, и (iii) выполняется одно из следующих условий:

    • I является sealed, и все разрешённые непосредственные подклассы и подинтерфейсы I непересекающиеся с J.

    • J является sealed, и I непересекающийся со всеми разрешёнными непосредственными подклассами и подинтерфейсами J.

Факт, является ли класс final, имеет наибольшее влияние на то, является ли класс непересекающимся с интерфейсами. Рассмотрим следующие объявления:

interface I   {}
final class C {}

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

В противовес этому, рассмотрим следующие объявления:

interface J {}
class D     {}

Даже если класс D не реализует J, всё ещё возможно, что экземпляр D может быть экземпляром J, например, если происходит следующее объявление:

class E extends D implements J {}

По этой причине D не непересекающийся с J, и существует сужение ссылочной конверсии от D к J.

Последнее условие выше подразумевает, что два свободно расширяемых интерфейса (§9.1.1.4) не непересекающиеся.

5.1.6.2. Проверка и отсутствие проверки сужающих преобразований ссылок

Преобразование сужающей ссылки может быть проверяемым или непроверяемым. Эти термины относятся к возможности виртуальной машины Java проверить или не проверить корректность типа преобразования.

Если преобразование сужающей ссылки непроверяемое, виртуальная машина Java не сможет полностью проверить корректность типа, что может привести к загрязнению кучи (§4.12.2). Чтобы сообщить об этом программисту, непроверяемое сужающее преобразование ссылок вызывает предупреждение о компиляции unchecked warning, если оно не подавлено с помощью @SuppressWarnings (§9.6.4.5). И наоборот, если преобразование сужающей ссылки не непроверяемое, то оно проверяемое; виртуальная машина Java сможет полностью проверить корректность типа, поэтому предупреждение во время компиляции не выдаётся.

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

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

    • Все аргументы типа T являются неограниченными универсальными типами.

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

  • Преобразование сужающей ссылки от типа S к переменной типа T является непроверяемым.

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

5.1.6.3. Сужение ссылочных преобразований во время выполнения

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

Некоторые непроверяемые сужающие ссылочные преобразования требуют проверки допустимости во время выполнения. Это зависит от того, является ли непроверяемое сужающее ссылочное преобразование полностью непроверяемым или частично непроверяемым. Частично непроверяемое сужающее ссылочное преобразование требует проверки допустимости во время выполнения, в то время как полностью непроверяемое сужающее ссылочное преобразование — нет.

Эти термины относятся к совместимости типов, участвующих в преобразовании, при рассмотрении их как типов без параметров. Если преобразование концептуально является «расширением», то преобразование является полностью непроверяемым; проверка во время выполнения не требуется, потому что преобразование является допустимым в системе типов 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, также необходима для преобразования к пересекающемуся типу.

5.1.7. Упаковка примитивов

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

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

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

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

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

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

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

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

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

  • Из типа null в тип null

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

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

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

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

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

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

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

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

  • Если p имеет тип float, то:

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

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

  • Если p имеет тип double, то:

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

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

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

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

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

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

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

5.1.8. Распаковка

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

  • Из типа 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), или это ссылочный тип, который может быть преобразован в числовой тип путем распаковки.

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

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 Framework в 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 на экзистенциалах требует, чтобы захваченная переменная типа не покидала открытое выражение. Преобразование захвата, которое соответствует преобразованию захвата, всегда находится в достаточно большой области, чтобы захваченная переменная типа никогда не была видна вне этой области. Преимущество этой схемы заключается в том, что нет необходимости в операции close, как определено в статье О варьируемости, основанной на подтипах, для параметризованных типов Ацуси Игараси и Мирко Вироли, в материалах 16-й европейской конференции по объектно-ориентированному программированию (ECOOP 2002). Подробное описание диких символов см. в Wild 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.1.12. Запрещенные преобразования

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

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.29) типа byte, short, char или int:

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

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

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

byte theAnswer = 42;

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

f=12.0
l=0x123
d=1.2300000190734863

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

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

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


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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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 (единственное такое значение — ссылка null) может быть присвоено любому ссылочному типу.

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

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

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

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

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

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

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

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

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

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

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

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

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

Контексты приведения типов позволяют преобразовать операнд выражения приведения типов (§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.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.6. Числовые контексты

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

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

  • Операнд унарного оператора плюс +, унарного оператора минус - или поразрядного оператора дополнения ~ (§15.15.3, §15.15.4, §15.15.5)

  • Операнд оператора умножения *, / или % (§15.17)

  • Операнд оператора сложения или вычитания для числовых типов + или - (§15.18.2)

  • Операнд оператора сдвига <<, >> или >>> (§15.19). Операнды этих операторов сдвига обрабатываются отдельно, а не как группа. long величина сдвига (правый операнд) не повышает значение, подвергаемое сдвигу (левый операнд), к типу long.

  • Операнд оператора числового сравнения <, <=, > или >= (§15.20.1)

  • Операнд оператора числового равенства == или != (§15.21.1)

  • Операнд поразрядного целочисленного оператора &, ^ или | (§15.22.1)

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

  • Выражение размерности в выражении создания массива (§15.10.1)

  • Выражение индекса в выражении доступа к массиву (§15.10.3)

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

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

  • Выражение результата автономного switch выражения (§15.28.1), где все выражения результатов могут быть преобразованы к числовому типу

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

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

  2. Далее, применяются расширяющее преобразование примитивного типа (§5.1.2) и сужающее преобразование примитивного типа (§5.1.3) к некоторым выражениям в соответствии со следующими правилами:

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

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

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

    • В противном случае ни одно из выражений не имеет тип double, float или long. В этом случае тип, получаемый после продвижения, выбирается в зависимости от вида контекста.

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

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

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

      • (Остальные правила перевода аналогичным образом)

Унарное числовое продвижение заключается в применении числового продвижения к одному выражению, которое встречается в числовом арифметическом контексте или числовом контексте массива.

Бинарное числовое продвижение заключается в применении числового продвижения к паре выражений, которые встречаются в числовом арифметическом контексте.

Общее числовое продвижение заключается в применении числового продвижения ко всем выражениям, которые встречаются в числовом контексте выбора.

Пример 5.6-1. Унарное числовое продвижение

class Test {
    public static void main(String[] args) {
        byte b = 2;
        int a[] = new int[b];  // dimension expression promotion
        char c = '\u0001';
        a[c] = 1;              // index expression promotion
        a[0] = -c;             // unary - promotion
        System.out.println("a: " + a[0] + "," + a[1]);
        b = -1;
        int i = ~b;            // bitwise complement promotion
        System.out.println("~0x" + Integer.toHexString(b)
                           + "==0x" + Integer.toHexString(i));
        i = b << 4L;           // shift promotion (left operand)
        System.out.println("0x" + Integer.toHexString(b)
                           + "<<4L==0x" + Integer.toHexString(i));
    }
}

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

a: -1,1
~0xffffffff==0x0
0xffffffff<<4L==0xfffffff0

Пример 5.6-2. Бинарное числовое продвижение

class Test {
    public static void main(String[] args) {
        int i    = 0;
        float f  = 1.0f;
        double d = 2.0;
        // First int*float is promoted to float*float, then
        // float==double is promoted to double==double:
        if (i * f == d) System.out.println("oops");
		
        // A char&byte is promoted to int&int:
        byte b = 0x1f;
        char c = 'G';
        int control = c & b;
        System.out.println(Integer.toHexString(control));
		
        // Here int:float is promoted to float:float:
        f = (b==0) ? i : 4.0f;
        System.out.println(1.0/f);
    }
}

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

7
0.25

Пример преобразует ASCII-символ G в ASCII-управляющий символ G (BEL), скрывая все, кроме 5 младших битов символа. 7 — это числовое значение этого управляющего символа.


© Oracle and/or its affiliates. All rights reserved.
Licensed under the Oracle Technology Network License Agreement.

Spec-Zone.ru

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