Spec-Zone .ru
спецификации, руководства, описания, API
Содержание | Предыдущий | Следующий | Индекс

ГЛАВА 15

Выражения


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

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

15.1 Оценка, Обозначение, и Результат

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

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

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

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

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

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

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

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

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

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

15.4 Выражения и Проверки на этапе выполнения

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

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

15.5 Нормальное и Резкое Завершение Оценки

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

Если, однако, оценка выражения выдает исключение, то выражение, как говорят, завершается резко. У резкого завершения всегда есть связанная причина, которая всегда является a throw с данным значением.

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

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

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

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

15.6 Порядок оценки

Java гарантирует, что операнды операторов, кажется, оцениваются в определенном порядке оценки, а именно, слева направо.

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

15.6.1 Оцените Левый Операнд Сначала

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

Таким образом:


class Test {
	public static void main(String[] args) {
		int i = 2;
		int j = (i=3) * i;
		System.out.println(j);
	}
}
печатные издания:

9
Не разрешается для этого напечатать 6 вместо 9.

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


class Test {
	public static void main(String[] args) {
		int a = 9;
		a += (a = 3);									// first example
		System.out.println(a);
		int b = 9;
		b = b + (b = 3);									// second example
		System.out.println(b);
	}
}
печатные издания:


12
12
потому что эти два оператора присваивания и выбирают и помнят значение левого операнда, который является 9, прежде, чем правый операнд дополнения оценивается, таким образом устанавливая переменную в 3. Не разрешается ни для одного примера привести к результату 6. Отметьте, что у обоих из этих примеров есть неуказанное поведение в C согласно стандарту ANSI/ISO.

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

Таким образом, тестовая программа:


class Test {

	public static void main(String[] args) {

		int j = 1;

		try {
			int i = forgetIt() / (j = 2);
		} catch (Exception e) {
			System.out.println(e);
			System.out.println("Now j = " + j);
		}
	}

	static int forgetIt() throws Exception {
		throw new Exception("I'm outta here!");
	}
}
печатные издания:


java.lang.Exception: I'm outta here!
Now j = 1
потому что левый операнд forgetIt() из оператора / выдает исключение перед правым операндом и его встроенным присвоением 2 к j происходит.

15.6.2 Оцените Операнды перед Работой

Java также гарантирует что каждый операнд оператора (кроме условных операторов &&, ||, и ? :), кажется, полностью оценивается прежде, чем любая часть самой работы выполняется.

Если бинарный оператор является целочисленным делением / (§15.16.2) или целочисленный остаток % (§15.16.3), тогда его выполнение может повысить ArithmeticException, но это исключение выдается только после того, как оба операнда бинарного оператора были оценены и только если эти оценки, завершаемые обычно.

Так, например, программа:


class Test {

	public static void main(String[] args) {
		int divisor = 0;
		try {
			int i = 1 / (divisor * loseBig());
		} catch (Exception e) {
			System.out.println(e);
		}
	}

static int loseBig() throws Exception { throw new Exception("Shuffle off to Buffalo!"); }
}
всегда печатные издания:

java.lang.Exception: Shuffle off to Buffalo!
и нет:

java.lang.ArithmeticException: / by zero
так как никакая часть операции деления, включая сигнализацию исключения дележа на нуль, может казаться, не происходит перед вызовом loseBig завершается, даже при том, что реализация может быть в состоянии обнаружить или вывести, что операция деления, конечно, привела бы к исключению дележа на нуль.

15.6.3 Круглые скобки Отношений оценки и Приоритет

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

В случае вычислений с плавающей точкой это правило применяет также для бесконечности и не-числа (НЭН) значения. Например, !(x<y) возможно, не переписывается как x>=y, потому что у этих выражений есть различные значения если также x или y НЭН.

Определенно, вычисления с плавающей точкой, которые, кажется, математически ассоциативны, вряд ли будут в вычислительном отношении ассоциативны. Такие вычисления не должны быть наивно переупорядочены. Например, это не корректно для компилятора Java, чтобы переписать 4.0*x*0.5 как 2.0*x; в то время как округление, оказывается, не проблема здесь, есть большие значения x для которого первое выражение производит бесконечность (из-за переполнения), но второе выражение приводит к конечному результату.

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


class Test {

	public static void main(String[] args) {
		double d = 8e+307;
		System.out.println(4.0 * d * 0.5);
		System.out.println(2.0 * d);
	}
}
печатные издания:


