Перейти на главную   
  helloworld.ru - документация и книги по программированию  
helloworld.ru - документация и книги по программированию
    главная     хостинг    
Поиск по сайту:  
Смотрите также
Языки программирования
C#
MS Visual C++
Borland C++
C++ Builder
Visual Basic
Quick Basic
Turbo Pascal
Delphi
JavaScript
Java
PHP
Perl
Assembler
AutoLisp
Fortran
Python
1C

Интернет-технологии
HTML
VRML
HTTP
CGI
FTP
Proxy
DNS
протоколы TCP/IP
Apache

Web-дизайн
HTML
Дизайн
VRML
PhotoShop
Cookie
CGI
SSI
CSS
ASP
PHP
Perl

Программирование игр
DirectDraw
DirectSound
Direct3D
OpenGL
3D-графика
Графика под DOS

Алгоритмы
Численные методы
Обработка данных

Сис. программирование
Драйверы

Базы данных
MySQL
SQL

Другое

Хостинг


Друзья
demaker.ru
Реклама

Лучший хостинг. Аренда серверов




helloworld.ru

ГЛАВА 5. Преобразования и расширения

Каждое выражение языка Ява имеет тип, который может быть выведен из структуры выражения, типов литералов, переменных и методов, упомянутых в выражении. Иногда разрешается писать выражение в контексте, где тип данного выражения не совпадает с типом, вытекающим из контекста. Но иногда, это ведет к ошибке времени компиляции, например, если выражение в операторе if (§14.8) имеет тип отличный от типа boolean, то происходит ошибка времени компиляции. В других случаях, контекст сможет принять тип, который связан с типом выражения, как результат преобразования, не требуя от программиста каких-то действий, чтобы явно задать преобразование типа. В таком случае язык Ява выполняет неявное преобразование от типа выражения к типу, приемлемому для окружающего контекста.

  • Преобразование от типа S к типу T разрешает обращаться во время компиляции к выражению типа S, как будто бы оно имеет тип T. В некоторых случаях для этого потребуются соответствующие действия во время выполнения, чтобы проверить правильность преобразования или перевести значение выражения в форму, соответствующую новому типу T. Например:
  • Преобразование от типа Object (§ 20.1) к типу Thread (§20.20) требует некоторой проверки во время выполнения, чтобы удостовериться, что значение в момент выполнения фактически является экземпляром класса Thread или одного из его подклассов; если же это не так, то генерируется исключение.
  • Преобразование от типа Thread к типу Object не требует никаких действий в момент выполнения; т.к. Thread - это подкласс Object, поэтому любая ссылка, порожденная выражением типа Thread является корректной ссылкой типа Object.
  • Преобразование типа int к типу long требует в момент выполнения расширить представление с 32 бит целочисленного значения до 64 бит long. При этом никакая информация не теряется.
  • Преобразование типа double к типу long требует нетривиального перевода из 64 битного значения с плавающей точкой в целочисленное 64 битное представление. В зависимости от фактического значения во время выполнения, информация может быть потеряна.

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

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

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

Одно из преобразований g)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.

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

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

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

 

