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


ГЛАВА 15

Выражения

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

Эта глава определяет значения выражений в языке Ява и правил для их оценки.

15.1 Вычисление, обозначение и результат

Когда выражение в Ява-программе оценено (выполнено), результат обозначает одно из следующих 

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

Выражение ничего не обозначает, тогда и только тогда, когда это - вызов метода (§15.11), который вызывает метод, не возвращающий значение, то есть метод объявленный void (§8.4). Такое выражение может использоваться только как оператор-выражения (§14.7), потому что каждый другой контекст, в котором выражение может появляться, требует, чтобы выражение что-нибудь обозначало. Оператор выражения, который является вызовом метода, может также вызывать метод, который возвращает результат; в этом случае значение, возвращенное методом отбрасывается.

Каждое выражение входит в объявления некоторого (классового или интерфейсного) типа, который описан: в инициализаторе поля, в статическом инициализаторе, в объявлениях конструктора или в самом коде метода.

15.2 Переменные как значения

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

15.3 Тип выражения

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

Значение выражения всегда совместимо по присваиванию(§5.2) с типом выражения, точно как значение, сохраненное в переменной всегда совместимо с типом переменной. Другими словами, значение выражения, чей тип - T, всегда соответствует для присваивания на переменную типа T.

Обратите внимание, что выражение, чей тип - классовый типа F, который объявлен final, точно будет иметь значение, которое является или пустой ссылкой или объектом, чей класс - непосредственно F, потому что final типы не имеют подклассов.

15.4 Выражения и проверки времени выполнения

Если тип выражения - примитивный тип, то значение выражения имеет тот же самый примитивный тип. Но если тип выражения - ссылочный тип, то класс вызванного объекта, может иметь значение ссылки на объект скорее чем пустой указатель, это не обязательно известно во времени компиляции. Существует несколько случаев в языке Ява, где фактический класс вызванного объекта оказывает воздействие на выполнение программы в методе, который не может быть получен из типа выражения. Они следующие: 

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

15.5 Нормальное и преждевременное завершение вычислений

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

Если, однако, во время вычисления выражения возникает исключение, то выражение, как говорится, завершается преждевременно. Причина преждевременного завершения вычислений всегда порождается некоторым значением.

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

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

Если возникает исключительная ситуация, тогда вычисление одного или более выражений может быть завершено прежде, чем завершатся все шаги их нормального вычисления ; такие выражения, завершаются преждевременно. Термины "завершено нормально" и "завершено преждевременно" также применяются к выполнению операторов (§14.1). Оператор может завершаться преждевременно по ряду причин, и не только, потому что сгенерирована исключительная ситуация.

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

15.9 Выражения создания массива

Выражение создания экземпляра массива используется, чтобы создавать новые массивы (§ 10).

Выражение создания массива порождает объект, являющийся новым массивом, элементы которого имеют тип, определенный как PrimitiveType или TypeName. TypeName может быть любым ссылочным типом, даже классовым типом с модификатором abstract (§ 8.1.2.1) или интерфейсным типом (§ 9).

Тип выражения создания - это тип массив, который может определяться с помощью выражения создания, из которого удаляются ключевое слово new и все выражения DimExpr; например, типом выражения создания:

new double[3][3][]

является:

double[][][]
Тип каждого выражения размерности DimExpr должен быть целочисленным, иначе происходит ошибка времени компиляции. Каждое выражение подвергается одноместному числовому расширению (§ 5.6.1). Расширенный тип должен быть типом int, иначе происходит ошибка времени компиляции; это означает, в частности, что тип выражения размерности не должен быть типом long.

15.9.1 Вычисление выражений создания массива времени выполнения

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

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

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

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

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

Если выражение создания массива содержит выражения DimExpr в количестве N, тогда выполняется, в действительности, несколько вложенных циклов глубины N -1 для создания массива из массивов. Например, объявление:

float[][] matrix = new float[3][3];

эквивалентно следующему:

float[][] matrix = new float[3][];
for (int d = 0; d < matrix.length; d++)
	matrix[d] = new float[3];

и:

Age[][][][][] Aquarius = new Age[6][10][8][12][];

эквивалентно:

Age[][][][][] Aquarius = new Age[6][][][][];
for (int d1 = 0; d1 < Aquarius.length; d1++) {
	Aquarius[d1] = new Age[8][][][];
	for (int d2 = 0; d2 < Aquarius[d1].length; d2++) {
		Aquarius[d1][d2] = new Age[10][][];
		for (int d3 = 0; d3 < Aquarius[d1][d2].length; d3++) {
			Aquarius[d1][d2][d3] = new Age[12][];
		}
	}
}

где d, d1, d2 и d3 - переменные, имена которых не совпадают с уже объявленными локальными переменными. Таким образом, единственное выражение с ключевым словом new фактически создает один массив длины 6, 6 массивов длины 10, 60 массивов длины 8, и 480 массивов длины 12. В этом примере не указывается пятая размерность, которая может также оказаться массивом, содержащим фактические элементы массива (ссылающиеся на объекты Age), которые первоначально инициализируются пустыми ссылками. Эти массивы могут быть заполнены позже разными способами, например так:

Age[] Hair = { new Age("quartz"), new Age("topaz") };
Aquarius[1][9][6][9] = Hair;

Многомерный массив не обязан иметь массив одинаковой длины на каждом

уровне; таким образом, может быть создана треугольная матрица:

float triang[][] = new float[100][];
for (int i = 0; i < triang.length; i++)
	triang[i] = new float[i+1];

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

15.9.2 Пример: порядок вычисления выражения создания массива

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

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

class Test {
	public static void main(String[] args) {
		int i = 4;
		int ia[][] = new int[i][i=3];
		System.out.println(
			"[" + ia.length + "," + ia[0].length + "]");
	}
}

напечатает:

[4,3]

потому что первая размерность вычисляется равной 4 перед тем, как второе выражение размерности установит значение i равным 3.

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

class Test {

	public static void main(String[] args) {
		int[][] a = { { 00, 01 }, { 10, 11 } };
		int i = 99;
		try {
			a[val()][i = 1]++;
		} catch (Exception e) {
			System.out.println(e + ", i=" + i);
		}
	}

	static int val() throws Exception {
		throw new Exception("unimplemented");
	}

}

печатает:

java.lang.Exception: unimplemented, i=99

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

15.9.3 Пример: создание массива и обнаружение переполнения памяти

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

Так, например, тестирующая программа:

class Test {
	public static void main(String[] args) {
		int len = 0, oldlen = 0;
		Object[] a = new Object[0];
		try {
			for (;;) {
				++len;
				Object[] temp = new Object[oldlen = len];
				temp[0] = a;
				a = temp;
			}
		} catch (Error e) {
			System.out.println(e + ", " + (oldlen==len));
		}
	}
}

напечатает:

java.lang.OutOfMemoryError, true

потому что состояние переполнения памяти обнаруживается после того, как вычисляется выражение oldlen = len аргумента.

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

15.10 Выражения доступа к полю

Выражение доступа к полю может обращаться к полю объекта или массиву, ссылка на который является значением выражения или значением специального зарезервированного слова super. (Также возможно обратиться к полю текущего экземпляра или текущего класса с помощью простого имени; см. §15.13.1.)

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

15.10.1 Доступ к полю с использованием первичного выражения

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

Если поле имеет модификатор static:

Если поле не имеет модификатора static :

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

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

 

class S { int x = 0; }
class T extends S { int x = 1; }
class Test {
	public static void main(String[] args) {

		T t = new T();
		System.out.println("t.x=" + t.x + when("t", t));

		S s = new S();
		System.out.println("s.x=" + s.x + when("s", s));

		s = t;
		System.out.println("s.x=" + s.x + when("s", s));

	}


	static String when(String name, Object t) {
		return " when " + name + " holds a "
			+ t.getClass() + " at run time.";
	}
}

печатает:

t.x=1 when t holds a class T at run time.
s.x=0 when s holds a class S at run time.
s.x=0 when s holds a class T at run time.

Последняя строка показывает, что поле, к которому осуществляется доступ, действительно не зависит от текущего класса, на который осуществляется ссылка; даже если в s хранится ссылка на объект класса T, выражение s.x обращается к полю x класса S, потому что тип выражения s есть S. Объект класса T содержит два поля, названные x, одно для класса T и одно для суперкласса S.

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

class S { int x = 0; int z() { return x; } }
class T extends S { int x = 1; int z() { return x; } }
class Test {

	public static void main(String[] args) {
		T t = new T();
		System.out.println("t.z()=" + t.z() + when("t", t));
		S s = new S();
		System.out.println("s.z()=" + s.z() + when("s", s));
		s = t;
		System.out.println("s.z()=" + s.z() + when("s", s));
	}

