Большая часть работы в программе Ява –
вычисление выражений, их побочных эффектов,
типа назначений на переменные, или их значений,
которые могут использоваться как параметры или
операнды в больших выражениях, или их
воздействие на последовательность выполнения в
операторах, или и то и другое.
Эта глава определяет значения выражений в
языке Ява и правил для их оценки.
Когда выражение в Ява-программе оценено
(выполнено), результат обозначает одно из
следующих
- Переменную (§4.5)
(в Си, это называлось lvalue)
- Значение (§4.2 , §4.3 ,).
- Ничего (void)
Вычисление выражения может также производить
побочные эффекты, потому что выражения могут
содержать вложенные назначения: операторы
инкремента, операторы декремента, и вызовы методов.
Выражение ничего не обозначает, тогда и только
тогда, когда это - вызов метода (§15.11), который вызывает
метод, не возвращающий значение, то есть метод
объявленный void (§8.4). Такое выражение может
использоваться только как оператор-выражения (§14.7), потому что
каждый другой контекст, в котором выражение
может появляться, требует, чтобы выражение
что-нибудь обозначало. Оператор выражения,
который является вызовом метода, может также
вызывать метод, который возвращает результат; в
этом случае значение, возвращенное методом
отбрасывается.
Каждое выражение входит в объявления
некоторого (классового или интерфейсного) типа,
который описан: в инициализаторе поля, в
статическом инициализаторе, в объявлениях
конструктора или в самом коде метода.
Если выражение обозначает переменную, и
значение требуется для использования в
дальнейшей оценке, тогда используется значение
переменной. В этом контексте, если выражение
обозначает переменную или значение, мы можем
говорить просто значение выражения.
Если выражение обозначает переменную или
значение, то тип выражение считается известным
во времени компиляции. Правила для определения
типа выражения объясняются отдельно ниже для
каждого вида выражения.
Значение выражения всегда совместимо по
присваиванию(§5.2)
с типом выражения, точно как значение,
сохраненное в переменной всегда совместимо с
типом переменной. Другими словами, значение
выражения, чей тип - T, всегда соответствует
для присваивания на переменную типа T.
Обратите внимание, что выражение, чей тип -
классовый типа F, который объявлен final,
точно будет иметь значение, которое является или
пустой ссылкой или объектом, чей класс -
непосредственно F, потому что final
типы не имеют подклассов.
Если тип выражения - примитивный тип, то
значение выражения имеет тот же самый
примитивный тип. Но если тип выражения -
ссылочный тип, то класс вызванного объекта, может
иметь значение ссылки на объект скорее чем
пустой указатель, это не обязательно известно во
времени компиляции. Существует несколько
случаев в языке Ява, где фактический класс
вызванного объекта оказывает воздействие на
выполнение программы в методе, который не может
быть получен из типа выражения. Они следующие:
- Вызов метода (§15.11).
Особый метод, используемый для вызова o.m
(...) выделен исходя из методов, которые
являются частью класса или интерфейса, который
имеет тип o. Для методов экземпляра, объекта
класса, вызывается значением o во время
выполнения и выполняется, потому что подкласс
может замещать конкретный метод, уже объявленный
в родительском классе так, чтобы этот замещенный
метод вызывался. (Замещенный метод может или не
может выбран чтобы далее вызвать оригинал
отмененного m. метода .)
- Операция instanceof (§15.19.2). Выражение,
ссылочного типа, может быть проверено, используя instanceof, чтобы выяснить является ли
класс объекта, вызванного значением выражения во
время выполнения совместимым (§5.2) с некоторым другим
ссылочным типом.
- Приведение (§5.4 , §15.15 ). Класс объекта,
вызванного операндом выражения во время
выполнения не может быть совместим с типом,
определенным в приведении. Для ссылочных типов,
это может требовать проверки во время
выполнения, которая вызывает ошибку если класс
вызванного объекта, как определено во время
выполнения, не является совместимым (§5.2) с целевым типом.
- Присваивание компонентам массива ссылочный тип
(§10.10, §15.12, §15.25.1).
Правила контроля соответствия типов позволяют
массиву, типа S[], быть обработанным как подтип
T[], если S - подтип T, но это требует,
чтобы время выполнения проверялись типы
компонентов массивов, аналогичной проверке,
выполняемой для приведения.
- Исключение захватывается (§14.18) предложением catch только если
класс сгенерированного объекта-исключения
является потомком типа
формального параметра предложения catch. То есть операция instanceof для этого исключения и типа формального параметра
дает значение true.
Первый из двух случаев, выше перечисленных,
никогда не приведет к ошибке несовместимости
типов. Таким образом, в Ява, ошибка типа, времени
выполнения может происходить только в следующих
ситуациях:
- В приведении типов, когда фактический класс
объекта, вызванного значением выражения
операнда не совместим с конечным типом,
определенным операцией приведения (§5.4, §15.15 ); в
этом случае вызывается ClassCastException.
- В присваивании компонентам массива ссылочного
типа, тогда фактический класс объекта,
вызванного значением, которое будет
присваиваться к не совместимым, с фактическим
типом компонентам массива (§10.10, §15.12, §15.25.1), во время
выполнения; в этом случае вызывается ArrayStoreException.
- Когда исключение не захвачено каким-нибудь catch обработчиком (§11.3), то в этом случае управление
потоком, которое столкнулось с исключением,
сначала вызывает метод UnCaughtException (§20.21.31), а
затем потоки группируют, и затем завершается
выполнение потока.
Каждое выражение в нормальном режиме
вычисления, имеет выполненными некоторые
вычислительные шаги. Следующие разделы
описывают нормальный режим вычисления для
каждого вида выражения. Если все шаги выполнены
без возникновения исключения, выражение, как
говорится, завершается нормально.
Если, однако, во время вычисления выражения
возникает исключение, то выражение, как
говорится, завершается преждевременно.
Причина преждевременного завершения вычислений
всегда порождается некоторым значением.
Исключения, во время выполнения, генерируются
предопределенными следующими операциями:
- Выражение создания экземпляра класса (§15.8), выражение
создания массива(§15.9),
или выражение объединения строк (§15.17.1) вызывает OutOfMemoryError,
если недостаточно памяти.
- Выражение создания массива вызывает ArrayNegativeSizeException,
если значение любого выражения в размерности
массива отрицательно(§15.9).
- Доступ к полю (§15.10) вызывает
NullPointerException, если выражение,
ссылающееся на объект равно null.
- Выражение вызова метода (§15.11), который
вызывает метод экземпляра, вызывает NullPointerException,
если последняя ссылка пуста (null).
- Доступ к массиву (§15.12)
вызывает NullPointerException, если
значение ссылающегося на массив выражения равно null.
- Доступ к массиву (§15.12)
вызывает IndexOutOfBoundsException, если
индекс массива отрицательный или большее или
равен длине массива.
- Приведение (§15.15)
вызывает ClassCastException, если во время
выполнения обнаружено, что приведение
недопустимо.
- Операции целочисленного деления (§15.16.2) или
получения остатка от деления (§15.16.3) вызывает ArithmeticException
если значение правого операнда равно нолю.
- Присваивание компоненту массива ссылочного
типа (§15.25.1)
вызывает ArrayStoreException, когда
присваиваемое значение несовместимо с типом
компонент массива.
Выражение вызова метода может также
генерировать исключение, если причина генерации
исключения может возникнуть в теле метода, то
метод завершается преждевременно. Например,
выражение создания экземпляра класса может
также заканчиваться вызовом исключения, если
причина генерации исключения возникает в
конструкторе то создание класса завершается
преждевременно. Различные ошибки компоновщиков
классов и виртуальной машины могут происходить в
течении вычисления выражения. По их типу, такие
ошибки сложно предсказать и трудно обработать.
Если возникает исключительная ситуация, тогда
вычисление одного или более выражений может быть
завершено прежде, чем завершатся все шаги их
нормального вычисления ; такие выражения,
завершаются преждевременно. Термины
"завершено нормально" и "завершено
преждевременно" также применяются к
выполнению операторов (§14.1).
Оператор может завершаться преждевременно по
ряду причин, и не только, потому что
сгенерирована исключительная ситуация.
Если вычисление выражения требует вычисление
подвыражения, то преждевременное завершение
подвыражения, всегда влечет преждевременное
завершение родительского выражения, по той же
самой причине, и все следующие шаги в нормальном
режиме вычисления не выполняются.
Выражение создания экземпляра массива
используется, чтобы создавать новые массивы (§ 10).
ArrayCreationExpression:
new PrimitiveType DimExprs Dimsopt
new TypeName DimExprs Dimsopt
DimExprs:
DimExpr
DimExprs DimExpr
DimExpr:
[ Expression ]
Dims:
[ ]
Dims [ ]
Выражение создания массива порождает объект,
являющийся новым массивом, элементы которого
имеют тип, определенный как PrimitiveType или TypeName.
TypeName может быть любым ссылочным типом, даже
классовым типом с модификатором abstract (§ 8.1.2.1) или
интерфейсным типом (§ 9).
Тип выражения создания - это тип массив, который
может определяться с помощью выражения создания,
из которого удаляются ключевое слово new
и все выражения DimExpr; например, типом
выражения создания:
new double[3][3][]
является:
double[][][]
Тип каждого выражения размерности DimExpr должен быть целочисленным, иначе происходит ошибка времени компиляции. Каждое выражение подвергается одноместному числовому расширению (§ 5.6.1). Расширенный тип должен быть типом int, иначе происходит ошибка времени компиляции; это означает, в частности, что тип выражения размерности не должен быть типом long.
Во время выполнения вычисление выражения
создания массива осуществляется следующим
образом.
Сначала слева направо вычисляются выражения
размерности. Если какое-либо вычисление
выражений заканчивается преждевременно,
выражения справа от текущего положения не
вычисляются.
Затем проверяются значения выражений
размерности. Если значения любого выражения DimExpr
меньше нуля, тогда генерируется NegativeArraySizeException.
Затем выделяется место для нового массива. Если
для размещения массива места недостаточно,
вычисление выражения создания массива
заканчивается преждевременно, при этом
генерируется OutOfMemoryError.
Затем, если DimExpr встречается один раз, то
создается одномерный массив указанной длины, а
каждой компоненте массива по умолчанию
присваивается стандартное начальное значение (§
4.5.4).
Если выражение создания массива содержит
выражения DimExpr в количестве N,
тогда выполняется, в
действительности, несколько вложенных циклов
глубины N -1 для создания
массива из массивов. Например, объявление:
float[][] matrix = new float[3][3];
эквивалентно следующему:
float[][] matrix = new float[3][];
for (int d = 0; d < matrix.length; d++)
matrix[d] = new float[3];
и:
Age[][][][][] Aquarius = new Age[6][10][8][12][];
эквивалентно:
Age[][][][][] Aquarius = new Age[6][][][][];
for (int d1 = 0; d1 < Aquarius.length; d1++) {
Aquarius[d1] = new Age[8][][][];
for (int d2 = 0; d2 < Aquarius[d1].length; d2++) {
Aquarius[d1][d2] = new Age[10][][];
for (int d3 = 0; d3 < Aquarius[d1][d2].length; d3++) {
Aquarius[d1][d2][d3] = new Age[12][];
}
}
}
где d, d1, d2 и d3 - переменные, имена
которых не совпадают с уже объявленными
локальными переменными. Таким образом,
единственное выражение с ключевым словом new фактически создает один массив
длины 6, 6 массивов длины 10, 60 массивов длины 8, и 480
массивов длины 12. В этом примере не указывается
пятая размерность, которая может также оказаться
массивом, содержащим фактические
элементы массива (ссылающиеся на объекты Age), которые первоначально
инициализируются пустыми ссылками. Эти массивы
могут быть заполнены позже разными способами,
например так:
Age[] Hair = { new Age("quartz"), new Age("topaz") };
Aquarius[1][9][6][9] = Hair;
Многомерный массив не обязан иметь массив
одинаковой длины на каждом
уровне; таким образом, может быть создана
треугольная матрица:
float triang[][] = new float[100][];
for (int i = 0; i < triang.length; i++)
triang[i] = new float[i+1];
Однако, невозможно добиться этого результата с
помощью единственного выражения создания.
В выражении создания массива (§15.9) может быть одно или
больше выражений размерности, каждое из которых
заключается в скобки. Каждое выражение
размерности полностью вычисляется до вычисления
выражения размерности, стоящего справа.
Таким образом, программа:
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, никогда не
выполняется.
Если при анализе выражения создания массива
обнаружено, что для создания
массива памяти недостаточно, тогда генерируется 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).
Выражение доступа к полю может обращаться к
полю объекта или массиву, ссылка на который
является значением выражения или значением
специального зарезервированного слова super. (Также возможно обратиться к полю
текущего экземпляра или текущего класса с
помощью простого имени; см. §15.13.1.)
FieldAccess:
Primary . Identifier
super . Identifier
Значение выражения доступа к полю определяется
по тем же правилам, как для квалифицированных
имен (§6.6), за
исключением того факта, что это выражение не
может указывать на пакет, классовый или
интерфейсный типы.
Тип Primary должен быть ссылочным типом T,
иначе происходит ошибка времени компиляции.
Значение выражения доступа к полю определяется
следующим образом:
- Если идентификатор называет несколько
доступных полей элемента типа T, тогда доступ
к полю неоднозначен, и происходит ошибка времени
компиляции.
- Если идентификатор не называет доступное поле
элемента типа T, тогда доступ к полю не
определен, и происходит ошибка времени
компиляции.
- Иначе, идентификатор называет единственное
доступное поле типа T, и типом выражения
доступа к полю является объявленный тип поля. Во
время выполнения результат выражения доступа к
полю вычисляется следующим образом:
Если поле имеет модификатор static:
- Если поле с модификатором final,
тогда результатом является значение указанной
переменной класса в классе или интерфейсе,
который является типом выражения Primary.
- Если поле не имеет модификатора final,
тогда результатом является переменная, а именно,
указанная переменная класса в классе, который
является типом выражения Primary.
Если поле не имеет модификатора static
:
- Если значением выражения Primary является null, тогда генерируется NullPointerException.
- Если поле имеет модификатор final,
тогда результат - значение указанной переменной
экземпляра в объекте, на который ссылаются с
помощью значения выражения Primary.
- Если поле не имеет модификатора final,
тогда результатом является переменная, а именно,
указанная переменная экземпляра в объекте, на
который ссылаются с помощью значения выражения Primary.
Заметим, что только тип выражения Primary, а не
класс фактического объекта, на который ссылаются
во время выполнения, используется в определении
того, которое поле использовать.
Таким образом, пример:
class S { int x = 0; }
class T extends S { int x = 1; }
class Test {
public static void main(String[] args) {
T t = new T();
System.out.println("t.x=" + t.x + when("t", t));
S s = new S();
System.out.println("s.x=" + s.x + when("s", s));
s = t;
System.out.println("s.x=" + s.x + when("s", s));
}
static String when(String name, Object t) {
return " when " + name + " holds a "
+ t.getClass() + " at run time.";
}
}
печатает:
t.x=1 when t holds a class T at run time.
s.x=0 when s holds a class S at run time.
s.x=0 when s holds a class T at run time.
Последняя строка показывает, что поле, к
которому осуществляется доступ, действительно
не зависит от текущего класса, на который
осуществляется ссылка; даже если в s
хранится ссылка на объект класса T,
выражение s.x обращается к полю x класса S, потому что
тип выражения s есть S.
Объект класса T содержит два поля,
названные x, одно для класса T и одно для суперкласса S.
Отсутствие динамического поиска для доступа к
полю позволяет Ява-системе работать эффективно
при простой реализации. В Ява-системе есть
возможность позднего связывания и перегрузки, но
только в том случае, когда используются методы
экземпляра. Рассмотрим тот же самый пример,
использующий методы экземпляра при доступе к
полю:
class S { int x = 0; int z() { return x; } }
class T extends S { int x = 1; int z() { return x; } }
class Test {
public static void main(String[] args) {
T t = new T();
System.out.println("t.z()=" + t.z() + when("t", t));
S s = new S();
System.out.println("s.z()=" + s.z() + when("s", s));
s = t;
System.out.println("s.z()=" + s.z() + when("s", s));
}
static String when(String name, Object t) {
return " when " + name + " holds a "
+ t.getClass() + " at run time.";
}
}
Теперь в качестве вывода получим:
t.z()=1 when t holds a class T at run time.
s.z()=0 when s holds a class S at run time.
s.z()=1 when s holds a class T at run time.
Последняя строка показывает, что, метод, к
которому происходит обращение, действительно
зависит от текущего класса объекта, на который
осуществляется ссылка. Когда s
содержит ссылку на объект класса T,
выражение s.z () обращается к методу
z класса T, несмотря
на тот факт, что типом выражения s
является тип S. Метод z
класса T замещает метод
z класса S.
Следующий пример демонстрирует, что пустая
ссылка может использоваться для доступа к
переменной класса (с модификатором static),
не приводя к исключению:
class Test {
static String mountain = "Chocorua";
static Test favorite(){
System.out.print("Mount ");
return null;
}
public static void main(String[] args) {
System.out.println(favorite().mountain);
}
}
Этот класс компилируется, выполняется, и в
результате печатается:
Mount Chocorua
Хотя результатом favorite() является
null, NullPointerException не
генерируется. Строка "Mount " печатается,
демонстрируя, что выражение Primary
действительно полностью вычисляется во время
выполнения, несмотря на тот факт, что для того
чтобы определить, к которому полю устанавливать
доступ, используется только тип этого выражения,
а не значение, (потому что поле mountain имеет
модификатор static).
super
Специальное выражение, использующее
зарезервированное слово super,
может применяться только в методе экземпляра или
конструкторе или в инициализаторе переменной
экземпляра; эти ситуации – те же самые, что и
ситуации, в которых может быть использовано
ключевое слово this (§ 15.7.2).
Выражение, использующее зарезервированное слово
super, не может использоваться в
классе Object, поскольку Object
не имеет суперкласса; если зарезервированное
слово super появляется в классе Object, то в результате произойдет
ошибка времени компиляции.
Предположим, что выражение доступа к полю super.name появляется в пределах
класса C, а класс S – непосредственный
суперкласс класса C. Тогда выражение super.name обрабатывается так же, как
обрабатывалось бы выражение ((S)this).name;
таким образом, оно обращается с полю name
текущего объекта, но при условии, что текущий
объект рассматривается как экземпляр
суперкласса. Таким образом выражение может
обращаться к полю name, которое является
видимым в классе S, даже если в классе C это
поле скрыто объявлением поля name.
Использование super
демонстрируется следующим примером:
interface I { int x = 0; }
class T1 implements I { int x = 1; }
class T2 extends T1 { int x = 2; }
class T3 extends T2 {
int x = 3;
void test() {
System.out.println("x=\t\t"+x);
System.out.println("super.x=\t\t"+super.x);
System.out.println("((T2)this).x=\t"+((T2)this).x);
System.out.println("((T1)this).x=\t"+((T1)this).x);
System.out.println("((I)this).x=\t"+((I)this).x);
}
}
class Test {
public static void main(String[] args) {
new T3().test();
}
}
результатом которого является вывод:
x= 3
super.x= 2
((T2)this).x= 2
((T1)this).x= 1
((I)this).x= 0
В пределах класса T3 выражение super.x обрабатывается точно также, как
если это было бы выражение:
((T2)this).x
Выражение вызова метода используется, чтобы
вызвать метод класса или экземпляра.
MethodInvocation:
MethodName ( ArgumentListopt )
Primary . Identifier ( ArgumentListopt )
super . Identifier ( ArgumentListopt )
Определение ArgumentList из §
15.8 повторим здесь для удобства:
ArgumentList:
Expression
ArgumentList , Expression
Идентификация имени метода во время компиляции
сложнее, чем идентификация имени поля из-за
возможности перегрузки метода. Вызов метода во
время исполнения также более сложен, чем
обращение к полю из-за возможности замещения
метода экземпляра.
Определение метода, который будет вызван
выражением вызова метода, состоит из нескольких
шагов. В следующих трех параграфах описывается
обработка вызова метода во время компиляции;
определение типа выражения вызова метода
описано в § 15.11.3.
Первый шаг при компиляции при обработке вызова
метода состоит в определении имени метода,
который будет вызван, и в том, который класс или
интерфейс проверять для определения методов с
таким именем. В зависимости от выражения, которое
предшествует левый круглой скобке, существует
несколько случаев, которые описаны далее:
- Если выражение является MethodName, тогда
существуют три возможных случая:
- Если это простое имя, то есть Identifier, тогда
имя метода - Identifier, и класс или интерфейс,
который надо искать, - тот, чье объявление
содержит вызов метода.
- Если это - квалифицированное имя вида TypeName.
Identifier, тогда имя метода - Identifier и класс,
который следует искать, - это класс названный
этим TypeName. Если TypeName - имя интерфейса, а не
класса, тогда происходит ошибка времени
компиляции, потому что это выражение может
вызывать только методы с модификатором static, а интерфейсы не могут иметь
таких методов.
- Во всех других случаях квалифицированное имя
является FieldName.Identifier; тогда имя метода - Identifier,
и класс или интерфейс, который следует искать, -
объявленный тип поля, названного FieldName.
- Если выражение является Primary . Identifier, тогда
имя метода - Identifier, и класс или интерфейс,
который следует искать, - тип выражения Primary.
- Если выражение есть super . Identifier,
тогда имя метода - Identifier (идентификатор), и
класс, который следует искать, - суперкласс
класса, чье объявление содержит вызов метода.
Произойдет ошибка времени компиляции, если этот
вызов метода осуществляется в интерфейсе, или в
классе Object, или в методе с
модификатором static, статическом
инициализаторе, или инициализаторе для
переменной с модификатором static.
Из этого следует, что вызов метода этого вида
может появляться только в классе, не являющемся
классом Object, и только в теле
метода экземпляра, теле конструктора или
инициализатора для переменной экземпляра.
Стратегия для поиска метода зависит от способа
вызова.
Если способ вызова static, никакая
целевая ссылка не нужна и замещение не разрешается. Метод m
класса T - тот метод, который будет вызван.
Иначе, должен быть вызван метод экземпляра и
имеется целевая ссылка. Если целевая ссылка есть null, то в этой точке
генерируется NullPointerException. Иначе,
целевая ссылка указывает на целевой объект и
будет использоваться как значение ключевого
слова this в вызванном методе.
Другие четыре возможности для способа вызова
рассмотрены далее.
Если способ вызова nonvirtual,
замещение не разрешается. Метод m класса T
- тот метод, который будет вызван.
Если способ вызова interface, virtual или super, то
замещение может осуществляться. Используется динамический
поиск метода. Процесс динамического поиска
начинается с класса S, определенного
следующим образом:
- Если способ вызова interface или virtual, тогда S первоначально
фактический класс R целевого объекта во время
выполнения. Если целевой объект является
массивом, R - класс Object.
(Заметим, что для способа вызова interface
R обязательно реализует T; для способа
вызова virtual, R обязательно
является или T, или подклассом T.)
- Если способ вызова super, тогда S
первоначально суперкласс класса C, который
содержит вызов метода.
Динамический поиск метода использует
следующую далее процедуру для поиска класса S,
а затем, если необходимо, суперкласса класса S,
для метода m.
- Если класс S содержит объявление метода m
с таким же описанием (то же число параметров, те
же типы параметров и тот же возвращаемый тип), как
в вызове, определенном во время компиляции (§15.11.3), тогда
это метод, который может быть вызван, и процедура
завершается. (Заметим, что в процессе загрузки и
компоновки, который контролирует виртуальная
машина, этот замещающийся метод, по крайней мере,
так же доступен как замещенный метод; если дело
обстоит не так, происходит IncompatibleClassChangeError
.)
- Иначе, если класс S не является T,
выполняется та же самая процедура поиска с
использованием суперкласса S; что бы ни
произошло, она завершается, если получен
результат этого поиска.
Эта процедура найдет подходящий метод, когда
она достигнет класса T, потому что
в иных случаях будет сгенерирована IllegalAccessError
с помощью проверок предыдущего параграфа §15.11.4.3.
Заметим, что в то время как динамический
процесс поиска описан здесь явно, он часто будет
осуществляться неявно, например, как побочный
эффект конструкции и использования таблиц
контроля методов для классов или конструкции
других структур для классов, используемых для
эффективного контроля.
Метод m в некотором классе S был
идентифицирован как класс, который будет вызван.
Теперь создается новый фрейм инициализации,
содержащий целевую ссылку и значение аргумента,
а также достаточное количество места для
локальных переменные и стека для метода, который
будет вызван, с учетом использования любых
других системных ресурсов, которые могут
потребоваться при выполнении ( указатель стека,
программный счетчик, ссылка на предыдущий фрейм
инициализации и т. д.). Если недостаточно памяти,
доступной для создания такого фрейма
инициализации, генерируется OutOfMemoryError.
Новый созданный фрейм инициализации
становится текущим фреймом инициализации.
Результат этого состоит в присваивании значений
аргумента только что созданным соответствующим
параметрам-переменным метода, и в создании
целевой ссылки, доступной как this,
если имеется целевая ссылка.
Если метод m является методом с
модификатором native, но необходимый
родной зависимый от реализации двоичный код не
может быть загружен (§20.16.14, §20.16.13), иными словами не
может быть динамически скомпонован, тогда
генерируется UnsatisfiedLinkError.
Если метод m не является synchronized,
управление передается телу метода m, который
был вызван.
Если метод m является synchronized,
тогда объект должен быть блокирован перед
передачей управления. Никакое дальнейшее
выполнение не может осуществляться до того, как
текущий поток не будет разблокирован. Если
имеется целевая ссылка, тогда цель должна быть
блокирована; иначе объект Class для
класса S – класса метода m, должен быть
блокирован. Управление передается телу метода m,
который был вызван. Объект
автоматически разблокируется, когда завершается
выполнение тела метода, или нормально, или
преждевременно. Блокировка и разблокирование
ведет себя так, как будто тело метода вложено в
оператор synchronized (§14.17).
Чтобы сделать возможными некоторые виды
оптимизации кода, при реализации предоставлена
некоторая свобода комбинирования фреймов
инициализации. Предположим, что вызов метода в
пределах класса C должен вызвать метод m в
классе S. Тогда текущий фрейм инициализации
может использоваться для обеспечения места для S
вместо создания нового фрейма инициализации,
только если истинно одно из следующих условий:
- Класс C и класс S имеют один и тот
загрузчик класса (§20.14)
и класс S не является SecurityManager
или подклассом SecurityManager.
- Класс S не имеет никакого загрузчика класса
(это указывает на то, что это системный класс);
класс S не является SecurityManager
или подклассом SecurityManager; и
известно, что метод m не вызывает,
непосредственно или косвенно, любой метод SecurityManager (§20.17)
или любого из его подклассов.
Когда целевая ссылка вычислена, а затем
отброшена потому что способ вызова — 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),
рассматривается выражение, которое обозначает
объект, который будет вызван. Это выражение
оказывается полностью вычисленным до вычисления
любого выражения аргумента в вызове метода.
Так, например, в классе:
class Test {
public static void main(String[] args) {
String s = "one";
if (s.startsWith(s = "two"))
System.out.println("oops");
}
}
стоящее перед ".startsWith" s анализируется первым, до выражения
аргумента s="two". Поэтому,
ссылка на строку “one”
запоминается как целевая ссылка, прежде чем
локальная переменная s изменяется
и начинает указывать на строку "two".
В результате, метод startsWith (§20.12.20)
вызывается для целевого объекта “one”
с аргументом "two", таким
образом, результат вызова есть ложь (false),
поскольку строка “one” не
начинается с "two". Из этого
следует, что тестирующая программа не печатает "oops".
В примере:
class Point {
final int EDGE = 20;
int x, y;
void move(int dx, int dy) {
x += dx; y += dy;
if (Math.abs(x) >= EDGE || Math.abs(y) >= EDGE)
clear();
}
void clear() {
System.out.println("\tPoint clear");
x = 0; y = 0;
}
}
class ColoredPoint extends Point {
int color;
void clear() {
System.out.println("\tColoredPoint clear");
super.clear();
color = 0;
}
}
подкласс ColoredPoint расширяет clear, определенный суперклассом Point. Это осуществляется с помощью
замещения метода clear собственным
методом, который вызывает метод clear
суперкласса, используя форму super.clear.
Этот метод будет потом вызываться всякий раз,
когда целевым объектом для вызова метода clear будет являться ColoredPoint.
Даже метод move в Point
вызывает метод move класса ColoredPoint, когда классом this
является ColoredPoint, как
показывает вывод этой тестирующей программы:
class Test {
public static void main(String[] args) {
Point p = new Point();
System.out.println("p.move(20,20):");
p.move(20, 20);
ColoredPoint cp = new ColoredPoint();
System.out.println("cp.move(20,20):");
cp.move(20, 20);
p = new ColoredPoint();
System.out.println("p.move(20,20), p colored:");
p.move(20, 20);
}
}
вывод будет следующим:
p.move(20,20):
Point clear
cp.move(20,20):
ColoredPoint clear
Point clear
p.move(20,20), p colored:
ColoredPoint clear
Point clear
Замещение иногда называют "поздним
связыванием собственной ссылки"; в этом
примере это означает, что ссылка на clear
в теле Point.move (который в
действительности является сокращением
выражения this.clear) вызывает метод,
выбранный "позднее" ( во время выполнения на
основе текущего класса объекта, на которого
ссылаются с помощью this) скорее чем
метод, выбранный "раньше" (во время
компиляции, только на основе типа this).
Это обеспечивает программистам, работающим на
Яве, широкие возможности расширения абстракций,
что и является ключевой идеей
объектно-ориентированного программирования.
super
К перегруженному методу экземпляра
суперкласса можно обращаться, используя
зарезервированное слово super для
доступа к элементам непосредственного
суперкласса, обходя любое объявление в классе,
который содержит метод вызова.
При доступе к переменной экземпляра, super имеет то же значение, что и
приведенное this (§15.10.2), но это не справедливо
для вызова метода. Рассмотрим пример:
class T1 {
String s() { return "1"; }
}
class T2 extends T1 {
String s() { return "2"; }
}
class T3 extends T2 {
String s() { return "3"; }
void test() {
System.out.println("s()=\t\t"+s());
System.out.println("super.s()=\t"+super.s());
System.out.print("((T2)this).s()=\t");
System.out.println(((T2)this).s());
System.out.print("((T1)this).s()=\t");
System.out.println(((T1)this).s());
}
}
class Test {
public static void main(String[] args) {
T3 t3 = new T3();
t3.test();
}
}
результатом которого является вывод:
s()= 3
super.s()= 2
((T2)this).s()= 3
((T1)this).s()= 3
Приведения к типам T1 и T2 не изменяют метод, который
вызывается, потому что метод экземпляра, который
будет вызван, выбирается согласно текущему
классу объекта, к которому обращаются с помощью this. Приведение не изменяет класс
объекта; оно только проверяет, чтобы класс был
совместим с указанным типом.
Выражение доступа к массиву обращается к
переменной, которая является компонентой
массива.
ArrayAccess:
ExpressionName [ Expression ]
PrimaryNoNewArray [ Expression ]
Выражение доступа к массиву содержит два
подвыражения - выражение ссылки на массив
(перед левой скобкой) и индексное выражение (в
скобках). Заметим, что выражение ссылки на массив
может быть именем или любым первичным
выражением, которое не является выражением
создания массива (§15.9).
Тип выражения ссылки на массив должен быть
типом массив (назовем его T[] -
массив, элементы которого имеют тип T), или
произойдет ошибка времени компиляции.
Тогда T - тип выражения доступа к массиву.
Выражение индекса подвергается одноместному
числовому расширению (§5.6.1);
расширенный тип должен быть типом int.
Результатом ссылки на массив является
переменная типа T, а именно переменная
массива, выбранная по значению индексного
выражения. Полученная в результате переменная,
которая является компонентой массива, никогда не
рассматривается как final, даже если
ссылка на массив была получена от переменной с
модификатором final.
Выражение доступа к массиву вычисляется с
использованием следующей процедуры:
- Сначала вычисляется выражение ссылки на массив.
Если это вычисление заканчивается
преждевременно, тогда доступ к массиву
завершается преждевременно по той же причине, и
индексное выражение не вычисляется.
- Иначе, индексное выражение вычисляется. Если
это вычисление заканчивается преждевременно,
тогда доступ к массиву завершается
преждевременно по той же причине.
- Иначе, если значение выражения ссылки на массив
есть null, тогда генерируется NullPointerException.
- Иначе, значение выражения ссылки на массив
действительно указывает на массив. Если значение
индексного выражения меньше нуля или больше или
равно длине массива, тогда генерируется IndexOutOfBoundsException.
- Иначе, результат ссылки на массив - переменная
типа T , полученная по значению индексного
выражения. (Заметим, что полученная в результате
переменная, которая является компонентой
массива, никогда не рассматривается как final, даже если выражение ссылки на
массив есть переменная с модификатором final.)
В доступе к массиву, выражение слева от скобок
будет полностью вычислено прежде, чем вычислена
любая часть выражения в скобках. Например, в
выражении ( конечно чудовищному) a[(a=b)[3]],
выражение a полностью вычисляется
перед выражением (а=b)[3]; это
означает, что первоначальное значение a уже получено и сохранено в то время,
когда выражение (a=b) [3]
вычисляется. Этот массив, на который ссылаются с
помощью первоначального значения a,
потом проиндексирован значением, которое
является 3 элементом другого массива (возможно,
того же самого массива), на которого ссылались с
помощью b, а теперь ссылаются
также с помощью a.
Таким образом, в результате выполнения примера:
class Test {
public static void main(String[] args) {
int[] a = { 11, 12, 13, 14 };
int[] b = { 0, 1, 2, 3 };
System.out.println(a[(a=b)[3]]);
}
}
напечатается:
14
потому что значение ужасного выражения
эквивалентно a[b[3]] или a[3]
или 14.
Если вычисление выражения слева от скобок
завершается преждевременно, никакая часть
выражения в скобках не будет вычислена. Таким
образом, в результате выполнения примера:
class Test {
public static void main(String[] args) {
int index = 1;
try {
skedaddle()[index=2]++;
} catch (Exception e) {
System.out.println(e + ", index=" + index);
}
}
static int[] skedaddle() throws Exception {
throw new Exception("Ciao");
}
}
будет напечатано:
java.lang.Exception: Ciao, index=1
потому что вложенное присваивание значения 2 переменной index
никогда не произойдет.
Если выражение ссылки на массив порождает
ссылку типа null вместо ссылки на
массив, то во время выполнения генерируется NullPointerException, но только после того, как
будут вычислены все части выражения ссылки на
массив и только если это вычисление было
завершено успешно. Таким образом, в результате
выполнения приведенной тестирующей программы:
class Test {
public static void main(String[] args) {
int index = 1;
try {
nada()[index=2]++;
} catch (Exception e) {
System.out.println(e + ", index=" + index);
}
}
static int[] nada() { return null; }
}
будет напечатано:
java.lang.NullPointerException, index=2
потому что вложенное присваивание переменной index значения 2
происходит перед проверкой на указатель null. Рассмотрим программу, имеющую
отношение к этой же ситуации:
class Test {
public static void main(String[] args) {
int[] a = null;
try {
int i = a[vamoose()];
System.out.println(i);
} catch (Exception e) {
System.out.println(e);
}
}
static int vamoose() throws Exception {
throw new Exception("Twenty-three skidoo!");
}
}
в результате выполнения которой всегда будет
напечатано:
java.lang.Exception: Twenty-three skidoo!
NullPointerException никогда не происходит, потому
что индексное выражение должно быть полностью
вычислено, прежде чем произойдет любая часть
индексации, в это включается проверка на то, не
равно ли значение левого операнда null.
Постфиксные выражения включают использования
постфиксных операций ++ и -- . Так же, как оговорено
в §15.7, имена
не рассматриваются как первичные выражения, но
описываются в грамматике отдельно, чтобы
избежать некоторых двусмысленностей. Они
становятся взаимозаменяемыми только в смысле
приоритетов выполнения операций в постфиксных
выражениях.
PostfixExpression:
Primary
ExpressionName
PostIncrementExpression
PostDecrementExpression
Имя, встречающееся в выражении, может
синтаксически быть ExpressionName (§6.5). Значение такого имени ExpressionName
зависит от его вида:
- Если это простое имя, то есть Identifier, тогда
существуют два случая:
- Если Identifier встречается в пределах области
действия параметра или локальной переменной,
названной этим же Identifier, тогда типом ExpressionName
является объявленный тип параметра или
локальной переменной; кроме того, значение ExpressionName
- переменная, а именно, параметр или локальная
переменная.
- Иначе, под ExpressionName подразумевается
выражение доступа к полю (§15.10): this.
Identifier,
- как бы содержащее ключевое слово this
(§15.7.2).
- Иначе, если это квалифицированное имя вида PackageName
. Identifier, тогда происходит ошибка времени
компиляции.
- Иначе, если это квалифицированное имя вида TypeName
. Идентификатор, тогда оно обращается к полю с
модификатором static класса или
интерфейса, именуемого TypeName. Если TypeName не
именует класс или интерфейс, происходит ошибка
времени компиляции. Если класс или интерфейс,
именуемый TypeName, не содержит доступного
статического поля, именуемого Identifier,
происходит ошибка времени компиляции. Тип ExpressionName
- объявленный тип поля с модификатором static. Значением ExpressionName является
переменная, а именно, само поле с модификатором static.
- Иначе, это квалифицированное имя вида Ename .
Identifier, где Ename само ExpressionName, а ExpressionName
рассматривается так, как если это было бы
выражение доступа к полю (§15.10): (Ename). Identifier, содержащее
заключенное в скобки выражение (§15.7.3).
PostDecrementExpression:
PostfixExpression --
Постфиксное выражение, за которым следует
операция ++, есть постфиксное выражение
инкремента. Результатом постфиксного выражения
должна быть переменная числового типа, или
произойдет ошибка времени компиляции. Типом
постфиксного выражения инкремента является тип
переменной. Результат постфиксного выражения
инкремента - не переменная, а значение.
Если во время выполнения вычисления выражения
операнда заканчивает преждевременно, то
вычисление постфиксного выражения инкремента
преждевременно завершается по той же причине и
никакое увеличение не осуществляется. Иначе, к
значению переменной прибавляется 1 и значению
переменной присваивается соответствующая сумма.
Перед сложением осуществляется двуместное
числовое расширение для значения переменной и 1 (§5.6.2). Если
необходимо, перед сохранением сумма
преобразуется с помощью сужающего примитивного
преобразования (§5.1.3)
к типу переменной. Значением постфиксного
выражения инкремента является значение
переменной до сохранения нового значения.
Переменная, которая объявлена с модификатором final, не может быть увеличена, потому
что когда используется доступ к переменной с
модификатором final, результатом
является значение, а не переменная. Таким
образом, такая переменная не может
использоваться как операнд постфиксного
оператора инкремента.
PostDecrementExpression:
PostfixExpression --
Постфиксное выражение, за которым следует
оператор --, есть постфиксное выражение
декремента. Результатом постфиксного выражения
должна быть переменная числового типа, или
произойдет ошибка времени компиляции. Типом
постфиксного выражения декремента является тип
переменной. Результат постфиксного выражения
декремента есть значение, а не переменная.
Если во время выполнения вычисление выражения
операнда заканчивает преждевременно, тогда
вычисление постфиксного выражение декремента
завершается преждевременно по тот же причине и
никакого уменьшения не происходит. Иначе, из
значения переменной вычитается 1 и новое
значение записывается в эту переменную. Перед
вычитанием осуществляется двуместное числовое
расширение (§5.6.2)
для 1 и значения переменной. Если необходимо,
перед сохранением разность преобразуется с
помощью сужающего примитивного преобразования (§5.1.3) к типу
переменной. Значением постфиксного выражения
декремента является значение переменной до
сохранения нового значения.
Переменная, которая объявлена с модификатором final, не может быть уменьшена, потому
что когда доступ к переменной с модификатором final используется как выражение,
результатом является значение, а не переменная.
Таким образом, такая переменная не может
использоваться как операнд постфиксного
оператора декремента.
15.14 Унарные операции
Унарными операциями являются +, -, ++, --, ~,! и
операции приведения. Выражения с одноместными
операциями группируются справа налево, таким
образом -~x означает то же самое,
что -(~x).
UnaryExpression:
PreIncrementExpression
PreDecrementExpression
+ UnaryExpression
- UnaryExpression
UnaryExpressionNotPlusMinus
PreIncrementExpression:
++ UnaryExpression
PreDecrementExpression:
-- UnaryExpression
UnaryExpressionNotPlusMinus:
PostfixExpression
~ UnaryExpression
! UnaryExpression
CastExpression
Следующие продукции из §15.15 повторены здесь для
удобства:
CastExpression:
( PrimitiveType ) UnaryExpression
( ReferenceType ) UnaryExpressionNotPlusMinus
Эта часть грамматики Ява содержит некоторые
трюки, чтобы избежать двух потенциальных
синтаксических двусмысленностей.
Первая потенциальная двусмысленность возникла
бы в выражениях типа (p) +q,
которое программистами на Cи или Cи ++ может
рассматриваться так , как будто это обращение к
типу p унарного оператора +,
действующего на q, или как
двуместное сложение двух величин p
и q. В Cи и Cи ++ синтаксический
анализатор разрешает эту проблему за счет
выполнения ограниченного объема семантического
анализа во время синтаксического разбора, таким
образом анализатор знает является p
именем типа или именем переменной.
Ява-система использует другой подход.
Результат оператора + должен быть числовым, и все
имена типов, используемые в обращениях к
числовым значениям, должны быть известными
зарезервированными словами. Таким образом, если p - зарезервированное слово,
обозначающее элементарный тип, тогда (p)+q
может иметь смысл только как приведение к унарному выражению. Однако,
если p не является
зарезервированным словом, обозначающим
примитивный тип, тогда (p)+q может
иметь смысл только как бинарная арифметическая
опреция. Подобные замечания относятся и к
оператору -. Грамматика, показанная выше,
разделяет CastExpression на два случая, чтобы
показать различие. Нетерминальное выражение UnaryExpression
включает все унарные операции, а нетерминал UnaryExpressionNotPlusMinus
исключает использования всех унарных
операторов, которые также могли бы быть
бинарными операциями, в Яве такими операторами
являются + и -.
Вторая потенциальная двусмысленность состоит
в том, что для программистов на Cи или Cи++
выражение (p)++ могло бы быть как
постфиксным увеличением выражения в скобках, так
и началом приведения, например, в выражении (p)++q. Как и в вышеприведенном случае,
синтаксический анализатор для C и C++ знает,
является p именем типа или именем
переменной. Но синтаксический анализатор,
использующий предвидение только на одну лексему
и не осуществляющий семантического анализа при
синтаксическом разборе, не был бы способен
сообщить, когда ++ является текущей лексемой,
должно ли (p) рассматриваться как Primary
выражение или как левая составляющая, являющаяся
частью CastExpression.
В Яве результат оператора ++ должен быть
числовым, и все имена типов, использующиеся при
обращениях к числовым значениям, должны быть
известными зарезервированными словами. Таким
образом, если p является
зарезервированным словом, обозначающим
примитивный тип, то (p)++ может
иметь смысл только как приведение префиксного
выражения приращения, и вместо него лучше было бы
использовать один операнд q, за
которым бы следовала операция ++. Однако, если p не является зарезервированным
словом, обозначающим примитивный тип, то (p)++ может иметь смысл только как
постфиксное увеличение p.
Подобные замечания относятся и к операции --.
Поэтому нетерминал UnaryExpressionNotPlusMinus также
исключает использование префиксных операций ++ и
--.
Одноместное выражение, которому предшествует
++, является префиксным выражением инкремента.
Результатом одноместного выражения должна быть
переменная числового типа, или произойдет ошибка
времени компиляции. Типом префиксного выражения
инкремента является тип переменной. Результат
префиксного выражения инкремента есть не
переменная, а значение.
Если во время выполнения вычисление выражения
операнда заканчивается преждевременно, то
вычисление префиксного выражения инкремента
заканчивается преждевременно по той же самой
причине, и никакого увеличения не происходит.
Иначе, к значению переменной прибавляется 1 и
сумма сохраняется в той же переменной. Перед
сложением, для 1 и значения переменной
выполняется двуместное числовое расширение (§5.6.2). Если
необходимо, сумма до сохранения преобразуется по
правилам сужающего примитивного преобразования (§5.1.3) к типу
переменной. Значением префиксного выражения
инкремента является значение переменной после
сохранения нового значения.
Переменная, которая объявлена с модификатором final, не может быть увеличена, потому
что когда используется доступ к переменной с
модификатором final, результатом
является значение, а не переменная. Таким
образом, такая переменная не может
использоваться как операнд префиксной операции
инкремента.
Одноместное выражение, которому предшествует
оператор --, является префиксным выражением
декремента. Результатом одноместного выражения
должна быть переменная числового типа, или
произойдет ошибка времени компиляции. Типом
префиксного выражения декремента является тип
переменной. Результат префиксного выражения
декремента есть не переменная, а значение.
Если вычисление выражения заканчивается
преждевременно, то вычисление префиксного
выражения декремента заканчивается
преждевременно по той же самой причине, и
никакого уменьшения не происходит. Иначе, из
значения переменной вычитается 1 и разность
записывается в эту переменную. Перед вычитанием,
для 1 и значения переменной осуществляется
двуместное числовое расширение (§5.6.2). Если необходимо,
разность до сохранения преобразуется по
правилам сужающего примитивного преобразования (§5.1.3) к типу
переменной. Значением префиксного выражения
декремента является значение переменной после
сохранения нового значения.
Переменная, которая объявлена с модификатором final, не может быть уменьшена, потому
что когда используется доступ к переменной с
модификатором final, результатом
является значение, а не переменная. Таким
образом, подобная переменная не может
использоваться как операнд префиксной операции
декремента.
Типом выражения операнда унарной операции +
должен быть примитивный числовой тип, или
произойдет ошибка времени компиляции. Над
операндом осуществляется одноместное числовое
расширение (§5.6.1).
Типом одноместного выражения с плюсом является
расширенный тип операнда. Результатом
одноместного выражения с плюсом является не
переменная, а значение, даже если результат
выражения операнда - переменная.
Во время выполнения значением одноместного
выражения с плюсом является расширенное
значение операнда.
Типом выражения операнда унарной операции -
должен быть примитивный числовой тип, или
произойдет ошибка времени компиляции. Над
операндом осуществляется одноместное числовое
расширение (§5.6.1).
Типом одноместного выражения с минусом является
расширенный тип операнда.
Во время выполнения значением одноместного
выражения с минусом является арифметическое
отрицание расширенного значения операнда.
Для целых чисел отрицание - то же самое, что
вычитание из нуля. В Яве для целых используется
дополнительный обратный код, и диапазон значений в дополнительном обратном
коде не симметричен, таким образом, отрицание
максимального отрицательного числа ( имеется в
виду максимальное по абсолютной величине
отрицательное значение) типа int
или long в качестве результата
имеет то же самое максимальное отрицательное
число. В этом случае происходит переполнение, но
никакое исключение не генерируется. Для всех
целых x, -x равно (~x)+1.
Для значений с плавающей точкой отрицание - не
то же самое, что вычитание из нуля, потому что
если x есть +0.0, то 0.0-x равняется +0.0, а -x равняется -0.0.
Унарный минус просто инвертирует знак числа с
плавающей точкой. Особые случаи, представляющие
интерес:
- Если операндом является NaN, то результат - тоже NaN
( напоминаем, что NaN не имеет знака).
- Если операнд - бесконечность, то результат -
бесконечность противоположного знака.
- Если операнд - ноль, то результат - ноль
противоположного знака.
15.14.5 Операция поразрядного дополнения ~
Типом выражения операнда унарной операции ~
должен быть примитивный целый тип, или
произойдет ошибка времени компиляции. Над
операндом осуществляется одноместное числовое
расширение (§5.6.1).
Типом унарного поразрядного выражения
дополнения является расширенный тип операнда.
Во время выполнения значением одноместного
поразрядного выражения дополнения является
поразрядное дополнение расширенного значения
операнда; заметим, что во всех случаях ~x
равняется (-x)-1.
Выражение операнда унарной операции ! должно
иметь тип boolean, или произойдет
ошибка времени компиляции. Одноместное
логическое выражение дополнения имеет тип boolean.
Во время выполнения значением одноместного
логического выражения дополнения будет true, если значение операнда - false, и false, если
значение операнда - true.
Выражение приведения преобразует во время
выполнения значение одного числового типа к
подобному значению другого числового типа; или
проверяет, во времени
компиляции, что тип выражения boolean;
или проверяет, во время
выполнения, какое
значение ссылки чей класс совместим с данным
типом ссылки.
CastExpression:
( PrimitiveType Dimsopt ) UnaryExpression
( ReferenceType ) UnaryExpressionNotPlusMinus
См. § 15.14 для обсуждения различия
между UnaryExpression и UnaryExpressionNotPlusMinus.
Тип выражения приведения - тип, имени внутри
круглых скобок. (Круглые скобки и тип, который они
содержат, иногда называются операцией
приведения.) результат выражения приведения -
не переменная, а значение, даже если операнд -
переменная.
Во время выполнения, значение операнда
преобразуется преобразованием приведения (§
5.4) к типу, определенному оператором
приведения.
В Яве разрешены не все приведения. Некоторые
приведения кончаются по ошибке во времени
компиляции. Например, примитивное значение не
может приводиться к ссылочному типу.
Правильность некоторых приведений во время
выполнения может быть доказана во время
компиляции. Например, всегда можно преобразовать
значение типа класса к типу суперкласса; такое
приведение не должно требовать никакого
специального действия во время выполнения.
Наконец, правильность некоторых приведений
нельзя доказать во время компиляции. Такие
приведения требуют тестирования во время
выполнения. Генерируется ClassCastException
, если приведение найденное во время выполнения
недопустимо.
Операции *, /, и % называются мультипликативными
операциями. Они имеют одинаковое старшинство и
синтаксически лево-ассоциативны (группировка
слева направо).
MultiplicativeExpression:
UnaryExpression
MultiplicativeExpression * UnaryExpression
MultiplicativeExpression / UnaryExpression
MultiplicativeExpression % UnaryExpression
Тип каждого из операндов операции умножения
должен быть примитивным числовым типом, или во
время компиляции происходит ошибка.
К операндам применяется бинарное числовое
расширение (§ 5.6.2). Тип
мультипликативного выражения - расширенный тип
его операндов. Если этот расширенный тип - int или long, тогда
применяется целочисленная арифметика; если этот
расширенный тип float или double, тогда применяется вещественная
арифметика.
Бинарная операция * выполняет умножение,
выдавая произведение операндов. Умножение -
коммутативная операция, если выражения
операндов не имеют побочных эффектов. В то время
как целочисленное умножение ассоциативно, когда
все операнды одного типа, вещественное умножение
не ассоциативно.
Если в результате целочисленного умножения
возникает переполнение, тогда результатом
является младшие биты математического
произведения представленного в некотором
достаточно большом формате в обратном
дополнительном коде. В результате, если
происходит переполнение, то знак результата
может быть отличным от знака математического
произведения значений операндов.
Результат вещественного умножения согласуется
с правилами IEEE 754-арифметики:
- Если один операнд - NaN, результат -
NaN.
- Если результат - не NaN, знак
результата положительный, если оба операнда
одинаковых знаков, и отрицательный, если
операнды имеют различные знаки.
- Умножение бесконечности на ноль дает в
результате NaN.
- Умножение бесконечности на конечное значение
дает в результате бесконечность со знаком. Знак
определен правилом, установленным выше.
- В остальных случаях, где не встречается ни
бесконечность, ни NaN, произведение
вычисляется. Если значение
произведения слишком большое для представления,
мы говорим, что происходит переполнение. Результатом тогда является
бесконечность соответствующего знака. Если
значение слишком маленькое для представления, мы
говорим, что происходит потеря значимости;
результатом тогда является ноль
соответствующего знака. Иначе, произведение
округляется до самого близкого представимого
значения, используя Режим IEEE 754
округления к ближайшему. Язык Ява требует
поддержки постепенной потери значимости
определенной IEEE 754 (§ 4.2.4).
Несмотря на тот факт, что может происходить
переполнение, потеря значимости, или потеря
информации, вычисление операции умножения *
никогда не генерирует исключение
времени выполнения.
Результатом бинарной операции / является
частное операндов. Левый операнд - делимое,
правый операнд - делитель.
Целочисленное деление проводится в режиме округления по
направлению к нулю. То есть частное, полученное
для операндов n и d,
которые являются целыми числами после двоичного
числового расширения (§ 5.6.2) - целочисленное
значение q, чья величина в
большей степени удовлетворяет условию ; кроме того, q
- положительно, если и n, и d имеют
одинаковые знаки, но q -
отрицательно, если и n, и d имеют
противоположные знаки. Есть специальный случай,
который не удовлетворяет этому правилу: если
делимое - отрицательное целое число наибольшей
возможной величины данного типа, а делитель -1,
тогда происходит целочисленное переполнение, и
результат равен делимому. Несмотря на
переполнение, в этом случае не генерируется
никакое исключение. С другой стороны, если
значение делителя в целочисленном делении - 0,
тогда генерируется ArithmeticException.
Результат вещественного деления определен
спецификацией IEEE-арифметики:
- Если любой операнд - NaN, результат
- NaN.
- Если результат - не NaN, знак
результата положителен, если оба операнда
одинаковые знаки, отрицательный, если операнды
имеют различные знаки.
- Результат деления бесконечности на
бесконечность - NaN.
- Результат деления бесконечности на конечное
значение - бесконечность со знаком. Знак
определен правилом, установленным выше.
- Результат деления конечного значения на
бесконечность - ноль со знаком. Знак определен
правилом, установленным выше.
- Результат деления ноля на ноль - NaN;
результат деления ноля на любое другое конечное
значение - ноль со знаком. Знак определен
правилом, установленным выше.
- Результат деления конечного значения отличного
от нуля на ноль -бесконечность со знаком. Знак
определен правилом, установленным выше.
- В остальных случаях, где не встречается ни
бесконечность, ни ноль, ни NaN,
частное вычисляется. Если величина частного
слишком большая, для представления, мы говорим,
что происходит переполнение; тогда результатом
является бесконечность соответствующего знака.
Если величина слишком маленькая, для
представления, мы говорим, что происходит
потеря значимости и результатом является ноль
соответствующего знака. Иначе, частное
округляется до самого близкого представимого
значения, используя Режим IEEE 754 округления к
ближайшему. Язык Ява требует поддержки
постепенной потери значимости определенной IEEE 754 (§ 4.2.4).
Несмотря на тот факт, что могут происходить
переполнение, потеря значимости, деление на ноль,
или потеря информации, вычисление операции
деления с плавающей запятой / никогда не
генерирует исключение во время выполнения.
Бинарная операция %, дает остаток от операндов
из подразумеваемого деления; левый операнд -
делимое, правый операнд - делитель.
В Cи и Cи ++,
операция остатка допускает только целочисленные
операнды, но в Яве также допустимы вещественные
операнды.
Операция остатка для операндов, являющиеся
целыми числами после бинарного числового
расширения (§ 5.6.2), дает в
результате такую величину, что (a/b) * b +
(a%b) равно a. Это тождество
имеет силу даже в специальном случае, когда
делимое - отрицательное целое число наибольшей
возможной величины данного типа, и делитель -1
(остаток ноль). Это следует из того правила, что
результат операции остатка может быть
отрицателен только, если делимое отрицательно, и
может быть положительным только, если делимое
положительно; кроме того, величина результата -
всегда меньше чем величина делителя. Если
значение делителя для целочисленной операции
остатка - 0, тогда генерируется ArithmeticException.
Примеры:
5%3 дает 2 (обратите внимание, что 5/3 дает 1)
5%(-3) дает 2 (обратите внимание, что 5/ (-3) дает -1)
(-5)%3 дает -2 (обратите внимание, что (-5)/3 дает -1)
(-5)%(-3) дает -2 (обратите внимание, что (-5)/(-3) дает 1)
Результат операции вещественного остатка,
вычисленной операцией % не совпадает с
результатом операцией
остатка, определенной IEEE 754.
Операция остатка IEEE 754 вычисляет остаток от деления не округляя, а
отсекая его, и такое поведение, не аналогично
обычной целочисленной операции остатка. Взамен,
язык Ява определяет % на вещественных операциях,
способом аналогичным операции остатка в Яве для
целого числа; это можно сравнить с библиотечной
функцией fmod в C.
Операция остатка IEEE 754 может быть
вычислена библиотечной подпрограммой Явы Math.IEEEremainder (§
20.11.14).
Результат вещественной операции остатка в Яве
определен правилами арифметики IEEE:
- Если любой операнд - NaN, то
результат - NaN.
- Если результат - не NaN, то знак
результата равняется знаку делимого.
- Если делимое - бесконечность, или делитель -
ноль, или и то и другое, то результат - NaN.
- Если делимое конечно, а делитель -
бесконечность, то результат равняется делимому.
- Если делимое - ноль, а делитель конечен, то
результат равняется делимому.
- В остальных случаях, где не встречается ни
бесконечность, ни ноль, ни NaN,
вещественный остаток r от
деления делимого n на делитель
d определен математическим
отношением
,
где q - целое число, являющиеся
отрицательным только, если отрицательно и положительно только, если положительно,
и его величина - наиболее большая, не
превышающая величину истинного математического
частного n и d.
Вычисление вещественной операции остатка %
никогда не вызывает исключение времени
выполнения, даже если правый операнд - ноль. Не
могут происходить переполнение, потеря
значимости, или потеря точности.
Примеры:
5.0%3.0 дает 2.0
5.0% (-3.0) дает 2.0
(-5.0) %3.0 дает -2.0
% -3.0 (-5.0) дает -2.0
Операции + и - называются аддитивными
операциями. Они имеют одинаковое старшинство и
синтаксически лево-ассоциативны (группировка
слева направо).
AdditiveExpression:
MultiplicativeExpression
AdditiveExpression + MultiplicativeExpression
AdditiveExpression - MultiplicativeExpression
Если тип любого операнда операции + String, тогда операция - строковая
конкатенация.
Иначе, тип каждого из операндов операции +
должен быть примитивным числовым типом, или
происходит ошибка времени компиляции.
В любом случае, тип каждого из операндов
бинарной операции должен быть примитивным
числовым типом, иначе происходит ошибка времени
компиляции.
Любой тип можно преобразовать в тип String
с помощью строкового преобразования.
Значение x примитивного типа
T сначала преобразовывается в
ссылочное значение будто, являясь параметром
соответствующего выражения создания экземпляра
класса.
- Если T - boolean, тогда
используется new Boolean(x) (§ 20.4).
- Если T - char, тогда
используется new Character(x ) (§ 20.5).
- Если T - byte, short, или int, тогда
используется new Integer(x) (§ 20.7).
- Если T - long, тогда
используется new Long(x) (§ 20.8).
- Если T - float, тогда
используется new Float(x) (§ 20.9).
- Если T - double, тогда
используется new Double(x) (§ 20.10).
Это ссылочное значение затем
преобразовывается в тип String
строковым преобразованием.
Теперь нужно рассматривать только ссылочные
значения. Если ссылка - null, она
преобразуется в строку "null"
(четыре символа ASCII n,
u, l, l).
Иначе, преобразование выполняется как при вызове
метода toString объектом, на который
указывает ссылка, без параметров; но если
результат вызова метода toString - null, тогда взамен используется строка
"null". Метод toString(§
20.1.2) определен изначальным классом Object (§ 20.1); его
переопределяют многие классы, в частности Booolean, Character, Integer, Long, Float,
Double, и String.
Реализация может выполнять преобразование и
конкатенацию в один шаг, избегая создания, а
затем и отбрасывания промежуточного объекта String. Чтобы увеличить эффективность
повторения строковой конкатенации, транслятор
Явы может использовать класс StringBuffer
(§
20.13) или подобную методику, для
уменьшения числа промежуточных объектов String, созданных при вычислении
выражения.
Для примитивных объектов, реализация может
также оптимизировать длительное создание
объекта-оболочки, преобразуя непосредственно
примитивный тип в строку.
Например выражение:
"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:"
на две части, чтобы избежать неудобной длинной
строки в исходном коде.
Результатом бинарной операции + является сумма
двух операндов. Результатом бинарной операции -
является разность двух числовых
операндов.
К операндам применяется бинарная числовая
поддержка (§ 5.6.2). Тип аддитивного
выражения числовых операндов - расширенный тип
операндов. Если этот расширенный тип - int
или long, тогда применяется
целочисленная арифметика; если этот расширенный
тип - float или double,
тогда применяется вещественная арифметика.
Сложение - коммутативная операция, если
выражения операнда не имеют побочных эффектов.
Целочисленное сложение ассоциативно, когда все
операнды - одного типа, но вещественное сложение
не ассоциативно.
Если происходит переполнение при
целочисленном сложении, тогда результат -
младшие биты математической суммы
представленной в некотором достаточно большом
формате в обратном дополнительном коде. Если
происходит переполнение, тогда знак результата и
знак математической суммы двух операндов не
равны.
Результат вещественного сложения определяется
следующими правилами IEEE
арифметики:
- Если любой операнд - NaN, результат
- NaN.
- Сумма двух бесконечностей противоположного
знака - NaN.
- Сумма двух бесконечностей одного знака -
бесконечность того же знака.
- Сумма бесконечности и конечного значения равна
бесконечному операнду.
- Сумма двух нолей противоположных знаков -
положительный ноль.
- Сумма двух нолей одного знака - ноль того же
знака.
- Сумма ноля и конечного значения, отличного от
нуля равна операнду отличному от нуля.
- Сумма двух конечных значений, отличных от нуля
одинаковой величины и противоположных знаков -
положительный ноль.
- В оставшихся случаях, где не встречается ни
бесконечность, ни ноль, ни NaN, и
операнды имеют одинаковые знаки или имеют
различные величины, сумма вычисляется. Если величина суммы слишком
большая для представления мы говорим что
происходит переполнение; тогда результатом
является бесконечность соответствующего знака.
Если величина слишком маленькая для
представления мы говорим что происходит потеря
значимости; тогда результатом является ноль
соответствующего знака. Иначе, сумма округляется
до самого близкого представимого значения,
используя Режим IEEE 754 округления к ближайшему.
Язык Ява требует поддержки постепенной потери
значимости, определенной IEEE 754 (§
4.2.4).
Результатом бинарной операции -
разность двух операндов числового типа; левый
операнд - уменьшаемое, и правый операнд -
вычитаемое. И для целого и для вещественного
числа вычитание всегда для операции a-b дает тот же самый результат, что и a+(-b). Обратите
внимание, что для целочисленных значений,
вычитание из ноля - то же самое, что отрицание.
Однако, для вещественных операндов, вычитание из
ноля не то же, что отрицание, потому что, если x есть + 0.0, тогда 0.0-x
равняется + 0.0, но -x равняется -0.0.
Несмотря на тот факт, что могут происходить
переполнение, потеря значимости, или потеря
информации, вычисление числовой операции
сложения никогда не вызывает исключение времени
выполнения.
Операции сдвига включают сдвиг влево <<,
сдвиг вправо со знаком >>, и сдвиг вправо без
знака >>>; они синтаксически
лево-ассоциативны (группировка слева направо).
Левый операнд - значение, которое будет сдвинуто;
правый операнд определяет расстояние сдвига.
ShiftExpression:
AdditiveExpression
ShiftExpression << AdditiveExpression
ShiftExpression >> AdditiveExpression
ShiftExpression >>> AdditiveExpression
Тип каждого из операндов операции сдвига
должен быть примитивным целочисленным типом,
иначе происходит ошибка времени компиляции. На
операндах не выполняется бинарное числовое
расширение (§ 5.6.2); достаточно,
выполнения унарного числового
расширения (§ 5.6.1) на каждом
операнде отдельно. Тип выражения сдвига -
расширенный тип левого операнда.
Если расширенный тип левого операнда - int, как расстояние сдвига
используются только пять младших битов правого
операнда. Это эквивалентно, применению к правому
операнду поразрядного логического И операцией
& (§ 15.21.1) со значением
маски 0x1f . Следовательно фактически
используемое расстояние сдвига всегда
заключается в диапазоне от 0 до 31.
Если расширенный тип левого операнда - long, как расстояние сдвига
используются только шесть младших битов правого
операнда. Это эквивалентно, применению к правому
операнду поразрядного логического И операцией
& (§ 15.21.1) со значением
маски 0x3f .
Следовательно фактически используемое
расстояние сдвига всегда заключается в
диапазоне от 0 до 63.
Во время выполнения операции сдвига
исполняются на представлении целочисленного
значения левого операнда в дополнительном
обратном коде.
Значение n << s -
это n сдвинутое влево на s разрядных позиций; это эквивалентно
(даже если происходит переполнение) умножению на
два в степени s.
Значение n>> s -
это n сдвинутое вправо на s разрядных позиций с
распространением знака. Результирующее значение
- . Для
неотрицательных значений n, это
эквивалентно усеченному делению, вычисленному
целочисленной операцией деления /, на два в
степени s.
Значение n>>> s
-это n сдвинутое вправо на s разрядных позиций с расширением
ноля. Если n положителен, тогда
результат такой же как n>> s; если n отрицателен,
то результат равен такому выражению (n>>
s) + (2 << ~s) если
тип левого операнда - int, и
результату выражения (n>> s) + (2L << ~s),
если тип левого операнда - long.
Добавленное слагаемое (2 << ~s)
или (2L << ~s)
отменяет распространение знакового бита.
(Обратите внимание, что, из-за неявного
маскирования правого операнда операции сдвига, ~s как расстояние сдвига эквивалентно
31-s при сдвиге значения типа int и 63-s при сдвиге
значения типа long.)
Операции отношения синтаксически
лево-ассоциативны (группировка слева направо), но
это бесполезный факт; например, a<
b < c
рассматривается как (a< b) < c, что всегда
приводит к ошибке времени компиляции, потому что
тип a< b всегда boolean, а < - не является операцией на
значениях типа boolean.
RelationalExpression:
ShiftExpression
RelationalExpression < ShiftExpression
RelationalExpression > ShiftExpression
RelationalExpression <= ShiftExpression
RelationalExpression >= ShiftExpression
RelationalExpression instanceof ReferenceType
Тип выражения отношения всегда boolean.
Тип каждого из операндов числовой операции
сравнения должен быть примитивным числовым
типом, иначе происходит ошибка времени
компиляции. На операндах выполняется бинарное
числовое расширение (§ 5.6.2). Если расширенный
тип операндов - int или long,
тогда выполняется сравнение целого числа со
знаком ; если этот расширенный тип - float
или double, тогда выполняется
вещественное сравнение.
Результат вещественного сравнения определен
стандартом IEEE 754 :
- Если любой операнд - NaN, тогда
результат - false.
- Все значения отличные от NaN
упорядочены, от отрицательной бесконечности,
меньшей чем все конечные значения, до
положительной бесконечности большей чем все
конечные значения.
- Положительный ноль и отрицательный ноль
рассматривается как равные. Следовательно,
например -0.0 < 0.0 - это false, но -0.0 <
=0.0 - это true. (Заметим, однако, что
методы Math.min (§
20.11.27, §
20.11.28) и Math.max
(§
20.11.31, § 20.11.32)
считают отрицательный ноль строго меньшим
положительного ноля.)
Подчиненные рассмотренным правилам для
вещественных чисел, приводятся следующие
правила для целочисленных операндов или
вещественных операндов, отличных от NaN:
- Результат операции < - true, если
значение левого операнда - меньше значения
правого операнда, иначе - false.
- Результат операции <= - true, если
значение левого операнда - меньше или равно
значения правого операнда, иначе - false.
- Результат операции > - true, если
значение левого операнда - больше значения
правого операнда, иначе - false.
- Результат операции >= - true, если
значение левого операнда - больше или равно
значения правого операнда, иначе - false.
instanceof
Тип операнда RelationalExpression операции instanceof должен быть ссылочным типом
или типом null; иначе, происходит
ошибка времени компиляции.ReferenceType,
упомянутый после операции instanceof
должен обозначать тип ссылки; иначе, происходит
ошибка времени компиляции.
Во время выполнения, результат операции instanceof - true, если
значение RelationalExpression - не null и
ссылка может быть приведена (§ 15.15) к ReferenceType
не генерируя ClassCastException. Иначе
результат - false.
Если приведение RelationalExpression к ReferenceType было
бы запрещено как ошибка времени компиляции,
тогда instanceof аналогично
выражениям отношения приводит к ошибке времеи
компиляции. В такой ситуации, результат
выражения instanceof никогда не может
быть true.
Рассмотрим программу-пример:
class Point { int x, y; }
class Element { int atomicNumber; }
class Test {
public static void main(String[] args) {
Point p = new Point();
Element e = new Element();
if (e instanceof Point) { // compile-time error
System.out.println("I get your point!");
p = (Point)e; // compile-time error
}
}
}
Этот пример заканчивается по двум ошибкам
времени компиляции. Приведение (Point)
e неправильно, потому что никакой
экземпляр Element или любого из его
возможных подклассов (ни один здесь не показан)
не мог быть возможным экземпляром любого
подкласса Point. Выражение instanceof неправильно по точно той же
причине. С другой стороны, если класс Point
является подклассом Element :
class Point extends Element { int x, y; }
тогда приведение было бы возможно, хотя
требовалась бы проверка во время выполнения, и
выражение instanceof имело бы смысл.
Приведение (Point) e
никогда не вызвало бы исключение, потому что это
не будет выполнено, если значение e
не может правильно приводиться к типу Point.
Операции равенства синтаксически
лево-ассоциативны ( группировка слева направо),
но этот факт по существу не является полезным;
например,a == b == c рассматривается
как (a== b) == c. Тип результата a== b всегда - boolean, и c должно следовательно иметь тип boolean иначе происходит ошибка времени
компиляции. Таким образом, выражение a==
b == c не проверяет равны ли a,
b, и c.
EqualityExpression:
RelationalExpression
EqualityExpression == RelationalExpression
EqualityExpression != RelationalExpression
Операции == (равно) и !=
(не равно) аналогичны операциям отношения,
исключая их более низкого старшинства. Таким
образом, a < b == c < d - true
всякий раз, когда a< b и c < d имеют одинаковые значения
истинности.
Операции равенства могут использоваться для
сравнения двух операндов числового типа, или
двух операндов типа boolean, или двух
операндов, каждый из которых или ссылочного типа
или типа null. Во всех других
случаях во время компиляции происходит ошибка.
Тип выражения равенства всегда - boolean.
Во всех случаях, выражение a!=b
приводит к тому же самому результату, что и
выражение !(a== b). Операции
равенства - коммутативные, если
выражения операнда не имеют никаких побочных
эффектов.
Если операнды операции равенства - оба
примитивного числового типа, на операндах
выполняется бинарное числовое расширение (§
5.6.2). Если расширенный тип операндов - int или long, тогда
выполняется целочисленная проверка равенства;
если расширенный тип - float или double, тогда выполняется вещественная
проверка равенства.
Вещественная проверка равенства выполняется в
соответствии со стандартом IEEE 754:
- Если любой операнд - NaN, тогда
результатом == является false, но
результат != - true. Действительно,
тест x!=x - true, тогда и только тогда, когда
значение x - NaN.
(Могут также использоваться методы Float.isNaN (§ 20.9.19) и Double.isNaN (§ 20.10.17) , чтобы
проверить является ли значение NaN.)
- Положительный ноль и отрицательный ноль
считаются равными. Следовательно, -0.0
==0.0 - true.
- Иначе, два отличных вещественных значения
считаются операциями равенства неравными. В
частности, есть одно значение, представляющее
положительную бесконечность и одно значение,
представляющее отрицательную бесконечность;
каждый считается равным только с собой, и
неравным всем другим значениям.
Подчиненные этим правилам для вещественных
чисел, приводятся следующие правила для
целочисленных операндов или вещественных
операндов отличных от NaN:
- Результат операции = = - true, если
значение левого операнда равно значению правого
операнда; иначе, результат - false.
- Результат операции != - true, если
значение левого операнда не равно значению
правого операнда; иначе, результат - false.
Если операнды операции равенства - оба имеют
тип boolean, тогда операция
называется булевым равенством. Булевы операции
равенства ассоциативны.
Результат операции == - true , если операнды - оба true
или оба false; иначе, результат - false.
Результат операции != - false, если
операнды - оба true или оба false; иначе, результат - true.
Таким образом операция != ведет себя также как
операция ^ (§ 15.21.2), когда
применяется к булевым операндам.
Если операнды операции равенства - оба или
ссылочных типов, или типа null,
тогда операция называется объектным равенством.
Ошибка времени компиляции происходит, если
невозможно преобразовать тип одного операнда к
типу другого преобразованием приведения (§
5.4). Значения времени выполнения двух
операндов все равно были бы неравны.
Во время выполнения, результат операции == - true, если значения
операндов либо оба null, либо оба
ссылаются на один и тот же объект или массив;
иначе, результат - false.
Результат операции != - false, если
значения операндов либо оба null,
либо оба ссылаются на один и тот же объект или
массив; иначе, результат - true.
Если использовать операцию = = для сравнения
ссылок на тип String, то такая
проверка равенства определяет, ссылаются ли два
операнда на один и тот же объект String.
Результат - false, если операнды -
различные объекты String, даже если
они содержат одну и ту же последовательность
символов. Содержание двух строк s
и t может быть проверено на
равенство вызовом метода s.equals (t) (§ 20.12.9).
См. также § 3.10.5 и § 20.12.47.
Поразрядные и логические операции
включают операцию И - &, операцию исключающее
ИЛИ - ^, и операцию включающее ИЛИ - |. Эти операции
имеют различное старшинство, & - самое высокое
старшинство, а | - самое низкое старшинство.
Каждая из этих операций синтаксически лево -
ассоциативен (у всех группировка слева направо).
Каждая операция - коммутативна, если выражения
операнда не имеют никаких побочных эффектов.
Каждая операция ассоциативна.
AndExpression:
EqualityExpression
AndExpression & EqualityExpression
ExclusiveOrExpression:
AndExpression
ExclusiveOrExpression ^ AndExpression
InclusiveOrExpression:
ExclusiveOrExpression
InclusiveOrExpression | ExclusiveOrExpression
Поразрядные и логические операции могут
использоваться для сравнения двух операндов
числового типа или двух операндов булева типа. Во
всех других случаях во время компиляции
происходит ошибка.
Когда оба операнда операций &, ^, или | имеют
примитивные целочисленные типы, на операндах
сначала выполняется бинарное числовое
расширение (§ 5.6.2). Тип выражения
поразрядной операции - расширенный тип
операндов.
Для &, значением результата
является поразрядное И значений операндов.
Для ^, значением результата
является поразрядное исключающее ИЛИ значений
операндов.
Для |, значением результата
является поразрядное включающее ИЛИ значений
операндов.
Например, результатом выражения 0xff0
& 0xf0f0 является 0xf000.
Результатом 0xff00 ^ 0xf0f0 является 0x0ff0. Результатом 0xff00 | 0xf0f0
является 0xfff0.
15.21.2 Булевы логические операции &,
^, и |
Когда оба операнда операций &, ^, или | имеют
тип boolean, тогда тип выражения
поразрядной операции - boolean.
Для &, значение результата - true,
если оба значения обоих операндов true;
иначе, результат - false.
Для ^, значение результата - true,
если значения операндов различны; иначе,
результат - false.
Для |, значение результата - false,
если оба значения операндов false;
иначе, результат - true.
Операция && - подобна
операции & (§ 15.21.2), но вычисляет
правый операнд только, если значение левого
операнда - true. Она синтаксически
лево - ассоциативно (группировка слева направо).
Она полностью ассоциативна относительно и
побочных эффектов, и значения результата; то есть
для любых выражений a, b, и c,
вычисление выражения ((a) && (b)) && (c)
производит тот же самый результат, с теми же
самыми побочными эффектами, происходящих в том
же самом порядке, что и вычисление выражения (a) && ((b) && (c)).
ConditionalAndExpression:
InclusiveOrExpression
ConditionalAndExpression && InclusiveOrExpression
Каждый операнд операции && должен иметь тип boolean, или во время компиляции
происходит ошибка. Тип выражения условного И
всегда boolean.
Во время выполнения, первым вычисляется выражение левого операнда; если
значение - false, то значение
выражения условного И - false, и
выражение правого операнда не вычисляется. Если
значение левого операнда - true,
тогда вычисляется выражение правого операнда, и
это значение становится значением выражения
условного И. Таким образом, && получает тот же
результат, что и & на булевых операндах. Она
отличается только в том, что выражение правого
операнда вычисляется скорее условно, а не так как
обычно.
Операция || - подобна операции | (§ 15.21.2),
но вычисляет правый операнд только, если
значение левого операнда - false. Она
синтаксически лево-ассоциативна (группировка
слева направо). Она полностью ассоциативна
относительно и побочных эффектов, и значения
результата; то есть для любых выражений a, b, и c, вычисление выражения ((a) || (b)) ||(c)
производит тот же самый результат, с теми же
самыми побочными эффектами, происходящих в том
же самом порядке, что и вычисление выражения (a) || ((b) || (c)).
ConditionalOrExpression:
ConditionalAndExpression
ConditionalOrExpression || ConditionalAndExpression
Каждый операнд операции || должен иметь тип boolean, иначе происходит ошибка времени
компиляции. Тип выражения условного ИЛИ всегда boolean. Каждый операнд || должен иметь
тип boolean или во время компиляции
происходит ошибка. Тип выражения условного ИЛИ
всегда boolean.
Во время выполнения, первым вычисляется
выражение левого операнда; если значение - true, то значение выражения условного И
- true, и выражение правого операнда
не вычисляется. Если значение левого операнда - false, тогда вычисляется выражение
правого операнда, и это значение становится
значением выражения условного И. Таким образом, ||
получает тот же результат, что и | на
булевых операндах. Она отличается только в том,
что выражение правого операнда вычисляется
скорее условно, а не так как обычно.
Условная операция ?:
использует булево значение одного выражения,
чтобы решить, какое из двух других выражений
должно быть вычислено.
Условная операция синтаксически право -
ассоциативна ( группировка справа налево ),
поэтому a? b: c? d: e? f: g означает то же,
что a? b: (c? d: (e? f: g)).
ConditionalExpression:
ConditionalOrExpression
ConditionalOrExpression ? Expression : ConditionalExpression
Условная операция имеет три выражения
операндов; ? ставится между первым и вторым
выражениями, а : ставится между вторым и третьим
выражениями.
Первое выражение должно иметь тип boolean,
иначе происходит ошибка времени компиляции.
Условная операция может использоваться, для
выбора между вторым и третьим операндом
числового типа, или вторым и третьим операндом
типа boolean, или вторым и третьим
операндом, каждый из которых является или
ссылочным типом или типом null. Во
всех других случаях происходит ошибка времени
компиляции.
Обратите внимание, что не разрешено и для
второго, и для третьего выражений операндов
вызвать void-метод. Фактически, это
не разрешено для условного выражения в любом
контексте, где мог бы появляться вызов void-метода (§
14.7).
Тип условного выражения определяется
следующим образом:
- Если второй и третьий операнды имеют один и тот
же тип (который может быть типом null
), тогда, он является типом условного выражения.
- Иначе, если второй и третий операнды имеют
числовой тип, тогда возможны несколько случаев:
- Если один из операндов имеет тип byte,
а другой имеет тип short, тогда тип
условного выражения - short.
- Если один из операндов имеет тип T,
где T - байт, short
или char, а другой операнд -
константное выражение типа int, чье
значение представимо типом T,
тогда тип условного выражения - тип T.
- Иначе, к типам операнда применяется бинарное
числовое расширение (§ 5.6.2), и тип условного
выражения - расширенный тип второго и третьего
операндов.
- Если второй или третий операнд имеет тип null, а тип другого - ссылочный тип,
тогда тип условного выражения - этот ссылочный
тип.
- Если второй и третий операнды имеет различные
ссылочные типы, тогда должно быть возможным
преобразование одного типа к другому (называемый
далее типом T) преобразованием
присваивания (§
5.2); тип условного выражения - T.
Будет ошибка во время компиляции, если никакой
тип не совместим по присваиванию с другим типом.
Во время выполнения, первым вычисляется первое
выражение операнда условного выражения; его
булево значение после используется для выбора
второго или третьего выражения операнда:
- Если значение первого операнда - true,
тогда выбирается выражение второго операнда.
- Если значение первого операнда - false,
тогда выбирается выражение третьего операнда.
Далее вычисляется выбранное выражение
операнда, и возникающее в результате значение
преобразовывается в тип условного выражения как
определено правилами, установленными выше. Не
выбранное выражение операнда не вычисляется для
конкретного вычисления условного выражения.
15.25 Операция присваивания
Имеются 12 операций присваивания; все
синтаксически право-ассоциативны (группировка
справа налево). Таким образом, a=b=c означает a= (b=c),
которая присваивает значение c к b, а затем присваивает значение b к a.
AssignmentExpression:
ConditionalExpression
Assignment
Assignment:
LeftHandSide AssignmentOperator AssignmentExpression
LeftHandSide:
ExpressionName
FieldAccess
ArrayAccess
AssignmentOperator: one of
= *= /= %= += -= <<= >>= >>>= &= ^= |=
Результат первого операнда операции
присваивания должен быть переменной, иначе
происходит ошибка времени компиляции. Этот
операнд может быть именованной переменной, такой
как локальная переменная или поле текущего
объекта или класса, или он может быть вычисленной
переменной, как возможный результат доступа к
полю (§ 15.10) или доступа к
массиву (§ 15.12). Тип выражения
присваивания - тип переменной.
Во время выполнения, результатом выражения
присваивания является значение переменной после
того, как произошло присваивание. Результат
выражения присваивания сам не является
переменной.
К переменной, объявленной как final
нельзя ничего присваивать, потому что при
доступе к переменной final она
используется как выражение, а результат -
значение, не является переменной, поэтому она не
может использоваться как операнд операции
присваивания.
Ошибка времени компиляции происходит, если тип
правого операнда не может быть преобразован в
тип переменной преобразованием присваивания (§
5.2).
Во время выполнения, выражение вычисляется
одним из двух способов. Если левый операнд - не
выражение доступа к массиву, тогда требуется три
шага:
- Сначала вычисляется левый операнд, чтобы
получить переменную. Если это вычисление
завершается преждевременно, тогда по той же
самой причине выражение присваивания
преждевременно завершается; правый операнд не
вычисляется, и не происходит никакого
присваивания .
- Иначе, правый операнд вычисляется. Если
вычисление завершается преждевременно, тогда по
той же самой причине выражение присваивания
преждевременно завершается, и не происходит
никакого присваивания.
- Иначе, значение правого операнда преобразуется
в тип переменной левого операнда, и результат
преобразования сохраняется в переменной.
Если выражение левого операнда - выражение
доступа к массиву (§ 15.12), тогда требуется
несколько шагов:
- Сначала вычисляется подвыражение ссылки на
массив выражения доступа к массиву левого
операнда. Если это вычисление завершается
преждевременно, тогда по той же самой причине
выражение присваивания преждевременно
завершается; индексное подвыражение (выражение
доступа к массиву левого операнда) и правого
операнда не вычисляется, и не происходит
никакого присваивания.
- Иначе, вычисляется индексное подвыражение
выражения левого операнда доступа к массиву.
Если вычисление завершается преждевременно,
тогда по той же самой причине выражение
присваивания преждевременно завершается, и
правый операнд не вычисляется, и не происходит
никакого присваивания.
- Иначе, вычисляется правый операнд. Если
вычисление завершается преждевременно, тогда по
той же самой причине выражение присваивания
преждевременно завершается, и не происходит
никакого присваивания.
- Иначе, если значение подвыражения ссылки на
массив - null, тогда не происходит
никакого присваивания, и генерируется NullPointerException.
- Иначе, значение подвыражения ссылки на массив
действительно ссылается на массив. Если значение
индексного подвыражения меньше чем ноль, или
больше или равно длины массива, тогда не
происходит никакого присваивания, и
генерируется IndexOutOfBoundsException.
- Иначе, значение индексного подвыражения
используется для выбора компоненты массива, на
которое ссылается значение подвыражения ссылки
на массив. Эта компонента - переменная; назовем ее
тип SC. Также, пусть TC
- тип левого операнда оператора присваивания,
определенный во время компиляции.
- Если TC - примитивный тип,
тогда SC - обязательно такой же
как TC. Значение правого
операнда преобразуется в значение типа TC и сохраняется в выбранной
компоненте массива.
- Если T - ссылочный тип, тогда SC не может быть таким же как T, а скорее расширяет или
реализует TC. Пусть RC
- класс объекта, упоминаемый значением правого
операнда во время выполнения.
- Транслятор имеет возможность во время
компиляции выяснить, что компонента массива
будет иметь именно тип TC
(например, TC мог бы быть final). Но если транслятор не может во
время компиляции выяснить, что компонента
массива будет иметь именно тип TC
, тогда должна выполниться проверка во время
выполнения, чтобы гарантировать, что класс RC - совместим по присваиванию(§
5.2) с фактическим типом SC
компоненты массива. Эта проверка подобна
сужающему приведению (§ 5.4, § 15.15), за исключением
того, что, при обнаружении ошибки во время
проверки, генерируется ArrayStoreException
а не ClassCastException. Следовательно:
- Если класс RC несовместим по
присваиванию с типом SC, тогда
присваивания не происходит и вызывается ArrayStoreException.
- Иначе, значение ссылки правого операнда
сохраняется в выбранной компоненте массива.
Правила для присваивания компоненте массива
иллюстрируются следующей программой- примером:
class ArrayReferenceThrow extends RuntimeException { }
class IndexThrow extends RuntimeException { }
class RightHandSideThrow extends RuntimeException { }
class IllustrateSimpleArrayAssignment {
static Object[] objects = { new Object(), new Object() };
static Thread[] threads = { new Thread(), new Thread() };
static Object[] arrayThrow() {
throw new ArrayReferenceThrow();
}
static int indexThrow() { throw new IndexThrow(); }
static Thread rightThrow() {
throw new RightHandSideThrow();
}
static String name(Object q) {
String sq = q.getClass().getName();
int k = sq.lastIndexOf('.');
return (k < 0) ? sq : sq.substring(k+1);
}
static void testFour(Object[] x, int j, Object y) {
String sx = x == null ? "null" : name(x[0]) + "s";
String sy = name(y);
System.out.println();
try {
System.out.print(sx + "[throw]=throw => ");
x[indexThrow()] = rightThrow();
System.out.println("Okay!");
} catch (Throwable e) { System.out.println(name(e)); }
try {
System.out.print(sx + "[throw]=" + sy + " => ");
x[indexThrow()] = y;
System.out.println("Okay!");
} catch (Throwable e) { System.out.println(name(e)); }
try {
System.out.print(sx + "[" + j + "]=throw => ");
x[j] = rightThrow();
System.out.println("Okay!");
} catch (Throwable e) { System.out.println(name(e)); }
try {
System.out.print(sx + "[" + j + "]=" + sy + " => ");
x[j] = y;
System.out.println("Okay!");
} catch (Throwable e) { System.out.println(name(e)); }
}
public static void main(String[] args) {
try {
System.out.print("throw[throw]=throw => ");
arrayThrow()[indexThrow()] = rightThrow();
System.out.println("Okay!");
} catch (Throwable e) { System.out.println(name(e)); }
try {
System.out.print("throw[throw]=Thread => ");
arrayThrow()[indexThrow()] = new Thread();
System.out.println("Okay!");
} catch (Throwable e) { System.out.println(name(e)); }
try {
System.out.print("throw[1]=throw => ");
arrayThrow()[1] = rightThrow();
System.out.println("Okay!");
} catch (Throwable e) { System.out.println(name(e)); }
try {
System.out.print("throw[1]=Thread => ");
arrayThrow()[1] = new Thread();
System.out.println("Okay!");
} catch (Throwable e) { System.out.println(name(e)); }
testFour(null, 1, new StringBuffer());
testFour(null, 1, new StringBuffer());
testFour(null, 9, new Thread());
testFour(null, 9, new Thread());
testFour(objects, 1, new StringBuffer());
testFour(objects, 1, new Thread());
testFour(objects, 9, new StringBuffer());
testFour(objects, 9, new Thread());
testFour(threads, 1, new StringBuffer());
testFour(threads, 1, new Thread());
testFour(threads, 9, new StringBuffer());
testFour(threads, 9, new Thread());
}
}
Эта программа выводит:
throw[throw]=throw => ArrayReferenceThrow
throw[throw]=Thread => ArrayReferenceThrow
throw[1]=throw => ArrayReferenceThrow
throw[1]=Thread => ArrayReferenceThrow
null[throw]=throw => IndexThrow
null[throw]=StringBuffer => IndexThrow
null[1]=throw => RightHandSideThrow
null[1]=StringBuffer => NullPointerException
null[throw]=throw => IndexThrow
null[throw]=StringBuffer => IndexThrow
null[1]=throw => RightHandSideThrow
null[1]=StringBuffer => NullPointerException
null[throw]=throw => IndexThrow
null[throw]=Thread => IndexThrow
null[9]=throw => RightHandSideThrow
null[9]=Thread => NullPointerException
null[throw]=throw => IndexThrow
null[throw]=Thread => IndexThrow
null[9]=throw => RightHandSideThrow
null[9]=Thread => NullPointerException
Objects[throw]=throw => IndexThrow
Objects[throw]=StringBuffer => IndexThrow
Objects[1]=throw => RightHandSideThrow
Objects[1]=StringBuffer => Okay!
Objects[throw]=throw => IndexThrow
Objects[throw]=Thread => IndexThrow
Objects[1]=throw => RightHandSideThrow
Objects[1]=Thread => Okay!
Objects[throw]=throw => IndexThrow
Objects[throw]=StringBuffer => IndexThrow
Objects[9]=throw => RightHandSideThrow
Objects[9]=StringBuffer => IndexOutOfBoundsException
Objects[throw]=throw => IndexThrow
Objects[throw]=Thread => IndexThrow
Objects[9]=throw => RightHandSideThrow
Objects[9]=Thread => IndexOutOfBoundsException
Threads[throw]=throw => IndexThrow
Threads[throw]=StringBuffer => IndexThrow
Threads[1]=throw => RightHandSideThrow
Threads[1]=StringBuffer => ArrayStoreException
Threads[throw]=throw => IndexThrow
Threads[throw]=Thread => IndexThrow
Threads[1]=throw => RightHandSideThrow
Threads[1]=Thread => Okay!
Threads[throw]=throw => IndexThrow
Threads[throw]=StringBuffer => IndexThrow
Threads[9]=throw => RightHandSideThrow
Threads[9]=StringBuffer => IndexOutOfBoundsException
Threads[throw]=throw => IndexThrow
Threads[throw]=Thread => IndexThrow
Threads[9]=throw => RightHandSideThrow
Threads[9]=Thread => IndexOutOfBoundsException
Наиболее интересный случай из всех -
тринадцатый с конца:
Threads[1]=StringBuffer => ArrayStoreException
который указывает, что попытка сохранять
ссылку на StringBuffer в массиве, чья
компонента имеет тип Thread вызывает
ArrayStoreException. Во время компиляции
код является правильным: присваивание имеет
левую часть типа Object [] и правую
часть типа Object. Во время
выполнения, первый фактический параметр метода testFour -
ссылка на экземпляр "array of Thread" а третий
фактический параметр - ссылка на экземпляр
класса StringBuffer.
Все составные операции присваивания требуют,
чтобы оба операнда были примитивного типа, кроме
+=, который разрешает, чтобы правый операнд был
любого типа, если левый операнд имеет тип 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.16.2), то выражение
присваивания завершается преждевременно по той
же самой причине, и никакого присваивания не
происходит.
- Иначе, результат бинарной операции
преобразуется к типу стоящей слева переменной, и
результат преобразования записывается в
переменную.
Если выражение левого операнда – выражение
доступа к массиву (§15.12),
то требуется много шагов:
- Сначала вычисляется подвыражение выражения
доступа к массиву для стоящего слева операнда. Если этот процесс завершается
преждевременно, то выражение присваивания
завершается преждевременно по той же самой
причине; индексное подвыражение (выражения
доступа к массиву левого операнда) и правый
операнд не вычисляются, и никакого присваивания
не происходит.
- Иначе, вычисляется индексное подвыражение
выражения доступа к массиву левого операнда.
Если этот процесс завершается преждевременно, то выражение присваивания
завершается преждевременно по той же самой
причине, правый операнд не вычисляется, и
никакого присваивания не происходит.
- Иначе, если значение подвыражения ссылки на
массив равно null, то никакого
присваивания не происходит и возникает NullPointerException.
- Иначе, значение подвыражения ссылки на массив
действительно обращается к массиву. Если
значение индексного подвыражения меньше нуля или больше или равно, чем длина массива, то никакого
присваивания не происходит и возникает IndexOutOfBoundsException.
- Иначе, значение индексного подвыражения
используется, чтобы выбрать элемент массива, на
который указывает значение подвыражения ссылки
на массив. Значение этого элемента запоминается
и затем вычисляется правый операнд. Если этот
процесс завершается преждевременно, то
выражение присваивания завершается
преждевременно по той же самой причине, и
никакого присваивания не происходит. (Для
простого оператора присваивания, вычисление
правого операнда происходит раньше, чем проверка
подвыражения ссылки на массив и индексного
подвыражения, но для составного оператора
присваивания вычисление правого операнда
происходит после этих вычислений.)
- Иначе, рассмотрим элемент массива, выбранный на
предыдущем этапе, чье значение было сохранено.
Этот элемент есть переменная; назовем его тип S.
Также, пусть T есть тип левого операнда
операции присваивания, как
было определено во время трансляции.
- Если T – примитивный тип, то S –
обязательно такой же как T.
- Сохраненное значение элемента массива и
значение правого операнда используются, чтобы
выполнить бинарную операцию, обозначенную
составной операцией присваивания. Если эта
операция завершается преждевременно
(единственный случай - целочисленное деление на
ноль - см. §15.16.2), то выражение
присваивания завершается преждевременно по той
же самой причине, и никакого присваивания не
происходит.
- Иначе, результат бинарной операции
преобразуется к типу элемента массива, и
результат преобразования записывается в элемент
массива.
- Если T – ссылочный тип, то это должен быть
тип String. Поскольку класс String есть final класс, S
должен быть также типом String.
Следовательно, проверка в
процессе выполнения, которая иногда требуется
для простой операции присваивания, никогда не
требуется для составной операции присваивания.
- Сохраненное значение элемента массива и
значение правого операнда используются, чтобы
выполнить бинарную операцию (конкатенации строк), обозначенную составным
оператором присваивания (который обязательно
является +=). Если эта операция завершается
преждевременно, то выражение
присваивания завершается преждевременно по той
же самой причине, и никакого присваивания не
происходит.
- Иначе, результат типа String
бинарной операции записывается в элемент
массива.
Правила для составного присваивания элементу
массива иллюстрируются следующим примером
программы:
class ArrayReferenceThrow extends RuntimeException { }
class IndexThrow extends RuntimeException { }
class RightHandSideThrow extends RuntimeException { }
class IllustrateCompoundArrayAssignment {
static String[] strings = { "Simon", "Garfunkel" };
static double[] doubles = { Math.E, Math.PI };
static String[] stringsThrow() {
throw new ArrayReferenceThrow();
}
static double[] doublesThrow() {
throw new ArrayReferenceThrow();
}
static int indexThrow() { throw new IndexThrow(); }
static String stringThrow() {
throw new RightHandSideThrow();
}
static double doubleThrow() {
throw new RightHandSideThrow();
}
static String name(Object q) {
String sq = q.getClass().getName();
int k = sq.lastIndexOf('.');
return (k < 0) ? sq : sq.substring(k+1);
}
static void testEight(String[] x, double[] z, int j) {
String sx = (x == null) ? "null" : "Strings";
String sz = (z == null) ? "null" : "doubles";
System.out.println();
try {
System.out.print(sx + "[throw]+=throw => ");
x[indexThrow()] += stringThrow();
System.out.println("Okay!");
} catch (Throwable e) { System.out.println(name(e)); }
try {
System.out.print(sz + "[throw]+=throw => ");
z[indexThrow()] += doubleThrow();
System.out.println("Okay!");
} catch (Throwable e) { System.out.println(name(e)); }
try {
System.out.print(sx + "[throw]+=\"heh\" => ");
x[indexThrow()] += "heh";
System.out.println("Okay!");
} catch (Throwable e) { System.out.println(name(e)); }
try {
System.out.print(sz + "[throw]+=12345 => ");
z[indexThrow()] += 12345;
System.out.println("Okay!");
} catch (Throwable e) { System.out.println(name(e)); }
try {
System.out.print(sx + "[" + j + "]+=throw => ");
x[j] += stringThrow();
System.out.println("Okay!");
} catch (Throwable e) { System.out.println(name(e)); }
try {
System.out.print(sz + "[" + j + "]+=throw => ");
z[j] += doubleThrow();
System.out.println("Okay!");
} catch (Throwable e) { System.out.println(name(e)); }
try {
System.out.print(sx + "[" + j + "]+=\"heh\" => ");
x[j] += "heh";
System.out.println("Okay!");
} catch (Throwable e) { System.out.println(name(e)); }
try {
System.out.print(sz + "[" + j + "]+=12345 => ");
z[j] += 12345;
System.out.println("Okay!");
} catch (Throwable e) { System.out.println(name(e)); }
}
public static void main(String[] args) {
try {
System.out.print("throw[throw]+=throw => ");
stringsThrow()[indexThrow()] += stringThrow();
System.out.println("Okay!");
} catch (Throwable e) { System.out.println(name(e)); }
try {
System.out.print("throw[throw]+=throw => ");
doublesThrow()[indexThrow()] += doubleThrow();
System.out.println("Okay!");
} catch (Throwable e) { System.out.println(name(e)); }
try {
System.out.print("throw[throw]+=\"heh\" => ");
stringsThrow()[indexThrow()] += "heh";
System.out.println("Okay!");
} catch (Throwable e) { System.out.println(name(e)); }
try {
System.out.print("throw[throw]+=12345 => ");
doublesThrow()[indexThrow()] += 12345;
System.out.println("Okay!");
} catch (Throwable e) { System.out.println(name(e)); }
try {
System.out.print("throw[1]+=throw => ");
stringsThrow()[1] += stringThrow();
System.out.println("Okay!");
} catch (Throwable e) { System.out.println(name(e)); }
try {
System.out.print("throw[1]+=throw => ");
doublesThrow()[1] += doubleThrow();
System.out.println("Okay!");
} catch (Throwable e) { System.out.println(name(e)); }
try {
System.out.print("throw[1]+=\"heh\" => ");
stringsThrow()[1] += "heh";
System.out.println("Okay!");
} catch (Throwable e) { System.out.println(name(e)); }
try {
System.out.print("throw[1]+=12345 => ");
doublesThrow()[1] += 12345;
System.out.println("Okay!");
} catch (Throwable e) { System.out.println(name(e)); }
testEight(null, null, 1);
testEight(null, null, 9);
testEight(strings, doubles, 1);
testEight(strings, doubles, 9);
}
}
Программа выводит:
throw[throw]+=throw => ArrayReferenceThrow
throw[throw]+=throw => ArrayReferenceThrow
throw[throw]+="heh" => ArrayReferenceThrow
throw[throw]+=12345 => ArrayReferenceThrow
throw[1]+=throw => ArrayReferenceThrow
throw[1]+=throw => ArrayReferenceThrow
throw[1]+="heh" => ArrayReferenceThrow
throw[1]+=12345 => ArrayReferenceThrow
null[throw]+=throw => IndexThrow
null[throw]+=throw => IndexThrow
null[throw]+="heh" => IndexThrow
null[throw]+=12345 => IndexThrow
null[1]+=throw => NullPointerException
null[1]+=throw => NullPointerException
null[1]+="heh" => NullPointerException
null[1]+=12345 => NullPointerException
null[throw]+=throw => IndexThrow
null[throw]+=throw => IndexThrow
null[throw]+="heh" => IndexThrow
null[throw]+=12345 => IndexThrow
null[9]+=throw => NullPointerException
null[9]+=throw => NullPointerException
null[9]+="heh" => NullPointerException
null[9]+=12345 => NullPointerException
Strings[throw]+=throw => IndexThrow
doubles[throw]+=throw => IndexThrow
Strings[throw]+="heh" => IndexThrow
doubles[throw]+=12345 => IndexThrow
Strings[1]+=throw => RightHandSideThrow
doubles[1]+=throw => RightHandSideThrow
Strings[1]+="heh" => Okay!
doubles[1]+=12345 => Okay!
Strings[throw]+=throw => IndexThrow
doubles[throw]+=throw => IndexThrow
Strings[throw]+="heh" => IndexThrow
doubles[throw]+=12345 => IndexThrow
Strings[9]+=throw => IndexOutOfBoundsException
doubles[9]+=throw => IndexOutOfBoundsException
Strings[9]+="heh" => IndexOutOfBoundsException
doubles[9]+=12345 => IndexOutOfBoundsException
Наиболее интересные случаи – десятый и
одиннадцатый из последних:
Strings[1]+=throw => RightHandSideThrow
doubles[1]+=throw => RightHandSideThrow
Это случаи, где правая сторона, которая
фактически порождает исключение,
сама сталкивается с исключительной ситуацией, кроме того, здесь это
единственные такие случаи. Это показывает, что
вычисление правого операнда действительно
происходит после проверки значения нулевой
ссылки на массив и значения индекса на попадание его в дозволенный
интервал значений.
Следующая программа иллюстрирует тот факт, что
значение левого операнда составного
присваивания сохраняется прежде, чем будет
вычислен правый операнд:
class Test {
public static void main(String[] args) {
int k = 1;
int[] a = { 1 };
k += (k = 4) * (k + 2);
a[0] += (a[0] = 4) * (a[0] + 2);
System.out.println("k==" + k + " and a[0]==" + a[0]);
}
}
Эта программа выводит:
k==25 and a[0]==25
Значение k равное 1
сохраняется составным оператором присваивания += прежде, чем будет вычисляться
правый операнд (k = 4) * (k + 2). Затем
этот вычисленный правый операнд присваивает
значение 4 переменной k,
получает значение 6 для k + 2, и затем умножает 4
на 6, чтобы получить 24.
Это значение складывается с сохраненным
значением 1, чтобы получить 25, которое затем сохраняется в k оператором +=.
Идентичный анализ применяется в случаю, который
использует а[0]. Короче говоря, операторы
k += (k = 4) * (k + 2);
a[0] += (a[0] = 4) * (a[0] + 2);
Ведут себя точно таким же способом как
операторы:
k = k + (k = 4) * (k + 2);
a[0] = a[0] + (a[0] = 4) * (a[0] + 2);
Выражение - это любое
выражение присваивания:
Expression:
AssignmentExpression
В отличие от Cи и Cи ++, в языке Ява нет операции
запятая.
ConstantExpression:
Expression
Во время трансляции константного выражения
есть выражение, обозначающее значение
примитивного типа или типа String,
которое составлено, используя только следующее:
- Литералы примитивного типа и литералы типа String
- Приведения к примитивным типам и приведения к
типу String
- Унарные операции +, –,
~, и ! (Но не ++ или ––)
- Мультипликативные операции *, /, и %
- Аддитивные операции + и –
- Операции сдвига <<, >>, и >>>
- Операции отношений <, <=, >, и >= (но не instanceof)
- Операции равенства == и !=
- Поразрядные и логические операторы &, ^, и |
- Операторы && (условное "и")
и || (условное "или")
- Условная операция ? :
- Простые имена, которые относятся к конечным (final) переменным, чьими начальными значениями
являются константные выражения
- Квалифицированые имена формы ИмяТипа . Идентификатор,
который относится к конечным (final) переменным,
чьи инициализаторы есть константные выражения
Во время трансляции константные выражения
используются в 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."
|