Каковы правила для порядка оценки в Java?


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

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

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

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

6 78

6 ответов:

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

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

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

правила приоритета и ассоциативности накладывают ограничения что:

  • операция индексатора должна выполняться до операции, связанной с левой операцией назначения
  • операция, связанная с правой операцией назначения, должна выполняться до операции, связанной с левой операцией назначения.

приоритет и ассоциативность только говорят нам, что присвоение ноль до b должно произойти до задание a[b]. Старшинство и ассоциативность ничего не говорит о том,a[b] оценивается до или после the b=0.

опять же, это точно так же, как: A()[B()] = C() -- все, что мы знаем, это то, что индексация должна произойти до назначения. Мы не знаем, работает ли A (), B () или C() первым на основе приоритета и ассоциативности. Нам нужно другое правило, чтобы сказать нам это.

правило, опять же, "когда у вас есть выбор о том, что делать во-первых, всегда идите слева направо": the a[b] налево на b=0, так что a[b] работает первый, в результате a[1]. Тогда b=0 происходит, а затем присвоение значения!--14--> происходит в последнюю очередь.

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

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

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

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-хотя ему не хватает оператора присваивания per se по причинам, не относящимся здесь - и Python L2R но R2L в отношении назначения Это странно, но там вы идете.)

a[b] = b = 0;

1) оператор индексирования массива имеет более высокий приоритет, чем оператор присваивания (см. ответ):

(a[b]) = b = 0;

2) Согласно 15.26. Операторы присваивания JLS

существует 12 операторов присваивания; все они синтаксически правоассоциативны (они группируются справа налево). Таким образом, A=B=С означает А=(B=С), которая присваивает значение C к B, а затем присваивает значение b, чтобы а.

(a[b]) = (b=0);

3) Согласно 15.7. Порядок оценки JLS

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

и

левый операнд двоичного оператора, по-видимому, полностью вычисляется перед вычислением любой части правого операнда.

так:

a)(a[b]) оценивается сначала до a[1]

B), то (b=0) оценка для 0

c)(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/

public class TestClass{    
      public static void main(String args[] ){       
          int i = 0 ;
          int[] iA = {10, 20} ;
          iA[i] = i = 30 ;
          System.out.println(""+ iA[ 0 ] + " " + iA[ 1 ] + "  "+i) ;     
 } }

Он будет печатать 30 20 30

оператор iA[i] = i = 30; будет обработан следующим образом:

iA[i] = i = 30; = > iA[0] = i = 30 ; => i = 30; iA[0] = i ; => iA[0] = 30 ;

вот что говорит JLS по этому поводу:

1 Сначала Вычислите Левый Операнд
2 оцените операнды перед операцией
3 оценка учитывает скобки и приоритет
4 списки аргументов вычисляются слева-направо

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