	static String when(String name, Object t) {
		return " when " + name + " holds a "
			+ t.getClass() + " at run time.";
	}
}

Теперь в качестве вывода получим:

t.z()=1 when t holds a class T at run time.
s.z()=0 when s holds a class S at run time.
s.z()=1 when s holds a class T at run time.

Последняя строка показывает, что, метод, к которому происходит обращение, действительно зависит от текущего класса объекта, на который осуществляется ссылка. Когда s содержит ссылку на объект класса T, выражение s.z () обращается к методу z класса T, несмотря на тот факт, что типом выражения s является тип S. Метод z класса T замещает метод z класса S.

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

class Test {
	static String mountain = "Chocorua";

	static Test favorite(){
		System.out.print("Mount ");
		return null;
	}

	public static void main(String[] args) {
		System.out.println(favorite().mountain);
	}
}

Этот класс компилируется, выполняется, и в результате печатается:

Mount Chocorua

Хотя результатом favorite() является null, NullPointerException не генерируется. Строка "Mount " печатается, демонстрируя, что выражение Primary действительно полностью вычисляется во время выполнения, несмотря на тот факт, что для того чтобы определить, к которому полю устанавливать доступ, используется только тип этого выражения, а не значение, (потому что поле mountain имеет модификатор static).

15.10.2 Вызов элементов суперкласса с использованием зарезервированного слова super

Специальное выражение, использующее зарезервированное слово super, может применяться только в методе экземпляра или конструкторе или в инициализаторе переменной экземпляра; эти ситуации – те же самые, что и ситуации, в которых может быть использовано ключевое слово this (§ 15.7.2). Выражение, использующее зарезервированное слово super, не может использоваться в классе Object, поскольку Object не имеет суперкласса; если зарезервированное слово super появляется в классе Object, то в результате произойдет ошибка времени компиляции.

Предположим, что выражение доступа к полю super.name появляется в пределах класса C, а класс S – непосредственный суперкласс класса C. Тогда выражение super.name обрабатывается так же, как обрабатывалось бы выражение ((S)this).name; таким образом, оно обращается с полю name текущего объекта, но при условии, что текущий объект рассматривается как экземпляр суперкласса. Таким образом выражение может обращаться к полю name, которое является видимым в классе S, даже если в классе C это поле скрыто объявлением поля name.

Использование super демонстрируется следующим примером:

interface I { int x = 0; }
class T1 implements I { int x = 1; }
class T2 extends T1 { int x = 2; }
class T3 extends T2 {
	int x = 3;
	void test() {
		System.out.println("x=\t\t"+x);
		System.out.println("super.x=\t\t"+super.x);
		System.out.println("((T2)this).x=\t"+((T2)this).x);
		System.out.println("((T1)this).x=\t"+((T1)this).x);
		System.out.println("((I)this).x=\t"+((I)this).x);
	}
}
class Test {
	public static void main(String[] args) {
		new T3().test();
	}
}

результатом которого является вывод:

x=					3
super.x=					2
((T2)this).x=					2
((T1)this).x=					1
((I)this).x=					0

В пределах класса T3 выражение super.x обрабатывается точно также, как если это было бы выражение:

((T2)this).x

15.11 Выражения вызова метода

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

Определение ArgumentList из § 15.8 повторим здесь для удобства:

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

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

15.11.1 Первый шаг при компиляции: определение класса или интерфейса для поиска

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

 

 

15.11.4.4 Определение вызываемого метода

Стратегия для поиска метода зависит от способа вызова.

Если способ вызова static, никакая целевая ссылка не нужна и замещение не разрешается. Метод m класса T - тот метод, который будет вызван.

Иначе, должен быть вызван метод экземпляра и имеется целевая ссылка. Если целевая ссылка есть null, то в этой точке генерируется NullPointerException. Иначе, целевая ссылка указывает на целевой объект и будет использоваться как значение ключевого слова this в вызванном методе. Другие четыре возможности для способа вызова рассмотрены далее.

Если способ вызова nonvirtual, замещение не разрешается. Метод m класса T - тот метод, который будет вызван.

Если способ вызова interface, virtual или super, то замещение может осуществляться. Используется динамический поиск метода. Процесс динамического поиска начинается с класса S, определенного следующим образом:

Динамический поиск метода использует следующую далее процедуру для поиска класса S, а затем, если необходимо, суперкласса класса S, для метода m.

  1. Если класс S содержит объявление метода m с таким же описанием (то же число параметров, те же типы параметров и тот же возвращаемый тип), как в вызове, определенном во время компиляции (§15.11.3), тогда это метод, который может быть вызван, и процедура завершается. (Заметим, что в процессе загрузки и компоновки, который контролирует виртуальная машина, этот замещающийся метод, по крайней мере, так же доступен как замещенный метод; если дело обстоит не так, происходит IncompatibleClassChangeError .)
  2. Иначе, если класс S не является T, выполняется та же самая процедура поиска с использованием суперкласса S; что бы ни произошло, она завершается, если получен результат этого поиска.

Эта процедура найдет подходящий метод, когда она достигнет класса T, потому что в иных случаях будет сгенерирована IllegalAccessError с помощью проверок предыдущего параграфа §15.11.4.3.

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

15.11.4.5 Создание фрейма, синхронизация, передача управления

Метод m в некотором классе S был идентифицирован как класс, который будет вызван.

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

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

Если метод m является методом с модификатором native, но необходимый родной зависимый от реализации двоичный код не может быть загружен (§20.16.14, §20.16.13), иными словами не может быть динамически скомпонован, тогда генерируется UnsatisfiedLinkError.

Если метод m не является synchronized, управление передается телу метода m, который был вызван.

Если метод m является synchronized, тогда объект должен быть блокирован перед передачей управления. Никакое дальнейшее выполнение не может осуществляться до того, как текущий поток не будет разблокирован. Если имеется целевая ссылка, тогда цель должна быть блокирована; иначе объект Class для класса S – класса метода m, должен быть блокирован. Управление передается телу метода m, который был вызван. Объект автоматически разблокируется, когда завершается выполнение тела метода, или нормально, или преждевременно. Блокировка и разблокирование ведет себя так, как будто тело метода вложено в оператор synchronized (§14.17).

15.11.4.6 Замечание о реализации: комбинированные фреймы

Чтобы сделать возможными некоторые виды оптимизации кода, при реализации предоставлена некоторая свобода комбинирования фреймов инициализации. Предположим, что вызов метода в пределах класса C должен вызвать метод m в классе S. Тогда текущий фрейм инициализации может использоваться для обеспечения места для S вместо создания нового фрейма инициализации, только если истинно одно из следующих условий:

15.11.4.7 Пример: целевая ссылка и статические методы

Когда целевая ссылка вычислена, а затем отброшена потому что способ вызова — static, ссылка не исследуется на то, является ли она пустой (null):

class Test {
	static void mountain() {

		System.out.println("Monadnock");

	}

	static Test favorite(){
		System.out.print("Mount ");
		return null;
	}

	public static void main(String[] args) {
		favorite().mountain();
	}
}

напечатает:

Mount Monadnock

В приведенном примере favorite возвращает null, NullPointerException не генерируется.

15.11.4.8 Пример: порядок вычисления

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

Так, например, в классе:

class Test {
	public static void main(String[] args) {
		String s = "one";
		if (s.startsWith(s = "two"))
			System.out.println("oops");
	}
}

стоящее перед ".startsWith" s анализируется первым, до выражения аргумента s="two". Поэтому, ссылка на строку “one” запоминается как целевая ссылка, прежде чем локальная переменная s изменяется и начинает указывать на строку "two". В результате, метод startsWith (§20.12.20) вызывается для целевого объекта “one” с аргументом "two", таким образом, результат вызова есть ложь (false), поскольку строка “one” не начинается с "two". Из этого следует, что тестирующая программа не печатает "oops".

15.11.4.9 Пример: замещение

В примере:

 

class Point {

	final int EDGE = 20;
	int x, y;

	void move(int dx, int dy) {
		x += dx; y += dy;
		if (Math.abs(x) >= EDGE || Math.abs(y) >= EDGE)
			clear();
	}

	void clear() {
		System.out.println("\tPoint clear");
		x = 0; y = 0;
	}
}

class ColoredPoint extends Point {
	int color;

	void clear() {
		System.out.println("\tColoredPoint clear");
		super.clear();
		color = 0;
	}
}

подкласс ColoredPoint расширяет clear, определенный суперклассом Point. Это осуществляется с помощью замещения метода clear собственным методом, который вызывает метод clear суперкласса, используя форму super.clear.

