Spec-Zone .ru
спецификации, руководства, описания, API
|
ГЛАВА 15
Большая часть работы в программе Java делается, оценивая выражения, или для их побочных эффектов, таких как присвоения на переменные, или для их значений, которые могут использоваться в качестве параметров или операндов в больших выражениях, или влиять на последовательность выполнения в операторах, или обоих.
Эта глава определяет значения выражений Java и правил для их оценки.
void
)
Выражение ничего не обозначает, если и только если это - вызов метода (§15.11), который вызывает метод, который не возвращает значение, то есть, объявленный метод void
(§8.4). Такое выражение может использоваться только в качестве оператора выражения (§14.7), потому что любой контекст, в котором может появиться выражение, требует, чтобы выражение обозначило что-то. Оператор выражения, который является вызовом метода, может также вызвать метод, который приводит к результату; в этом случае значение, возвращенное методом, спокойно отбрасывается.
Каждое выражение происходит в объявлении некоторых (класс или интерфейс) тип, который объявляется: в полевом инициализаторе, в статическом инициализаторе, в объявлении конструктора, или в коде для метода.
Значение выражения всегда является присвоением, совместимым (§5.2) с типом выражения, так же, как значение, сохраненное в переменной, является всегда совместимым с типом переменной. Другими словами значение выражения, тип которого является T, является всегда подходящим для присвоения на переменную типа T.
Отметьте, что выражение, тип которого является типом класса F, который объявляется final
как гарантируют, будет иметь значение, которое является или нулевой ссылкой или объектом, класс которого является F непосредственно, потому что final
у типов нет никаких подклассов.
null
, не обязательно известен во время компиляции. Есть несколько мест на языке Java, где фактический класс объекта, на который ссылаются, влияет на выполнение программы способом, который не может быть выведен из типа выражения. Они следующие: o.m(
...)
выбирается основанный на методах, которые являются частью класса или интерфейса, который является типом o
. Например методы, класс объекта, на который ссылается значение времени выполнения o
участвует, потому что подкласс может переопределить определенный метод, уже объявленный в родительском классе так, чтобы этот метод переопределения был вызван. (Метод переопределения может или, возможно, не хочет далее вызывать переопределенный оригинал m
метод.)
instanceof
оператор (§15.19.2). Выражение, тип которого является ссылочным типом, может быть протестировано, используя instanceof
узнать, является ли класс объекта, на который ссылается значение времени выполнения выражения, присвоением, совместимым (§5.2) с некоторым другим ссылочным типом.
[]
быть обработанным как подтип T[]
если S является подтипом T, но это требует проверки на этапе выполнения на присвоение на армейский компонент, подобный проверке, выполняемой для броска.
catch
пункт, только если класс брошенного объекта исключения instanceof
тип формального параметра catch
пункт. ClassCastException
бросается.
ArrayStoreException
бросается.
catch
обработчик (§11.3); в этом случае поток управления, которое встретилось с исключением сначала, вызывает метод uncaughtException
(§20.21.31) для его группы потока и затем завершается. Если, однако, оценка выражения выдает исключение, то выражение, как говорят, завершается резко. У резкого завершения всегда есть связанная причина, которая всегда является a throw
с данным значением.
Исключения на этапе выполнения бросаются предопределенными операторами следующим образом:
OutOfMemoryError
если есть недостаточная доступная память.
ArrayNegativeSizeException
если значение какого-либо выражения размерности является меньше чем нуль (§15.9).
NullPointerException
если значение выражения ссылки на объект null
.
NullPointerException
если целевая ссылка null
.
NullPointerException
если значение ссылочного выражения массива null
.
IndexOutOfBoundsException
если значение индексного выражения массива отрицательно или больше чем или равно length
из массива.
ClassCastException
если бросок, как находят, непозволителен во время выполнения.
ArithmeticException
если значение правого выражения операнда является нулем.
ArrayStoreException
когда значение, которое будет присвоено, не является совместимым с компонентным типом массива. Если исключение происходит, то оценка одного или более выражений может быть завершена прежде, чем все шаги их нормального режима оценки полны; такие выражения, как говорят, завершаются резко. Термины, "полные обычно" и "полный резко", также применяются к выполнению операторов (§14.1). Оператор может завершиться резко для множества причин, не только, потому что исключение выдается.
Если оценка выражения требует оценки подвыражения, резкое завершение подвыражения всегда вызывает непосредственное резкое завершение выражения непосредственно с той же самой причиной, и все последующие шаги в нормальном режиме оценки не выполняются.
Рекомендуется, чтобы код Java не положился кардинально на эту спецификацию. Код является обычно более четким, когда каждое выражение содержит самое большее один побочный эффект как его наиболее удаленная работа, и когда код не зависит от точно, какое исключение возникает как следствие слева направо оценка выражений.
class Test { public static void main(String[] args) { int i = 2; int j = (i=3) * i; System.out.println(j); } }печатные издания:
9
Не разрешается для этого напечатать 6
вместо 9
. Если оператор является составным оператором присваивания (§15.25.2), то оценка левого операнда включает и запоминание переменной, которую левый операнд обозначает и выборка и сохранение что значение переменной для использования в подразумеваемой работе объединения. Так, например, тестовая программа:
class Test { public static void main(String[] args) { int a = 9; a += (a = 3); // first example System.out.println(a); int b = 9; b = b + (b = 3); // second example System.out.println(b); } }печатные издания:
12 12потому что эти два оператора присваивания и выбирают и помнят значение левого операнда, который является
9
, прежде, чем правый операнд дополнения оценивается, таким образом устанавливая переменную в 3
. Не разрешается ни для одного примера привести к результату 6
. Отметьте, что у обоих из этих примеров есть неуказанное поведение в C согласно стандарту ANSI/ISO. Если оценка левого операнда бинарного оператора завершается резко, никакая часть правого операнда, кажется, не была оценена.
Таким образом, тестовая программа:
class Test { public static void main(String[] args) { int j = 1; try { int i = forgetIt() / (j = 2); } catch (Exception e) { System.out.println(e); System.out.println("Now j = " + j); } } static int forgetIt() throws Exception { throw new Exception("I'm outta here!"); } }печатные издания:
java.lang.Exception: I'm outta here! Now j = 1потому что левый операнд
forgetIt()
из оператора /
выдает исключение перед правым операндом и его встроенным присвоением 2
к j
происходит. &&
, ||
, и ?
:
), кажется, полностью оценивается прежде, чем любая часть самой работы выполняется. Если бинарный оператор является целочисленным делением /
(§15.16.2) или целочисленный остаток %
(§15.16.3), тогда его выполнение может повысить ArithmeticException
, но это исключение выдается только после того, как оба операнда бинарного оператора были оценены и только если эти оценки, завершаемые обычно.
class Test { public static void main(String[] args) { int divisor = 0; try { int i = 1 / (divisor * loseBig()); } catch (Exception e) { System.out.println(e); } }всегда печатные издания:
static int loseBig() throws Exception { throw new Exception("Shuffle off to Buffalo!"); }
}
java.lang.Exception: Shuffle off to Buffalo!и нет:
java.lang.ArithmeticException: / by zeroтак как никакая часть операции деления, включая сигнализацию исключения дележа на нуль, может казаться, не происходит перед вызовом
loseBig
завершается, даже при том, что реализация может быть в состоянии обнаружить или вывести, что операция деления, конечно, привела бы к исключению дележа на нуль. В случае вычислений с плавающей точкой это правило применяет также для бесконечности и не-числа (НЭН) значения. Например, !(x<y)
возможно, не переписывается как x>=y
, потому что у этих выражений есть различные значения если также x
или y
НЭН.
Определенно, вычисления с плавающей точкой, которые, кажется, математически ассоциативны, вряд ли будут в вычислительном отношении ассоциативны. Такие вычисления не должны быть наивно переупорядочены. Например, это не корректно для компилятора Java, чтобы переписать 4.0*x*0.5
как 2.0*x
; в то время как округление, оказывается, не проблема здесь, есть большие значения x
для которого первое выражение производит бесконечность (из-за переполнения), но второе выражение приводит к конечному результату.
Так, например, тестовая программа:
class Test { public static void main(String[] args) { double d = 8e+307; System.out.println(4.0 * d * 0.5); System.out.println(2.0 * d); } }печатные издания:
Infinity 1.6e+308потому что первые переполнения выражения и второе не делают.
Напротив, целочисленное дополнение и умножение доказуемо ассоциативны в Java; например a+b+c
, где a
, b
, и c
локальные переменные (это предположение упрощения избегает проблем, включающих многократные потоки и volatile
переменные), будет всегда производить тот же самый ответ, оцененный ли как (a+b)+c
или a+(b+c)
; если выражение b+c
происходит поблизости в коде, умный компилятор может быть в состоянии использовать это общее подвыражение.
class Test {
public static void main(String[] args) { String s = "going, "; print3(s, s, s = "gone"); } static void print3(String a, String b, String c) { System.out.println(a + b + c); } }всегда печатные издания:
going, going, goneпотому что присвоение строки
"gone"
к s
происходит после первых двух параметров print3
были оценены. Если оценка выражения параметра завершается резко, никакая часть любого выражения параметра с его правой стороны от него, кажется, не была оценена.
class Test { static int id; public static void main(String[] args) { try { test(id = 1, oops(), id = 3); } catch (Exception e) { System.out.println(e + ", id=" + id); } }печатные издания:
static int oops() throws Exception { throw new Exception("oops"); }
static int test(int a, int b, int c) { return a + b + c; }
}
java.lang.Exception: oops, id=1потому что присвоение
3
к id
не выполняется. Primary:Поскольку грамматики языка программирования идут, эта часть грамматики Java необычна двумя способами. Во-первых, можно было бы ожидать простые имена, такие как имена локальных переменных и параметров метода, чтобы быть основными выражениями. Для технических причин имена смешиваются с основными выражениями немного позже, когда постфиксные выражения представляются (§15.13).
PrimaryNoNewArray
ArrayCreationExpression PrimaryNoNewArray:
Literal
this
(
Expression)
ClassInstanceCreationExpression
FieldAccess
MethodInvocation
ArrayAccess
Технические причины имеют отношение к разрешению слева направо анализирующий программ Java с только предвидением с одним маркером. Рассмотрите выражения (z[3])
и (z[])
. Первым является заключенный в скобки доступ массива (§15.12), и вторым является запуск броска (§15.15). В точке, которая предварительный символ [
, слева направо синтаксический анализ уменьшит z
к нетерминальному Имени. В контексте броска мы предпочитаем не должными быть уменьшать имя к Основному устройству, но если бы Имя было одной из альтернатив для Основного, то мы не могли сказать, сделать ли сокращение (то есть, мы не могли бы определить, окажется ли текущая ситуация, будет заключенным в скобки доступом массива или броском), не предусматривая два маркера, к маркеру после [
. Грамматика Java, представленная здесь, избегает проблемы, сохраняя Имя и Основной отдельный и позволяя любому войти определенные другие правила синтаксиса (те для MethodInvocation, ArrayAccess, PostfixExpression, но не для FieldAccess, потому что это покрывается по имени). Эта стратегия эффективно задерживает вопрос того, должно ли Имя быть обработано как Основное устройство, пока больше контекста не может быть исследовано. (Другие проблемы остаются с выражениями броска; см. §19.1.5.)
Вторая необычная функция избегает потенциальной грамматической неоднозначности в выражении:
new int[3][3]который в Java всегда означает единственное создание многомерного массива, но который, без соответствующего грамматического изящества, мог бы также быть интерпретирован как значение того же самого как:
(new int[3])[3]Эта неоднозначность устраняется, разделяя ожидаемое определение Основных в Основной и PrimaryNoNewArray. (Это может быть по сравнению с разделением Оператора в Оператор и StatementNoShortIf (§14.4), чтобы избежать "свисания
else
"проблема.) Следующее производство от §3.10 повторяется здесь для удобства:
Literal:Тип литерала определяется следующим образом:
IntegerLiteral
FloatingPointLiteral
BooleanLiteral
CharacterLiteral
StringLiteral
NullLiteral
L
или l
long
; тип любого другого целочисленного литерала int
.
F
или f
float
; тип любого другого литерала с плавающей точкой double
.
boolean
.
char
.
String
.
null
нулевой тип; его значение является нулевой ссылкой. this
this
может использоваться только в теле метода экземпляра или конструктора, или в инициализаторе переменной экземпляра класса. Если это появляется где-нибудь еще, ошибка времени компиляции происходит. Когда использующийся в качестве основного выражения, ключевого слова this
обозначает значение, которое является ссылкой на объект, для которого метод экземпляра был вызван (§15.11), или к создаваемому объекту. Тип this
класс C в пределах который ключевое слово this
происходит. Во время выполнения класс фактического упомянутого объекта может быть классом C или любым подклассом C.
class IntVector { int[] v; boolean equals(IntVector other) { if (this == other) return true; if (v.length != other.v.length) return false; for (int i = 0; i < v.length; i++) if (v[i] != other.v[i]) return false; return true; }класс
}
IntVector
реализует метод equals
, который сравнивает два вектора. Если other
вектор является тем же самым векторным объектом как тот для который equals
метод был вызван, тогда проверка может пропустить длину и оценить сравнения. equals
метод реализует, это проверяет сравнение ссылки на other
объект к this
. Ключевое слово this
также используется в специальном явном операторе вызова конструктора, который может появиться в начале тела конструктора (§8.6.5).
ClassInstanceCreationExpression:В выражении создания экземпляра класса ClassType должен назвать класс, который не является
new
ClassType(
ArgumentListopt)
ArgumentList:
Expression
ArgumentList,
Expression
abstract
. Этот тип класса является типом выражения создания.Параметры в списке параметров, если таковые вообще имеются, используются, чтобы выбрать конструктора, объявленного в теле именованного типа класса, используя те же самые правила соответствия что касается вызовов метода (§15.11). Как в вызовах метода, заканчивается метод времени компиляции, соответствующий ошибку, если нет никакого уникального конструктора, который является и применимым к обеспеченным параметрам и самым определенным из всех применимых конструкторов.
Во-первых, место выделяется для нового экземпляра класса. Если есть недостаточное пространство, чтобы выделить объект, оценка выражения создания экземпляра класса завершается резко, бросая OutOfMemoryError
(§15.8.2).
Новый объект содержит новые экземпляры всех полей, объявленных в указанном типе класса и всех его суперклассов. Поскольку каждый новый полевой экземпляр создается, он инициализируется к его стандартному значению по умолчанию (§4.5.4).
Затем, список параметров оценивается, слева направо. Если какая-либо из оценок параметра завершается резко, любые выражения параметра с его правой стороны от него не оцениваются, и выражение создания экземпляра класса завершается резко по той же самой причине.
Затем, выбранный конструктор указанного типа класса вызывается. Это приводит к вызову по крайней мере одного конструктора для каждого суперкласса типа класса. Этот процесс может быть направлен явными операторами вызова конструктора (§8.6) и описывается подробно в §12.5.
Значение выражения создания экземпляра класса является ссылкой на недавно создаваемый объект указанного класса. Каждый раз, когда выражение оценивается, новый объект создается.
OutOfMemoryError
бросается. Эта проверка происходит прежде, чем любые выражения параметра оцениваются. Так, например, тестовая программа:
class List { int value; List next; static List head = new List(0); List(int n) { value = n; next = head; head = this; } } class Test { public static void main(String[] args) { int id = 0, oldid = 0; try { for (;;) { ++id; new List(oldid = id); } } catch (Error e) { System.out.println(e + ", " + (oldid==id)); } } }печатные издания:
java.lang.OutOfMemoryError: List, falseпотому что условие "или память" обнаруживаются перед выражением параметра
oldid
=
id
оценивается. Сравните это с обработкой выражений создания массива (§15.9), для которого условие из памяти обнаруживается после оценки выражений размерности (§15.9.3).
ArrayCreationExpression:Выражение создания массива создает объект, который является новым массивом, элементы которого имеют тип, определенный PrimitiveType или TypeName. TypeName может назвать любой ссылочный тип, даже
new
PrimitiveTypeDimExprs
Dimsopt
new
TypeNameDimExprs
Dimsopt DimExprs:
DimExpr
DimExprsDimExpr DimExpr:
[
Expression]
Dims:
[ ]
Dims
[ ]
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, то оно эффективно выполняет ряд вложенных циклов глубины, чтобы создать подразумеваемые массивы массивов. Например, объявление:
float[][] matrix = new float[3][3];
эквивалентно в поведении: float[][] matrix = new float[3][]; for (int d = 0; d < matrix.length; d++) matrix[d] = new float[3];и:
Age[][][][][] Aquarius = new Age[6][10][8][12][];эквивалентно:
Age[][][][][] Aquarius = new Age[6][][][][]; for (int d1 = 0; d1 < Aquarius.length; d1++) { Aquarius[d1] = new Age[8][][][]; for (int d2 = 0; d2 < Aquarius[d1].length; d2++) { Aquarius[d1][d2] = new Age[10][][]; for (int d3 = 0; d3 < Aquarius[d1][d2].length; d3++) { Aquarius[d1][d2][d3] = new Age[12][]; } } }с d, d1, d2 и d3, замененный именами, которые уже локально не объявляются. Таким образом, сингл
new
выражение фактически создает один массив длины 6, 6 массивов длины 10, массивы длины 8, и массивы длины 12. Эти листы в качестве примера пятая размерность, которая была бы массивами, содержащими фактические элементы массива (ссылки на Age
объекты), инициализированный только к нулевым ссылкам. Эти массивы могут быть переполнены в позже другим кодом, таковы как: Age[] Hair = { new Age("quartz"), new Age("topaz") }; Aquarius[1][9][6][9] = Hair;У многомерного массива не должно быть массивов той же самой длины на каждом уровне; таким образом треугольная матрица может быть создана:
float triang[][] = new float[100][]; for (int i = 0; i < triang.length; i++) triang[i] = new float[i+1];Нет, однако, никакого способа получить этот эффект с единственным выражением создания.
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:Значение выражения доступа к полю определяется, используя те же самые правила что касается полностью определенных имен (§6.6), но ограничивается фактом, что выражение не может обозначить пакет, тип класса, или соединить интерфейсом с типом.
Primary.
Identifier
super .
Identifier
static
: final
, тогда результатом является значение указанной переменной класса в классе, или взаимодействуйте через интерфейс, который является типом Основного выражения.
final
, тогда результатом является переменная, а именно, указанная переменная класса в классе, который является типом Основного выражения. static
: null
, тогда a NullPointerException
бросается.
final
, тогда результатом является значение указанной переменной экземпляра в объекте, на который ссылается значение Основного устройства.
final
, тогда результатом является переменная, а именно, указанная переменная экземпляра в объекте, на который ссылается значение Основного устройства.
class S { int x = 0; } class T extends S { int x = 1; } class Test { public static void main(String[] args) {
T t = new T(); System.out.println("t.x=" + t.x + when("t", t)); S s = new S(); System.out.println("s.x=" + s.x + when("s", s)); s = t; System.out.println("s.x=" + s.x + when("s", s));производит вывод:
}
static String when(String name, Object t) { return " when " + name + " holds a " + t.getClass() + " at run time."; } }
t.x=1 when t holds a class T at run time. s.x=0 when s holds a class S at run time. s.x=0 when s holds a class T at run time.Последняя строка показывает, что, действительно, поле, к которому получают доступ, не зависит от класса времени выполнения объекта, на который ссылаются; даже если
s
содержит ссылку на объект класса T
, выражение s.x
обращается к x
поле класса S
, потому что тип выражения s
S
. Объекты класса T
содержите два названные поля x
, один для класса T
и один для его суперкласса S
. Эта нехватка динамического поиска для доступов к полю позволяет Java работать эффективно с прямыми реализациями. Питание позднего связывания и переопределения доступно в Java, но только когда методы экземпляра используются. Полагайте, что тот же самый пример, используя методы экземпляра получает доступ к полям:
class S { int x = 0; int z() { return x; } } class T extends S { int x = 1; int z() { return x; } } class Test {
public static void main(String[] args) { T t = new T(); System.out.println("t.z()=" + t.z() + when("t", t)); S s = new S(); System.out.println("s.z()=" + s.z() + when("s", s)); s = t; System.out.println("s.z()=" + s.z() + when("s", s)); } static String when(String name, Object t) { return " when " + name + " holds a " + t.getClass() + " at run time."; } }Теперь вывод:
t.z()=1 when t holds a class T at run time. s.z()=0 when s holds a class S at run time. s.z()=1 when s holds a class T at run time.Последняя строка показывает, что, действительно, метод, к которому получают доступ, действительно зависит от класса времени выполнения объекта, на который ссылаются; когда
s
содержит ссылку на объект класса T
, выражение s.z()
обращается к z
метод класса T
, несмотря на то, что тип выражения s
S
. Метод z
из класса T
метод переопределений z
из класса S
. Следующий пример демонстрирует, что нулевая ссылка может использоваться, чтобы получить доступ к классу (static
) переменная, не вызывая исключение:
class Test { static String mountain = "Chocorua"; static Test favorite(){ System.out.print("Mount "); return null; } public static void main(String[] args) { System.out.println(favorite().mountain); } }Это компилирует, выполняется, и печатные издания:
Mount ChocoruaДаже при том, что результат
favorite()
null
, a NullPointerException
не бросается. Это"Mount
"печатается, демонстрирует, что Основное выражение действительно полностью оценивается во время выполнения, несмотря на то, что только его тип, не его значение, используется, чтобы определить который поле к доступу (потому что поле mountain
static
). super
super
допустимо только в методе экземпляра или конструкторе, или в инициализаторе переменной экземпляра класса; они - точно те же самые ситуации в который ключевое слово this
может использоваться (§15.7.2). Включение формы super
возможно, не используется нигде в классе Object
, с тех пор Object
не имеет никакого суперкласса; если super
появляется в классе Object
, тогда ошибка времени компиляции заканчивается. Предположите что выражение доступа к полю super.
имя появляется в пределах класса C, и непосредственный суперкласс C является классом S. Затем super.
имя обрабатывается точно, как будто это было выражение ((
S)this).
имя; таким образом это обращается к полю, названному именем текущего объекта, но с текущим объектом, просматриваемым как экземпляр суперкласса. Таким образом это может получить доступ к полю, названному именем, которое видимо в классе S, даже если то поле скрывается объявлением поля, названного именем в классе C.
Использование super
демонстрируется следующим примером:
interface I { int x = 0; } class T1 implements I { int x = 1; } class T2 extends T1 { int x = 2; } class T3 extends T2 { int x = 3; void test() { System.out.println("x=\t\t"+x); System.out.println("super.x=\t\t"+super.x); System.out.println("((T2)this).x=\t"+((T2)this).x); System.out.println("((T1)this).x=\t"+((T1)this).x); System.out.println("((I)this).x=\t"+((I)this).x); } } class Test { public static void main(String[] args) { new T3().test(); } }который производит вывод:
x= 3 super.x= 2 ((T2)this).x= 2 ((T1)this).x= 1 ((I)this).x= 0В пределах класса
T3
, выражение super.x
обрабатывается точно, как будто это было: ((T2)this).x
MethodInvocation:Определение ArgumentList от §15.8 повторяется здесь для удобства:
MethodName(
ArgumentListopt)
Primary
.
Identifier(
ArgumentListopt)
Identifier
super .(
ArgumentListopt)
ArgumentList:Разрешение имени метода во время компиляции более усложняется чем разрешение имени поля из-за возможности перегрузки метода. Вызов метода во время выполнения также более усложняется чем доступ к полю из-за возможности переопределения метода экземпляра.
Expression
ArgumentList,
Expression
Определение метода, который будет вызван выражением вызова метода, включает несколько шагов. Следующие три раздела описывают время компиляции, обрабатывая вызова метода; определение типа выражения вызова метода описывается в §15.11.3.
.
Идентификатор, тогда именем метода является Идентификатор, и класс поиска является тем, названным TypeName. Если TypeName является именем интерфейса, а не класса, то ошибка времени компиляции происходит, потому что эта форма может вызвать только static
методы и интерфейсы имеют нет static
методы.
.
Идентификатор; тогда именем метода является Идентификатор и класс, или интерфейс, чтобы искать является объявленным типом поля, названного FieldName. .
Идентификатор, тогда именем метода является Идентификатор и класс, или интерфейс, который будет искаться, является типом Основного выражения.
super
.
Идентификатор, тогда именем метода является Идентификатор, и класс, который будет искаться, является суперклассом класса, объявление которого содержит вызов метода. Ошибка времени компиляции происходит, если такой вызов метода происходит в интерфейсе, или в классе Object
, или в a static
метод, статический инициализатор, или инициализатор для a static
переменная. Из этого следует, что вызов метода этой формы может появиться только в классе кроме Object
, и только в теле метода экземпляра, теле конструктора, или инициализаторе для переменной экземпляра. int
неявно никогда не сужаются к byte
, short
, или char
.
Доступно ли объявление метода для вызова метода, зависит от модификатора доступа (public
, ни один, protected
, или private
) в объявлении метода и на том, где вызов метода появляется.
Если у класса или интерфейса нет никакого объявления метода, которое и применимо и доступно, то ошибка времени компиляции происходит.
public class Doubler { static int two() { return two(1); } private static int two(int i) { return 2*i; } } class Test extends Doubler { public static long two(long j) {return j+j; } public static void main(String[] args) { System.out.println(two(3)); System.out.println(Doubler.two(3)); // compile-time error } }для вызова метода
two(1)
в пределах класса Doubler
, есть два доступных названные метода two
, но только второй применим, и так, чтобы был тот, вызванный во время выполнения. Для вызова метода two(3)
в пределах класса Test
, есть два применимых метода, но только тот в классе Test
доступно, и так, чтобы был тот, который будет вызван во время выполнения (параметр 3
преобразовывается в тип long
). Для вызова метода Doubler.two(3)
, класс Doubler
, не класс Test
, ищется названные методы two
; единственный применимый метод не доступен, и таким образом, этот вызов метода вызывает ошибку времени компиляции.
class ColoredPoint { int x, y; byte color; void setColor(byte color) { this.color = color; } } class Test { public static void main(String[] args) { ColoredPoint cp = new ColoredPoint(); byte color = 37; cp.setColor(color); cp.setColor(37); // compile-time error } }Здесь, ошибка времени компиляции происходит для второго вызова
setColor
, потому что никакой применимый метод не может быть найден во время компиляции. Тип литерала 37
int
, и int
не может быть преобразован в byte
преобразованием вызова метода. Преобразование присвоения, которое используется в инициализации переменной color
, выполняет неявное преобразование константы от типа int
к byte
, который разрешается потому что значение 37
является достаточно маленьким, чтобы быть представленным в типе byte
; но такое преобразование не позволяется для преобразования вызова метода. Если метод setColor
как, однако, объявляли, взял int
вместо a byte
, тогда оба вызова метода были бы корректны; первый вызов был бы позволен, потому что преобразование вызова метода действительно разрешает расширяющееся преобразование из byte
к int
. Однако, бросок сужения тогда требовался бы в теле setColor
:
void setColor(int color) { this.color = (byte)color; }
Неофициальная интуиция - то, что одно объявление метода является более определенным чем другой, если какой-либо вызов, обработанный первым методом, мог бы быть передан другому без ошибки типа времени компиляции.
Точное определение следующие. Позвольте м. быть именем и предполагать, что есть два объявления методов, названных м., каждый имеющий n параметры. Предположите, что одно объявление появляется в пределах класса или интерфейса T и что типы параметров являются T1..., Tn; предположите кроме того, что другое объявление появляется в пределах класса или интерфейса U и что типы параметров являются U1..., Un. Затем метод м. объявленного в T является более определенным, чем метод м. объявил в U если и только если оба из следующего являются истиной:
преобразованием вызова метода.
преобразованием вызова метода, для всего j от 1
к n. Если есть точно один максимально определенный метод, то это - фактически самый определенный метод; это является обязательно более определенным чем любой другой метод, который применим и доступен. Это тогда подвергается некоторым дальнейшим проверкам времени компиляции как описано в §15.11.3.
Возможно, что никакой метод не является самым определенным, потому что есть два или больше максимально определенных объявления метода. В этом случае мы говорим, что вызов метода неоднозначен, и ошибка времени компиляции происходит.
class Point { int x, y; } class ColoredPoint extends Point { int color; }
class Test { static void test(ColoredPoint p, Point q) { System.out.println("(ColoredPoint, Point)"); } static void test(Point p, ColoredPoint q) { System.out.println("(Point, ColoredPoint)"); } public static void main(String[] args) { ColoredPoint cp = new ColoredPoint(); test(cp, cp); // compile-time error } }Этот пример производит ошибку во время компиляции. Проблема состоит в том, что есть два объявления
test
это применимо и доступно, и ни один не является более определенным чем другой. Поэтому, вызов метода неоднозначен. Если третье определение test
были добавлены:
static void test(ColoredPoint p, ColoredPoint q) { System.out.println("(ColoredPoint, ColoredPoint)"); }тогда это было бы более определенным чем другие два, и вызов метода больше не будет неоднозначен.
class Point { int x, y; } class ColoredPoint extends Point { int color; } class Test {
static int test(ColoredPoint p) { return color; } static String test(Point p) { return "Point"; } public static void main(String[] args) { ColoredPoint cp = new ColoredPoint(); String s = test(cp); // compile-time error } }Здесь самое определенное объявление метода
test
тот, берущий параметр типа ColoredPoint
. Поскольку тип результата метода int
, ошибка времени компиляции - происходит потому что int
не может быть преобразован в a String
преобразованием присвоения. Этот пример показывает, что в Java типы результата методов не участвуют в разрешении перегруженных методов, так, чтобы второе test
метод, который возвращает a String
, не выбирается, даже при том, что у этого есть тип результата, который позволил бы примеру программы компилировать без ошибки. Так, например, рассмотрите две единицы компиляции, один для класса Point
:
package points; public class Point { public int x, y; public Point(int x, int y) { this.x = x; this.y = y; } public String toString() { return toString(""); }
public String toString(String s) { return "(" + x + "," + y + s + ")"; } }и один для класса
ColoredPoint
: package points; public class ColoredPoint extends Point {
public static final int
RED = 0, GREEN = 1, BLUE = 2;
public static String[] COLORS =
{ "red", "green", "blue" };
public byte color;
public ColoredPoint(int x, int y, int color) {
super(x, y); this.color = (byte)color;
}
/** Copy all relevant fields of the argument into
this ColoredPoint
object. */
public void adopt(Point p) { x = p.x; y = p.y; }
public String toString() {
String s = "," + COLORS[color];
return super.toString(s);
}
}
Теперь рассмотрите третью единицу компиляции, которая использует ColoredPoint
: import points.*; class Test { public static void main(String[] args) { ColoredPoint cp = new ColoredPoint(6, 6, ColoredPoint.RED); ColoredPoint cp2 = new ColoredPoint(3, 3, ColoredPoint.GREEN); cp.adopt(cp2); System.out.println("cp: " + cp); } }Вывод:
cp: (3,3,red)Прикладной программист, который кодировал класс
Test
ожидал видеть слово green
, потому что фактический параметр, a ColoredPoint
, имеет a color
поле, и color
казалось бы, был бы "соответствующим полем" (конечно, документация для пакета Points
должно быть намного более точным!).
Заметьте, между прочим, что самый определенный метод (действительно, единственный применимый метод) для вызова метода adopt
имеет подпись, которая указывает на метод одного параметра, и параметр имеет тип Point
. Эта подпись становится частью двоичного представления класса Test
произведенный компилятором и используется вызовом метода во время выполнения.
Предположите программиста, о котором сообщают эта ошибка программного обеспечения и специалист по обслуживанию points
пакет решил, после должного обдумывания, исправить это, добавляя метод к классу ColoredPoint
:
public void adopt(ColoredPoint p) { adopt((Point)p); color = p.color; }Если прикладной программист тогда выполняет старый двоичный файл для
Test
с новым двоичным файлом для ColoredPoint
, вывод все еще:
cp: (3,3,red)потому что старый двоичный файл для
Test
все еще имеет дескриптор "один параметр, тип которого Point
; void
"связанный с вызовом метода cp.adopt(cp2)
. Если исходный код для Test
перекомпилирован, компилятор тогда обнаружит, что есть теперь два применимо adopt
методы, и что подпись для более определенного является "одним параметром, тип которого ColoredPoint
; void
"; выполнение программы тогда произведет требуемый вывод: cp: (3,3,green)С предусмотрительностью о таких проблемах, специалисте по обслуживанию
points
пакет мог фиксировать ColoredPoint
класс, чтобы работать и с недавно скомпилированным и со старым кодом, добавляя оборону кодирует к старому adopt
метод ради старого кода, который все еще вызывает это на ColoredPoint
параметры:
public void adopt(Point p) { if (p instanceof ColoredPoint) color = ((ColoredPoint)p).color; x = p.x; y = p.y; }Подобное рассмотрение применяется, если метод должен быть перемещен от класса до суперкласса. В этом случае передающий метод может быть оставлен позади ради старого кода. Специалист по обслуживанию
points
пакет мог бы хотеть перемещаться adopt
метод, который берет a Point
параметр до класса Point
, так, чтобы все Point
объекты могут обладать adopt
функциональность. Чтобы избежать проблем совместимости со старым двоичным кодом, специалист по обслуживанию должен оставить передающий метод в классе ColoredPoint
:
public void adopt(Point p) { if (p instanceof ColoredPoint) color = ((ColoredPoint)p).color; super.adopt(p); }Идеально, код Java должен быть перекомпилирован всякий раз, когда код, от которого он зависит, изменяется. Однако, в среде, где различные классы Java сохраняются различными организациями, это не всегда выполнимо. Безопасное программирование с внимательным отношением к проблемам развития класса может сделать обновленный код намного больше устойчивым. См. §13 для детального обсуждения совместимости на уровне двоичных кодов и введите развитие.
static
метод, статический инициализатор, или инициализатор для a static
переменная, тогда объявление времени компиляции должно быть static
. Если, вместо этого, объявление времени компиляции для вызова метода для метода экземпляра, то ошибка времени компиляции происходит. (Причина состоит в том, что вызов метода этой формы не может использоваться, чтобы вызвать метод экземпляра в местах где this
(§15.7.2) не определяется.)
.
Идентификатор, тогда объявление времени компиляции должно быть static
. Если объявление времени компиляции для вызова метода для метода экземпляра, то ошибка времени компиляции происходит. (Причина состоит в том, что вызов метода этой формы не определяет ссылку на объект, который может служить this
в пределах метода экземпляра.)
void
, тогда вызов метода должен быть высокоуровневым выражением, то есть, Выражением в операторе выражения (§14.7) или в части ForInit или ForUpdate a for
оператор (§14.12), или ошибка времени компиляции происходит. (Причина состоит в том, что такой вызов метода не производит значения и так должен использоваться только в ситуации, где значение не необходимо.)
void
, как объявлено в объявлении времени компиляции.
static
модификатор, тогда режим вызова static
.
private
модификатор, тогда режим вызова nonvirtual
.
super
.
Идентификатор, тогда режим вызова super
.
interface
.
virtual
. void
, тогда тип выражения вызова метода является типом результата, определенным в объявлении времени компиляции.static
, тогда нет никакой целевой ссылки.
this
. .
Идентификатор, тогда нет никакой целевой ссылки.
.
Идентификатор, тогда есть два подслучая:
super
, включается, тогда целевая ссылка является значением this
. Виртуальная машина Java должна обеспечить как часть редактирования, что метод м. все еще существует в типе T. Если это не истина, то a NoSuchMethodError
(который является подклассом IncompatibleClassChangeError
) происходит. Если режим вызова interface
, тогда виртуальная машина должна также проверить, что целевой ссылочный тип все еще реализует указанный интерфейс. Если целевой ссылочный тип все еще не реализует интерфейс, то IncompatibleClassChangeError
происходит.
Виртуальная машина должна также обеспечить, во время редактирования, чтобы тип T и метод м. были доступны. Для типа T:
public
, тогда T доступен. public
, тогда м. доступен. (Все элементы интерфейсов public
(§9.2)).
protected
, тогда м. доступен, если и только если или T находится в том же самом пакете как C, или C является T или подклассом T.
private
, тогда м. доступен, если и только если и C T. IllegalAccessError
происходит (§12.3). Если режим вызова static
, никакая целевая ссылка не необходима, и переопределение не позволяется. Метод м. класса T является тем, который будет вызван.
Иначе, метод экземпляра должен быть вызван и есть целевая ссылка. Если целевая ссылка null
, a NullPointerException
бросается в эту точку. Иначе, целевая ссылка, как говорят, обращается к целевому объекту и будет использоваться в качестве значения ключевого слова this
в вызванном методе. Другие четыре возможности для режима вызова тогда рассматривают.
Если режим вызова nonvirtual
, переопределение не позволяется. Метод м. класса T является тем, который будет вызван.
Иначе, режим вызова interface
, virtual
, или super
, и переопределение может произойти. Используется динамический поиск метода. Динамический процесс поиска запускается с класса S, определенного следующим образом:
interface
или virtual
, тогда S является первоначально фактическим классом R времени выполнения целевого объекта. Если целевой объект является массивом, R является классом Object
. (Отметьте это режимом вызова interface
, R обязательно реализует T; для режима вызова virtual
, R является обязательно или T или подклассом T.),
super
, тогда S является первоначально суперклассом класса C, который содержит вызов метода. IncompatibleClassChangeError
происходит если дело обстоит не так.)
T
, потому что иначе IllegalAccessError
был бы брошен проверками предыдущего раздела §15.11.4.3. Мы отмечаем, что динамический процесс поиска, в то время как описано здесь явно, будет часто реализовываться неявно, например поскольку побочный эффект конструкции и использование метода на класс диспетчеризируют таблицы, или конструкция других структур на класс, используемых для эффективного, диспетчеризирует.
Теперь новый фрейм активации создается, содержа целевую ссылку (если любой) и значения аргументов (если любой), так же как достаточно пространства для локальных переменных и стека для метода, который будет вызван и любая другая бухгалтерская информация, которая может быть запрошена реализацией (указатель вершины стека, счетчик программы, ссылка на предыдущий фрейм активации, и т.п.). Если нет достаточной памяти, доступной, чтобы создать такой фрейм активации, OutOfMemoryError
бросается.
Недавно создаваемый фрейм активации становится текущим фреймом активации. Эффект этого состоит в том, чтобы присвоить значения аргументов соответствующим недавно создаваемым переменным параметра метода, и сделать целевую ссылку, доступную как this
, если есть целевая ссылка.
Если метод м. является a native
метод, но необходимый собственный, зависящий от реализации двоичный код не был загружен (§20.16.14, §20.16.13) или иначе не может быть динамически соединен, затем UnsatisfiedLinkError
бросается.
Если метод м. не synchronized
, управление передается телу метода м., который будет вызван.
Если метод м. synchronized
, тогда объект должен быть заблокирован перед передачей управления. Никакие дальнейшие успехи не могут быть сделаны, пока текущий поток не может получить блокировку. Если есть целевая ссылка, то цель должна быть заблокирована; иначе Class
объект для класса S, класса метода м., должен быть заблокирован. Управление тогда передается телу метода м., который будет вызван. Объект автоматически разблокирован, когда выполнение тела метода завершилось, или обычно или резко. Блокировка и разблокирование поведения состоят точно в том, как будто тело метода было встроено в a synchronized
оператор (§14.17).
SecurityManager
или подкласс SecurityManager
.
SecurityManager
или подкласс SecurityManager
; и метод м., как известно, не вызывает, прямо или косвенно, любой метод 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
бросается.
class Test { public static void main(String[] args) { String s = "one"; if (s.startsWith(s = "two")) System.out.println("oops"); } }возникновение
s
прежде".startsWith
"оценивается сначала, перед выражением параметра s="two"
. Поэтому, ссылка на строку "one"
помнится как целевая ссылка прежде, чем локальная переменная s будет изменена, чтобы обратиться к строке "two"
. В результате startsWith
метод (§20.12.20) вызывается для целевого объекта "one"
с параметром "two"
, таким образом, результат вызова false
, как строка "one"
не запускается с "two"
. Из этого следует, что тестовая программа не печатает"oops
". class Point { final int EDGE = 20; int x, y; void move(int dx, int dy) { x += dx; y += dy; if (Math.abs(x) >= EDGE || Math.abs(y) >= EDGE) clear(); } void clear() { System.out.println("\tPoint clear"); x = 0; y = 0; } } class ColoredPoint extends Point { int color;
void clear() { System.out.println("\tColoredPoint clear"); super.clear(); color = 0; } }подкласс
ColoredPoint
расширяется clear
абстракция определяется ее суперклассом Point
. Это делает так, переопределяя clear
метод с его собственным методом, который вызывает clear
метод ее суперкласса, используя форму super.clear
. Этот метод тогда вызывается всякий раз, когда целевой объект для вызова clear
a ColoredPoint
. Даже метод move
в Point
вызывает clear
метод класса ColoredPoint
когда класс this
ColoredPoint
, как показано выводом этой тестовой программы:
class Test { public static void main(String[] args) { Point p = new Point(); System.out.println("p.move(20,20):"); p.move(20, 20); ColoredPoint cp = new ColoredPoint(); System.out.println("cp.move(20,20):"); cp.move(20, 20); p = new ColoredPoint(); System.out.println("p.move(20,20), p colored:"); p.move(20, 20); } }который является:
p.move(20,20): Point clear cp.move(20,20): ColoredPoint clear Point clear p.move(20,20), p colored: ColoredPoint clear Point clearПереопределение иногда вызывают "последней ограниченной самоссылкой"; в этом примере это означает что ссылка на
clear
в теле Point.move
(который является действительно синтаксическим сокращением для this.clear
) вызывает метод, выбранный "поздно" (во время выполнения, основанное на классе времени выполнения объекта, на который ссылаются this
) а не метод, выбранный "рано" (во время компиляции, базируемый только на типе this
). Это предоставляет программисту Java мощный способ расширить абстракции и является ключевой идеей в объектно-ориентированном программировании.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:Выражение доступа массива содержит два подвыражения, ссылочное выражение массива (перед левой скобкой) и индексное выражение (в пределах скобок). Отметьте, что ссылочное выражение массива может быть именем или любым основным выражением, которое не является выражением создания массива (§15.9).
ExpressionName[
Expression]
PrimaryNoNewArray
[
Expression]
Тип ссылочного выражения массива должен быть типом массива (вызовите это T[]
, заканчивается массив, компоненты которого имеют тип T) или ошибка времени компиляции. Затем тип выражения доступа массива является T.
Индексное выражение подвергается унарному числовому продвижению (§5.6.1); продвинутый тип должен быть int
.
Результатом ссылки массива является переменная типа T, а именно, переменная в пределах массива, выбранного значением индексного выражения. Эту получающуюся переменную, которая является компонентом массива, никогда не рассматривают final
, даже если ссылка массива была получена из a final
переменная.
null
, тогда a NullPointerException
бросается.
IndexOutOfBoundsException
бросается.
final
, даже если ссылочное выражение массива является a final
переменная.) a[(a=b)[3]]
, выражение a
полностью оценивается перед выражением (a=b)[3]
; это означает что исходное значение a
выбирается и помнится в то время как выражение (a=b)[3]
оценивается. Этот массив, на который ссылается исходное значение a
тогда преобразовывается в нижний индекс значением, которое является элементом 3
из другого массива (возможно тот же самый массив), которым сослались b
и теперь также ссылаются a
.
class Test { public static void main(String[] args) { int[] a = { 11, 12, 13, 14 }; int[] b = { 0, 1, 2, 3 }; System.out.println(a[(a=b)[3]]); } }печатные издания:
14потому что значение чудовищного выражения эквивалентно
a[b[3]]
или a[3]
или 14
. Если оценка выражения налево от скобок завершится резко, то никакая часть выражения в пределах скобок, будет казаться, не была оценена. Таким образом, пример:
class Test { public static void main(String[] args) { int index = 1; try { skedaddle()[index=2]++; } catch (Exception e) { System.out.println(e + ", index=" + index); } } static int[] skedaddle() throws Exception { throw new Exception("Ciao"); } }печатные издания:
java.lang.Exception: Ciao, index=1потому что встроенное присвоение
2
к index
никогда не происходит. Если ссылочное выражение массива производит null
вместо ссылки на массив, тогда a NullPointerException
бросается во время выполнения, но только после того, как все части ссылочного выражения массива были оценены и только если эти оценки, завершаемые обычно. Таким образом, пример:
class Test { public static void main(String[] args) { int index = 1; try { nada()[index=2]++; } catch (Exception e) { System.out.println(e + ", index=" + index); } } static int[] nada() { return null; } }печатные издания:
java.lang.NullPointerException, index=2потому что встроенное присвоение
2
к index
происходит перед проверкой на нулевого указателя. Как связанный пример, программа: class Test { public static void main(String[] args) { int[] a = null; try { int i = a[vamoose()]; System.out.println(i); } catch (Exception e) { System.out.println(e); } } static int vamoose() throws Exception { throw new Exception("Twenty-three skidoo!"); } }всегда печатные издания:
java.lang.Exception: Twenty-three skidoo!A
NullPointerException
никогда не происходит, потому что индексное выражение должно быть полностью оценено прежде, чем любая часть работы индексации происходит, и это включает проверку относительно того, является ли значение левого операнда null
. ++
и --
операторы. Кроме того, как обсуждено в §15.7, имена, как полагают, не являются основными выражениями, но обрабатываются отдельно в грамматике, чтобы избежать определенных неоднозначностей. Они становятся взаимозаменяемыми только здесь на уровне приоритета постфиксных выражений. PostfixExpression:
Primary
ExpressionName
PostIncrementExpression
PostDecrementExpression
this.
Идентификатор this
(§15.7.2). .
Идентификатор, затем ошибка времени компиляции происходит.
.
Идентификатор, тогда это, обращается к a static
поле класса или интерфейса называет TypeName. Ошибка времени компиляции происходит, если TypeName не называет класс или интерфейс. Ошибка времени компиляции происходит, если класс или интерфейс, названный TypeName, не содержат доступное статическое поле, названное Идентификатором. Тип ExpressionName является объявленным типом static
поле. Значение ExpressionName является переменной, а именно, static
поле непосредственно.
.
Идентификатор, где Ename является самостоятельно ExpressionName, и ExpressionName, обрабатывается точно, как будто это было выражение доступа к полю (§15.10): (Ename).Identifier ++
PostIncrementExpression:Постфиксное выражение следовало a
PostfixExpression++
++
оператор является постфиксным инкрементным выражением. Результатом постфиксного выражения должна быть переменная числового типа, или ошибка времени компиляции происходит. Тип постфиксного инкрементного выражения является типом переменной. Результатом постфиксного инкрементного выражения не является переменная, а значение.
Во время выполнения, если оценка выражения операнда завершается резко, то постфиксное инкрементное выражение завершается резко по той же самой причине и никакому приращению, происходит. Иначе, значение 1
добавляется к значению переменной, и сумма сохранена назад в переменную. Перед дополнением двоичное числовое продвижение (§5.6.2) выполняется на значении 1
и значение переменной. В случае необходимости сумма сужается сужающимся примитивным преобразованием (§5.1.3) к типу переменной прежде, чем это будет сохранено. Значение постфиксного инкрементного выражения является значением переменной прежде, чем новое значение будет сохранено.
Переменная, которая объявляется final
не может быть постепенно увеличен, потому что когда доступ a final
переменная используется в качестве выражения, результатом является значение, не переменная. Таким образом это не может использоваться в качестве операнда постфиксного инкрементного оператора.
--
PostDecrementExpression:Постфиксное выражение следовало a
PostfixExpression--
--
оператор является постфиксным декрементным выражением. Результатом постфиксного выражения должна быть переменная числового типа, или ошибка времени компиляции происходит. Тип постфиксного декрементного выражения является типом переменной. Результатом постфиксного декрементного выражения не является переменная, а значение.
Во время выполнения, если оценка выражения операнда завершается резко, то постфиксное декрементное выражение завершается резко по той же самой причине и никакому decrementation, происходит. Иначе, значение 1
вычитается из значения переменной, и различие сохранено назад в переменную. Перед вычитанием двоичное числовое продвижение (§5.6.2) выполняется на значении 1
и значение переменной. В случае необходимости различие сужается сужающимся примитивным преобразованием (§5.1.3) к типу переменной прежде, чем это будет сохранено. Значение постфиксного декрементного выражения является значением переменной прежде, чем новое значение будет сохранено.
Переменная, которая объявляется final
не может быть постепенно уменьшен, потому что когда доступ a final
переменная используется в качестве выражения, результатом является значение, не переменная. Таким образом это не может использоваться в качестве операнда постфиксного декрементного оператора.
+
, -
, ++
, --
, ~
, !
, и операторы броска. Выражения с группой унарных операторов справа налево, так, чтобы -~x
означает то же самое как -(~x)
. UnaryExpression:Следующее производство от §15.15 повторяется здесь для удобства:
PreIncrementExpression
PreDecrementExpression
+
UnaryExpression
-
UnaryExpression
UnaryExpressionNotPlusMinus PreIncrementExpression:
++
UnaryExpression PreDecrementExpression:
--
UnaryExpression UnaryExpressionNotPlusMinus:
PostfixExpression
~
UnaryExpression
!
UnaryExpression
CastExpression
CastExpression:Эта часть грамматики Java содержит некоторые приемы, чтобы избежать двух потенциальных синтаксических неоднозначностей.
(
PrimitiveType)
UnaryExpression
(
ReferenceType)
UnaryExpressionNotPlusMinus
Первая потенциальная неоднозначность возникла бы в выражениях такой как (p)+q
, который смотрит к C или программисту на C++, как если бы это могло быть любой быть броском, чтобы ввести p
из унарного +
работа на q
, или сложение в двоичной системе двух количеств p
и q
. В C и C++, синтаксический анализатор решает эту проблему, выполняя ограниченное количество семантического анализа, поскольку это анализирует, так, чтобы это знало ли p
имя типа или имя переменной.
Java берет другой подход. Результат +
оператор должен быть числовым, и все имена типов, включенные в, набирает числовые значения, известные ключевые слова. Таким образом, если p
ключевое слово, называя тип примитива, тогда (p)+q
может иметь смысл только как бросок унарного выражения. Однако, если p
не ключевое слово, называя тип примитива, тогда (p)+q
может иметь смысл только как работу двоичной арифметики. Подобные комментарии применяются к -
оператор. Грамматика, показанная выше разделений CastExpression в два случая, чтобы сделать это различие. Нетерминальный UnaryExpression включает весь унарный оператор, но нетерминальный UnaryExpressionNotPlusMinus исключает использование всех унарных операторов, которые могли также быть бинарными операторами, которые в Java являются +
и -
.
Вторая потенциальная неоднозначность то, что выражение (p)++
к C или программисту на C++, могло казаться, был или постфиксным инкрементом заключенного в скобки выражения или начало броска, например, в (p)++q
. Как прежде, синтаксические анализаторы для C и C++ знают ли p
имя типа или имя переменной. Но синтаксический анализатор, используя только предвидение с одним маркером и никакой семантический анализ во время синтаксического анализа не был бы в состоянии сказать, когда ++
предварительный маркер, ли (p)
должен считаться Основным выражением или оставлен в покое для более позднего рассмотрения как часть CastExpression.
В Java, результате ++
оператор должен быть числовым, и все имена типов, включенные в, набирает числовые значения, известные ключевые слова. Таким образом, если p
ключевое слово, называя тип примитива, тогда (p)++
может иметь смысл только как бросок префиксного инкрементного выражения, и должен быть операнд такой как q
после ++
. Однако, если p
не ключевое слово, называя тип примитива, тогда (p)++
может иметь смысл только как постфиксный инкремент p
. Подобные комментарии применяются к --
оператор. Нетерминальный UnaryExpressionNotPlusMinus поэтому также исключает использование префиксных операторов ++
и --
.
++
++
оператор является префиксным инкрементным выражением. Результатом унарного выражения должна быть переменная числового типа, или ошибка времени компиляции происходит. Тип префиксного инкрементного выражения является типом переменной. Результатом префиксного инкрементного выражения не является переменная, а значение. Во время выполнения, если оценка выражения операнда завершается резко, то префиксное инкрементное выражение завершается резко по той же самой причине и никакому приращению, происходит. Иначе, значение 1
добавляется к значению переменной, и сумма сохранена назад в переменную. Перед дополнением двоичное числовое продвижение (§5.6.2) выполняется на значении 1
и значение переменной. В случае необходимости сумма сужается сужающимся примитивным преобразованием (§5.1.3) к типу переменной прежде, чем это будет сохранено. Значение префиксного инкрементного выражения является значением переменной после того, как новое значение сохранено.
Переменная, которая объявляется final
не может быть постепенно увеличен, потому что когда доступ a final
переменная используется в качестве выражения, результатом является значение, не переменная. Таким образом это не может использоваться в качестве операнда префиксного инкрементного оператора.
--
--
оператор является префиксным декрементным выражением. Результатом унарного выражения должна быть переменная числового типа, или ошибка времени компиляции происходит. Тип префиксного декрементного выражения является типом переменной. Результатом префиксного декрементного выражения не является переменная, а значение. Во время выполнения, если оценка выражения операнда завершается резко, то префиксное декрементное выражение завершается резко по той же самой причине и никакому decrementation, происходит. Иначе, значение 1
вычитается из значения переменной, и различие сохранено назад в переменную. Перед вычитанием двоичное числовое продвижение (§5.6.2) выполняется на значении 1
и значение переменной. В случае необходимости различие сужается сужающимся примитивным преобразованием (§5.1.3) к типу переменной прежде, чем это будет сохранено. Значение префиксного декрементного выражения является значением переменной после того, как новое значение сохранено.
Переменная, которая объявляется final
не может быть постепенно уменьшен, потому что когда доступ a final
переменная используется в качестве выражения, результатом является значение, не переменная. Таким образом это не может использоваться в качестве операнда префиксного декрементного оператора.
+
+
оператор должен быть примитивным числовым типом, или ошибка времени компиляции происходит. Унарное числовое продвижение (§5.6.1) выполняется на операнде. Тип унарного плюс выражение является продвинутым типом операнда. Результатом унарного плюс выражение не является переменная, а значение, даже если результатом выражения операнда является переменная. Во время выполнения значение унарного плюс выражение является продвинутым значением операнда.
-
-
оператор должен быть примитивным числовым типом, или ошибка времени компиляции происходит. Унарное числовое продвижение (§5.6.1) выполняется на операнде. Тип унарного минус выражение является продвинутым типом операнда. Во время выполнения значение унарного плюс выражение является арифметическим отрицанием продвинутого значения операнда.
Для целочисленных значений отрицание является тем же самым как вычитанием от нуля. Использование Java two's-дополнительное представление для целых чисел, и диапазон two's-дополнительных значений не симметрично, таким образом, отрицание максимального отрицания int
или long
результаты в том же самом максимальном отрицательном числе. Переполнение происходит в этом случае, но никакое исключение не выдается. Для всех целочисленных значений x
, -x
равняется (~x)+1
.
Для значений с плавающей точкой отрицание не является тем же самым как вычитанием от нуля, потому что если x
+0.0
, тогда 0.0-x
равняется +0.0
, но -x
равняется -0.0
. Унарный минус просто инвертирует знак числа с плавающей точкой. Особые случаи интереса:
~
~
оператор должен быть примитивным целочисленным типом, или ошибка времени компиляции происходит. Унарное числовое продвижение (§5.6.1) выполняется на операнде. Тип унарного поразрядного дополнительного выражения является продвинутым типом операнда. Во время выполнения значение унарного поразрядного дополнительного выражения является поразрядным дополнением продвинутого значения операнда; отметьте что во всех случаях, ~x
равняется (-x)-1
.
!
!
оператор должен быть boolean,
или ошибка времени компиляции происходит. Тип унарного логического дополнительного выражения boolean
. Во время выполнения значение унарного логического дополнительного выражения true
если значение операнда false
и false
если значение операнда true
.
boolean
; или проверки, во время выполнения, что ссылочное значение обращается к объекту, класс которого является совместимым с указанным ссылочным типом. CastExpression:См. §15.14 для обсуждения различия между UnaryExpression и UnaryExpressionNotPlusMinus.
(
PrimitiveTypeDimsopt
)
UnaryExpression
(
ReferenceType)
UnaryExpressionNotPlusMinus
Тип выражения броска является типом, имя которого появляется в пределах круглых скобок. (Круглые скобки и тип, который они содержат, иногда вызывают оператором броска.) Результатом выражения броска не является переменная, а значение, даже если результатом выражения операнда является переменная.
Во время выполнения значение операнда преобразовывается, бросая преобразование (§5.4) к типу, определенному оператором броска.
Не все броски разрешаются языком Java. Некоторые броски приводят к ошибке во время компиляции. Например, примитивное значение не может быть брошено к ссылочному типу. Некоторые броски, как могут доказывать, во время компиляции, всегда корректны во время выполнения. Например, это всегда корректно, чтобы преобразовать значение типа класса к типу его суперкласса; во время выполнения такой бросок не должен потребовать никакого специального действия. Наконец, некоторые броски, как могут доказывать, не являются или всегда корректными или всегда неправильными во время компиляции. Во время выполнения такие броски требуют теста. A ClassCastException
бросается, если бросок, как находят, во время выполнения непозволителен.
*
, /
, и %
вызываются мультипликативными операторами. Они имеют тот же самый приоритет и синтаксически левоассоциативны (они группируются слева направо). MultiplicativeExpression:Тип каждого из операндов мультипликативного оператора должен быть примитивным числовым типом, или ошибка времени компиляции происходит. Двоичное числовое продвижение выполняется на операндах (§5.6.2). Тип мультипликативного выражения является продвинутым типом своих операндов. Если этот продвинутый тип
UnaryExpression
MultiplicativeExpression*
UnaryExpression
MultiplicativeExpression/
UnaryExpression
MultiplicativeExpression%
UnaryExpression
int
или long
, тогда целочисленная арифметика выполняется; если этот продвинутый тип float
или double
, тогда арифметика с плавающей точкой выполняется.*
*
оператор выполняет умножение, производя продукт его операндов. Умножение является коммутативной работой, если у выражений операнда нет никаких побочных эффектов. В то время как целочисленное умножение ассоциативно, когда операнды являются всем тем же самым типом, умножение с плавающей точкой не ассоциативно. Если целочисленное умножение переполняется, то результатом являются биты младшего разряда математического продукта как представлено в некотором достаточно большом two's-дополнительном формате. В результате, если переполнение происходит, то знак результата, возможно, не то же самое как знак математического продукта двух значений операнда.
Результатом умножения с плавающей точкой управляют правила арифметики IEEE 754:
*
никогда не бросает исключение на этапе выполнения./
/
оператор выполняет подразделение, производя частное его операндов. Левый операнд является дивидендом, и правый операнд является делителем. Целочисленное деление округляется к 0
. Таким образом, частное, произведенное для операндов n и d, которые являются целыми числами после двоичного числового продвижения (§5.6.2), является целочисленным значением q, чья величина как можно больше, удовлетворяя; кроме того q положителен, когда и n и d имеют тот же самый знак, но q отрицателен, когда и n и d имеют противоположные знаки. Есть один особый случай, который не удовлетворяет это правило: если дивиденд является отрицательным целым числом самой большой величины для ее типа, и делитель -1
, тогда целочисленное переполнение происходит, и результат равен дивиденду. Несмотря на переполнение, никакое исключение не выдается в этом случае. С другой стороны, если значение делителя в целочисленном делении 0
, тогда ArithmeticException
бросается.
Результат подразделения с плавающей точкой определяется спецификацией арифметики IEEE:
/
никогда не бросает исключение на этапе выполнения.%
%
оператор, как говорят, приводит к остатку от его операндов от подразумеваемого подразделения; левый операнд является дивидендом, и правый операнд является делителем. В C и C++, оператор остатка принимает только интегральные операнды, но в Java, это также принимает операнды с плавающей точкой.
Работа остатка для операндов, которые являются целыми числами после двоичного числового продвижения (§5.6.2), производит значение результата так, что (a/b)*b+(a%b)
равно a
. Эти идентификационные данные содержат даже в особом случае, что дивиденд является отрицательным целым числом самой большой величины для ее типа, и делитель -1
(остаток 0
). Это следует из этого правила, что результат работы остатка может быть отрицательным, только если дивиденд отрицателен, и может быть положительным, только если дивиденд положителен; кроме того величина результата всегда является меньше чем величина делителя. Если значение делителя для целочисленного оператора остатка 0
, тогда ArithmeticException
бросается.
5%3 produces 2 (note that 5/3 produces 1) 5%(-3) produces 2 (note that 5/(-3) produces -1) (-5)%3 produces -2 (note that (-5)/3 produces -1) (-5)%(-3) produces -2 (note that (-5)/(-3) produces 1)Результат работы остатка с плавающей точкой как вычислено
%
оператор не является тем же самым как произведенным работой остатка, определенной IEEE 754. Работа остатка IEEE 754 вычисляет остаток от округляющегося подразделения, не подразделения усечения, и таким образом, его поведение не походит на это обычного целочисленного оператора остатка. Вместо этого язык Java определяет %
на операциях с плавающей точкой, чтобы вести себя способом, аналогичным тому из целочисленного оператора остатка Java; это может быть по сравнению с библиотечной функцией C fmod
. Работа остатка IEEE 754 может быть вычислена библиотечной подпрограммой Java Math.IEEEremainder
(§20.11.14).Результат Java работа остатка с плавающей точкой определяется правилами арифметики IEEE:
%
никогда не бросает исключение на этапе выполнения, даже если правый операнд является нулем. Переполнение, потеря значимости, или потеря точности не могут произойти.
5.0%3.0 produces 2.0 5.0%(-3.0) produces 2.0 (-5.0)%3.0 produces -2.0 (-5.0)%(-3.0) produces -2.0
+
и -
вызываются аддитивными операторами. Они имеют тот же самый приоритет и синтаксически левоассоциативны (они группируются слева направо). AdditiveExpression:Если тип любого операнда + оператор
MultiplicativeExpression
AdditiveExpression+
MultiplicativeExpression
AdditiveExpression
-
MultiplicativeExpression
String
, тогда работа является конкатенацией строк.
Иначе, тип каждого из операндов +
оператор должен быть примитивным числовым типом, или ошибка времени компиляции происходит.
В каждом случае, типе каждого из операндов двоичного файла -
оператор должен быть примитивным числовым типом, или ошибка времени компиляции происходит.
+
String
, тогда преобразование строк выполняется на другом операнде, чтобы произвести строку во время выполнения. Результатом является ссылка на недавно создаваемый String
объект, который является связью двух строк операнда. Символы левого операнда предшествуют символам правого операнда в недавно создаваемой строке. String
преобразованием строк. Значение x типа примитива T сначала преобразовывается в ссылочное значение как будто, давая это как параметр соответствующему выражению создания экземпляра класса:
boolean
, тогда используйте new
Boolean(
x)
(§20.4).
char
, тогда используйте new
Character(
x)
(§20.5).
byte
, short
, или int
, тогда используйте new
Integer(
x)
(§20.7).
long
, тогда используйте new
Long(
x)
(§20.8).
float
, тогда используйте new
Float(
x)
(§20.9).
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); много классов переопределяют это, особенно Boolean
, Character
, Integer
, Long
, Float
, Double,
и String
.
String
объект. Чтобы увеличить производительность повторной конкатенации строк, компилятор Java может использовать 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
, тогда арифметика с плавающей точкой выполняется.
Дополнение является коммутативной работой, если у выражений операнда нет никаких побочных эффектов. Целочисленное дополнение ассоциативно, когда операнды являются всем тем же самым типом, но дополнение с плавающей точкой не ассоциативно.
Если целочисленное дополнение переполняется, то результатом являются биты младшего разряда математической суммы как представлено в некотором достаточно большом two's-дополнительном формате. Если переполнение происходит, то знак результата не является тем же самым как знаком математической суммы двух значений операнда.
Результат дополнения с плавающей точкой определяется, используя следующие правила арифметики IEEE:
-
оператор выполняет вычитание когда применено к два операнда числового типа, производящего различие его операндов; левый операнд является уменьшаемым, и правый операнд является вычитаемым. И для целочисленного и для вычитания с плавающей точкой, это всегда имеет место это a-b
приводит к тому же самому результату как a+(-b)
. Отметьте, что для целочисленных значений вычитание от нуля является тем же самым как отрицанием. Однако, для операндов с плавающей точкой, вычитание от нуля не является тем же самым как отрицанием, потому что если x
+0.0
, тогда 0.0-x
равняется +0.0
, но -x
равняется -0.0
. Несмотря на то, что переполнение, потеря значимости, или потеря информации могут произойти, оценка числового аддитивного оператора никогда не бросает исключение на этапе выполнения.
<<
, сдвиг вправо со знаком >>
, и сдвиг вправо без знака >>>
; они синтаксически левоассоциативны (они группируются слева направо). Лево-ручной операнд оператора сдвига является значением, которое будет смещено; правый операнд определяет расстояние сдвига. ShiftExpression:Тип каждого из операндов оператора сдвига должен быть примитивным целочисленным типом, или ошибка времени компиляции происходит. Двоичное числовое продвижение (§5.6.2) не выполняется на операндах; скорее унарное числовое продвижение (§5.6.1) выполняется на каждом операнде отдельно. Тип выражения сдвига является продвинутым типом левого операнда.
AdditiveExpression
ShiftExpression<<
AdditiveExpression
ShiftExpression>>
AdditiveExpression
ShiftExpression>>>
AdditiveExpression
Если продвинутый тип левого операнда int
, только пять битов самых низкоуровневых правого операнда используются в качестве расстояния сдвига. Это - как будто правый операнд был подвергнут поразрядной логической операции И &
(§15.21.1) со значением маски 0x1f
. Расстояние сдвига, фактически используемое, находится поэтому всегда в диапазоне от 0 до 31, включительно.
Если продвинутый тип левого операнда long
, тогда только шесть битов самых низкоуровневых правого операнда используются в качестве расстояния сдвига. Это - как будто правый операнд был подвергнут поразрядной логической операции И &
(§15.21.1) со значением маски 0x3f
. Расстояние сдвига, фактически используемое, находится поэтому всегда в диапазоне от 0 до 63, включительно.
Во время выполнения операции сдвига выполняются на дополнительном целочисленном представлении two значения левого операнда.
Значение n<<s
n
лево-смещенный s
позиции двоичного разряда; это эквивалентно (даже если переполнение происходит) к умножению два к питанию s
.
Значение n>>s
n
смещенный на право s
позиции двоичного разряда с расширением знака. Получающееся значение. Для неотрицательных значений n
, это эквивалентно усечению целочисленного деления, как вычислено оператором целочисленного деления /
, два к питанию s
.
Значение n>>>s
n
смещенный на право s
позиции двоичного разряда с дополнением нулями. Если n
положительно, тогда результатом является то же самое как тот из n>>s
; если n
отрицательно, результат равен тому из выражения (n>>s)+(2<<~s)
если тип левого операнда int
, и к результату выражения (n>>s)+(2L<<~s)
если тип левого операнда long
. Добавленный термин (2<<~s)
или (2L<<~s)
уравновешивает распространенный знаковый бит. (Отметьте что из-за неявного маскирования правого операнда оператора сдвига, ~s
поскольку расстояние сдвига эквивалентно 31-s
смещаясь int
значение и к 63-s
смещаясь a long
значение.)
a<b<c
синтаксические анализы как (a<b)<c
, который всегда является ошибкой времени компиляции, потому что тип a<b
всегда boolean
и <
не оператор на boolean
значения. RelationalExpression:Тип реляционного выражения всегда
ShiftExpression
RelationalExpression<
ShiftExpression
RelationalExpression>
ShiftExpression
RelationalExpression<=
ShiftExpression
RelationalExpression>=
ShiftExpression
RelationalExpressioninstanceof
ReferenceType
boolean
. <
, <=
, >
, и >=
int
или long
, тогда сравнение целого числа со знаком выполняется; если этот продвинутый тип float
или double
, тогда сравнение с плавающей точкой выполняется. Результат сравнения с плавающей точкой, как определено спецификацией стандарта IEEE 754:
false
.
-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), обрабатывают отрицательный нуль, как являющийся строго меньшим чем положительный нуль.)
<
оператор true
если значение левого операнда является меньше чем значение правого операнда, и иначе false
.
<=
оператор true
если значение левого операнда меньше чем или равно значению правого операнда, и иначе false
.
>
оператор true
если значение левого операнда больше чем значение правого операнда, и иначе false
.
>=
оператор true
если значение левого операнда больше чем или равно значению правого операнда, и иначе false
. instanceof
instanceof
оператор должен быть ссылочным типом или нулевым типом; иначе, ошибка времени компиляции происходит. ReferenceType упоминал после instanceof
оператор должен обозначить ссылочный тип; иначе, ошибка времени компиляции происходит. Во время выполнения, результат instanceof
оператор true
если значение RelationalExpression не null
и ссылка могла быть брошена (§15.15) к ReferenceType, не повышая a ClassCastException
. Иначе результат false
.
Если бы бросок RelationalExpression к ReferenceType был бы отклонен как ошибка времени компиляции, то instanceof
реляционное выражение аналогично производит ошибку времени компиляции. В такой ситуации, результате instanceof
выражение никогда не могло быть true
.
class Point { int x, y; } class Element { int atomicNumber; } class Test { public static void main(String[] args) { Point p = new Point(); Element e = new Element(); if (e instanceof Point) { // compile-time error System.out.println("I get your point!"); p = (Point)e; // compile-time error } } }Этот пример приводит к двум ошибкам времени компиляции. Бросок
(Point)e
является неправильным потому что никакой экземпляр Element
или любой из его возможных подклассов (ни один не показывают здесь) мог возможно быть экземпляром любого подкласса Point
. instanceof
выражение является неправильным по точно той же самой причине. Если, с другой стороны, класс Point
был подкласс Element
(по общему признанию странное понятие в этом примере): class Point extends Element { int x, y; }тогда бросок был бы возможен, хотя он потребует проверки на этапе выполнения, и
instanceof
выражение тогда было бы разумно и допустимо. Бросок (Point)e
никогда не повышал бы исключение, потому что оно не будет выполняться если значение e
не мог правильно быть брошен, чтобы ввести Point
. 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
, или два операнда, которые являются каждым из ссылочного типа или нулевого типа. Все другие случаи приводят к ошибке времени компиляции. Тип выражения равенства всегда boolean
.
Во всех случаях, a!=b
приводит к тому же самому результату как !(a==b)
. Операторы равенства являются коммутативными, если у выражений операнда нет никаких побочных эффектов.
==
и !=
int
или long
, тогда целочисленный тест равенства выполняется; если продвинутый тип float
или double
, тогда тест равенства с плавающей точкой выполняется. Тестирование равенства с плавающей точкой выполняется в соответствии с правилами стандарта IEEE 754:
==
false
но результат !=
true
. Действительно, тест x!=x
истина если и только если значение x
НЭН. (Методы Float.isNaN
(§20.9.19) и Double.isNaN
(§20.10.17) может также использоваться, чтобы протестировать, является ли значением НЭН.)
-0.0==0.0
true
, например.
==
оператор true
если значение левого операнда равно значению правого операнда; иначе, результат false
.
!=
оператор true
если значение левого операнда не равно значению правого операнда; иначе, результат false
. ==
и !=
boolean
, тогда работа является булевым равенством. boolean
операторы равенства ассоциативны. Результат ==
true
если операнды - оба true
или оба false
; иначе, результат false
.
Результат !=
false
если операнды - оба true
или оба false
; иначе, результат true
. Таким образом !=
ведет себя то же самое как ^
(§15.21.2), когда применялся к булевым операндам.
==
и !=
Ошибка времени компиляции происходит, если невозможно преобразовать тип любого операнда к типу другого преобразованием кастинга (§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
boolean
. Все другие случаи приводят к ошибке времени компиляции.&
, ^
, и |
&
, ^
, или |
имеют примитивный целочисленный тип, двоичное числовое продвижение сначала выполняется на операндах (§5.6.2). Тип выражения логического оператора является продвинутым типом операндов. Для &
, значение результата является поразрядным И значений операнда.
Для ^
, значение результата является битовым исключающим "ИЛИ" значений операнда.
Для |
, значение результата является поразрядным содержащим ИЛИ значений операнда.
Например, результат выражения 0xff00
&
0xf0f0
0xf000
. Результат 0xff00
^
0xf0f0
0x0ff0
Результат.The 0xff00
|
0xf0f0
0xfff0
.
&
, ^
, и |
&
, ^
, или |
оператор имеет тип 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
, тогда правое выражение оценивается, и его значение становится значением условного выражения - и выражение. Таким образом, &&
вычисляет тот же самый результат как &
на boolean
операнды. Это отличается только по этому, правое выражение операнда оценивается условно, а не всегда.
||
||
оператор походит |
(§15.21.2), но оценивает его правый операнд, только если значение его левого операнда false
. Это синтаксически левоассоциативно (это группируется слева направо). Это полностью ассоциативно относительно обоих побочных эффектов и значения результата; то есть, для любых выражений a, b, и c, оценки выражения ((
a)||(
b))||(
c)
приводит к тому же самому результату, с теми же самыми побочными эффектами, происходящими в том же самом порядке, как оценка выражения (
a)||((
b)||(
c))
. ConditionalOrExpression:Каждый операнд
ConditionalAndExpression
ConditionalOrExpression||
ConditionalAndExpression
||
должен иметь тип boolean,
или ошибка времени компиляции происходит. Тип условного выражения - или выражение всегда boolean
.
Во время выполнения левое выражение операнда оценивается сначала; если его значение true
, значение условного выражения - или выражение true
и правое выражение операнда не оценивается. Если значение левого операнда false
, тогда правое выражение оценивается, и его значение становится значением условного выражения - или выражение. Таким образом, ||
вычисляет тот же самый результат как |
на boolean
операнды. Это отличается только по этому, правое выражение операнда оценивается условно, а не всегда.
? :
? :
использует булево значение одного выражения, чтобы решить, какое из двух других выражений должно быть оценено. Условный оператор синтаксически правоассоциативен (это группируется справа налево), так, чтобы a?b:c?d:e?f:g
означает то же самое как a?b:(c?d:(e?f:g))
.
ConditionalExpression:У условного оператора есть три выражения операнда;
ConditionalOrExpression
ConditionalOrExpression?
Expression:
ConditionalExpression
?
появляется между первыми и вторыми выражениями, и :
появляется между вторыми и третьими выражениями.
Первое выражение должно иметь тип boolean
, или ошибка времени компиляции происходит.
Условный оператор может использоваться, чтобы выбрать между вторыми и третьими операндами числового типа, или вторыми и третьими операндами типа boolean
, или вторые и третьи операнды, которые являются каждым из ссылочного типа или нулевого типа. Все другие случаи приводят к ошибке времени компиляции.
Отметьте, что это не разрешается или для второго или для третьего выражения операнда, чтобы быть вызовом a void
метод. Фактически, не разрешается для условного выражения появиться в любом контексте где вызов a void
метод мог появиться (§14.7).
Тип условного выражения определяется следующим образом:
byte
и другой имеет тип short
, тогда тип условного выражения short
.
byte
, short
, или char
, и другой операнд является константным выражением типа int
чье значение является представимым в типе T, тогда тип условного выражения является T.
boolean
значение тогда используется, чтобы выбрать или второе или третье выражение операнда:
true
, тогда второе выражение операнда выбирается.
false
, тогда третье выражение операнда выбирается. a=b=c
средства a=(b=c)
, который присваивает значение c
к b
и затем присваивает значение b
к a
. AssignmentExpression:Результатом первого операнда оператора присваивания должна быть переменная, или ошибка времени компиляции происходит. Этот операнд может быть именованной переменной, такой как локальная переменная или поле текущего объекта или класса, или это может быть вычисленная переменная, как может следовать из доступа к полю (§15.10) или доступ массива (§15.12). Тип выражения присвоения является типом переменной.
ConditionalExpression
Assignment Assignment:
LeftHandSideAssignmentOperator
AssignmentExpression LeftHandSide:
ExpressionName
FieldAccess
ArrayAccess AssignmentOperator: one of
= *= /= %= += -= <<= >>= >>>= &= ^= |=
Во время выполнения результатом выражения присвоения является значение переменной после того, как присвоение произошло. Результатом выражения присвоения не является самостоятельно переменная.
Переменная, которая объявляется final
не может быть присвоен, потому что когда доступ a final
переменная используется в качестве выражения, результатом является значение, не переменная, и таким образом, это не может использоваться в качестве операнда оператора присваивания.
=
Во время выполнения выражение оценивается одним из двух способов. Если левое выражение операнда не является выражением доступа массива, то три шага требуются:
null
, тогда никакое присвоение не происходит и a NullPointerException
бросается.
IndexOutOfBoundsException
бросается.
final
). Но если компилятор не может доказать во время компиляции, что компонент массива будет иметь TC типа точно, затем проверка должна быть выполнена во время выполнения, чтобы гарантировать, что RC класса является присвоением, совместимым (§5.2) с фактическим SC типа компонента массива. Эта проверка подобна броску сужения (§5.4, §15.15), за исключением того, что, если проверка перестала работать, ArrayStoreException
бросается, а не a ClassCastException
. Поэтому:
class ArrayReferenceThrow extends RuntimeException { }
class IndexThrow extends RuntimeException { }
class RightHandSideThrow extends RuntimeException { }
class IllustrateSimpleArrayAssignment {Эта программа печатные издания:
static Object[] objects = { new Object(), new Object() };
static Thread[] threads = { new Thread(), new Thread() };
static Object[] arrayThrow() { throw new ArrayReferenceThrow(); }
static int indexThrow() { throw new IndexThrow(); }
static Thread rightThrow() { throw new RightHandSideThrow(); }
static String name(Object q) { String sq = q.getClass().getName(); int k = sq.lastIndexOf('.'); return (k < 0) ? sq : sq.substring(k+1); }
static void testFour(Object[] x, int j, Object y) { String sx = x == null ? "null" : name(x[0]) + "s"; String sy = name(y); System.out.println(); try { System.out.print(sx + "[throw]=throw => "); x[indexThrow()] = rightThrow(); System.out.println("Okay!"); } catch (Throwable e) { System.out.println(name(e)); } try { System.out.print(sx + "[throw]=" + sy + " => "); x[indexThrow()] = y; System.out.println("Okay!"); } catch (Throwable e) { System.out.println(name(e)); } try { System.out.print(sx + "[" + j + "]=throw => "); x[j] = rightThrow(); System.out.println("Okay!"); } catch (Throwable e) { System.out.println(name(e)); } try { System.out.print(sx + "[" + j + "]=" + sy + " => "); x[j] = y; System.out.println("Okay!"); } catch (Throwable e) { System.out.println(name(e)); } }
public static void main(String[] args) { try { System.out.print("throw[throw]=throw => "); arrayThrow()[indexThrow()] = rightThrow(); System.out.println("Okay!"); } catch (Throwable e) { System.out.println(name(e)); } try { System.out.print("throw[throw]=Thread => "); arrayThrow()[indexThrow()] = new Thread(); System.out.println("Okay!"); } catch (Throwable e) { System.out.println(name(e)); } try { System.out.print("throw[1]=throw => "); arrayThrow()[1] = rightThrow(); System.out.println("Okay!"); } catch (Throwable e) { System.out.println(name(e)); } try { System.out.print("throw[1]=Thread => "); arrayThrow()[1] = new Thread(); System.out.println("Okay!"); } catch (Throwable e) { System.out.println(name(e)); } testFour(null, 1, new StringBuffer()); testFour(null, 1, new StringBuffer()); testFour(null, 9, new Thread()); testFour(null, 9, new Thread()); testFour(objects, 1, new StringBuffer()); testFour(objects, 1, new Thread()); testFour(objects, 9, new StringBuffer()); testFour(objects, 9, new Thread()); testFour(threads, 1, new StringBuffer()); testFour(threads, 1, new Thread()); testFour(threads, 9, new StringBuffer()); testFour(threads, 9, new Thread()); }
}
throw[throw]=throw => ArrayReferenceThrow throw[throw]=Thread => ArrayReferenceThrow throw[1]=throw => ArrayReferenceThrow throw[1]=Thread => ArrayReferenceThrowСамый интересный случай партии является одной тринадцатой от конца:
null[throw]=throw => IndexThrow null[throw]=StringBuffer => IndexThrow null[1]=throw => RightHandSideThrow null[1]=StringBuffer => NullPointerException
null[throw]=throw => IndexThrow null[throw]=StringBuffer => IndexThrow null[1]=throw => RightHandSideThrow null[1]=StringBuffer => NullPointerException
null[throw]=throw => IndexThrow null[throw]=Thread => IndexThrow null[9]=throw => RightHandSideThrow null[9]=Thread => NullPointerException
null[throw]=throw => IndexThrow null[throw]=Thread => IndexThrow null[9]=throw => RightHandSideThrow null[9]=Thread => NullPointerException
Objects[throw]=throw => IndexThrow Objects[throw]=StringBuffer => IndexThrow Objects[1]=throw => RightHandSideThrow Objects[1]=StringBuffer => Okay!
Objects[throw]=throw => IndexThrow Objects[throw]=Thread => IndexThrow Objects[1]=throw => RightHandSideThrow Objects[1]=Thread => Okay!
Objects[throw]=throw => IndexThrow Objects[throw]=StringBuffer => IndexThrow Objects[9]=throw => RightHandSideThrow Objects[9]=StringBuffer => IndexOutOfBoundsException
Objects[throw]=throw => IndexThrow Objects[throw]=Thread => IndexThrow Objects[9]=throw => RightHandSideThrow Objects[9]=Thread => IndexOutOfBoundsException
Threads[throw]=throw => IndexThrow Threads[throw]=StringBuffer => IndexThrow Threads[1]=throw => RightHandSideThrow Threads[1]=StringBuffer => ArrayStoreException
Threads[throw]=throw => IndexThrow Threads[throw]=Thread => IndexThrow Threads[1]=throw => RightHandSideThrow Threads[1]=Thread => Okay!
Threads[throw]=throw => IndexThrow Threads[throw]=StringBuffer => IndexThrow Threads[9]=throw => RightHandSideThrow Threads[9]=StringBuffer => IndexOutOfBoundsException
Threads[throw]=throw => IndexThrow Threads[throw]=Thread => IndexThrow Threads[9]=throw => RightHandSideThrow Threads[9]=Thread => IndexOutOfBoundsException
Threads[1]=StringBuffer => ArrayStoreExceptionкоторый указывает что попытка сохранить ссылку на a
StringBuffer
в массив, компоненты которого имеют тип Thread
броски ArrayStoreException
. Код корректен типом во время компиляции: у присвоения есть левая сторона типа Object[]
и правая сторона типа Object
. Во время выполнения, первый фактический параметр методу testFour
ссылка на экземпляр "массива Thread
"и третьим фактическим параметром является ссылка на экземпляр класса StringBuffer
. +=
, который позволяет правому операнду иметь любой тип, если лево-ручной операнд имеет тип 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);Во время выполнения выражение оценивается одним из двух способов. Если левое выражение операнда не является выражением доступа массива, то четыре шага требуются:
null
, тогда никакое присвоение не происходит и a NullPointerException
бросается.
IndexOutOfBoundsException
бросается.
String
. Поскольку класс String
a 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Значение
1
из k
сохраняется составным оператором присваивания +=
перед его правым операндом (k
=
4)
*
(k
+
2)
оценивается. Оценка этого правого операнда тогда присваивается 4
к k
, вычисляет значение 6
для k
+
2
, и затем умножается 4
6
добраться 24
. Это добавляется к сохраненному значению 1
добраться 25
, который тогда сохранен в k
+=
оператор. Идентичный анализ применяется к случаю, который использует a[0]
. Короче говоря, операторы k += (k = 4) * (k + 2); a[0] += (a[0] = 4) * (a[0] + 2);ведите себя точно тем же самым способом как операторы:
k = k + (k = 4) * (k + 2); a[0] = a[0] + (a[0] = 4) * (a[0] + 2);
Expression:В отличие от C и C++, у языка Java нет никакого оператора запятой.
AssignmentExpression
ConstantExpression:Константное выражение времени компиляции является выражением, обозначающим значение типа примитива или a
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."
Содержание | Предыдущий | Следующий | Индекс
Спецификация языка Java (HTML, сгенерированный Блинчиком "сюзет" Pelouch 24 февраля 1998)
Авторское право © Sun Microsystems, Inc 1996 года. Все права защищены
Пожалуйста, отправьте любые комментарии или исправления к doug.kramer@sun.com