class Test {
public static void main(String[] args) {
// Сужение int в short, при котором теряются старшие 
// разряды:
System.out.println("(short)0x12345678==0x" +
Integer.toHexString((short)0x12345678));
// значение int, не удовлетворяющее типу byte изменяет
// знак и величину:
System.out.println("(byte)255==" + (byte)255);
// Слишком большое значение float дает самое большое 
// значение int:
System.out.println("(int)1e20f==" + (int)1e20f);
// NaN, преобразованный в int выдает ноль:
System.out.println("(int)NaN==" + (int)Float.NaN);
// значение double, слишком большое для float выдает 
// бесконечность:
System.out.println("(float)-1e100==" + (float)-1e100);
// значение double, слишком маленькое для float теряет 
// значимость до ноля:
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 Расширяющие ссылочные преобразования

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

  • Любой классовый тип S в любой классовый тип T, при условии, что S - подкласс T. (Важный специальный случай расширяющего преобразования в классовый тип Object любого другого классового типа.)
  • Любой классовый тип S в любой интерфейсный тип K, при условии, что S реализует K.
  • Преобразование null в любой классовый тип, интерфейсный тип или тип массив.
  • Любой интерфейсный тип J в любой интерфейсный тип K, при условии, что J - подинтерфейс K.
  • Любой интерфейсный тип в тип Object.
  • Любой тип массив в тип Object.
  • Любой тип массив в тип Cloneable.
  • Любой тип массив SC [] в любой тип массив TC [], при условии, что SC и TC - ссылочные типы и имеется расширяющее преобразование SC в TC.

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

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

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

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

  • Любой классовый тип S в любой классовый тип T, при условии, что S - суперкласс T. (Важный специальный случай - сужающее преобразование классового типа Object в любой другой классовый тип.)
  • Любой классовый тип S в любой интерфейсный тип K, при условии, что S не имеет модификатора доступа final и не реализует K. (Важный специальный случай - сужающее преобразование классового типа Object в любой интерфейсный тип.)
  • Тип Object в любой тип массив.
  • Тип Object в любой интерфейсный тип.
  • Любой интерфейсный тип J в любой классовый тип T, который не имеет модификатора final.
  • Любой интерфейсный тип J в любой классовый тип T, не имеющий модификатора final и при условии, что T реализует J.
  • Любой интерфейсный тип J в любой интерфейсный тип K, при условии, что J - не подинтерфейс K и не существует метода m такого ,что J и K объявлены как метод m с той же самой сигнатурой, но различными возвращаемыми типами.
  • Любой тип массив SC [] в любой тип массив TC [], при условии, что SC и TC - ссылочные типы и имеется сужающее преобразование SC в TC.

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

5.1.6 Строковые преобразования

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

5.1.7 Запрещенные преобразования

  • Запрещены преобразования любого ссылочного типа в любой примитивный тип.
  • Кроме строковых преобразований запрещены преобразования любого примитивного типа в любой ссылочный тип.
  • Запрещены преобразования null в любой примитивный тип.
  • Запрещены любые преобразования в null, кроме тождественного преобразования.
  • Запрещены любые преобразования в тип boolean, кроме тождественного преобразования.
  • Запрещены любые преобразования из типа boolean, кроме тождественного и строкового преобразования.
  • Запрещены любые преобразования, кроме строкового преобразования классового типа S в другой классовый тип T, если S не является подкласс T, а T - не подкласс S.
  • Запрещены преобразования классового типа S в интерфейсный тип K, если S не имеет модификатора final и не реализует K.
  • Запрещены преобразования классового типа S в любой тип массив, если S - не Object.
  • Запрещены любые преобразования, кроме строкового преобразование интерфейсного типа J в классовый тип T, если T не имеет модификатора final и не реализует J.
  • Запрещены любые преобразования интерфейсного типа J в интерфейсный тип K, если в J и K объявлены методы с одной и той же сигнатурой, но с различными возвращаемыми типами.
  • Запрещены любые преобразования любого типа массив в любой классовый тип, кроме Object или String.
  • Запрещены преобразования любого типа массив в любой интерфейсный тип, исключая преобразование в интерфейсный тип Cloneable, который реализуется всеми массивами.
  • Запрещены преобразования типа массив SC [] в тип массив TC [], если не разрешаются любые преобразования, кроме строкового преобразования SC в TC.

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

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

  • Если выражение является константным и типа int.
  • Если тип переменной - byte, short или char.
  • Если значение выражения (известно во время компиляции, так как является константным выражением) представимо в типе переменной.

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

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

Преобразование присваивания никогда не генерирует исключительной ситуации. (Заметим однако, что присваивание может привести к возникновению исключительной ситуации в специальном случае, когда в присваивании участвуют элементы массива - смотри §10.10 и §15.25.1.)

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

byte theAnswer = 42;

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

 

byte theAnswer = (byte) 42; // приведение разрешено, но не требуется

Значение примитивного типа не может быть присвоено переменной ссылочного типа ; попытка сделать это приведет к ошибке времени компиляции. Значение типа boolean может быть присвоено только переменной типа boolean.

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

 

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	
i=0x123
d=1.2300000190734863

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


class Test {
	public static void main(String[] args) {
		short s = 123;
		char c = s;				  // ошибка: требуется приведение
		  s = c;			      //  ошибка: требуется приведение
	}
}

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

Значение ссылочного типа не может быть присвоено переменной примитивного типа; попытка сделать это приведет к ошибке времени компиляции.

Значение типа null (null - единственное такое значение) может быть присвоено любому ссылочному типу .

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


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

		// Присваивание переменных классового типа:
		Point p = new Point();
		p = new Point3D();							// ok: потому что Point3d - это
									// подкласс Point
		
		Point3D p3d = p;							// ошибка: требуется присваивание потому что
                         
									// Point не может быть Point3D
									// (даже динамически,
									// как в этом примере.)

		// Присваивание переменных типа Object:
		Object o = p;							// ok: любой объект в Object
		int[] a = new int[3];
		Object o2 = a;							// ok: массив в Object


		// Присваивание переменных интерфейсного типа:
		ColoredPoint cp = new ColoredPoint();
		Colorable c = cp;							// ok: ColoredPoint реализует
									// Colorable


		// Присваивание переменных типа массив:
		byte[] b = new byte[4];
		a = b;							// ошибка: они - не массивы
// одного примитивного типа
									
		Point3D[] p3da = new Point3D[3];
		Point[] pa = p3da;							// ok: так как мы можем Point3D присвоить
									// Point
		p3da = pa;							// ошибка: (необходимо приведение) так как Point
									//  не может быть присвоен Point3D

	}

}

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

  • Если S - классовый тип:
  • Если T - классовый тип, тогда либо S должен быть тем же самым классом, что и T, либо S должен быть подклассом T, либо происходит ошибка времени компиляции.
  • Если T - интерфейсный тип, тогда S должен реализовывать интерфейс T, либо происходит ошибка времени компиляции.
  • Если T - тип массив, тогда происходит ошибка времени компиляции.
  • Если S - интерфейсный тип:
  • Если T - классовый тип, тогда T должен быть Object или происходит ошибка времени компиляции.
  • Если T - интерфейсный тип, тогда T должен быть тот же самый интерфейс как и S или суперинтерфейс S, либо происходит ошибка времени компиляции.
  • Если T - тип массив, тогда происходит ошибка времени компиляции.
  • Если S - тип массив SC [], то есть массив компонент типа SC:
  • Если T - классовый тип, тогда T должен быть Object или происходит ошибка времени компиляции.
  • Если T - интерфейсный тип, тогда происходит ошибка времени компиляции, если T не интерфейсный тип Cloneable - единственный интерфейс, реализованный массивами.
  • Если T - тип массив TC [], то есть массив компонент типа TC, тогда происходит ошибка времени компиляции, если не выполняется ни одно из следующих условий:
  • TC и SC - одинаковые примитивные типы.
  • TC и SC являются ссылочными типами, и тип SC может быть присвоен TC, как определено рекурсивно применяемыми правилами совместимости по присваиванию времени компиляции.

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

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

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();
// Правильно, потому что ColoredPoint - подкласс
// Point:
p = cp;
// Правильно, потому что ColoredPoint реализует //Colorable:
Colorable c = cp;
// Следующая причина ошибки времени компиляции в том,
// что мы не можем убедиться, что они наследуют, что
// зависит от типа p времени выполнения; проверка во
// время выполнения будет необходимой для нужного
// сужающего преобразования и
// должна быть обозначена, вставкой присваивания:
cp = p; 			// p не может быть ни ColoredPoint,
	// ни подклассом ColoredPoint