Этот метод будет потом вызываться всякий раз, когда целевым объектом для вызова метода clear будет являться ColoredPoint. Даже метод move в Point вызывает метод move класса ColoredPoint, когда классом this является ColoredPoint, как

показывает вывод этой тестирующей программы:

class Test {
	public static void main(String[] args) {
		Point p = new Point();
		System.out.println("p.move(20,20):");
		p.move(20, 20);
		ColoredPoint cp = new ColoredPoint();
		System.out.println("cp.move(20,20):");
		cp.move(20, 20);
		p = new ColoredPoint();
		System.out.println("p.move(20,20), p colored:");
		p.move(20, 20);
	}
}

вывод будет следующим:

p.move(20,20):
	Point clear
cp.move(20,20):
	ColoredPoint clear
	Point clear
p.move(20,20), p colored:
	ColoredPoint clear
	Point clear

Замещение иногда называют "поздним связыванием собственной ссылки"; в этом примере это означает, что ссылка на clear в теле Point.move (который в действительности является сокращением выражения this.clear) вызывает метод, выбранный "позднее" ( во время выполнения на основе текущего класса объекта, на которого ссылаются с помощью this) скорее чем метод, выбранный "раньше" (во время компиляции, только на основе типа this). Это обеспечивает программистам, работающим на Яве, широкие возможности расширения абстракций, что и является ключевой идеей объектно-ориентированного программирования.

15.11.4.10 Пример: вызов метода с использованием super

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

При доступе к переменной экземпляра, super имеет то же значение, что и приведенное this (§15.10.2), но это не справедливо для вызова метода. Рассмотрим пример:

class T1 {
	String s() { return "1"; }
}

class T2 extends T1 {
	String s() { return "2"; }
}

class T3 extends T2 {
	String s() { return "3"; }

	void test() {
		System.out.println("s()=\t\t"+s());
		System.out.println("super.s()=\t"+super.s());
		System.out.print("((T2)this).s()=\t");
			System.out.println(((T2)this).s());
		System.out.print("((T1)this).s()=\t");
			System.out.println(((T1)this).s());
	}
}

class Test {
	public static void main(String[] args) {
		T3 t3 = new T3();
		t3.test();
	}
}

результатом которого является вывод:

s()=					3
super.s()=					2
((T2)this).s()=					3
((T1)this).s()=					3

Приведения к типам T1 и T2 не изменяют метод, который вызывается, потому что метод экземпляра, который будет вызван, выбирается согласно текущему классу объекта, к которому обращаются с помощью this. Приведение не изменяет класс объекта; оно только проверяет, чтобы класс был совместим с указанным типом.

15.12 Выражения доступа к массиву

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

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

Тип выражения ссылки на массив должен быть типом массив (назовем его T[] - массив, элементы которого имеют тип T), или произойдет ошибка времени компиляции. Тогда T - тип выражения доступа к массиву.

Выражение индекса подвергается одноместному числовому расширению (§5.6.1); расширенный тип должен быть типом int.

Результатом ссылки на массив является переменная типа T, а именно переменная массива, выбранная по значению индексного выражения. Полученная в результате переменная, которая является компонентой массива, никогда не рассматривается как final, даже если ссылка на массив была получена от переменной с модификатором final.

15.12.1 Вычисление доступа к массиву во время выполнения

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

15.12.2 Примеры: порядок вычисления доступа к массиву

В доступе к массиву, выражение слева от скобок будет полностью вычислено прежде, чем вычислена любая часть выражения в скобках. Например, в выражении ( конечно чудовищному) a[(a=b)[3]], выражение a полностью вычисляется перед выражением (а=b)[3]; это означает, что первоначальное значение a уже получено и сохранено в то время, когда выражение (a=b) [3] вычисляется. Этот массив, на который ссылаются с помощью первоначального значения a, потом проиндексирован значением, которое является 3 элементом другого массива (возможно, того же самого массива), на которого ссылались с помощью b, а теперь ссылаются также с помощью a.

Таким образом, в результате выполнения примера:

class Test {
	public static void main(String[] args) {
		int[] a = { 11, 12, 13, 14 };
		int[] b = { 0, 1, 2, 3 };
		System.out.println(a[(a=b)[3]]);
	}
}

напечатается:

14

потому что значение ужасного выражения эквивалентно a[b[3]] или a[3] или 14.

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

class Test {
	public static void main(String[] args) {
		int index = 1;
		try {
			skedaddle()[index=2]++;
		} catch (Exception e) {
			System.out.println(e + ", index=" + index);
		}
	}
	static int[] skedaddle() throws Exception {
		throw new Exception("Ciao");
	}
}

будет напечатано:

java.lang.Exception: Ciao, index=1

потому что вложенное присваивание значения 2 переменной index никогда не произойдет.

Если выражение ссылки на массив порождает ссылку типа null вместо ссылки на массив, то во время выполнения генерируется NullPointerException, но только после того, как будут вычислены все части выражения ссылки на массив и только если это вычисление было завершено успешно. Таким образом, в результате выполнения приведенной тестирующей программы:

class Test {

	public static void main(String[] args) {
		int index = 1;
		try {
			nada()[index=2]++;
		} catch (Exception e) {
			System.out.println(e + ", index=" + index);
		}
	}
	static int[] nada() { return null; }
}

будет напечатано:

java.lang.NullPointerException, index=2

потому что вложенное присваивание переменной index значения 2 происходит перед проверкой на указатель null. Рассмотрим программу, имеющую отношение к этой же ситуации:

class Test {

	public static void main(String[] args) {
		int[] a = null;
		try {
			int i = a[vamoose()];
			System.out.println(i);
		} catch (Exception e) {
			System.out.println(e);
		}
	}

	static int vamoose() throws Exception {
		throw new Exception("Twenty-three skidoo!");
	}
}

в результате выполнения которой всегда будет напечатано:

 

java.lang.Exception: Twenty-three skidoo!

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

15.13 Постфиксные выражения

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

15.13.1 Имена

Имя, встречающееся в выражении, может синтаксически быть ExpressionName (§6.5). Значение такого имени ExpressionName зависит от его вида:

 

 

15.13.2 Постфиксная операция инкремента ++

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

Если во время выполнения вычисления выражения операнда заканчивает преждевременно, то вычисление постфиксного выражения инкремента преждевременно завершается по той же причине и никакое увеличение не осуществляется. Иначе, к значению переменной прибавляется 1 и значению переменной присваивается соответствующая сумма. Перед сложением осуществляется двуместное числовое расширение для значения переменной и 1 (§5.6.2). Если необходимо, перед сохранением сумма преобразуется с помощью сужающего примитивного преобразования (§5.1.3) к типу переменной. Значением постфиксного выражения инкремента является значение переменной до сохранения нового значения.

Переменная, которая объявлена с модификатором final, не может быть увеличена, потому что когда используется доступ к переменной с модификатором final, результатом является значение, а не переменная. Таким образом, такая переменная не может использоваться как операнд постфиксного оператора инкремента.

15.13.3 Постфиксная операция декремента --

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

Если во время выполнения вычисление выражения операнда заканчивает преждевременно, тогда вычисление постфиксного выражение декремента завершается преждевременно по тот же причине и никакого уменьшения не происходит. Иначе, из значения переменной вычитается 1 и новое значение записывается в эту переменную. Перед вычитанием осуществляется двуместное числовое расширение (§5.6.2) для 1 и значения переменной. Если необходимо, перед сохранением разность преобразуется с помощью сужающего примитивного преобразования (§5.1.3) к типу переменной. Значением постфиксного выражения декремента является значение переменной до сохранения нового значения.

Переменная, которая объявлена с модификатором final, не может быть уменьшена, потому что когда доступ к переменной с модификатором final используется как выражение, результатом является значение, а не переменная. Таким образом, такая переменная не может использоваться как операнд постфиксного оператора декремента.

15.14 Унарные операции

Унарными операциями являются +, -, ++, --, ~,! и операции приведения. Выражения с одноместными операциями группируются справа налево, таким образом -~x означает то же самое, что -(~x).

Следующие продукции из §15.15 повторены здесь для удобства:

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

Первая потенциальная двусмысленность возникла бы в выражениях типа (p) +q,

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

Ява-система использует другой подход. Результат оператора + должен быть числовым, и все имена типов, используемые в обращениях к числовым значениям, должны быть известными зарезервированными словами. Таким образом, если p - зарезервированное слово, обозначающее элементарный тип, тогда (p)+q может иметь смысл только как приведение к унарному выражению. Однако, если p не является зарезервированным словом, обозначающим примитивный тип, тогда (p)+q может иметь смысл только как бинарная арифметическая опреция. Подобные замечания относятся и к оператору -. Грамматика, показанная выше, разделяет CastExpression на два случая, чтобы показать различие. Нетерминальное выражение UnaryExpression включает все унарные операции, а нетерминал UnaryExpressionNotPlusMinus исключает использования всех унарных операторов, которые также могли бы быть бинарными операциями, в Яве такими операторами являются + и -.

