Содержание | Предыдущая | Следующая


ГЛАВА 5

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

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

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

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

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

В начале данной главы описано шесть категорий преобразований (§5.1), включая специальные преобразования типа String, разрешающие для строки операцию конкатенации "+". Далее описаны пять преобразований контекстов:

Ниже приведены некоторые примеры различных преобразований контекстов:

 

class Test {			

	public static void main(String[] args) {

// Преобразование приведения(§5.4)литерала  //float к типу int.Без операции приведения, //здесь произошла бы ошибка времени //компиляции, потому что это - сужающее //преобразование (§5.1.3):int i = (int)12.5f;

// Строковое преобразование (§5.4) значения //i к типу int:

System.out.println("(int)12.5f==" + i);

// Преобразование присваивания (§5.2) //значения i's к типу float. Это - //расширяющее преобразование (§5.1.2): float f = i;

// Строковое преобразование значения float //f's:
System.out.println("after float widening:"+ f);

// Арифметическое расширение  (§5.6) //значения i's к типу float. Это - двоичное //арифметическое расширение. После //расширения получается - float*float:
System.out.print(f);
f = f * i;
//Два строковые преобразования i и f:

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

// Преобразование при вызове метода (§5.3) 
// значения f's к типу double, необходимо,
// потому что метод Math.sin принимает 
// только аргумент double:
double d = Math.sin(f);
// Два строковые преобразования f и d:
System.out.println("Math.sin(" + f + ")==" + d);

}

}

выводят результат:

 

(int)12.5f==12
после расширения float: 12.0
12.0*12==144.0
Math.sin(144.0)==-0.49102159389846934

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

Преобразования типа в языке Ява разделены на шесть категорий.

5.1.1 Тождественное преобразование

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

Единственное разрешенное преобразование применимое к типу boolean - это тождественное преобразование от boolean к boolean.

5.1.2 Расширяющее примитивное преобразование

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

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

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

Несмотря на тот факт, что может происходить потеря точности, расширяющие преобразования относительно примитивных типов никогда не приводят во время выполнения к исключительным ситуациям (§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 Сужающие примитивные преобразования

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

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

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

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

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

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

  • Если T - long, и это целочисленное значение, может представляться как long, тогда результат первого шага - значение V типа long.
  • Иначе, если это целочисленное значение может представляться как int, тогда результат первого шага есть значение V типа int.
  • Если значение слишком маленькое (отрицательное число с большим по модулю значением или минус бесконечность), и результат первого шага - минимальное возможное значение типа int или long.
  • Если значение очень большое (положительное числом с большим по модулю значением или плюс бесконечность), и результат первого шага - максимальное возможное значение типа int или long.

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.

Сужающее преобразование 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 Расширяющие ссылочные преобразования

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

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

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

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

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

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

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

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

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

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

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

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

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

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

  • 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; // ошибка времени компиляции
}
}

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

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 (цель) следующие:

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

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

  • 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 Одноместное числовое расширение

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

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

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

 

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) для того, чтобы преобразовать операнды так, как необходимо:

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

Пример двуместного числового расширения упомянут выше в §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- числовое значение этого управляющего символа.


Содержание | Предыдущая | Следующая