Infinity
1.6e+308
потому что первые переполнения выражения и второе не делают.

Напротив, целочисленное дополнение и умножение доказуемо ассоциативны в Java; например a+b+c, где a, b, и c локальные переменные (это предположение упрощения избегает проблем, включающих многократные потоки и volatile переменные), будет всегда производить тот же самый ответ, оцененный ли как (a+b)+c или a+(b+c); если выражение b+c происходит поблизости в коде, умный компилятор может быть в состоянии использовать это общее подвыражение.

15.6.4 Списки параметров Оцениваются Слева направо

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

Таким образом:

class Test {

	public static void main(String[] args) {
		String s = "going, ";
		print3(s, s, s = "gone");
	}

	static void print3(String a, String b, String c) {
		System.out.println(a + b + c);
	}
}
всегда печатные издания:

going, going, gone
потому что присвоение строки "gone" к s происходит после первых двух параметров print3 были оценены.

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

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


class Test {
	static int id;
	public static void main(String[] args) {
		try {
			test(id = 1, oops(), id = 3);
		} catch (Exception e) {
			System.out.println(e + ", id=" + id);
		}
	}

static int oops() throws Exception { throw new Exception("oops"); }
static int test(int a, int b, int c) { return a + b + c; }
}
печатные издания:

java.lang.Exception: oops, id=1
потому что присвоение 3 к id не выполняется.

15.6.5 Порядок оценки на Другие Выражения

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

15.7 Основные Выражения

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

Поскольку грамматики языка программирования идут, эта часть грамматики Java необычна двумя способами. Во-первых, можно было бы ожидать простые имена, такие как имена локальных переменных и параметров метода, чтобы быть основными выражениями. Для технических причин имена смешиваются с основными выражениями немного позже, когда постфиксные выражения представляются (§15.13).