Вторая потенциальная двусмысленность состоит в том, что для программистов на Cи или Cи++ выражение (p)++ могло бы быть как постфиксным увеличением выражения в скобках, так и началом приведения, например, в выражении (p)++q. Как и в вышеприведенном случае, синтаксический анализатор для C и C++ знает, является p именем типа или именем переменной. Но синтаксический анализатор, использующий предвидение только на одну лексему и не осуществляющий семантического анализа при синтаксическом разборе, не был бы способен сообщить, когда ++ является текущей лексемой, должно ли (p) рассматриваться как Primary выражение или как левая составляющая, являющаяся частью CastExpression.

В Яве результат оператора ++ должен быть числовым, и все имена типов, использующиеся при обращениях к числовым значениям, должны быть известными зарезервированными словами. Таким образом, если p является зарезервированным словом, обозначающим примитивный тип, то (p)++ может иметь смысл только как приведение префиксного выражения приращения, и вместо него лучше было бы использовать один операнд q, за которым бы следовала операция ++. Однако, если p не является зарезервированным словом, обозначающим примитивный тип, то (p)++ может иметь смысл только как постфиксное увеличение p. Подобные замечания относятся и к операции --. Поэтому нетерминал UnaryExpressionNotPlusMinus также исключает использование префиксных операций ++ и --.

15.14.1 Префиксная операция инкремента ++

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

Если во время выполнения вычисление выражения операнда заканчивается преждевременно, то вычисление префиксного выражения инкремента заканчивается преждевременно по той же самой причине, и никакого увеличения не происходит. Иначе, к значению переменной прибавляется 1 и сумма сохраняется в той же переменной. Перед сложением, для 1 и значения переменной выполняется двуместное числовое расширение (§5.6.2). Если необходимо, сумма до сохранения преобразуется по правилам сужающего примитивного преобразования (§5.1.3) к типу переменной. Значением префиксного выражения инкремента является значение переменной после сохранения нового значения.

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

15.14.2 Префиксная операция декремента --

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

Если вычисление выражения заканчивается преждевременно, то вычисление префиксного выражения декремента заканчивается преждевременно по той же самой причине, и никакого уменьшения не происходит. Иначе, из значения переменной вычитается 1 и разность записывается в эту переменную. Перед вычитанием, для 1 и значения переменной осуществляется двуместное числовое расширение (§5.6.2). Если необходимо, разность до сохранения преобразуется по правилам сужающего примитивного преобразования (§5.1.3) к типу переменной. Значением префиксного выражения декремента является значение переменной после сохранения нового значения.

Переменная, которая объявлена с модификатором final, не может быть уменьшена, потому что когда используется доступ к переменной с модификатором final, результатом является значение, а не переменная. Таким образом, подобная переменная не может использоваться как операнд префиксной операции декремента.

15.14.3 Унарная операция плюс +

Типом выражения операнда унарной операции + должен быть примитивный числовой тип, или произойдет ошибка времени компиляции. Над операндом осуществляется одноместное числовое расширение (§5.6.1). Типом одноместного выражения с плюсом является расширенный тип операнда. Результатом одноместного выражения с плюсом является не переменная, а значение, даже если результат выражения операнда - переменная.

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

15.14.4 Унарная операция минус -

Типом выражения операнда унарной операции - должен быть примитивный числовой тип, или произойдет ошибка времени компиляции. Над операндом осуществляется одноместное числовое расширение (§5.6.1). Типом одноместного выражения с минусом является расширенный тип операнда.

Во время выполнения значением одноместного выражения с минусом является арифметическое отрицание расширенного значения операнда.

Для целых чисел отрицание - то же самое, что вычитание из нуля. В Яве для целых используется дополнительный обратный код, и диапазон значений в дополнительном обратном коде не симметричен, таким образом, отрицание максимального отрицательного числа ( имеется в виду максимальное по абсолютной величине отрицательное значение) типа int или long в качестве результата имеет то же самое максимальное отрицательное число. В этом случае происходит переполнение, но никакое исключение не генерируется. Для всех целых x, -x равно (~x)+1.

Для значений с плавающей точкой отрицание - не то же самое, что вычитание из нуля, потому что если x есть +0.0, то 0.0-x равняется +0.0, а -x равняется -0.0. Унарный минус просто инвертирует знак числа с плавающей точкой. Особые случаи, представляющие интерес:

15.14.5 Операция поразрядного дополнения ~

Типом выражения операнда унарной операции ~ должен быть примитивный целый тип, или произойдет ошибка времени компиляции. Над операндом осуществляется одноместное числовое расширение (§5.6.1). Типом унарного поразрядного выражения дополнения является расширенный тип операнда.

Во время выполнения значением одноместного поразрядного выражения дополнения является поразрядное дополнение расширенного значения операнда; заметим, что во всех случаях ~x равняется (-x)-1.

15.14.6 Логическая операция дополнения !

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

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

15.15 Выражения приведения

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

См. § 15.14 для обсуждения различия между UnaryExpression и UnaryExpressionNotPlusMinus.

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

Во время выполнения, значение операнда преобразуется преобразованием приведения (§ 5.4) к типу, определенному оператором приведения.

В Яве разрешены не все приведения. Некоторые приведения кончаются по ошибке во времени компиляции. Например, примитивное значение не может приводиться к ссылочному типу. Правильность некоторых приведений во время выполнения может быть доказана во время компиляции. Например, всегда можно преобразовать значение типа класса к типу суперкласса; такое приведение не должно требовать никакого специального действия во время выполнения. Наконец, правильность некоторых приведений нельзя доказать во время компиляции. Такие приведения требуют тестирования во время выполнения. Генерируется ClassCastException , если приведение найденное во время выполнения недопустимо.

15.16 Мультипликативные операции

Операции *, /, и % называются мультипликативными операциями. Они имеют одинаковое старшинство и синтаксически лево-ассоциативны (группировка слева направо).

Тип каждого из операндов операции умножения должен быть примитивным числовым типом, или во время компиляции происходит ошибка. К операндам применяется бинарное числовое расширение (§ 5.6.2). Тип мультипликативного выражения - расширенный тип его операндов. Если этот расширенный тип - int или long, тогда применяется целочисленная арифметика; если этот расширенный тип float или double, тогда применяется вещественная арифметика.

15.16.1 Операция умножения *

Бинарная операция * выполняет умножение, выдавая произведение операндов. Умножение - коммутативная операция, если выражения операндов не имеют побочных эффектов. В то время как целочисленное умножение ассоциативно, когда все операнды одного типа, вещественное умножение не ассоциативно.

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

Результат вещественного умножения согласуется с правилами IEEE 754-арифметики:

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

15.16.2 Операция деления /

Результатом бинарной операции / является частное операндов. Левый операнд - делимое, правый операнд - делитель.

Целочисленное деление проводится в режиме округления по направлению к нулю. То есть частное, полученное для операндов n и d, которые являются целыми числами после двоичного числового расширения (§ 5.6.2) - целочисленное значение q, чья величина в большей степени удовлетворяет условию ; кроме того, q - положительно, если и n, и d имеют одинаковые знаки, но q - отрицательно, если и n, и d имеют противоположные знаки. Есть специальный случай, который не удовлетворяет этому правилу: если делимое - отрицательное целое число наибольшей возможной величины данного типа, а делитель -1, тогда происходит целочисленное переполнение, и результат равен делимому. Несмотря на переполнение, в этом случае не генерируется никакое исключение. С другой стороны, если значение делителя в целочисленном делении - 0, тогда генерируется ArithmeticException.

Результат вещественного деления определен спецификацией IEEE-арифметики:

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

15.16.3 Операция остатка %

Бинарная операция %, дает остаток от операндов из подразумеваемого деления; левый операнд - делимое, правый операнд - делитель.

В и ++, операция остатка допускает только целочисленные операнды, но в Яве также допустимы вещественные операнды.

Операция остатка для операндов, являющиеся целыми числами после бинарного числового расширения (§ 5.6.2), дает в результате такую величину, что (a/b) * b + (a%b) равно a. Это тождество имеет силу даже в специальном случае, когда делимое - отрицательное целое число наибольшей возможной величины данного типа, и делитель -1 (остаток ноль). Это следует из того правила, что результат операции остатка может быть отрицателен только, если делимое отрицательно, и может быть положительным только, если делимое положительно; кроме того, величина результата - всегда меньше чем величина делителя. Если значение делителя для целочисленной операции остатка - 0, тогда генерируется ArithmeticException.

