Я читаю текст Java и получаю следующий код:
int[] a = {4,4};
int b = 1;
a[b] = b = 0;
В тексте автор не дал четкого объяснения, и эффект последней строки таков: a[1] = 0;
Не уверен, что понимаю: как прошла оценка?
Я читаю текст Java и получаю следующий код:
int[] a = {4,4};
int b = 1;
a[b] = b = 0;
В тексте автор не дал четкого объяснения, и эффект последней строки таков: a[1] = 0;
Не уверен, что понимаю: как прошла оценка?
Ответы:
Позвольте мне сказать это очень четко, потому что люди все время неправильно понимают это:
Порядок оценки подвыражений не зависит ни от ассоциативности, ни от приоритета . Ассоциативность и приоритет определяют, в каком порядке выполняются операторы , но не определяют, в каком порядке оцениваются подвыражения . Ваш вопрос касается порядка, в котором оцениваются подвыражения .
Посмотрим A() + B() + C() * D()
. Умножение имеет более высокий приоритет, чем сложение, а сложение является левоассоциативным, поэтому это эквивалентно (A() + B()) + (C() * D())
Но, зная, что это только говорит вам, что первое сложение произойдет до второго сложения, и что умножение произойдет до второго сложения. Он не сообщает вам, в каком порядке будут вызываться A (), B (), C () и D ()! (Он также не сообщает вам, происходит ли умножение до или после первого сложения.) Было бы вполне возможно соблюдать правила приоритета и ассоциативности , скомпилировав это как:
d = D() // these four computations can happen in any order
b = B()
c = C()
a = A()
sum = a + b // these two computations can happen in any order
product = c * d
result = sum + product // this has to happen last
Здесь соблюдаются все правила старшинства и ассоциативности - первое сложение происходит перед вторым сложением, а умножение происходит перед вторым сложением. Очевидно, что мы можем выполнять вызовы A (), B (), C () и D () в любом порядке и при этом соблюдать правила приоритета и ассоциативности!
Нам нужно правило, не связанное с правилами приоритета и ассоциативности, чтобы объяснить порядок, в котором оцениваются подвыражения. Соответствующее правило в Java (и C #) гласит: «Подвыражения оцениваются слева направо». Поскольку A () появляется слева от C (), A () вычисляется первым, независимо от того, что C () участвует в умножении, а A () участвует только в сложении.
Итак, теперь у вас есть достаточно информации, чтобы ответить на ваш вопрос. В a[b] = b = 0
правилах ассоциативности сказано, что это есть, a[b] = (b = 0);
но это не значит, что b=0
запускается первым! Правила приоритета говорят, что индексирование имеет более высокий приоритет, чем присвоение, но это не означает, что индексатор запускается перед крайним правым назначением .
(ОБНОВЛЕНИЕ: в более ранней версии этого ответа были некоторые небольшие и практически несущественные упущения в следующем разделе, который я исправил. Я также написал статью в блоге, описывающую, почему эти правила разумны в Java и C # здесь: https: // ericlippert.com/2019/01/18/indexer-error-cases/ )
Старшинство и ассоциативность только говорят нам , что присвоение нуля , чтобы b
должно произойти , прежде чем присваивание a[b]
, поскольку присваивание нуля вычисляет значение, которое присваивается в операции индексирования. Старшинство и ассоциативность в одиночку ничего не говорят о ли a[b]
оценивается до того или послеb=0
того, как .
Опять же, это то же самое, что: A()[B()] = C()
- Все, что мы знаем, это то, что индексация должна происходить до присвоения. Мы не знаем, запускается ли A (), B () или C () первым, исходя из приоритета и ассоциативности . Нам нужно другое правило, чтобы сказать нам это.
Правило опять же: «когда у вас есть выбор, что делать в первую очередь, всегда идите слева направо». Однако в этом конкретном сценарии есть интересная загвоздка. Считается ли побочный эффект выброшенного исключения, вызванного пустой коллекцией или индексом вне допустимого диапазона, частью вычисления левой части присвоения или частью вычисления самого присвоения? Java выбирает последнее. (Конечно, это различие имеет значение только в том случае, если код уже неверен , потому что правильный код в первую очередь не разыменовывает null и не передает плохой индекс.)
Так что же происходит?
a[b]
находится слева от b=0
, поэтому сначалаa[b]
выполняется , в результате чего . Однако проверка правильности этой операции индексации откладывается.a[1]
b=0
случается.a
действительна и a[1]
находится в диапазоне.a[1]
происходит в последнюю очередь.Итак, хотя в этом конкретном случае есть некоторые тонкости, которые следует учитывать для тех редких случаев ошибок, которые в первую очередь не должны возникать в правильном коде, в целом вы можете рассуждать: что-то слева происходит раньше, чем что-то справа . Это то правило, которое вы ищете. Разговоры о приоритете и ассоциативности сбивают с толку и неуместны.
Люди все время ошибаются , даже те, кому следует знать лучше. Я редактировал слишком много книг по программированию, в которых правила указаны неверно, поэтому неудивительно, что многие люди имеют совершенно неверные представления о взаимосвязи между приоритетом / ассоциативностью и порядком оценки, а именно, что на самом деле такой взаимосвязи нет. ; они независимы.
Если эта тема вас интересует, смотрите мои статьи по этой теме для дальнейшего чтения:
http://blogs.msdn.com/b/ericlippert/archive/tags/precedence/
Они посвящены C #, но большая часть из них одинаково хорошо применима и к Java.
Мастерский ответ Эрика Липперта, тем не менее, не совсем полезен, потому что он говорит о другом языке. Это Java, где спецификация языка Java является окончательным описанием семантики. В частности, актуален §15.26.1, потому что он описывает порядок вычисления =
оператора (мы все знаем, что он правоассоциативен , да?). Сокращая его немного до тех частей, которые нас волнуют в этом вопросе:
Если выражение левого операнда является выражением доступа к массиву ( §15.13 ), то требуется много шагов:
- Сначала оценивается подвыражение ссылки на массив левого выражения доступа к массиву операнда. Если эта оценка завершается внезапно, то выражение присваивания завершается внезапно по той же причине; подвыражение индекса (выражения доступа к массиву левого операнда) и правый операнд не оцениваются, и присваивание не происходит.
- В противном случае вычисляется подвыражение индекса левого выражения доступа к массиву операндов. Если эта оценка завершается внезапно, то выражение присваивания завершается внезапно по той же причине, и правый операнд не вычисляется, и присваивание не происходит.
- В противном случае оценивается правый операнд. Если эта оценка завершается внезапно, то выражение присваивания завершается внезапно по той же причине, и присваивание не происходит.
[... затем описывается фактическое значение самого задания, которое мы можем здесь для краткости игнорировать ...]
Короче говоря, Java имеет очень четко определенный порядок оценки, который в значительной степени точно слева направо в аргументах для любого вызова оператора или метода. Назначение массивов - один из самых сложных случаев, но даже там это L2R. (JLS действительно рекомендует вам не писать код, который требует такого рода сложных семантических ограничений , и я тоже: вы можете столкнуться с более чем достаточным количеством проблем, используя всего одно присвоение для каждого оператора!)
C и C ++ определенно отличаются от Java в этой области: их определения языка намеренно оставляют порядок оценки неопределенным, чтобы обеспечить большую оптимизацию. C # очевидно похож на Java, но я недостаточно хорошо знаю его литературу, чтобы указать на формальное определение. (Это действительно зависит от языка, Ruby строго L2R, как и Tcl, хотя в нем отсутствует оператор присваивания как таковой по причинам, не имеющим отношения к здесь), а Python - это L2R, но R2L в отношении присваивания , что я нахожу странным, но вы идете .)
a[-1]=c
, c
оценивается, прежде чем -1
будет признан недействительным.
a[b] = b = 0;
1) оператор индексации массива имеет более высокий приоритет, чем оператор присваивания (см. Этот ответ ):
(a[b]) = b = 0;
2) Согласно 15.26. Операторы присваивания JLS
Есть 12 операторов присваивания; все они синтаксически ассоциативны справа (они группируются справа налево). Таким образом, a = b = c означает a = (b = c), который присваивает значение c переменной b, а затем присваивает значение b переменной a.
(a[b]) = (b=0);
3) Согласно 15.7. Порядок оценки JLS
Язык программирования Java гарантирует, что операнды операторов будут оцениваться в определенном порядке оценки, а именно слева направо.
а также
Левый операнд бинарного оператора, по-видимому, полностью вычисляется до того, как оценивается какая-либо часть правого операнда.
Так:
а) (a[b])
оценивается сначалаa[1]
б) затем (b=0)
оценивается как0
в) (a[1] = 0)
оценивается последним
Ваш код эквивалентен:
int[] a = {4,4};
int b = 1;
c = b;
b = 0;
a[c] = b;
что объясняет результат.
Рассмотрим еще один более подробный пример ниже.
Лучше всего иметь таблицу правил приоритета и ассоциативности, доступную для чтения при решении этих вопросов, например http://introcs.cs.princeton.edu/java/11precedence/
Вот хороший пример:
System.out.println(3+100/10*2-13);
Вопрос: Что дает указанная выше строка?
Ответ: Применяйте правила приоритета и ассоциативности.
Шаг 1: Согласно правилам приоритета: операторы / и * имеют приоритет над операторами + -. Следовательно, отправная точка для выполнения этого уравнения будет сужена до:
100/10*2
Шаг 2: Согласно правилам и приоритету: / и * имеют одинаковый приоритет.
Поскольку операторы / и * имеют одинаковый приоритет, нам нужно посмотреть на ассоциативность между этими операторами.
В соответствии с ПРАВИЛАМИ АССОЦИАЦИИ этих двух конкретных операторов, мы начинаем выполнять уравнение СЛЕВА НАПРАВО, т.е. 100/10 выполняется первым:
100/10*2
=100/10
=10*2
=20
Шаг 3: Теперь уравнение находится в следующем состоянии выполнения:
=3+20-13
Согласно правилам и приоритету: + и - равны по приоритету.
Теперь нам нужно посмотреть на ассоциативность между операторами + и -. В соответствии с ассоциативностью этих двух конкретных операторов, мы начинаем выполнять уравнение слева направо, т.е. сначала выполняется 3 + 20:
=3+20
=23
=23-13
=10
10 - правильный вывод при компиляции
Опять же, при решении этих вопросов важно иметь при себе таблицу правил приоритета и ассоциативности, например http://introcs.cs.princeton.edu/java/11precedence/
10 - 4 - 3
.
+
стоит унарный оператор (который имеет ассоциативность справа налево), но аддитивные + и - имеют точно такие же мультипликативные * /% left к правильной ассоциативности.