Технические причины имеют отношение к разрешению слева направо анализирующий программ Java с только предвидением с одним маркером. Рассмотрите выражения (z[3]) и (z[]). Первым является заключенный в скобки доступ массива (§15.12), и вторым является запуск броска (§15.15). В точке, которая предварительный символ [, слева направо синтаксический анализ уменьшит z к нетерминальному Имени. В контексте броска мы предпочитаем не должными быть уменьшать имя к Основному устройству, но если бы Имя было одной из альтернатив для Основного, то мы не могли сказать, сделать ли сокращение (то есть, мы не могли бы определить, окажется ли текущая ситуация, будет заключенным в скобки доступом массива или броском), не предусматривая два маркера, к маркеру после [. Грамматика Java, представленная здесь, избегает проблемы, сохраняя Имя и Основной отдельный и позволяя любому войти определенные другие правила синтаксиса (те для MethodInvocation, ArrayAccess, PostfixExpression, но не для FieldAccess, потому что это покрывается по имени). Эта стратегия эффективно задерживает вопрос того, должно ли Имя быть обработано как Основное устройство, пока больше контекста не может быть исследовано. (Другие проблемы остаются с выражениями броска; см. §19.1.5.)

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

new int[3][3]
который в Java всегда означает единственное создание многомерного массива, но который, без соответствующего грамматического изящества, мог бы также быть интерпретирован как значение того же самого как:

(new int[3])[3]
Эта неоднозначность устраняется, разделяя ожидаемое определение Основных в Основной и PrimaryNoNewArray. (Это может быть по сравнению с разделением Оператора в Оператор и StatementNoShortIf (§14.4), чтобы избежать "свисания else"проблема.)

15.7.1 Литералы

Литерал (§3.10) обозначает фиксированное, неизменное значение.

Следующее производство от §3.10 повторяется здесь для удобства:

Тип литерала определяется следующим образом:

Оценка литерала всегда обычно завершается.

15.7.2 this

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

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

В примере:


class IntVector {
	int[] v;
	boolean equals(IntVector other) {
		if (this == other)
			return true;
		if (v.length != other.v.length)
			return false;
		for (int i = 0; i < v.length; i++)
			if (v[i] != other.v[i])
				return false;
		return true;
	}

}
класс IntVector реализует метод equals, который сравнивает два вектора. Если other вектор является тем же самым векторным объектом как тот для который equals метод был вызван, тогда проверка может пропустить длину и оценить сравнения. equals метод реализует, это проверяет сравнение ссылки на other объект к this.

Ключевое слово this также используется в специальном явном операторе вызова конструктора, который может появиться в начале тела конструктора (§8.6.5).

15.7.3 Заключенные в скобки Выражения

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

15.8 Выражения Создания Экземпляра класса

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

В выражении создания экземпляра класса ClassType должен назвать класс, который не является abstract. Этот тип класса является типом выражения создания.

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

15.8.1 Оценка времени выполнения Выражений Создания Экземпляра Класса

Во время выполнения оценка выражения создания экземпляра класса следующие.

Во-первых, место выделяется для нового экземпляра класса. Если есть недостаточное пространство, чтобы выделить объект, оценка выражения создания экземпляра класса завершается резко, бросая OutOfMemoryError (§15.8.2).

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

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

Затем, выбранный конструктор указанного типа класса вызывается. Это приводит к вызову по крайней мере одного конструктора для каждого суперкласса типа класса. Этот процесс может быть направлен явными операторами вызова конструктора (§8.6) и описывается подробно в §12.5.

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

15.8.2 Пример: Порядок Оценки и Обнаружение Из памяти

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

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


class List {
	int value;
	List next;
	static List head = new List(0);
	List(int n) { value = n; next = head; head = this; }
}

class Test {
	public static void main(String[] args) {
		int id = 0, oldid = 0;
		try {
			for (;;) {
				++id;
				new List(oldid = id);
			}
		} catch (Error e) {
			System.out.println(e + ", " + (oldid==id));
		}
	}
}
печатные издания:

java.lang.OutOfMemoryError: List, false
потому что условие "или память" обнаруживаются перед выражением параметра oldid = id оценивается.

Сравните это с обработкой выражений создания массива (§15.9), для которого условие из памяти обнаруживается после оценки выражений размерности (§15.9.3).

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, то оно эффективно выполняет ряд вложенных циклов глубины, чтобы создать подразумеваемые массивы массивов. Например, объявление:

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, массивы длины 8, и массивы длины 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 Доступ к полю Используя Основное устройство

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

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

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

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.

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

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, a NullPointerException не бросается. Это"Mount "печатается, демонстрирует, что Основное выражение действительно полностью оценивается во время выполнения, несмотря на то, что только его тип, не его значение, используется, чтобы определить который поле к доступу (потому что поле mountain static).

15.10.2 Доступ к Задействованному использованию Суперкласса super

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

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

Использование 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 Шаг 1 времени компиляции: Определите Класс или Интерфейс, чтобы Искать

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

15.11.2 Шаг 2 времени компиляции: Определите Сигнатуру метода

Второй шаг ищет класс или интерфейс, определенный в предыдущем шаге для объявлений метода. Этот шаг использует имя метода и типы выражений параметра, чтобы определить местоположение объявлений метода, которые и применимы и доступны, то есть, объявления, которые могут быть правильно вызваны на данные параметры. Может быть больше чем одно такое объявление метода, когда самый определенный выбирается. Дескриптор (подпись плюс тип возврата) самого определенного объявления метода является тем, используемым во время выполнения, чтобы сделать, метод диспетчеризирует.

15.11.2.1 Найдите Методы, которые Применимы и Доступны

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

Класс или интерфейс, определенный процессом, описанным в §15.11.1, ищутся все объявления метода, применимые к этому вызову метода; определения метода, наследованные от суперклассов и суперинтерфейсов, включаются в этот поиск.

Доступно ли объявление метода для вызова метода, зависит от модификатора доступа (public, ни один, protected, или private) в объявлении метода и на том, где вызов метода появляется.

Если у класса или интерфейса нет никакого объявления метода, которое и применимо и доступно, то ошибка времени компиляции происходит.

В примере программы:


public class Doubler {
	static int two() { return two(1); }
	private static int two(int i) { return 2*i; }
}

class Test extends Doubler {	
	public static long two(long j) {return j+j; }

	public static void main(String[] args) {
		System.out.println(two(3));
		System.out.println(Doubler.two(3)); // compile-time error
	}
}
для вызова метода two(1) в пределах класса Doubler, есть два доступных названные метода two, но только второй применим, и так, чтобы был тот, вызванный во время выполнения. Для вызова метода two(3) в пределах класса Test, есть два применимых метода, но только тот в классе Test доступно, и так, чтобы был тот, который будет вызван во время выполнения (параметр 3 преобразовывается в тип long). Для вызова метода Doubler.two(3), класс Doubler, не класс Test, ищется названные методы two; единственный применимый метод не доступен, и таким образом, этот вызов метода вызывает ошибку времени компиляции.

Другой пример:


class ColoredPoint {
	int x, y;
	byte color;
	void setColor(byte color) { this.color = color; }
}

class Test {
	public static void main(String[] args) {
		ColoredPoint cp = new ColoredPoint();
		byte color = 37;
		cp.setColor(color);
		cp.setColor(37);											// compile-time error
	}
}
Здесь, ошибка времени компиляции происходит для второго вызова setColor, потому что никакой применимый метод не может быть найден во время компиляции. Тип литерала 37 int, и int не может быть преобразован в byte преобразованием вызова метода. Преобразование присвоения, которое используется в инициализации переменной color, выполняет неявное преобразование константы от типа int к byte, который разрешается потому что значение 37 является достаточно маленьким, чтобы быть представленным в типе byte; но такое преобразование не позволяется для преобразования вызова метода.

Если метод setColor как, однако, объявляли, взял int вместо a byte, тогда оба вызова метода были бы корректны; первый вызов был бы позволен, потому что преобразование вызова метода действительно разрешает расширяющееся преобразование из byte к int. Однако, бросок сужения тогда требовался бы в теле setColor:

	void setColor(int color) { this.color = (byte)color; }

15.11.2.2 Выберите Самый определенный Метод

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

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

Точное определение следующие. Позвольте м. быть именем и предполагать, что есть два объявления методов, названных м., каждый имеющий n параметры. Предположите, что одно объявление появляется в пределах класса или интерфейса T и что типы параметров являются T1..., Tn; предположите кроме того, что другое объявление появляется в пределах класса или интерфейса U и что типы параметров являются U1..., Un. Затем метод м. объявленного в T является более определенным, чем метод м. объявил в U если и только если оба из следующего являются истиной:

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

Если есть точно один максимально определенный метод, то это - фактически самый определенный метод; это является обязательно более определенным чем любой другой метод, который применим и доступен. Это тогда подвергается некоторым дальнейшим проверкам времени компиляции как описано в §15.11.3.

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

15.11.2.3 Пример: Перегрузка Неоднозначности

Рассмотрите пример:

class Point { int x, y; }
class ColoredPoint extends Point { int color; }

class Test {

	static void test(ColoredPoint p, Point q) {
		System.out.println("(ColoredPoint, Point)");
	}

	static void test(Point p, ColoredPoint q) {
		System.out.println("(Point, ColoredPoint)");
	}

	public static void main(String[] args) {
		ColoredPoint cp = new ColoredPoint();
		test(cp, cp);											// compile-time error
	}
}
Этот пример производит ошибку во время компиляции. Проблема состоит в том, что есть два объявления test это применимо и доступно, и ни один не является более определенным чем другой. Поэтому, вызов метода неоднозначен.

Если третье определение test были добавлены:


	static void test(ColoredPoint p, ColoredPoint q) {
		System.out.println("(ColoredPoint, ColoredPoint)");
	}
тогда это было бы более определенным чем другие два, и вызов метода больше не будет неоднозначен.

15.11.2.4 Пример: Возвратите Тип, Не Продуманный

Как другой пример, рассмотрите:

class Point { int x, y; }
class ColoredPoint extends Point { int color; }
class Test {

	static int test(ColoredPoint p) {
		return color;
	}

	static String test(Point p) {
		return "Point";
	}

	public static void main(String[] args) {
		ColoredPoint cp = new ColoredPoint();
		String s = test(cp);											// compile-time error
	}
}
Здесь самое определенное объявление метода test тот, берущий параметр типа ColoredPoint. Поскольку тип результата метода int, ошибка времени компиляции - происходит потому что int не может быть преобразован в a String преобразованием присвоения. Этот пример показывает, что в Java типы результата методов не участвуют в разрешении перегруженных методов, так, чтобы второе test метод, который возвращает a String, не выбирается, даже при том, что у этого есть тип результата, который позволил бы примеру программы компилировать без ошибки.

15.11.2.5 Пример: Разрешение времени компиляции

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

Так, например, рассмотрите две единицы компиляции, один для класса Point:

package points;
public class Point {
	public int x, y;
	public Point(int x, int y) { this.x = x; this.y = y; }
	public String toString() { return toString(""); }

	public String toString(String s) {
		return "(" + x + "," + y + s + ")";
	}
}
и один для класса ColoredPoint:

package points;
public class ColoredPoint extends Point {

	public static final int
		RED = 0, GREEN = 1, BLUE = 2;

	public static String[] COLORS =
		{ "red", "green", "blue" };
	public byte color;

	public ColoredPoint(int x, int y, int color) {
		super(x, y); this.color = (byte)color;
	}

	/** Copy all relevant fields of the argument into
		    this ColoredPoint object. */
	public void adopt(Point p) { x = p.x; y = p.y; }

	public String toString() {
		String s = "," + COLORS[color];
		return super.toString(s);
	}
}
Теперь рассмотрите третью единицу компиляции, которая использует ColoredPoint:

import points.*;
class Test {
	public static void main(String[] args) {
		ColoredPoint cp =
			new ColoredPoint(6, 6, ColoredPoint.RED);
		ColoredPoint cp2 =
			new ColoredPoint(3, 3, ColoredPoint.GREEN);
		cp.adopt(cp2);
		System.out.println("cp: " + cp);
	}
}
Вывод:

cp: (3,3,red)
Прикладной программист, который кодировал класс Test ожидал видеть слово green, потому что фактический параметр, a ColoredPoint, имеет a color поле, и color казалось бы, был бы "соответствующим полем" (конечно, документация для пакета Points должно быть намного более точным!).

Заметьте, между прочим, что самый определенный метод (действительно, единственный применимый метод) для вызова метода adopt имеет подпись, которая указывает на метод одного параметра, и параметр имеет тип Point. Эта подпись становится частью двоичного представления класса Test произведенный компилятором и используется вызовом метода во время выполнения.

Предположите программиста, о котором сообщают эта ошибка программного обеспечения и специалист по обслуживанию points пакет решил, после должного обдумывания, исправить это, добавляя метод к классу ColoredPoint:


public void adopt(ColoredPoint p) {
	adopt((Point)p); color = p.color;
}
Если прикладной программист тогда выполняет старый двоичный файл для Test с новым двоичным файлом для ColoredPoint, вывод все еще:

cp: (3,3,red)
потому что старый двоичный файл для Test все еще имеет дескриптор "один параметр, тип которого Point; void"связанный с вызовом метода cp.adopt(cp2). Если исходный код для Test перекомпилирован, компилятор тогда обнаружит, что есть теперь два применимо adopt методы, и что подпись для более определенного является "одним параметром, тип которого ColoredPoint; void"; выполнение программы тогда произведет требуемый вывод:

cp: (3,3,green)
С предусмотрительностью о таких проблемах, специалисте по обслуживанию points пакет мог фиксировать ColoredPoint класс, чтобы работать и с недавно скомпилированным и со старым кодом, добавляя оборону кодирует к старому adopt метод ради старого кода, который все еще вызывает это на ColoredPoint параметры:


public void adopt(Point p) {
	if (p instanceof ColoredPoint)
		color = ((ColoredPoint)p).color;
	x = p.x; y = p.y;
}
Подобное рассмотрение применяется, если метод должен быть перемещен от класса до суперкласса. В этом случае передающий метод может быть оставлен позади ради старого кода. Специалист по обслуживанию points пакет мог бы хотеть перемещаться adopt метод, который берет a Point параметр до класса Point, так, чтобы все Point объекты могут обладать adopt функциональность. Чтобы избежать проблем совместимости со старым двоичным кодом, специалист по обслуживанию должен оставить передающий метод в классе ColoredPoint:


public void adopt(Point p) {
	if (p instanceof ColoredPoint)
		color = ((ColoredPoint)p).color;
	super.adopt(p);
}
Идеально, код Java должен быть перекомпилирован всякий раз, когда код, от которого он зависит, изменяется. Однако, в среде, где различные классы Java сохраняются различными организациями, это не всегда выполнимо. Безопасное программирование с внимательным отношением к проблемам развития класса может сделать обновленный код намного больше устойчивым. См. §13 для детального обсуждения совместимости на уровне двоичных кодов и введите развитие.

15.11.3 Шаг 3 времени компиляции: Выбранный Метод является Соответствующим?

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

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

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

15.11.4 Оценка времени выполнения Вызова метода

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

15.11.4.1 Вычислите Целевую Ссылку (В случае необходимости)

Есть несколько случаев, чтобы рассмотреть, в зависимости от какого из этих трех производства для MethodInvocation включается (§15.11):

15.11.4.2 Оцените Параметры

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

15.11.4.3 Проверьте Доступность Типа и Метода

Позвольте C быть классом, содержащим вызов метода, и позволять T быть классом или интерфейсом, который содержал метод, вызываемый, и м. быть именем метода, как определено во время компиляции (§15.11.3).

Виртуальная машина Java должна обеспечить как часть редактирования, что метод м. все еще существует в типе T. Если это не истина, то a NoSuchMethodError (который является подклассом IncompatibleClassChangeError) происходит. Если режим вызова interface, тогда виртуальная машина должна также проверить, что целевой ссылочный тип все еще реализует указанный интерфейс. Если целевой ссылочный тип все еще не реализует интерфейс, то IncompatibleClassChangeError происходит.

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

Для метода м.:

Если или T или м. не доступны, то IllegalAccessError происходит (§12.3).

15.11.4.4 Определите местоположение Метода, чтобы Вызвать

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

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

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

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

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

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

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

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

15.11.4.5 Создайте Фрейм, Синхронизируйтесь, Управление Передачей

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

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

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

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

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

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

15.11.4.6 Примечание реализации: Объединение Фреймов

Чтобы позволить определенные виды оптимизации кода, реализациям разрешают некоторую свободу объединить фреймы активации. Предположите, что вызов метода в пределах класса C должен вызвать метод м. в пределах класса 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");
	}
}
возникновение s прежде".startsWith"оценивается сначала, перед выражением параметра 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 a ColoredPoint. Даже метод move в Point вызывает clear метод класса 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). Это предоставляет программисту Java мощный способ расширить абстракции и является ключевой идеей в объектно-ориентированном программировании.