Примеры:


5%3 дает 2							(обратите внимание, что 5/3 дает 1)
5%(-3) дает 2							(обратите внимание, что 5/ (-3) дает -1)
(-5)%3 дает -2							(обратите внимание, что (-5)/3 дает -1)
(-5)%(-3) дает -2							(обратите внимание, что (-5)/(-3) дает 1)

Результат операции вещественного остатка, вычисленной операцией % не совпадает с результатом операцией остатка, определенной IEEE 754. Операция остатка IEEE 754 вычисляет остаток от деления не округляя, а отсекая его, и такое поведение, не аналогично обычной целочисленной операции остатка. Взамен, язык Ява определяет % на вещественных операциях, способом аналогичным операции остатка в Яве для целого числа; это можно сравнить с библиотечной функцией fmod в C. Операция остатка IEEE 754 может быть вычислена библиотечной подпрограммой Явы Math.IEEEremainder (§ 20.11.14).

Результат вещественной операции остатка в Яве определен правилами арифметики IEEE:

Вычисление вещественной операции остатка % никогда не вызывает исключение времени выполнения, даже если правый операнд - ноль. Не могут происходить переполнение, потеря значимости, или потеря точности.

Примеры:

 

5.0%3.0 дает 2.0

5.0% (-3.0) дает 2.0

(-5.0) %3.0 дает -2.0

% -3.0 (-5.0) дает -2.0

15.17 Аддитивные операции

Операции + и - называются аддитивными операциями. Они имеют одинаковое старшинство и синтаксически лево-ассоциативны (группировка слева направо).

Если тип любого операнда операции + String, тогда операция - строковая конкатенация.

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

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

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

Любой тип можно преобразовать в тип String с помощью строкового преобразования.

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

Это ссылочное значение затем преобразовывается в тип String строковым преобразованием.

Теперь нужно рассматривать только ссылочные значения. Если ссылка - null, она преобразуется в строку "null" (четыре символа ASCII n, u, l, l). Иначе, преобразование выполняется как при вызове метода toString объектом, на который указывает ссылка, без параметров; но если результат вызова метода toString - null, тогда взамен используется строка "null". Метод toString(§ 20.1.2) определен изначальным классом Object (§ 20.1); его переопределяют многие классы, в частности Booolean, Character, Integer, Long, Float, Double, и String.

15.17.1.2 Оптимизация конкатенации строк

Реализация может выполнять преобразование и конкатенацию в один шаг, избегая создания, а затем и отбрасывания промежуточного объекта String. Чтобы увеличить эффективность повторения строковой конкатенации, транслятор Явы может использовать класс StringBuffer (§ 20.13) или подобную методику, для уменьшения числа промежуточных объектов String, созданных при вычислении выражения.

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

15.17.1.3 Примеры конкатенации строк

Например выражение:

"The square root of 2 is " + Math.sqrt(2)

Выдает результат:

"The square root of 2 is 1.4142135623730952"

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

a + b + c

всегда расценивается как значение:

(a + b) + c

Следовательно результатом выражения:

 

1 + 2 + " fiddlers"

является:

"3 fiddlers"

но результатом:

 

"fiddlers " + 1 + 2

является:

"fiddlers 12"

В этом шутливом небольшом примере:

class Bottles {

	static void printSong(Object stuff, int n) {
		String plural = "s";
		loop: while (true) {
			System.out.println(n + " bottle" + plural
				+ " of " + stuff + " on the wall,");
			System.out.println(n + " bottle" + plural
				+ " of " + stuff + ";");
			System.out.println("You take one down "
				+ "and pass it around:");
			--n;
			plural = (n == 1) ? "" : "s";
			if (n == 0)
				break loop;
			System.out.println(n + " bottle" + plural
				+ " of " + stuff + " on the wall!");
			System.out.println();
		}
		System.out.println("No bottles of " +
								stuff + " on the wall!");
	}

}

 

метод printSong будет печатать вариант детской песни. Популярные значения наполнения бутылки - "pop"(кукруза“) и "beer"(“пиво”); наиболее популярное значение n - 100. Приведем вывод, при вызове Bottles.printSong ("slime"(“слизь), 3):

 

3 bottles of slime on the wall,
3 bottles of slime;
You take one down and pass it around:
2 bottles of slime on the wall!

2 bottles of slime on the wall,
2 bottles of slime;
You take one down and pass it around:
1 bottle of slime on the wall!

1 bottle of slime on the wall,
1 bottle of slime;
You take one down and pass it around:
No bottles of slime on the wall!

Обратите внимание, что единственное число "bottle" осторожно условно генерируется ранее соответствующего множественного числа "bottles"; также обратите внимание как использовался операция конкатенации строк, для разбиения длинной не меняющейся строки:

"You take one down and pass it around:"

на две части, чтобы избежать неудобной длинной строки в исходном коде.

15.17.2 Аддитивные операции (+ и -) для числовых типов

Результатом бинарной операции + является сумма двух операндов. Результатом бинарной операции - является разность двух числовых операндов.

К операндам применяется бинарная числовая поддержка (§ 5.6.2). Тип аддитивного выражения числовых операндов - расширенный тип операндов. Если этот расширенный тип - int или long, тогда применяется целочисленная арифметика; если этот расширенный тип - float или double, тогда применяется вещественная арифметика.

Сложение - коммутативная операция, если выражения операнда не имеют побочных эффектов. Целочисленное сложение ассоциативно, когда все операнды - одного типа, но вещественное сложение не ассоциативно.

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

Результат вещественного сложения определяется следующими правилами IEEE арифметики:

Результатом бинарной операции - разность двух операндов числового типа; левый операнд - уменьшаемое, и правый операнд - вычитаемое. И для целого и для вещественного числа вычитание всегда для операции a-b дает тот же самый результат, что и a+(-b). Обратите внимание, что для целочисленных значений, вычитание из ноля - то же самое, что отрицание. Однако, для вещественных операндов, вычитание из ноля не то же, что отрицание, потому что, если x есть + 0.0, тогда 0.0-x равняется + 0.0, но -x равняется -0.0.

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

15.18 Операции сдвига

Операции сдвига включают сдвиг влево <<, сдвиг вправо со знаком >>, и сдвиг вправо без знака >>>; они синтаксически лево-ассоциативны (группировка слева направо). Левый операнд - значение, которое будет сдвинуто; правый операнд определяет расстояние сдвига.

Тип каждого из операндов операции сдвига должен быть примитивным целочисленным типом, иначе происходит ошибка времени компиляции. На операндах не выполняется бинарное числовое расширение (§ 5.6.2); достаточно, выполнения унарного числового расширения (§ 5.6.1) на каждом операнде отдельно. Тип выражения сдвига - расширенный тип левого операнда.

Если расширенный тип левого операнда - int, как расстояние сдвига используются только пять младших битов правого операнда. Это эквивалентно, применению к правому операнду поразрядного логического И операцией & (§ 15.21.1) со значением маски 0x1f . Следовательно фактически используемое расстояние сдвига всегда заключается в диапазоне от 0 до 31.

Если расширенный тип левого операнда - long, как расстояние сдвига используются только шесть младших битов правого операнда. Это эквивалентно, применению к правому операнду поразрядного логического И операцией & (§ 15.21.1) со значением маски 0x3f . Следовательно фактически используемое расстояние сдвига всегда заключается в диапазоне от 0 до 63.

Во время выполнения операции сдвига исполняются на представлении целочисленного значения левого операнда в дополнительном обратном коде.

Значение n << s - это n сдвинутое влево на s разрядных позиций; это эквивалентно (даже если происходит переполнение) умножению на два в степени s.

Значение n>> s - это n сдвинутое вправо на s разрядных позиций с распространением знака. Результирующее значение - . Для неотрицательных значений n, это эквивалентно усеченному делению, вычисленному целочисленной операцией деления /, на два в степени s.

Значение n>>> s -это n сдвинутое вправо на s разрядных позиций с расширением ноля. Если n положителен, тогда результат такой же как n>> s; если n отрицателен, то результат равен такому выражению (n>> s) + (2 << ~s) если тип левого операнда - int, и результату выражения (n>> s) + (2L << ~s), если тип левого операнда - long. Добавленное слагаемое (2 << ~s) или (2L << ~s) отменяет распространение знакового бита. (Обратите внимание, что, из-за неявного маскирования правого операнда операции сдвига, ~s как расстояние сдвига эквивалентно 31-s при сдвиге значения типа int и 63-s при сдвиге значения типа long.)

15.19 Операции отношения

Операции отношения синтаксически лево-ассоциативны (группировка слева направо), но это бесполезный факт; например, a< b < c рассматривается как (a< b) < c, что всегда приводит к ошибке времени компиляции, потому что тип a< b всегда boolean, а < - не является операцией на значениях типа boolean.

