Spec-Zone.ru › Java Language Specification 11

Глава 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.1.13. Преобразование множества значений
5.2. Контексты присваивания
5.3. Контексты вызова
5.4. Контексты строк
5.5. Контексты приведения типов
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 является подтипом 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| или |T| <: |S|.

    2. S и T являются типами интерфейсов.

    3. S — это тип класса, T — тип интерфейса, и S не называет final класс (§8.1.1).

    4. S — это тип класса, T — тип интерфейса, и S называет final класс, который реализует интерфейс, названный T.

    5. S — это тип интерфейса, T — тип класса, и T не называет final класс.

    6. S — это тип интерфейса, T — тип класса, и T называет final класс, который реализует интерфейс, названный S.

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

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

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

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

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

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

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

Сужение преобразования ссылок является либо проверенным, либо непроверенным. Эти термины относятся к возможности Java Virtual Machine проверять или не проверять правильность типа преобразования.

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

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

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

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

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

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

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

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. Преобразование в обертку

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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 над экзистенциальными значениями требует, чтобы захваченная переменная типа не выходила за пределы открытого выражения. Преобразование 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.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, Short или Character, и значение константного выражения представимо в типе byte, short или char соответственно.

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

byte theAnswer = 42;

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

f=12.0
l=0x123
d=1.2300000190734863

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

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

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


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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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).

Если тип выражения аргумента равен 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.

    END_OF_DOCUMENT_MARKER

    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.13) применяется после преобразования типа.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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


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

    class Point {
        int x, y;
        Point(int x, int y) { this.x = x; this.y = y; }
        public String toString() { return "("+x+","+y+")"; }
    }
    interface Colorable { void setColor(int color); }
    class ColoredPoint extends Point implements Colorable {
        int color;
        ColoredPoint(int x, int y, int color) {
            super(x, y); setColor(color);
        }
        public void setColor(int color) { this.color = color; }
        public String toString() {
            return super.toString() + "@" + color;
        }
    }
    
    class Test {
        public static void main(String[] args) {
            Point[] pa = new ColoredPoint[4];
            pa[0] = new ColoredPoint(2, 2, 12);
            pa[1] = new ColoredPoint(4, 5, 24);
            ColoredPoint[] cpa = (ColoredPoint[])pa;
            System.out.print("cpa: {");
            for (int i = 0; i < cpa.length; i++)
                System.out.print((i == 0 ? " " : ", ") + cpa[i]);
            System.out.println(" }");
        }
    }
    

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

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

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

    class Point { int x, y; }
    interface Colorable { void setColor(int color); }
    class ColoredPoint extends Point implements Colorable {
        int color;
        public void setColor(int color) { this.color = color; }
    }
    
    class Test {
        public static void main(String[] args) {
            Point[] pa = new Point[100];
    
            // The following line will throw a ClassCastException:
            ColoredPoint[] cpa = (ColoredPoint[])pa;
            System.out.println(cpa[0]);
            int[] shortvec = new int[2];
            Object o = shortvec;
    
            // The following line will throw a ClassCastException:
            Colorable c = (Colorable)o;
            c.setColor(0);
        }
    }
    

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


    5.6. Числовые контексты

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

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

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

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

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

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

    • преобразование распаковки (§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