15.11.4.10 Пример: Вызов метода, использующий супер

К переопределенному методу экземпляра суперкласса можно получить доступ при использовании ключевого слова 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, даже если ссылка массива была получена из a final переменная.

15.12.1 Оценка времени выполнения Доступа Массива

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

15.12.2 Примеры: Порядок Оценки Доступа Массива

В доступе массива выражение налево от скобок, кажется, полностью оценивается прежде, чем любая часть выражения в пределах скобок оценивается. Например, в (по общему признанию чудовищном) выражении a[(a=b)[3]], выражение a полностью оценивается перед выражением (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 вместо ссылки на массив, тогда a 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
потому что встроенное присвоение 2 к index происходит перед проверкой на нулевого указателя. Как связанный пример, программа:


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!
A NullPointerException никогда не происходит, потому что индексное выражение должно быть полностью оценено прежде, чем любая часть работы индексации происходит, и это включает проверку относительно того, является ли значение левого операнда null.

15.13 Постфиксных Выражений

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

15.13.1 Имена

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

15.13.2 Постфиксный Инкрементный Оператор ++

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

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

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

15.13.3 Постфиксный Декрементный Оператор --

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

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

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

15.14 Унарных операторов

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

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

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

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

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

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

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

15.14.1 Префиксный Инкрементный Оператор ++

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

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

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

15.14.2 Префиксный Декрементный Оператор --

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

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

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

15.14.3 Унарный Плюс Оператор +

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

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

15.14.4 Оператор унарный минус -

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

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

Для целочисленных значений отрицание является тем же самым как вычитанием от нуля. Использование Java two's-дополнительное представление для целых чисел, и диапазон two's-дополнительных значений не симметрично, таким образом, отрицание максимального отрицания 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) к типу, определенному оператором броска.

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

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

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

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

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

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

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

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

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