Тип выражения отношения всегда boolean.

15.19.1 Числовые операции сравнения <, < =, >, и > =

Тип каждого из операндов числовой операции сравнения должен быть примитивным числовым типом, иначе происходит ошибка времени компиляции. На операндах выполняется бинарное числовое расширение (§ 5.6.2). Если расширенный тип операндов - int или long, тогда выполняется сравнение целого числа со знаком ; если этот расширенный тип - float или double, тогда выполняется вещественное сравнение.

Результат вещественного сравнения определен стандартом IEEE 754 :

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

15.19.2 Тип операции сравнения instanceof

Тип операнда RelationalExpression операции instanceof должен быть ссылочным типом или типом null; иначе, происходит ошибка времени компиляции.ReferenceType, упомянутый после операции instanceof должен обозначать тип ссылки; иначе, происходит ошибка времени компиляции.

Во время выполнения, результат операции instanceof - true, если значение RelationalExpression - не null и ссылка может быть приведена (§ 15.15) к ReferenceType не генерируя ClassCastException. Иначе результат - false.

Если приведение RelationalExpression к ReferenceType было бы запрещено как ошибка времени компиляции, тогда instanceof аналогично выражениям отношения приводит к ошибке времеи компиляции. В такой ситуации, результат выражения instanceof никогда не может быть true.

Рассмотрим программу-пример:

class Point { int x, y; }
class Element { int atomicNumber; }
class Test {
	public static void main(String[] args) {
		Point p = new Point();
		Element e = new Element();
		if (e instanceof Point) {											// compile-time error
			System.out.println("I get your point!");
			p = (Point)e;										// compile-time error
		}
	}
}

Этот пример заканчивается по двум ошибкам времени компиляции. Приведение (Point) e неправильно, потому что никакой экземпляр Element или любого из его возможных подклассов (ни один здесь не показан) не мог быть возможным экземпляром любого подкласса Point. Выражение instanceof неправильно по точно той же причине. С другой стороны, если класс Point является подклассом Element :

class Point extends Element { int x, y; }

тогда приведение было бы возможно, хотя требовалась бы проверка во время выполнения, и выражение instanceof имело бы смысл. Приведение (Point) e никогда не вызвало бы исключение, потому что это не будет выполнено, если значение e не может правильно приводиться к типу Point.

15.20 Операции равенства

Операции равенства синтаксически лево-ассоциативны ( группировка слева направо), но этот факт по существу не является полезным; например,a == b == c рассматривается как (a== b) == c. Тип результата a== b всегда - boolean, и c должно следовательно иметь тип boolean иначе происходит ошибка времени компиляции. Таким образом, выражение a== b == c не проверяет равны ли a, b, и c.

Операции == (равно) и != (не равно) аналогичны операциям отношения, исключая их более низкого старшинства. Таким образом, a < b == c < d - true всякий раз, когда a< b и c < d имеют одинаковые значения истинности.

Операции равенства могут использоваться для сравнения двух операндов числового типа, или двух операндов типа boolean, или двух операндов, каждый из которых или ссылочного типа или типа null. Во всех других случаях во время компиляции происходит ошибка. Тип выражения равенства всегда - boolean.

Во всех случаях, выражение a!=b приводит к тому же самому результату, что и выражение !(a== b). Операции равенства - коммутативные, если выражения операнда не имеют никаких побочных эффектов.

15.20.1 Числовые операции равенства == и !=

Если операнды операции равенства - оба примитивного числового типа, на операндах выполняется бинарное числовое расширение (§ 5.6.2). Если расширенный тип операндов - int или long, тогда выполняется целочисленная проверка равенства; если расширенный тип - float или double, тогда выполняется вещественная проверка равенства.

Вещественная проверка равенства выполняется в соответствии со стандартом IEEE 754:

Подчиненные этим правилам для вещественных чисел, приводятся следующие правила для целочисленных операндов или вещественных операндов отличных от NaN:

15.20.2 Булевы операции равенства == и !=

Если операнды операции равенства - оба имеют тип boolean, тогда операция называется булевым равенством. Булевы операции равенства ассоциативны.

Результат операции == - true , если операнды - оба true или оба false; иначе, результат - false.

Результат операции != - false, если операнды - оба true или оба false; иначе, результат - true. Таким образом операция != ведет себя также как операция ^ (§ 15.21.2), когда применяется к булевым операндам.

15.20.3 Ссылочные операции равенства == и !=

Если операнды операции равенства - оба или ссылочных типов, или типа null, тогда операция называется объектным равенством.

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

Во время выполнения, результат операции == - true, если значения операндов либо оба null, либо оба ссылаются на один и тот же объект или массив; иначе, результат - false.

Результат операции != - false, если значения операндов либо оба null, либо оба ссылаются на один и тот же объект или массив; иначе, результат - true.

Если использовать операцию = = для сравнения ссылок на тип String, то такая проверка равенства определяет, ссылаются ли два операнда на один и тот же объект String. Результат - false, если операнды - различные объекты String, даже если они содержат одну и ту же последовательность символов. Содержание двух строк s и t может быть проверено на равенство вызовом метода s.equals (t) (§ 20.12.9). См. также § 3.10.5 и § 20.12.47.

15.21 Поразрядные и логические операции

Поразрядные и логические операции включают операцию И - &, операцию исключающее ИЛИ - ^, и операцию включающее ИЛИ - |. Эти операции имеют различное старшинство, & - самое высокое старшинство, а | - самое низкое старшинство. Каждая из этих операций синтаксически лево - ассоциативен (у всех группировка слева направо). Каждая операция - коммутативна, если выражения операнда не имеют никаких побочных эффектов. Каждая операция ассоциативна.

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

15.21.1 Целочисленные поразрядные операции &, ^, и |

Когда оба операнда операций &, ^, или | имеют примитивные целочисленные типы, на операндах сначала выполняется бинарное числовое расширение (§ 5.6.2). Тип выражения поразрядной операции - расширенный тип операндов.

Для &, значением результата является поразрядное И значений операндов.

Для ^, значением результата является поразрядное исключающее ИЛИ значений операндов.

Для |, значением результата является поразрядное включающее ИЛИ значений операндов.

Например, результатом выражения 0xff0 & 0xf0f0 является 0xf000. Результатом 0xff00 ^ 0xf0f0 является 0x0ff0. Результатом 0xff00 | 0xf0f0 является 0xfff0.

15.21.2 Булевы логические операции &, ^, и |

Когда оба операнда операций &, ^, или | имеют тип boolean, тогда тип выражения поразрядной операции - boolean.

Для &, значение результата - true, если оба значения обоих операндов true; иначе, результат - false.

Для ^, значение результата - true, если значения операндов различны; иначе, результат - false.

Для |, значение результата - false, если оба значения операндов false; иначе, результат - true.

15.22 Операция условное И &&

Операция && - подобна операции & (§ 15.21.2), но вычисляет правый операнд только, если значение левого операнда - true. Она синтаксически лево - ассоциативно (группировка слева направо). Она полностью ассоциативна относительно и побочных эффектов, и значения результата; то есть для любых выражений a, b, и c, вычисление выражения ((a) && (b)) && (c) производит тот же самый результат, с теми же самыми побочными эффектами, происходящих в том же самом порядке, что и вычисление выражения (a) && ((b) && (c)).

Каждый операнд операции && должен иметь тип boolean, или во время компиляции происходит ошибка. Тип выражения условного И всегда boolean.

Во время выполнения, первым вычисляется выражение левого операнда; если значение - false, то значение выражения условного И - false, и выражение правого операнда не вычисляется. Если значение левого операнда - true, тогда вычисляется выражение правого операнда, и это значение становится значением выражения условного И. Таким образом, && получает тот же результат, что и & на булевых операндах. Она отличается только в том, что выражение правого операнда вычисляется скорее условно, а не так как обычно.

15.23 Операция условного ИЛИ ||

Операция || - подобна операции | (§ 15.21.2), но вычисляет правый операнд только, если значение левого операнда - false. Она синтаксически лево-ассоциативна (группировка слева направо). Она полностью ассоциативна относительно и побочных эффектов, и значения результата; то есть для любых выражений a, b, и c, вычисление выражения ((a) || (b)) ||(c) производит тот же самый результат, с теми же самыми побочными эффектами, происходящих в том же самом порядке, что и вычисление выражения (a) || ((b) || (c)).

Каждый операнд операции || должен иметь тип boolean, иначе происходит ошибка времени компиляции. Тип выражения условного ИЛИ всегда boolean. Каждый операнд || должен иметь тип boolean или во время компиляции происходит ошибка. Тип выражения условного ИЛИ всегда boolean.

