![]() |
Spec-Zone .ru
спецификации, руководства, описания, API
|
Содержание | Предыдущий | Следующий | Индекс | Спецификация языка Java Третий Выпуск |
ГЛАВА 5
У каждого выражения, записанного в языке программирования Java, есть тип, который может быть выведен из структуры выражения и типов литералов, переменных, и методов, упомянутых в выражении. Возможно, однако, записать выражение в контексте, где тип выражения не является соответствующим. В некоторых случаях это приводит к ошибке во время компиляции. В других случаях контекст может быть в состоянии принять тип, который связывается с типом выражения; как удобство, вместо того, чтобы требовать, чтобы программист указал на преобразование типов явно, язык выполняет неявное преобразование из типа выражения к типу, приемлемому для его окружающего контекста.
Определенное преобразование от типа S до типа T позволяет выражению типа S быть обработанным во время компиляции, как будто у этого был тип T вместо этого. В некоторых случаях это потребует, чтобы соответствующее действие во время выполнения проверило законность преобразования или преобразовало значение времени выполнения выражения в форму, подходящую для нового типа T. Например:
Object
вводить Thread
требует, чтобы проверка на этапе выполнения удостоверилась, что значение времени выполнения является фактически экземпляром класса Thread
или один из его подклассов; если это не, исключение выдается.
Thread
вводить Object
не требует никакого действия времени выполнения; Thread
подкласс Object
, таким образом, любая ссылка производится выражением типа Thread
допустимое ссылочное значение типа Object
.
int
вводить long
требует расширения знака времени выполнения 32-разрядного целочисленного значения к 64-разрядному long
представление. Никакая информация не теряется. Преобразование из типа double
вводить long
требует нетривиального преобразования от 64-разрядного значения с плавающей точкой до 64-разрядного целочисленного представления. В зависимости от фактического значения времени выполнения может быть потеряна информация.
В каждом контексте преобразования только разрешаются определенные определенные преобразования. Для удобства описания определенные преобразования, которые возможны в языке программирования Java, группируются в несколько широких категорий:
Один контекст преобразования является операндом числового оператора такой как +
или *
. Процесс преобразования для таких операндов вызывают числовым продвижением. Продвижение является особенным в этом, в случае бинарных операторов, преобразование, выбранное для одного операнда, может зависеть частично от типа другого выражения операнда.
Эта глава сначала описывает одиннадцать категорий преобразований (§5.1), включая специальные преобразования в String
учтенный оператор конкатенации строк +
. Затем пять контекстов преобразования описываются:
OutOfMemoryError
(в результате упаковки преобразования (§5.1.7)), a NullPointerException
(в результате распаковывания преобразования (§5.1.8)), или a ClassCastException
(в результате преобразования непроверенного (§5.1.9)), чтобы быть брошенным во время выполнения.
OutOfMemoryError
(в результате упаковки преобразования (§5.1.7)), a NullPointerException
(в результате распаковывания преобразования (§5.1.8)), или a ClassCastException
(в результате преобразования непроверенного (§5.1.9)), чтобы быть брошенным во время выполнения.
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
Это может казаться тривиальным, но у этого есть два практических последствия. Во-первых, всегда разрешается для выражения иметь требуемый тип для начала, таким образом позволяя просто установленное правило, что каждое выражение подвергается преобразованию, если только тривиальное преобразование идентификационных данных. Во-вторых, это подразумевает, что разрешается для программы включать избыточные операторы броска ради ясности.
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
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
не точны к девяти существенным цифрам.
short
к byte
или char
char
к byte
или short
int
к byte
, short
, или char
long
к byte
, short
, char
, или int
float
к byte
, short
, char
, int
, или long
double
к byte
, short
, char
, int
, long
, или float
Сужающееся преобразование целого числа со знаком к целочисленному типу T просто отбрасывает все кроме n битов самых низкоуровневых, где n является числом битов, используемых, чтобы представить тип T. В дополнение к возможной потере информации о величине числового значения это может вызвать знак получающегося значения отличаться от знака входного значения.
Сужающееся преобразование a char
к целочисленному типу T аналогично просто отбрасывает все кроме n битов самых низкоуровневых, где n является числом битов, используемых, чтобы представить тип T. В дополнение к возможной потере информации о величине числового значения это может заставить получающееся значение быть отрицательным числом, даже при том, что char
s представляют 16-разрядные значения целого без знака.
Сужающееся преобразование числа с плавающей точкой к целочисленному типу T делает два шага:
long
, если T long
, или к int
, если T byte
, short
, char
, или int
, следующим образом: int
или long
0
.
long
, и это целочисленное значение может быть представлено как a long
, тогда результат первого шага long
значение V.
int
, тогда результат первого шага int
значение V. int
или long
.
int
или long
. int
или long
, результатом преобразования является результат первого шага.
byte
, char
, или short
, результатом преобразования является результат сужающегося преобразования в тип T (§5.1.3) результата первого шага.
производит вывод: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
.
Вот маленькая тестовая программа, которая демонстрирует много сужающихся преобразований, которые теряют информацию:
Эта тестовая программа производит следующий вывод: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
byte
к char
byte
преобразовывается в int
через расширение примитивного преобразования, и затем получающегося int
преобразовывается в a char
сужая примитивное преобразование.Расширяющиеся ссылочные преобразования никогда не требуют специального действия во время выполнения и поэтому никогда не выдают исключение во время выполнения. Они состоят просто в оценке ссылки как имеющий, некоторый другой вводит способ, который может быть доказан корректным во время компиляции.
См. §8 для подробных спецификаций для классов, §9 для интерфейсов, и §10 для массивов.
Object
к любому другому ссылочному типу.)
final
.
Cloneable
и java.io.Serializable
к любому типу T массива [].
ClassCastException
бросается.
boolean
вводить Boolean
byte
вводить Byte
char
вводить Character
short
вводить Short
int
вводить Integer
long
вводить Long
float
вводить Float
double
вводить Double
boolean
, тогда упаковка преобразования преобразовывает p в ссылку r класса и типа Boolean
, так, что r.booleanValue()
== p
byte
, тогда упаковка преобразования преобразовывает p в ссылку r класса и типа Byte
, так, что r.byteValue()
== p
char
, тогда упаковка преобразования преобразовывает p в ссылку r класса и типа Character
, так, что r.charValue()
== p
short
, тогда упаковка преобразования преобразовывает p в ссылку r класса и типа Short
, так, что r.shortValue()
== p
int
, тогда упаковка преобразования преобразовывает p в ссылку r класса и типа Integer
, так, что r.intValue()
== p
long
, тогда упаковка преобразования преобразовывает p в ссылку r класса и типа Long
, так, что r.longValue()
== p
float
тогда: Float
, так, что r.floatValue()
оценивает к p
Float
так, что r.isNaN()
оценивает к истине. double
, тогда Double
, так, что r.doubleValue()
оценивает к p
Double
так, что r.isNaN()
оценивает к истине. 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
) потребности, которые будут выделены и недостаточное хранение, доступны.
Boolean
вводить boolean
Byte
вводить byte
Character
вводить char
Short
вводить short
Integer
вводить int
Long
вводить long
Float
вводить float
Double
вводить double
Boolean
, тогда распаковывание преобразования преобразовывает r в r.booleanValue()
Byte
, тогда распаковывание преобразования преобразовывает r в r.byteValue()
Character
, тогда распаковывание преобразования преобразовывает r в r.charValue()
Short
, тогда распаковывание преобразования преобразовывает r в r.shortValue()
Integer
, тогда распаковывание преобразования преобразовывает r в r.intValue()
Long
, тогда распаковывание преобразования преобразовывает r в r.longValue()
Float
, распаковывание преобразования преобразовывает r в r.floatValue()
Double
, тогда распаковывание преобразования преобразовывает r в r.doubleValue()
null
, распаковывание преобразования бросает a NullPointerException
SuppressWarnings
аннотация (§9.6.1.5)), если параметризованный Г типа не является параметризованным, вводят, который всеми параметрами типа являются неограниченные подстановочные знаки (§4.5.1).
Обсуждение
Преобразование непроверенное используется, чтобы включить гладкому взаимодействию кода наследства, записанного перед введением универсальных типов, с библиотеками, которые подверглись преобразованию, чтобы использовать genericity (процесс, мы вызываем generification).
При таких обстоятельствах (наиболее особенно, клиенты платформы наборов в java.util
), код наследства использует необработанные типы (например, Collection
вместо Collection<String>
). Выражения необработанных типов передают как параметры методам библиотеки что использование параметризованные версии тех тех же самых типов как типы их соответствующих формальных параметров.
Такие вызовы, как могут показывать, не статически безопасны под системой типов, используя обобщения. Отклонение таких требований лишило бы законной силы большие тела существующего кода, и препятствовало бы тому, чтобы они использовали более новые версии библиотек. Это поочередно, отговорил бы поставщиков библиотеки использовать в своих интересах genericity.
Чтобы предотвратить такой нежелательный поворот событий, необработанный тип может быть преобразован в произвольный вызов универсального описания типа, к которому обращается необработанный тип. В то время как преобразование необоснованно, оно допускается как концессия практичности. Предупреждение (известный как предупреждение непроверенное) выпускается в таких случаях.
?
тогда Сай является новой переменной типа, верхней границей которой является Ui [A1: = S1...: = Sn] и чья нижняя граница является нулевым типом.
? extends
Висмут, тогда Сай является новой переменной типа, верхняя граница которой является glb (висмут, Ui [A1: = S1...: = Sn]) и чья нижняя граница является нулевым типом, где glb (V1..., Vm) является V1 &... & Vm. Это - ошибка времени компиляции, если для каких-либо двух классов (не интерфейсы) Вай и Вдж, Вай не является подклассом Vj или наоборот.
? super
Висмут, тогда Сай является новой переменной типа, верхней границей которой является Ui [A1: = S1...: = Sn] и чья нижняя граница является висмутом.
Преобразование получения не применяется рекурсивно.
Обсуждение
Преобразование получения разрабатывается, чтобы сделать подстановочные знаки более полезными. Чтобы понять побуждение, давайте начнем, смотря на метод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()
был бы вынужден записать его подпись как:
Это - нежелательный, поскольку он представляет информацию о реализации вызывающей стороне. Хуже, разработчик API мог бы рассуждать, что подпись, используя подстановочный знак - то, чего вызывающие стороны API требуют, и только позже понимают, что была устранена безопасная с точки зрения типов реализация.public static <T> void reverse(List<T> list)
Вызов от reverse()
к rev()
фактически безопасно, но это не может быть выровнено по ширине на основе общего отношения выделения подтипов между List<?>
и List<T>
. Вызов безопасен, потому что входящим параметром является, несомненно, список некоторого типа (хотя неизвестный). Если мы можем получить этот неизвестный тип в переменной типа X
, мы можем вывести T
быть X
. Это - сущность преобразования получения. Спецификация, конечно, должна справиться со сложностями, как нетривиальный (и возможно рекурсивно определенный) верхние или нижние границы, присутствие многократных параметров и т.д.
Обсуждение
Математически сложные читатели будут хотеть связать преобразование получения в установленную теорию типов. Читатели, незнакомые с теорией типов, могут пропустить это обсуждение - или иначе изучить подходящий текст, такой как Типы и Языки программирования Бенджамином Пирсом, и затем повторно посетить этот раздел.Вот тогда краткая сводка отношения преобразования получения в установленный тип теоретические понятия.
Подстановочные типы являются ограниченной формой экзистенциальных типов. Преобразование получения соответствует свободно открытию значения экзистенциального типа. Преобразование получения выражения e
, может считаться открытым из e
в контексте, который включает высокоуровневое выражение, которое включает e
.
Классическое open
работа на existentials требует, чтобы полученная переменная типа не вышла из открытого выражения. open
это соответствует преобразованию получения, всегда находится на контексте, достаточно большом, что полученная переменная типа никогда не может быть видимой вне того контекста.
Преимущество этой схемы состоит в том, что нет никакой потребности в a close
работа, как определено в статье Об Основанном на различии Выделении подтипов для Параметрических Типов Ацуши Игараши и Мирко Вироли, в продолжениях 16-ой европейской Конференции по Объектно-ориентированному программированию (ECOOP 2002).
Для формальной учетной записи подстановочных знаков см. Дикий FJ Мэдсом Торджерсеном, Эриком Эрнстом и Кристианом Плеснером Хансеном, на 12-ой мастерской на Основах Объектно-ориентированного программирования (ДУРАК 2005).
String
от любого типа, включая нулевой тип. См. (§5.4) для деталей контекста преобразования строк.В пределах выражения, которое не строго FP (§15.4), преобразование набора значений обеспечивает варианты для реализации языка программирования Java:
float
и не элемент набора значений плавающего, тогда реализация должна отобразить значение на самый близкий элемент набора значений плавающего. Это преобразование может привести к переполнению или потере значимости.
double
и не элемент двойного набора значений, тогда реализация должна отобразить значение на самый близкий элемент двойного набора значений. Это преобразование может привести к переполнению или потере значимости.
Ли в строгом FP коде или коде, который не строг FP, преобразование набора значений всегда листы, неизменные любое значение, тип которого ни один не float
ни double
.
Обсуждение
Пример такой недопустимой цепочки был бы:
Integer
, Comparable<Integer>
, Comparable
, Comparable<String>
Первые три элемента цепочки связываются, расширяя ссылочное преобразование, в то время как последняя запись получается от ее предшественника преобразованием непроверенным. Однако, эта скидка не допустимое преобразование присвоения, потому что цепочка содержит два параметризованных типа, Comparable<Integer>
и Comparable<String>
, это не подтипы.
Кроме того, если выражение является константным выражением (§15.28) типа byte
, short
, char
или int
:
byte
, short
, или char
, и значение константного выражения является представимым в типе переменной.
Если тип переменной float
или double
, тогда преобразование набора значений применяется к значению v, который является результатами преобразования типов:
v
имеет тип float
и элемент набора значений "расширенная экспонента плавающая", тогда реализация должна отобразиться v
к самому близкому элементу набора значений плавающего. Это преобразование может привести к переполнению или потере значимости.
v
имеет тип double
и элемент набора значений "двойная расширенная экспонента", тогда реализация должна отобразиться v
к самому близкому элементу двойного набора значений. Это преобразование может привести к переполнению или потере значимости. Если, после того, как преобразования типов выше были применены, получающееся значение является объектом, который не является экземпляром подкласса или подынтерфейсом стирания типа переменной, то a ClassCastException
бросается.
Обсуждение
Это обстоятельство может только возникнуть в результате загрязнения "кучи" (§4.12.2.1).Практически, реализации нуждаются только perfom в бросках, получая доступ к полю или методу объекта типа parametized, когда стертый тип поля, или стертый тип результата метода отличается от их нестертого типа.
Единственные исключения, которые может вызвать преобразование присвоения:
OutOfMemoryError
в результате преобразования упаковки.
ClassCastException
при особых обстоятельствах, обозначенных выше.
NullPointerException
в результате преобразования распаковывания на нулевой ссылке. Время компиляции, сужаясь констант означает что код, такой как:
позволяется. Без сужения, факт, что целочисленный литерал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 } }
veclong
не может быть присвоен a Long
переменная, потому что Long
тип класса кроме Object
. Массив может быть присвоен только переменной совместимого типа массива, или к переменной типа Object
, Cloneable
или java.io.Serializable
.
veclong
не может быть присвоен vecshort
, потому что они - массивы типа примитива, и short
и long
не тот же самый тип примитива.
cpvec
может быть присвоен pvec
, потому что любая ссылка, которая могла быть значением выражения типа ColoredPoint
может быть значение переменной типа Point
. Последующее присвоение нового Point
к компоненту pvec
тогда бросил бы ArrayStoreException
(если программа была иначе исправлена так, чтобы она могла быть скомпилирована), потому что a ColoredPoint
у массива не может быть экземпляра Point
как значение компонента.
pvec
не может быть присвоен cpvec
, потому что не каждая ссылка, которая могла быть значением выражения типа ColoredPoint
может правильно быть значение переменной типа Point
. Если значение pvec
во время выполнения была ссылка на экземпляр Point[]
, и присвоение на cpvec
были позволены, простая ссылка на компонент cpvec
, Скажем, cpvec[0]
, мог возвратить a Point
, и a Point
не a ColoredPoint
. Таким образом позволить такое присвоение позволило бы нарушение системы типов. Бросок может использоваться (§5.5, §15.16), чтобы гарантировать это pvec
ссылки a ColoredPoint[]
: cpvec = (ColoredPoint[])pvec; // okay, but may throw an // exception at run time
Если, после того, как упомянутые выше преобразования были применены, получающийся тип является необработанным типом (§4.8), преобразование непроверенное (§5.1.9) может тогда быть применено. Это - ошибка времени компиляции, если цепочка преобразований содержит два параметризованных типа, которые не находятся не в отношении подтипа.
Если тип выражения параметра также float
или double
, тогда преобразование набора значений (§5.1.13) применяется после преобразования типов:
float
элемент набора значений "расширенная экспонента плавающая", тогда реализация должна отобразить значение на самый близкий элемент набора значений плавающего. Это преобразование может привести к переполнению или потере значимости.
double
элемент набора значений "двойная расширенная экспонента", тогда реализация должна отобразить значение на самый близкий элемент двойного набора значений. Это преобразование может привести к переполнению или потере значимости. Если, после того, как преобразования типов выше были применены, получающееся значение является объектом, который не является экземпляром подкласса или подынтерфейсом стирания соответствующего типа формального параметра, то 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). Язык, который включал неявное сужение целочисленных констант, будет нуждаться в дополнительных правилах разрешить случаи как этот пример.+
оператор, когда одним из параметров является a String
. В этом единственном особом случае, другом параметре +
преобразовывается в a String
, и новое String
то, который является связью двух строк, является результатом +
. Преобразование строк определяется подробно в пределах описания конкатенации строк +
оператор (§15.18.1).
Преобразование набора значений (§5.1.13) применяется после преобразования типов.
Некоторые броски могут быть доказаны неправильными во время компиляции; такие броски приводят к ошибке времени компиляции.
Значение типа примитива может быть брошено к другому типу примитива преобразованием идентификационных данных, если типы являются тем же самым, или расширяющимся примитивным преобразованием или сужающимся примитивным преобразованием.
Значение типа примитива может быть брошено к ссылочному типу, упаковывая преобразование (§5.1.7).
Значение ссылочного типа может быть брошено к типу примитива, распаковывая преобразование (§5.1.8).
Остающиеся случаи включают преобразование ссылочного типа времени компиляции S (источник) к ссылочному типу времени компиляции T (цель).
Бросок от типа S до типа T, как статически известно, корректен если и только если S <: T (§4.10).
Бросок от типа S до параметризованного типа (§4.5) T непроверен, если по крайней мере одно из следующих условий не содержит:
Бросок непроверенный от S до T абсолютно непроверен, если бросок от |S | к |T |, как статически известно, корректен. Иначе это частично непроверено. Бросок непроверенный вызывает предупреждение непроверенное произойти (если он не подавляется, используя SuppressWarnings
аннотация (§9.6.1.5)).
Бросок является проверенным броском, если он, как статически известно, не корректен, и это не непроверено.
Подробные правила для законности времени компиляции преобразования кастинга значения ссылочного типа времени компиляции S к ссылочному типу времени компиляции T следующие:
final
класс (§8.1.1), тогда, если там существует супертип X из T, и супертип Y S, так, что и X и Y, является доказуемо отличными параметризованными типами, и что стирания X и Y являются тем же самым, ошибка времени компиляции происходит. Иначе, бросок является всегда законным во время компиляции (потому что, даже если S не реализует T, подкласс S мог бы).
final
класс (§8.1.1), тогда S должен реализовать T, или ошибка времени компиляции происходит. Object
, или ошибка времени компиляции происходит. final
(§8.1.1), затем если там существует супертип X из T, и супертип Y S, так, что и X и Y, являются доказуемо отличными параметризованными типами, и что стирания X и Y являются тем же самым, ошибка времени компиляции происходит. Иначе, бросок является всегда законным во время компиляции (потому что, даже если T не реализует S, подкласс T мог бы).
final
, тогда: Object
, тогда ошибка времени компиляции происходит (потому что Object
единственный тип класса, которому массивы могут быть присвоены).
java.io.Serializable
или тип Cloneable
, единственные интерфейсы реализуются массивами.
Object
или тип java.io.Serializable
или тип Cloneable
, или переменная типа, что S мог по закону быть брошен к, рекурсивно применяя эти правила, тогда бросок, является законной (хотя непроверенный).
Если бросок к ссылочному типу не является ошибкой времени компиляции, есть несколько случаев:
null
, тогда бросок позволяется. Иначе, позвольте R быть классом объекта, упомянутого ссылочным значением времени выполнения, и позволять T быть стиранием типа, названного в операторе броска. Преобразование броска должно проверить во время выполнения, что класс R является присвоением, совместимым с типом T. (Отметьте, что R не может быть интерфейсом, когда эти правила сначала применяются для любого данного броска, но R может быть интерфейсом, если правила применяются рекурсивно, потому что ссылочное значение времени выполнения может обратиться к массиву, тип элемента которого является интерфейсным типом.) Алгоритм для того, чтобы выполнить проверку показывают здесь: Object
(§4.3.2), или исключение на этапе выполнения бросается.
Object
(§4.3.2), или исключение на этапе выполнения бросается.
java.io.Serializable
или тип Cloneable
, единственные интерфейсы, реализованные массивами (этот случай мог уменьшиться мимо времени компиляции, проверяя, была ли, например, ссылка на массив сохранена в переменной типа Object
).
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.1) и двоичным числовым продвижением (§5.6.2).
Byte
, Short
, Character
, или Integer
это подвергается распаковыванию преобразования. Результат тогда продвигается на значение типа int
расширяющимся преобразованием (§5.1.2) или преобразованием идентификационных данных.
Long
, Float
, или Double
это подвергается распаковыванию преобразования.
byte
, short
, или char
, унарное числовое продвижение продвигает это значение типа int
расширяющимся преобразованием (§5.1.2).
Унарное числовое продвижение выполняется по выражениям в следующих ситуациях:
+
(§15.15.3)
-
(§15.15.4)
~
(§15.15.5)
>>
, >>>
, или <<
(§15.19); поэтому a long
расстояние сдвига (правильный операнд) не продвигает смещаемое значение (оставленный операнд) к long
Вот тестовая программа, которая включает примеры унарного числового продвижения:
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
double
, другой преобразовывается в double
.
float
, другой преобразовывается в float
.
long
, другой преобразовывается в long
.
int
. Двоичное числовое продвижение выполняется на операндах определенных операторов:
*
, /
и %
(§15.17)
+
и -
(§15.18.2)
<
, <=
, >
, и >=
(§15.20.1)
==
и !=
(§15.21.1)
&
, ^
, и |
(§15.22.1)
? :
(§15.25)
Пример двоичного числового продвижения появляется выше в §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. Все права защищены
Пожалуйста, отправьте любые комментарии или исправления через нашу