15.16.2 Оператор подразделения /

Двоичный файл / оператор выполняет подразделение, производя частное его операндов. Левый операнд является дивидендом, и правый операнд является делителем.

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

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

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

15.16.3 Оператор остатка %

Двоичный файл % оператор, как говорят, приводит к остатку от его операндов от подразумеваемого подразделения; левый операнд является дивидендом, и правый операнд является делителем.

В C и C++, оператор остатка принимает только интегральные операнды, но в Java, это также принимает операнды с плавающей точкой.

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

Примеры:


5%3 produces 2							(note that 5/3 produces 1)
5%(-3) produces 2							(note that 5/(-3) produces -1)
(-5)%3 produces -2							(note that (-5)/3 produces -1)
(-5)%(-3) produces -2							(note that (-5)/(-3) produces 1)
Результат работы остатка с плавающей точкой как вычислено % оператор не является тем же самым как произведенным работой остатка, определенной IEEE 754. Работа остатка IEEE 754 вычисляет остаток от округляющегося подразделения, не подразделения усечения, и таким образом, его поведение не походит на это обычного целочисленного оператора остатка. Вместо этого язык Java определяет % на операциях с плавающей точкой, чтобы вести себя способом, аналогичным тому из целочисленного оператора остатка Java; это может быть по сравнению с библиотечной функцией C fmod. Работа остатка IEEE 754 может быть вычислена библиотечной подпрограммой Java Math.IEEEremainder (§20.11.14).