Во время выполнения, первым вычисляется выражение левого операнда; если значение - true, то значение выражения условного И - true, и выражение правого операнда не вычисляется. Если значение левого операнда - false, тогда вычисляется выражение правого операнда, и это значение становится значением выражения условного И. Таким образом, || получает тот же результат, что и | на булевых операндах. Она отличается только в том, что выражение правого операнда вычисляется скорее условно, а не так как обычно.

15.24 Условная операция ?:

Условная операция ?: использует булево значение одного выражения, чтобы решить, какое из двух других выражений должно быть вычислено.

Условная операция синтаксически право - ассоциативна ( группировка справа налево ), поэтому a? b: c? d: e? f: g означает то же, что a? b: (c? d: (e? f: g)).

Условная операция имеет три выражения операндов; ? ставится между первым и вторым выражениями, а : ставится между вторым и третьим выражениями.

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

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

Обратите внимание, что не разрешено и для второго, и для третьего выражений операндов вызвать void-метод. Фактически, это не разрешено для условного выражения в любом контексте, где мог бы появляться вызов void-метода (§ 14.7).

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

 

 

Во время выполнения, первым вычисляется первое выражение операнда условного выражения; его булево значение после используется для выбора второго или третьего выражения операнда:

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

15.25 Операция присваивания

Имеются 12 операций присваивания; все синтаксически право-ассоциативны (группировка справа налево). Таким образом, a=b=c означает a= (b=c), которая присваивает значение c к b, а затем присваивает значение b к a.

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

Во время выполнения, результатом выражения присваивания является значение переменной после того, как произошло присваивание. Результат выражения присваивания сам не является переменной.

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

15.25.1 Простая операция присваивания =

Ошибка времени компиляции происходит, если тип правого операнда не может быть преобразован в тип переменной преобразованием присваивания (§ 5.2).

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

Если выражение левого операнда - выражение доступа к массиву (§ 15.12), тогда требуется несколько шагов:

 

 

Правила для присваивания компоненте массива иллюстрируются следующей программой- примером:

class ArrayReferenceThrow extends RuntimeException { }


class IndexThrow extends RuntimeException { }


class RightHandSideThrow extends RuntimeException { }

class IllustrateSimpleArrayAssignment {

	static Object[] objects = { new Object(), new Object() };


	static Thread[] threads = { new Thread(), new Thread() };


	static Object[] arrayThrow() {
		throw new ArrayReferenceThrow();
	}


	static int indexThrow() { throw new IndexThrow(); }


	static Thread rightThrow() {
		throw new RightHandSideThrow();
	}


	static String name(Object q) {
		String sq = q.getClass().getName();
		int k = sq.lastIndexOf('.');
		return (k < 0) ? sq : sq.substring(k+1);
	}


	static void testFour(Object[] x, int j, Object y) {
		String sx = x == null ? "null" : name(x[0]) + "s";
		String sy = name(y);
		System.out.println();
		try {
			System.out.print(sx + "[throw]=throw => ");
			x[indexThrow()] = rightThrow();
			System.out.println("Okay!");
		} catch (Throwable e) { System.out.println(name(e)); }
		try {
			System.out.print(sx + "[throw]=" + sy + " => ");
			x[indexThrow()] = y;
			System.out.println("Okay!");
		} catch (Throwable e) { System.out.println(name(e)); }
		try {
			System.out.print(sx + "[" + j + "]=throw => ");
			x[j] = rightThrow();
			System.out.println("Okay!");
		} catch (Throwable e) { System.out.println(name(e)); }
		try {
			System.out.print(sx + "[" + j + "]=" + sy + " => ");
			x[j] = y;
			System.out.println("Okay!");
		} catch (Throwable e) { System.out.println(name(e)); }
	}


	public static void main(String[] args) {
		try {
			System.out.print("throw[throw]=throw => ");
			arrayThrow()[indexThrow()] = rightThrow();
			System.out.println("Okay!");
		} catch (Throwable e) { System.out.println(name(e)); }
		try {
			System.out.print("throw[throw]=Thread => ");
			arrayThrow()[indexThrow()] = new Thread();
			System.out.println("Okay!");
		} catch (Throwable e) { System.out.println(name(e)); }
		try {
			System.out.print("throw[1]=throw => ");
			arrayThrow()[1] = rightThrow();
			System.out.println("Okay!");
		} catch (Throwable e) { System.out.println(name(e)); }
		try {
			System.out.print("throw[1]=Thread => ");
			arrayThrow()[1] = new Thread();
			System.out.println("Okay!");
		} catch (Throwable e) { System.out.println(name(e)); }

		testFour(null, 1, new StringBuffer());
		testFour(null, 1, new StringBuffer());
		testFour(null, 9, new Thread());
		testFour(null, 9, new Thread());
		testFour(objects, 1, new StringBuffer());
		testFour(objects, 1, new Thread());
		testFour(objects, 9, new StringBuffer());
		testFour(objects, 9, new Thread());
		testFour(threads, 1, new StringBuffer());
		testFour(threads, 1, new Thread());
		testFour(threads, 9, new StringBuffer());
		testFour(threads, 9, new Thread());
	}

}

Эта программа выводит:

 

throw[throw]=throw => ArrayReferenceThrow
throw[throw]=Thread => ArrayReferenceThrow
throw[1]=throw => ArrayReferenceThrow
throw[1]=Thread => ArrayReferenceThrow


null[throw]=throw => IndexThrow
null[throw]=StringBuffer => IndexThrow
null[1]=throw => RightHandSideThrow
null[1]=StringBuffer => NullPointerException


null[throw]=throw => IndexThrow
null[throw]=StringBuffer => IndexThrow
null[1]=throw => RightHandSideThrow
null[1]=StringBuffer => NullPointerException


null[throw]=throw => IndexThrow
null[throw]=Thread => IndexThrow
null[9]=throw => RightHandSideThrow
null[9]=Thread => NullPointerException


null[throw]=throw => IndexThrow
null[throw]=Thread => IndexThrow
null[9]=throw => RightHandSideThrow
null[9]=Thread => NullPointerException


Objects[throw]=throw => IndexThrow
Objects[throw]=StringBuffer => IndexThrow
Objects[1]=throw => RightHandSideThrow
Objects[1]=StringBuffer => Okay!


Objects[throw]=throw => IndexThrow
Objects[throw]=Thread => IndexThrow
Objects[1]=throw => RightHandSideThrow
Objects[1]=Thread => Okay!


Objects[throw]=throw => IndexThrow
Objects[throw]=StringBuffer => IndexThrow
Objects[9]=throw => RightHandSideThrow
Objects[9]=StringBuffer => IndexOutOfBoundsException


Objects[throw]=throw => IndexThrow
Objects[throw]=Thread => IndexThrow
Objects[9]=throw => RightHandSideThrow
Objects[9]=Thread => IndexOutOfBoundsException


Threads[throw]=throw => IndexThrow
Threads[throw]=StringBuffer => IndexThrow
Threads[1]=throw => RightHandSideThrow
Threads[1]=StringBuffer => ArrayStoreException


Threads[throw]=throw => IndexThrow
Threads[throw]=Thread => IndexThrow
Threads[1]=throw => RightHandSideThrow
Threads[1]=Thread => Okay!


Threads[throw]=throw => IndexThrow
Threads[throw]=StringBuffer => IndexThrow
Threads[9]=throw => RightHandSideThrow
Threads[9]=StringBuffer => IndexOutOfBoundsException


Threads[throw]=throw => IndexThrow
Threads[throw]=Thread => IndexThrow
Threads[9]=throw => RightHandSideThrow
Threads[9]=Thread => IndexOutOfBoundsException

Наиболее интересный случай из всех - тринадцатый с конца:

Threads[1]=StringBuffer => ArrayStoreException

который указывает, что попытка сохранять ссылку на StringBuffer в массиве, чья компонента имеет тип Thread вызывает ArrayStoreException. Во время компиляции код является правильным: присваивание имеет левую часть типа Object [] и правую часть типа Object. Во время выполнения, первый фактический параметр метода testFour - ссылка на экземпляр "array of Thread" а третий фактический параметр - ссылка на экземпляр класса StringBuffer.

15.25.2 Составные операции присваивания

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

Составное выражение присваивания формы E1 op= E2 эквивалентно E1 = (T) ((E1) op (E2)), где T - это тип E1, за исключением того, что в первом случае выражение E1 вычисляется только один раз. Обратите внимание, что подразумеваемое приведение к типу T может быть или преобразованием тождества (§5.1.1) или сужающим примитивным преобразованием (§5.1.3). Например, следующая запись является правильной:

 

