Каковы правила оценки порядка в Java? - PullRequest
81 голосов
/ 23 июля 2011

Я читаю какой-то текст на Java и получил следующий код:

int[] a = {4,4};
int b = 1;
a[b] = b = 0;

В тексте автор не дал четкого объяснения, и эффект последней строки: a[1] = 0;

Я не уверен, что понимаю: как проходила оценка?

Ответы [ 5 ]

167 голосов
/ 23 июля 2011

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

Порядок оценки подвыражений не зависит от ассоциативности и приоритета .Ассоциативность и приоритет определяют, в каком порядке операторы выполняются, но не не определяют, в каком порядке подвыражения оцениваются.Ваш вопрос касается порядка, в котором подвыражений оцениваются.

Рассмотрим 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выбирает последнее.(Разумеется, это различие имеет значение , только если код уже неправильный , потому что правильный код не разыменовывает нуль и не передает плохой индекс.)

Так что жепроисходит?

  • a[b] находится слева от b=0, поэтому a[b] запускает first , в результате a[1].Однако проверка достоверности этой операции индексации задерживается.
  • Затем происходит b=0.
  • Затем происходит проверка того, что a является действительным и a[1] находится в диапазоне
  • Присвоение значения a[1] происходит последним.

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

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

Если эта тема вас интересует, см. мои статьи на эту тему для дальнейшего чтения:

http://blogs.msdn.com/b/ericlippert/archive/tags/precedence/

Они о C #, но большая часть этогоматериал одинаково хорошо применим к Java.

33 голосов
/ 24 июля 2011

Мастерский ответ Эрика Липперта, тем не менее, не совсем полезен, потому что он говорит о другом языке. Это Java, где Спецификация языка Java является окончательным описанием семантики. В частности, §15.26.1 является релевантным, потому что он описывает порядок вычисления для оператора = (мы все знаем, что он ассоциативно справа, да?). Сокращая это немного до кусочков, которые нас интересуют в этом вопросе:

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

  • Сначала оценивается подвыражение ссылки на массив выражения доступа к массиву левого операнда. Если эта оценка завершается преждевременно, то выражение присваивания завершается преждевременно по той же причине; подвыражение индекса (выражения доступа к массиву левого операнда) и правый операнд не оцениваются, и присваивания не происходит.
  • В противном случае вычисляется подвыражение индекса выражения доступа к массиву левого операнда. Если эта оценка завершается преждевременно, то выражение присваивания завершается преждевременно по той же причине, и правый операнд не оценивается, и назначение не происходит.
  • В противном случае вычисляется правый операнд. Если эта оценка завершается преждевременно, то выражение присваивания завершается преждевременно по той же причине, и присвоение не происходит.

[… далее описывается фактическое значение самого назначения, которое мы здесь для краткости можем игнорировать…]

Короче говоря, в Java очень четко определен порядок оценки , который почти точно слева направо в аргументах любого оператора или вызова метода. Назначение массивов - один из самых сложных случаев, но даже там это все еще L2R. (JLS рекомендует, чтобы вы не писали код, который нуждается в таких видах сложных семантических ограничений , и я тоже: вы можете столкнуться с более чем достаточными проблемами только с одним назначением на оператор!)

C и C ++ определенно отличаются от Java в этой области: их определения языка оставляют порядок оценки преднамеренно предопределенным, чтобы обеспечить больше оптимизаций. C # похож на Java, но я не знаю его литературы достаточно хорошо, чтобы указывать на формальное определение. (Это действительно зависит от языка, хотя Ruby является строго L2R, как и Tcl - хотя в нем нет оператора присваивания per se по причинам, не имеющим здесь значения, - а Python - L2R, но R2L в отношении присвоения , что я нахожу странным, но вы идете.)

5 голосов
/ 07 октября 2015
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) (a[b]) оценивается сначала до a[1]

b) затем (b=0) оценивается до 0

в) (a[1] = 0) оценены последними

1 голос
/ 23 июля 2011

Ваш код эквивалентен:

int[] a = {4,4};
int b = 1;
c = b;
b = 0;
a[c] = b;

, что объясняет результат.

0 голосов
/ 16 ноября 2016

Рассмотрим еще один более подробный пример ниже.

Как общее правило:

Лучше иметь таблицу порядка приоритетов и ассоциативности, доступную для чтения при решенииэти вопросы, например, 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/

...