c = p; 				// p не может реализовать 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; //правильно
Long l = veclong; // ошибка времени компиляции
short[] vecshort = veclong;//ошибка времени компиляции
Point[] pvec = new Point[100];
ColoredPoint[] cpvec = new ColoredPoint[100];
pvec = cpvec; // правильно
pvec[0] = new Point();// разрешено временем
// компиляции, но сгенерировало
// бы исключение времени
// выполнения
cpvec = pvec; // ошибка времени компиляции
}
}

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

  • Значение veclong не может быть присвоено переменной типа Long, потому что у Long другой классовый тип (§20.8), чем у Object. Массив может быть присвоен только переменной совместимого типа массив, или переменной типа Object.
  • Значение veclong не может быть присвоено vecshort, потому что они - массивы примитивного типа, а long и short - не один и тот же примитивный тип.
  • Значение cpvec может быть присвоено pvec, потому что любая ссылка, которая могла бы быть значением выражения типа ColoredPoint, может быть значением переменной типа Point. Последующее присваивание новой переменной Point к компоненте pvec затем привело бы к ArrayStoreException (если программа была бы исправлена так, чтобы она могла бы компилироваться), потому что массив ColoredPoint не может иметь экземпляр Point как значение компонента.
  • Значение pvec не может быть присвоено к cpvec, потому что не каждая ссылка, которая могла бы быть значением выражения типа ColoredPoint, может быть правильным значением переменной типа Point. Если значение pvec во время выполнения было ссылкой на экземпляр Point [], и присваивание cpvec было разрешено, то простая ссылка на компонент cpvec, скажем, cpvec [0], могла бы вернуть Point, а Point - не ColoredPoint. Таким образом, чтобы позволить такое присваивание сделало возможным бы нарушение системы типов. Приведение может использоваться ( §5.4, §15.15) чтобы гарантировать, что pvec ссылается на ColoredPoint []:
cpvec = (ColoredPoint[])pvec;			// разрешено, но может генерировать
// исключение во время выполнения

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

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

Преобразование вызова метода как таковое не включает неявное сужение констант целочисленного типа, которое является частью преобразования присваивания (§5.2). Разработчики языка Ява считают, что включение этого неявного сужающего преобразования усложнило бы решение вопроса о соответствии перегружаемых методов (§15.11.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));										// ошибка времени компиляции
	}

}

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

5.4 Строковое преобразование

Строковое преобразование применяется только к операндам двуместной операции "+", когда один из аргументов - String. В этом случае, другой аргумент операции "+" преобразовывается в String и результатом операции "+" будет новая строка, которая является конкатенацией двух строк. Строковое преобразование подробнее определено в описании операции "+"(конкатенация строк) (§15.17.1).

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

Преобразование приведения применяется к операнду операции приведения (§15.15): тип выражения операнда должен быть преобразован в тип, явно указанный в операции приведения. Контексты приведения позволяют использовать тождественные преобразования (§5.1.1), расширяющие примитивные преобразования (§5.1.2), сужающие примитивные преобразования (§5.1.3), расширяющие ссылочные преобразования (§5.1.4) или сужающие ссылочные преобразования (§5.1.5). Таким образом, преобразования приведения более общие, чем преобразования вызова, присваивания или метода: приведение может производить любое разрешенное преобразование иначе, чем строковое преобразование.

Некоторые приведения времени компиляции считаются неверными; такие приведения заканчиваются ошибкой времени компиляции.

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

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

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

  • Если S - классовый тип:
  • Если T - классовый тип, тогда S и T должны быть связанными классами, т.е. S и T должны быть одинаковыми классами или S - подкласс T, или T - подкласс S; происходит ошибка времени компиляции.
  • Если T - интерфейсный тип:
  • Если S - не final класс (§8.1.2), тогда приведение всегда правильное (потому что, даже если S не реализует T, подкласс S может существовать).
  • Если S - final класс (§8.1.2), тогда S должен реализовывать T, иначе произойдет ошибка времени компиляции.
  • Если T - тип массив, тогда S должен быть классом Object, иначе произойдет ошибка времени компиляции.
  • Если S - интерфейсный тип:
  • Если T - классовый тип, который не является final (§8.1.2) , тогда приведение всегда правильное (потому что даже если T не реализует S, подкласс T может существовать).
  • Если T - классовый тип, который является final (§8.1.2) , тогда T должен реализовывать S, иначе произойдет ошибка времени компиляции.
  • Если T - интерфейсный тип и, если T и S содержат методы с одинаковой сигнатурой (§8.4.2) , но с различными типами возвращаемого результата, тогда происходит ошибка времени компиляции.
  • Если S - тип массив SC [ ], то есть массив компонент типа SC:
  • Если T - классовый тип, тогда, если T - не класса Object, то происходит ошибка времени компиляции (потому что Object - единственный классовый тип, которому могут быть присвоены массивы).
  • Если T - интерфейсный тип, тогда происходит ошибка времени компиляции за исключением того случая, когда T - интерфейсный тип Cloneable, единственный интерфейс, реализованный массивами
  • Если T - тип массив TC [ ], то есть массив компонент типа TC, тогда ошибки времени компиляции не произойдет в одном из следующих случаев:
  • TC и SC - одного и того же примитивного типа.
  • TC и SC - ссылочные типы и тип SC, может быть приведен к TC с помощью рекурсивного применения этих правил для приведения.