short x = 3;
x += 4.6;

и в результате x становится равно 7, потому что это эквивалентно:

 

short x = 3;
x = (short)(x + 4.6);

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

Если выражение левого операнда – выражение доступа к массиву (§15.12), то требуется много шагов:

 

 

 

 

Правила для составного присваивания элементу массива иллюстрируются следующим примером программы:

 

class ArrayReferenceThrow extends RuntimeException { }


class IndexThrow extends RuntimeException { }


class RightHandSideThrow extends RuntimeException { }

class IllustrateCompoundArrayAssignment {

	static String[] strings = { "Simon", "Garfunkel" };


	static double[] doubles = { Math.E, Math.PI };


	static String[] stringsThrow() {
		throw new ArrayReferenceThrow();
	}


	static double[] doublesThrow() {
		throw new ArrayReferenceThrow();
	}


	static int indexThrow() { throw new IndexThrow(); }


	static String stringThrow() {
		throw new RightHandSideThrow();
	}


	static double doubleThrow() {
		throw new RightHandSideThrow();
	}


	static String name(Object q) {
		String sq = q.getClass().getName();
		int k = sq.lastIndexOf('.');
		return (k < 0) ? sq : sq.substring(k+1);
	}


	static void testEight(String[] x, double[] z, int j) {
		String sx = (x == null) ? "null" : "Strings";
		String sz = (z == null) ? "null" : "doubles";
		System.out.println();
		try {
			System.out.print(sx + "[throw]+=throw => ");
			x[indexThrow()] += stringThrow();
			System.out.println("Okay!");
		} catch (Throwable e) { System.out.println(name(e)); }
		try {
			System.out.print(sz + "[throw]+=throw => ");
			z[indexThrow()] += doubleThrow();
			System.out.println("Okay!");
		} catch (Throwable e) { System.out.println(name(e)); }

		try {
			System.out.print(sx + "[throw]+=\"heh\" => ");
			x[indexThrow()] += "heh";
			System.out.println("Okay!");
		} catch (Throwable e) { System.out.println(name(e)); }
		try {
			System.out.print(sz + "[throw]+=12345 => ");
			z[indexThrow()] += 12345;
			System.out.println("Okay!");
		} catch (Throwable e) { System.out.println(name(e)); }
		try {
			System.out.print(sx + "[" + j + "]+=throw => ");
			x[j] += stringThrow();
			System.out.println("Okay!");
		} catch (Throwable e) { System.out.println(name(e)); }
		try {
			System.out.print(sz + "[" + j + "]+=throw => ");
			z[j] += doubleThrow();
			System.out.println("Okay!");
		} catch (Throwable e) { System.out.println(name(e)); }
		try {
			System.out.print(sx + "[" + j + "]+=\"heh\" => ");
			x[j] += "heh";
			System.out.println("Okay!");
		} catch (Throwable e) { System.out.println(name(e)); }
		try {
			System.out.print(sz + "[" + j + "]+=12345 => ");
			z[j] += 12345;
			System.out.println("Okay!");
		} catch (Throwable e) { System.out.println(name(e)); }
	}


	public static void main(String[] args) {
		try {
			System.out.print("throw[throw]+=throw => ");
			stringsThrow()[indexThrow()] += stringThrow();
			System.out.println("Okay!");
		} catch (Throwable e) { System.out.println(name(e)); }
		try {
			System.out.print("throw[throw]+=throw => ");
			doublesThrow()[indexThrow()] += doubleThrow();
			System.out.println("Okay!");
		} catch (Throwable e) { System.out.println(name(e)); }
		try {
			System.out.print("throw[throw]+=\"heh\" => ");
			stringsThrow()[indexThrow()] += "heh";
			System.out.println("Okay!");
		} catch (Throwable e) { System.out.println(name(e)); }

		try {
			System.out.print("throw[throw]+=12345 => ");
			doublesThrow()[indexThrow()] += 12345;
			System.out.println("Okay!");
		} catch (Throwable e) { System.out.println(name(e)); }
		try {
			System.out.print("throw[1]+=throw => ");
			stringsThrow()[1] += stringThrow();
			System.out.println("Okay!");
		} catch (Throwable e) { System.out.println(name(e)); }
		try {
			System.out.print("throw[1]+=throw => ");
			doublesThrow()[1] += doubleThrow();
			System.out.println("Okay!");
		} catch (Throwable e) { System.out.println(name(e)); }
		try {
			System.out.print("throw[1]+=\"heh\" => ");
			stringsThrow()[1] += "heh";
			System.out.println("Okay!");
		} catch (Throwable e) { System.out.println(name(e)); }
		try {
			System.out.print("throw[1]+=12345 => ");
			doublesThrow()[1] += 12345;
			System.out.println("Okay!");
		} catch (Throwable e) { System.out.println(name(e)); }

		testEight(null, null, 1);
		testEight(null, null, 9);
		testEight(strings, doubles, 1);
		testEight(strings, doubles, 9);
	}

}

Программа выводит:

throw[throw]+=throw => ArrayReferenceThrow
throw[throw]+=throw => ArrayReferenceThrow
throw[throw]+="heh" => ArrayReferenceThrow
throw[throw]+=12345 => ArrayReferenceThrow
throw[1]+=throw => ArrayReferenceThrow
throw[1]+=throw => ArrayReferenceThrow
throw[1]+="heh" => ArrayReferenceThrow
throw[1]+=12345 => ArrayReferenceThrow


null[throw]+=throw => IndexThrow
null[throw]+=throw => IndexThrow
null[throw]+="heh" => IndexThrow
null[throw]+=12345 => IndexThrow
null[1]+=throw => NullPointerException
null[1]+=throw => NullPointerException
null[1]+="heh" => NullPointerException
null[1]+=12345 => NullPointerException


null[throw]+=throw => IndexThrow
null[throw]+=throw => IndexThrow
null[throw]+="heh" => IndexThrow
null[throw]+=12345 => IndexThrow
null[9]+=throw => NullPointerException
null[9]+=throw => NullPointerException
null[9]+="heh" => NullPointerException
null[9]+=12345 => NullPointerException


Strings[throw]+=throw => IndexThrow
doubles[throw]+=throw => IndexThrow
Strings[throw]+="heh" => IndexThrow
doubles[throw]+=12345 => IndexThrow
Strings[1]+=throw => RightHandSideThrow
doubles[1]+=throw => RightHandSideThrow
Strings[1]+="heh" => Okay!
doubles[1]+=12345 => Okay!


Strings[throw]+=throw => IndexThrow
doubles[throw]+=throw => IndexThrow
Strings[throw]+="heh" => IndexThrow
doubles[throw]+=12345 => IndexThrow
Strings[9]+=throw => IndexOutOfBoundsException
doubles[9]+=throw => IndexOutOfBoundsException
Strings[9]+="heh" => IndexOutOfBoundsException
doubles[9]+=12345 => IndexOutOfBoundsException

Наиболее интересные случаи – десятый и одиннадцатый из последних:

 

Strings[1]+=throw => RightHandSideThrow
doubles[1]+=throw => RightHandSideThrow

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

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

class Test {
	public static void main(String[] args) {
		int k = 1;
		int[] a = { 1 };
		k += (k = 4) * (k + 2);
		a[0] += (a[0] = 4) * (a[0] + 2);
		System.out.println("k==" + k + " and a[0]==" + a[0]);
	}
}

Эта программа выводит:

k==25 and a[0]==25

Значение k равное 1 сохраняется составным оператором присваивания += прежде, чем будет вычисляться правый операнд (k = 4) * (k + 2). Затем этот вычисленный правый операнд присваивает значение 4 переменной k, получает значение 6 для k + 2, и затем умножает 4 на 6, чтобы получить 24. Это значение складывается с сохраненным значением 1, чтобы получить 25, которое затем сохраняется в k оператором +=. Идентичный анализ применяется в случаю, который использует а[0]. Короче говоря, операторы

k += (k = 4) * (k + 2);
a[0] += (a[0] = 4) * (a[0] + 2);

Ведут себя точно таким же способом как операторы:

k = k + (k = 4) * (k + 2);
a[0] = a[0] + (a[0] = 4) * (a[0] + 2);

15.26 Выражение

Выражение - это любое выражение присваивания:

В отличие от Cи и Cи ++, в языке Ява нет операции запятая.

15.27 Константное выражение

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

Во время трансляции константные выражения используются в case метках в операторах switch (§14.9) и имеют специальное значение для преобразования присваивания (§5.2).

Примеры константных выражений:

true
(short)(1*2*3*4*5*6)
Integer.MAX_VALUE / 2
2.0 * Math.PI
"The integer " + Long.MAX_VALUE + " is mighty big."

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