Spec-Zone .ru
спецификации, руководства, описания, API
Содержание | Предыдущий | Следующий | Индекс Спецификация языка Java
Третий Выпуск


ГЛАВА 5

Преобразования и Продвижения


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

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

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

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

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

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

Эта глава сначала описывает одиннадцать категорий преобразований (§5.1), включая специальные преобразования в String учтенный оператор конкатенации строк +. Затем пять контекстов преобразования описываются:

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

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

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

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

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

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

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

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

        }
}
который производит вывод:

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

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

Преобразования определенного типа в языке программирования Java делятся на следующие категории.

5.1.1 Преобразования идентификационных данных

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

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

5.1.2 Расширение Примитивного Преобразования

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

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

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

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

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

Вот пример расширяющегося преобразования, которое теряет точность:

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 определенных преобразования на типах примитивов вызывают сужающимися примитивными преобразованиями:

Сужение преобразований может потерять информацию о полной величине числового значения и может также потерять точность.

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

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

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

  1. В первом шаге число с плавающей точкой преобразовывается любой в a long, если T long, или к int, если T byte, short, char, или int, следующим образом:
  2. Во втором шаге:
Пример:

class Test {
        public static void main(String[] args) {
                float fmin = Float.NEGATIVE_INFINITY;
                float fmax = Float.POSITIVE_INFINITY;
                System.out.println("long: " + (long)fmin +
                                        ".." + (long)fmax);
                System.out.println("int: " + (int)fmin +
                                        ".." + (int)fmax);
                System.out.println("short: " + (short)fmin +
                                        ".." + (short)fmax);
                System.out.println("char: " + (int)(char)fmin +
                                        ".." + (int)(char)fmax);
                System.out.println("byte: " + (byte)fmin +
                                        ".." + (byte)fmax);
        }
}
производит вывод:

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

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

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

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

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

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));
                // A int value not fitting in 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 Расширение и Сужение Примитивных Преобразований

Следующее преобразование комбинирует и расширение и сужение примитивного convesions:

Во-первых, byte преобразовывается в int через расширение примитивного преобразования, и затем получающегося int преобразовывается в a char сужая примитивное преобразование.

5.1.5 Расширение Ссылочных Преобразований

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

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

См. §8 для подробных спецификаций для классов, §9 для интерфейсов, и §10 для массивов.

5.1.6 Сужение Ссылочных Преобразований

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

Такие преобразования требуют, чтобы тест во время выполнения узнал, является ли фактическое ссылочное значение законным значением нового типа. В противном случае тогда a ClassCastException бросается.

5.1.7 Упаковка Преобразования

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

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

Если значение p быть упакованным true, false, a byte, a char в диапазоне \u0000 к \u007f, или int или short число между-128 и 127, затем позвольте r1 и r2 быть результатами любых двух преобразований упаковки p. Это всегда имеет место это r1 == r2.


Обсуждение

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

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

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


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

5.1.8 Распаковывание Преобразования

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

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

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

5.1.9 Преобразование непроверенное

Позвольте Г называть универсальное описание типа с n формальными параметрами типа. Есть преобразование непроверенное из необработанного типа (§4.8) Г к любому параметризованному типу Г формы <T1... Tn>. Использование преобразования непроверенного генерирует обязательное время компиляции, предупреждая (который может только быть подавлен, используя SuppressWarnings аннотация (§9.6.1.5)), если параметризованный Г типа не является параметризованным, вводят, который всеми параметрами типа являются неограниченные подстановочные знаки (§4.5.1).


Обсуждение

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

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

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

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


5.1.10 Преобразование получения

Позвольте Г называть универсальное описание типа с n формальными параметрами типа A1... С соответствующими границами U1... Un. Там существует преобразование получения из Г <T1... Tn> к Г <S1... Sn>, где, для 1 дюйма:

Преобразование получения на любом типе кроме параметризованного типа (§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 really 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, может считаться открытым из e в контексте, который включает высокоуровневое выражение, которое включает e.

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

Преимущество этой схемы состоит в том, что нет никакой потребности в a close работа, как определено в статье Об Основанном на различии Выделении подтипов для Параметрических Типов Ацуши Игараши и Мирко Вироли, в продолжениях 16-ой европейской Конференции по Объектно-ориентированному программированию (ECOOP 2002).

Для формальной учетной записи подстановочных знаков см. Дикий FJ Мэдсом Торджерсеном, Эриком Эрнстом и Кристианом Плеснером Хансеном, на 12-ой мастерской на Основах Объектно-ориентированного программирования (ДУРАК 2005).


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

Есть преобразование строк, чтобы ввести String от любого типа, включая нулевой тип. См. (§5.4) для деталей контекста преобразования строк.

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

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

5.1.13 Преобразование Набора значений

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

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

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

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

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

5.2 Преобразование присвоения

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

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


Обсуждение

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

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

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


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

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

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

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

Если, после того, как преобразования типов выше были применены, получающееся значение является объектом, который не является экземпляром подкласса или подынтерфейсом стирания типа переменной, то a ClassCastException бросается.


Обсуждение

Это обстоятельство может только возникнуть в результате загрязнения "кучи" (§4.12.2.1).

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


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

(Отметьте, однако, что присвоение может привести к исключению в особых случаях, включающих элементы массива, или доступ к полю - см. §10.10 и §15.26.1.)

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

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

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

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

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 значения.

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

Вот пример программы, иллюстрирующий присвоения ссылок:

public class Point { int x, y; }
public class Point3D extends Point { int z; }
public interface Colorable {
        void setColor(int color);
}
public 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
        }

}

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