Результат Java работа остатка с плавающей точкой определяется правилами арифметики IEEE:

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

Примеры:


5.0%3.0 produces 2.0
5.0%(-3.0) produces 2.0
(-5.0)%3.0 produces -2.0
(-5.0)%(-3.0) produces -2.0

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

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

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

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

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

15.17.1 Оператор Конкатенации строк +

Если только одно выражение операнда имеет тип String, тогда преобразование строк выполняется на другом операнде, чтобы произвести строку во время выполнения. Результатом является ссылка на недавно создаваемый 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); много классов переопределяют это, особенно Boolean, Character, Integer, Long, Float, Double, и String.

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

Реализация может хотеть выполнять преобразование и связь за один шаг, чтобы избежать создавать и затем отбрасывать промежуточное звено String объект. Чтобы увеличить производительность повторной конкатенации строк, компилятор Java может использовать 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, тогда арифметика с плавающей точкой выполняется.

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

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

Результат дополнения с плавающей точкой определяется, используя следующие правила арифметики 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, включительно.

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

Значение 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 смещаясь a 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:

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

15.19.2 Оператор Сравнения типов instanceof

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

Во время выполнения, результат instanceof оператор true если значение RelationalExpression не null и ссылка могла быть брошена (§15.15) к ReferenceType, не повышая a 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, или два операнда, которые являются каждым из ссылочного типа или нулевого типа. Все другие случаи приводят к ошибке времени компиляции. Тип выражения равенства всегда boolean.

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

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

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

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

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

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

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

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

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

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

