Кен Арнольд Джеймс Гослинг

Вид материалаДокументы

Содержание


5.12. Тип выражения
5.13. Приведение типов
5.13.1. Неявное приведение типов
5.13.2. Явное приведение и instanceof
5.13.3. Строковое приведение
Подобный материал:
1   ...   26   27   28   29   30   31   32   33   ...   81

5.12. Тип выражения


У каждого выражения имеется определенный тип. Он задается типом компонентов выражения и семантикой операторов. Если арифметический или поразрядный оператор применяется к выражению целого типа, то результат будет иметь тип int, если только в выражении не участвует значение типа long — в этом случае выражение также будет иметь тип long. Все целочисленные операции выполняются с точностью int или long, так что меньшие целые типы short и byte всегда преобразуются в int перед выполнением вычислений.

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

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

При использовании в выражении значение char преобразуется в int по-средством обнуления старших 16 бит. Например, символ Unicode \uffff является эквивалентом целого значения 0x0000ffff. Несколько иначе рассматривается значение типа short, равное 0xffff, — с учетом знака оно равно –1, поэтому его эквивалент в типе int будет равен 0xffffffff.

5.13. Приведение типов


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

5.13.1. Неявное приведение типов


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

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

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

Сохранение диапазона не следует путать с сохранением точности. При некоторых неявных преобразованиях возможна потеря точности. Например, рассмотрим преобразование long в float. Значения float являются 32-разрядными, а значения long — 64-разрядными. float содержит меньше значащих цифр, чем long, даже несмотря на то, что этот тип способен хранить числа из большего диапазона. Присваивание значения long переменной типа float может привести к потере данных. Рассмотрим следующий фрагмент:

long orig = 0x7effffffffffffffL;

float fval = orig;

long lose = (long)fval;


System.out.println("orig = " + orig);

System.out.println("fval = " + fval);

System.out.println("losw = " + lose);

Первые два оператора создают значение long и присваивают его переменной float. Чтобы продемонстрировать, что при этом происходит потеря точности, мы производим явное приведение fval к long и присваиваем значение другой переменной (явное приведение типов рассматривается ниже). Результаты, выводимые программой, позволяют убедиться в том, что значение float потеряло часть своей точности, так как значение исходной переменной orig типа long отличается от того, что было получено при явном обратном приведении значения переменной fval к типу long:

orig = 9151314442816847871

fval = 9.15131e+18

lose = 9151314442816847872

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

Значение null может быть присвоено ссылке на объект любого типа, в том числе и ссылке на массив.

5.13.2. Явное приведение и instanceof


Когда значение одного типа не может быть присвоено переменной другого типа посредством неявного приведения, довольно часто можно воспользоваться явным приведением типов (cast). Явное приведение требует, чтобы новое значение нового типа как можно лучше соответствовало старому значению старого типа. Некоторые явные приведения недопустимы (вы не сможете преобразовать boolean в int), однако разрешается, например, явное приведение double к значению типа long, как показано в следующем фрагменте:

double d = 7.99;

long l = (long)d;

Когда значение с плавающей точкой преобразуется к целому типу, его дробная часть отбрасывается; например, (int)-72.3 равняется –72. В классе Math имеются методы, которые иначе осуществляют округление чисел с плавающей точкой при преобразовании в целое — см. раздел “Класс Math”.

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

Приведение целых типов заключается в “срезании” их старших битов. Если значение большего целого умещается в меньшем типе, к которому осуществляется преобразование, то ничего страшного не происходит. Однако если величина более широкого целого типа лежит за пределами более узкого типа, то потеря старших битов изменяет значение, а возможно — и знак. Фрагмент:

short s = -134;

byte b = (byte)s;


System.out.println("s = " + s + ", b = " + b);

выводит следующий результат (поскольку старшие биты s теряются при сохранении значения в b):

s = -134, b = 122

char можно преобразовать к любому целому типу и наоборот. При приведении целого типа в char используются только младшие 16 бит, а остальные биты отбрасываются. При преобразовании char в целый тип старшие 16 бит заполняются нулями. Тем не менее впоследствии работа с этими битами осуществляется точно так же, как и с любыми другими. В приведенном ниже фрагменте программы максимальный символ Unicode преобразуется к типу int (неявно) и к типу short (явно). Значение типа int (0x0000ffff) оказывается положительным, поскольку старшие биты символа обнулены. Однако при приведении к типу short получается отрицательная величина, так как старшим битом типа short является знаковый бит:

class CharCast {

public static void main(String[] args) {

int i = '\uffff';

short s = (short)'\uffff';


System.out.println("i = " + i);

System.out.println("s = " + s);

}

}

А вот как выглядит результат работы:

i = 65535

s = -1

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



Ссылка типа Coffee не обязательно относится к типу Mocha — объект также может иметь тип Latte. Следовательно, неверно, вообще говоря, ставить ссылку на объект типа Coffee там, где требуется ссылка на объект типа Mocha. Подобное приведение называется сужением (narrowing), или понижающим приведением, в иерархии классов. Иногда его также называют ненадежным приведением (unsafe casting), поскольку оно не всегда допустимо. Переход от типа, расположенного ниже в иерархии, к расположенному выше называется повышающим приведением типа; кроме того, употребляется термин надежное приведение, поскольку оно работает во всех случаях.

Но иногда вы совершенно точно знаете, что объект Coffee на самом деле является экземпляром класса Mocha. В этом случае можно осуществлять явное понижающее приведение. Это делается следующим образом:

Mocha fancy = (Mocha)joe;

Если такое приведение сработает (то есть ссылка joe действительно указывает на объект типа Mocha), то ссылка fancy будет указывать на тот же объект, что и joe, однако с ее помощью можно получить доступ к дополнительным функциям, предоставляемым классом Mocha. Если же преобразование окажется недопустимым, будет возбуждено исключение ClassCastException. В случае, если приведение даже потенциально не может быть правильным (например, если бы Mocha вообще не являлся подклассом того класса, к которому относится joe), будет выдано сообщение об ошибке во время компиляции программы. Таким образом предотвращаются возможные проблемы, связанные с неверными предположениями по поводу иерархии классов.

Иногда метод не требует, чтобы объект относился к расширенному типу, но может предоставить объектам расширенного типа дополнительные функции. Можно выполнить приведение типа и обработать исключение, однако использование исключений для подобных целей оказывается сравнительно медленным и потому считается проявлением плохого стиля. Для определения того, относится ли объект к конкретному типу, применяется метод instanceof, который возвращает true для допустимых преобразований:

public void quaff(Coffee joe) {

// ...

if (joe instanceof Mocha) {

Mocha fancy = (Mocha)joe;

// ... использовать функциональность Mocha

}

}

Ссылка null не указывает ни на какой конкретный объект, так что результат выражения

null instanceof Type

всегда равен false для любого типа Type.

5.13.3. Строковое приведение


Класс String отличается от остальных: это неявно используется в операторе конкатенации +, а строковые литералы ссылаются на объекты String. Примеры нам уже встречались в программах: при выполнении конкатенации Java пытается преобразовать в String все, что еще не относится к этому типу. Подобные приведения определены для всех примитивных типов и осуществляются вызовом метода toString объекта (см. раздел “Метод toString”).

Если преобразовать в String пустую ссылку, то результатом будет строка “null”. Если для данного класса метод toString не определен, то используется метод, унаследованный от класса Object и возвращающий строковое представление типа объекта.