Для более подробного рассмотрения классов, интерфейсов и массивов см. §8 , §9 , §10 - соответственно

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

  • Приведение может быть произведено во время компиляции. Приведение типа S к типу T правильно во время компиляции тогда и только тогда, когда S может быть преобразован в T с помощью преобразования присваивания (§5.2).
  • Приведение требует проверки во время выполнения. Если значение во время выполнения есть null, тогда приведение допускается. В противном случае, пусть R- класс объекта, на который указывает ссылка времени выполнения, и пусть T - тип, указанный в операции приведения. Преобразование приведения должно проверять, во время выполнения, что класс R совместим по присваиванию с типом T, используя алгоритм, указанный в §5.2, но, используя класс R вместо типа S как там определено. (Заметьте, что R не может быть интерфейсом, когда эти правила впервые применяются для любого данного приведения, но R может быть интерфейсом, если правила применяются рекурсивно, потому что во время выполнения ссылочное значение указывает на массив, чей тип элемента - интерфейсный тип.) Этот измененный алгоритм показан здесь:
  • Если R - обычный класс (не класс массива):
  • Если T - классовый тип, тогда R с классом T или подклассом T должны быть одинаковыми (§4.3.4), или во время выполнения генерируется исключительная ситуация
  • Если T - интерфейсный тип, тогда R должен реализовывать интерфейс T (§8.1.4) или во время выполнения генерируется исключительная ситуация.
  • Если T - тип массив, тогда во время выполнения генерируется исключительная ситуация.
  • Если R - интерфейс:
  • Если T - классовый тип, тогда T должен быть типа Object ( §4.3.2, §20.1) или во время выполнения генерируется исключительная ситуация.
  • Если T - интерфейсный тип, тогда R должен быть одним из интерфейсов, одинаковым с T или подинтерфейсом T, или во время выполнения генерируется исключительная ситуация.
  • Если T - тип массив, то во время выполнения генерируется исключительная ситуация.
  • Если R - класс, представленный типом массив RC [ ] , то есть массивом из компонент типа RC:
  • Если T - классовый тип, тогда T должен быть типа Object ( §4.3.2, §20.1) или во время выполнения генерируется исключительная ситуация.
  • Если T - интерфейсный тип, тогда исключительная ситуация генерируется во время выполнения пока T не интерфейсный тип Cloneable, единственный интерфейс, реализованный массивами (этот случай мог бы исчезнуть после проверки времени компиляции, например, если ссылка на массив была сохранена в переменной типа Object).
  • Если T - тип массив TC [ ], то есть массив компонент типа TC, то исключительная ситуация времени выполнения не генерируется в одном из следующих случаев:
  • TC и RC - одинаковые примитивные типы.
  • TC и RC - ссылочные типы и тип RC может быть приведен к типу TC рекурсивным применением этих правил для приведения.

Если генерируется исключение времени выполнения, то это - ClassCastException ( §11.5.1.1, §20.22).

Здесь приведены некоторые примеры преобразований приведения ссылочных типов подобные примеру в §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;
// Следующее может давать ошибки времени выполнения,
// потому что мы не можем быть уверены в том, что
// приведения будут успешны; эта возможность
// обусловлена приведениями:
Cp = (ColoredPoint) p; 			// p может не быть ссылкой на
// объект, который является классом ColoredPoint
// или подклассом ColoredPoint
c = (Colorable) p; 			// p может не быть Colorable
// Следующие приведения генерируют ошибки времени
// компиляции, потому что они никогда не будут успешно
// откомпилированы как показано в тексте:
Long l = (Long) p; // ошибка времени компиляции #1
EndPoint e = new EndPoint ();
c = (Colorable) e; // ошибка времени компиляции #2
}
}

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