Если операнды оператора равенства имеют оба или ссылочный тип или нулевой тип, то работа является объектным равенством.

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

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

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

15.21.1 Целочисленные Логические операторы &, ^, и |

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

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

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

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

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

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

Когда оба операнда a &, ^, или | оператор имеет тип 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, тогда правое выражение оценивается, и его значение становится значением условного выражения - и выражение. Таким образом, && вычисляет тот же самый результат как & на boolean операнды. Это отличается только по этому, правое выражение операнда оценивается условно, а не всегда.

15.23 Условных выражений - Или Оператор ||

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

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

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

15.24 Условных Операторов ? :

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

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

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

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

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

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

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

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

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

15.25 Операторов присваивания

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

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

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

Переменная, которая объявляется final не может быть присвоен, потому что когда доступ a 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
который указывает что попытка сохранить ссылку на a StringBuffer в массив, компоненты которого имеют тип Thread броски ArrayStoreException. Код корректен типом во время компиляции: у присвоения есть левая сторона типа Object[] и правая сторона типа Object. Во время выполнения, первый фактический параметр методу testFour ссылка на экземпляр "массива 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
Значение 1 из k сохраняется составным оператором присваивания += перед его правым операндом (k = 4) * (k + 2) оценивается. Оценка этого правого операнда тогда присваивается 4 к k, вычисляет значение 6 для k + 2, и затем умножается 4 6 добраться 24. Это добавляется к сохраненному значению 1 добраться 25, который тогда сохранен в k += оператор. Идентичный анализ применяется к случаю, который использует a[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++, у языка Java нет никакого оператора запятой.

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

Константное выражение времени компиляции является выражением, обозначающим значение типа примитива или a 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."

Содержание | Предыдущий | Следующий | Индекс

Спецификация языка Java (HTML, сгенерированный Блинчиком "сюзет" Pelouch 24 февраля 1998)
Авторское право © Sun Microsystems, Inc 1996 года. Все права защищены
Пожалуйста, отправьте любые комментарии или исправления к doug.kramer@sun.com

free hit counter