public class Point { int x, y; }
public interface Colorable { void setColor(int color); }
public 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
        }
}
Вот другое присвоение включения в качестве примера объектов массива:

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
        }
}
В этом примере:

5.3 Преобразование Вызова метода

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

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

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

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


Обсуждение

Это обстоятельство может только возникнуть в результате загрязнения "кучи" (§4.12.2.1).


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

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

class Test {
        static int m(byte a, int b) { return a+b; }
        static int m(short a, short b) { return a-b; }
        public static void main(String[] args) {
                System.out.println(m(12, 2));           // compile-time error
        }
}
вызывает ошибку времени компиляции потому что целочисленные литералы 12 и 2 имейте тип int, так никакой метод m соответствия по правилам (§15.12.2). Язык, который включал неявное сужение целочисленных констант, будет нуждаться в дополнительных правилах разрешить случаи как этот пример.

5.4 Преобразование строк

Преобразование строк применяется только к операндам двоичного файла + оператор, когда одним из параметров является a String. В этом единственном особом случае, другом параметре + преобразовывается в a String, и новое String то, который является связью двух строк, является результатом +. Преобразование строк определяется подробно в пределах описания конкатенации строк + оператор (§15.18.1).

5.5 Кастинг Преобразования

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

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

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

Некоторые броски могут быть доказаны неправильными во время компиляции; такие броски приводят к ошибке времени компиляции.

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

Значение типа примитива может быть брошено к ссылочному типу, упаковывая преобразование (§5.1.7).

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

Остающиеся случаи включают преобразование ссылочного типа времени компиляции S (источник) к ссылочному типу времени компиляции T (цель).

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

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

Бросок к переменной типа (§4.4) всегда непроверен.

Бросок непроверенный от S до T абсолютно непроверен, если бросок от |S | к |T |, как статически известно, корректен. Иначе это частично непроверено. Бросок непроверенный вызывает предупреждение непроверенное произойти (если он не подавляется, используя SuppressWarnings аннотация (§9.6.1.5)).

Бросок является проверенным броском, если он, как статически известно, не корректен, и это не непроверено.

Подробные правила для законности времени компиляции преобразования кастинга значения ссылочного типа времени компиляции S к ссылочному типу времени компиляции T следующие:

См. §8 для спецификации классов, §9 для интерфейсов, и §10 для массивов.

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

Если исключение на этапе выполнения бросается, это - a ClassCastException.

Вот некоторые примеры кастинга преобразований ссылочных типов, подобных примеру в §5.2:

public class Point { int x, y; }

public interface Colorable { void setColor(int color); }

public 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 a final введите, и переменная a final тип всегда содержит значение того же самого типа времени выполнения как его тип времени компиляции. Поэтому, тип времени выполнения переменной e должен быть точно тип EndPoint, и введите EndPoint не реализует Colorable.

Вот является включение в качестве примера массивами (§10):

class Point {
        int x, y;

        Point(int x, int y) { this.x = x; this.y = y; }

        public String toString() { return "("+x+","+y+")"; }
}

public interface Colorable { void setColor(int color); }

public 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 }

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

public class Point { int x, y; }

public interface Colorable { void setColor(int color); }

public class ColoredPoint extends Point implements Colorable
{

        int color;
        
        public void setColor(int color) { this.color = color; }

}

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

5.6 Числовые Продвижения

Числовое продвижение применяется к операндам арифметического оператора. Числовые контексты продвижения позволяют использование преобразования идентификационных данных (§5.1.1) расширяющееся примитивное преобразование (§5.1.2), или преобразование распаковывания (§5.1.8).

Числовые продвижения используются, чтобы преобразовать операнды числового оператора к общему типу так, чтобы работа могла быть выполнена. Два вида числового продвижения являются унарным числовым продвижением (§5.6.1) и двоичным числовым продвижением (§5.6.2).

5.6.1 Унарное Числовое Продвижение

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

В любом случае преобразование набора значений (§5.1.13) тогда применяется.

Унарное числовое продвижение выполняется по выражениям в следующих ситуациях:

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

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 Двоичное Числовое Продвижение

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

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

Двоичное числовое продвижение выполняется на операндах определенных операторов:

Пример двоичного числового продвижения появляется выше в §5.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 (BEL), маскируя от всех кроме низких 5 битов символа. 7 числовое значение этого управляющего символа.


Содержание | Предыдущий | Следующий | Индекс Спецификация языка Java
Третий Выпуск

Авторское право © 1996-2005 Sun Microsystems, Inc. Все права защищены
Пожалуйста, отправьте любые комментарии или исправления через нашу форму обратной связи

free hit counter