Вторая ошибка происходит потому, что переменная типа EndPoint никогда не ссылается на значение, которое реализует интерфейс Colorable. Это происходит потому, что EndPoint - это final тип, и переменная 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];
// Следующая строка будет генерировать
// ClassCastException:
ColoredPoint [] cpa = (ColoredPoint []) pa;
System.out.println (cpa [0]);
int [] shortvec = new int [2];
Object o = shortvec;
// Следующая строка будет генерировать
// ClassCastException:
Colorable c = (Colorable) o;
c.setColor (0);
}
}

5.6 Числовое расширение

Числовое расширение применяется к операндам арифметической операции.

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

Числовые расширения используются для того, чтобы приводить операнды числовой операции к общему типу так, чтобы действие могло выполняться. Два вида числового расширения - это одноместное числовое расширение (§5.6.1) и двуместное числовое расширение (§5.6.2). Аналогичные преобразования в Cи называются "обычными одноместными преобразованиями" и "обычными двуместными преобразованиями".

Числовое расширение это не существенная особенность языка Ява, а свойство специфического определения встроенных операций.

5.6.1 Одноместное числовое расширение

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

  • Если операнд имеет тип byte, short или char, одноместное числовое расширение расширяет его до значения типа int расширяющим преобразованием (§5.1.2).
  • Иначе, одноместный числовой операнд остается как есть и не преобразовывается.

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

  • Выражения, задающие размерность массива при его создании (§15.9)
  • Индекс выражения при доступе к массиву (§15.12)
  • Операнды одноместных операций плюс + (§15.14.3) и минус - (§15.14.4)
  • Операнд поразрядной операции дополнения ~ (§15.14.5)
  • Каждый операнд операторов сдвига >>, >>> и << (§15.18), так, что размер сдвига long (правый операнд) не расширяет значение, которое будет сдвинуто (левый операнд) до long

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

 

Class Test {
public static void main(String[] args) {
byte b = 2;
int a[] = new int [b];  //расширение выражения,
// задающего размерность
char c = ' \u0001 ';
a[c] = 1; // расширение индексного выражения
a[0] = -c; // одноместное расширение
System.out.println (" a: " + a[0] + "," + a[1]);
b = -1;
int i = ~b; // расширение при поразрядном дополнении
System.out.println ("~0x" + Integer.toHexString (b)
+ "==0x" + Integer.toHexString (i));
i = b << 4L; // сдвинутое расширение(левый операнд)
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) для того, чтобы преобразовать операнды так, как необходимо:

  • Если какой-то операнд имеет тип double, то другой преобразовывается в тип double.
  • Иначе, если какой-то операнд имеет тип float, то другой преобразовывается в тип float.
  • Иначе, если какой-то операнд имеет тип long, то другой преобразовывается в тип long.
  • Иначе, оба операнда преобразовываются в тип int.

Двуместное числовое расширение выполняется на операндах некоторых операций:

  • Мультипликативные операции *, / и % (§15.16)
  • Операции сложения и вычитания для числовых типов + и - (§15.17.2)
  • Числовые операции сравнения <, < =, >, и > = (§15.19.1)
  • Числовые операции сравнения на равенство == и != (§15.20.1)
  • Целые поразрядные операции &, ^, и | (§15.21.1)
  • В некоторых случаях, условная операция ?: (§15.24)

Пример двуместного числового расширения упомянут выше в §5.1. Здесь показан другой пример:

 

class Test {
public static void main(String[] args) {
int i = 0;
float f = 1.0f;
double d = 2.0;
// Сначала i*f расширилось до float*float, затем
// float == double расширяется до double==double:
if (i*f == d)
System.out.println ("oops");
// char&byte расширяется до int&int:
byte b = 0x1f;
char c = 'G';
int control = c & b;
System.out.println (Integer.toHexString(control));
// int:float расширился до float:float:
f = (b ==0)? f : 4.0f;
System.out.println (1.0/f);
}
}

который выводит:

 

7

0.25

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


[ Назад | Оглавление | Далее ]










helloworld.ru © 2001-2021
Все права защищены