Почему префикс оценивается перед постфиксом в выражении, содержащем оба?

Согласно спецификации приоритета операций Oracle, такие операции, как:

x-- // Returns x, then subtracts 1 from x.

должно иметь приоритет над:

--x // Subtracts 1 from x, then returns x.

Итак, учитывая небольшой фрагмент ниже, почему:

int x = 5;
int y = --x * 5 + x--;
System.out.println(x+" vs "+y);

напечатать 3 vs 24 вместо 3 vs 20 ?

Доработка

Предполагая заданный порядок приоритета операторов, можно разбить строку № 2 фрагмента на следующие псевдоблоки (ранее оцененные значения заключены в квадратные скобки):

  1. Оценить x--

  2. Оценить --x

  3. Оценить [--x] * 5

  4. Оценить [--x * 5] + [x--]

  5. Оценить y = [--x * 5 + x--]

Что тогда разрешится следующим образом:

Через 1 returns 5 sets x to 4

Через 2 sets x to 3 returns 3

Через 3 multiplies 3 by 5 and returns 15

Через 4 adds 15 to 5 and returns 20

Через 5 sets y to 20

Почему возвращаемое значение равно 24, а не 20.

P.S. (Вы получите 24, если вычислите --x перед x--, но это не должно быть так из-за приоритета оператора).

Я слепой или просто плохо разбираюсь в математике или что?


person Ceiling Gecko    schedule 27.08.2015    source источник
comment
возможный дубликат Java: префикс/постфикс операторов инкремента/декремента?   -  person Aaron Kurtzhals    schedule 27.08.2015
comment
@AaronKurtzhals На самом деле это не дубликат. В этом OP знает, как операторы работают изолированно; вопрос касается порядка, в котором они оцениваются, когда они являются частью одного и того же выражения.   -  person Reinstate Monica -- notmaynard    schedule 27.08.2015
comment
Это просто порядок оценки и не слишком много общего с приоритетом оператора как таковым.   -  person Rahul Tripathi    schedule 27.08.2015


Ответы (5)


Операнды операторов всегда оцениваются слева направо, независимо от порядка операций, определяемого приоритетом.

Вот что происходит:

  1. Оцените --x. Устанавливает x в 4, дает 4.
  2. Оцените (--x) * 5. x на данный момент равно 4, поэтому это оценивает 4 * 5 и дает 20.
  3. Оцените x--. Дает 4, устанавливает x в 3.
  4. Оцените ((--x) * 5) + x--;. Добавляет 20 к 4, чтобы получить 24.
  5. Распечатать "3 vs 24".

JLS, раздел 15.7 , состояния:

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

person rgettman    schedule 27.08.2015
comment
Вы хотели исключить оператор присваивания? Вы разбили все остальные операции в этом примере, и хотя это не имеет значения здесь, это могло бы быть, если бы x также был целью присваивания. - person Rainbolt; 27.08.2015

Приоритет оператора не имеет ничего общего с порядком, в котором оцениваются операнды. Операнды всегда оцениваются слева направо.

Приоритет оператора определяет, как оцениваются выражения. Например, выражение 4 * 5 + 6 может означать (4 * 5) + 6 или 4 * (5 + 6). Поскольку * имеет более высокий приоритет оператора, правильным будет первый.

И префикс, и постфикс декремента и инкремента имеют высокий приоритет оператора, поэтому --x * 5 означает (--x) * 5, а не --(x * 5). Очевидно, что последнее выражение вызовет ошибку компилятора, поскольку x * 5 не является переменной. По этой причине декремент и инкремент префикса и постфикса имеют высокий приоритет операций: нет смысла оценивать --x * 5 как --(x * 5).

Таким образом, в вашем случае выражение эквивалентно ((--x) * 5) + (x--). Затем это выражение можно вычислить, где операнды оцениваются строго слева направо.

person Hoopje    schedule 27.08.2015
comment
В этом ответе есть правильная идея, но вы не совсем хорошо объясняете конкретный случай, связанный с операторами до и после увеличения. Кроме того, --(x * 5) не является допустимым кодом Java. - person Bernhard Barker; 27.08.2015

Операнды оцениваются слева направо, что вы считаете справа налево. Итак, --x получает значение 4, затем --x*5, что равно 4*5 как 20. А затем --x*5 + x-- делает его равным 24. И, наконец, x-- оценивается как 3.

Из документов Java:

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

Вот хорошая подробная статья о приоритете Java.

person Rahul Tripathi    schedule 27.08.2015

Итак, компилятор Java находит это выражение:

int x = 5;
int y = --x * 5 + x--;

и должен определить порядок операций для генерации байт-кодов. В строке четыре оператора: --, *, + и --, и ни одной скобки, поэтому хороший компилятор проверяет свою таблицу приоритетов и ставит скобку, которую вы, ленивый программист, пропустили:

((--OP1) * OP2) + (OP3--) 

Обратите внимание, что на этом этапе операнды являются непрозрачными объектами: не имеет значения, являются ли они переменными (которые подразумевают чтение из памяти), константами, вызовами методов, неявными распаковками или чем-то еще — процессу заключения в скобки это знать не нужно.

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

OP1
...
OP2
...
OP3

Давайте перейдем к объектно-ориентированному IntegerVariable, чтобы увидеть более наглядный пример:

public class IntegerVariable {

    private int val;

    public IntegerVariable(int val) {
        this.val = val;
    }

    public int getAndDecrement() {
        return val--;
    }

    public int decrementAndGet() {
        return --val;
    }

    public int get() {
        return val;
    }

    public static IntegerVariable Int(int val) {
        return new IntegerVariable(val);
    }

    public static void main(String... args) {
        IntegerVariable x = Int(5);
        int y = x.decrementAndGet() * Int(5).get() + x.getAndDecrement();
    }
}

Если вы установите точки останова на get(), decrementAndGet() и getAndDecrement(), вы ясно увидите разницу между

  • порядок оценки, который можно отлаживать с помощью виртуальных вызовов. Порядок оценки слева направо, и вам гарантируется, что операнд полностью вычисляется до выполнения операции.
  • приоритет оператора, который в основном просто ставит круглую скобку, которую вы пропустили: y = ((--x) * 5) + (x--)
person Raffaele    schedule 27.08.2015
comment
Это может как бы ответить на вопрос, но далеко не сразу становится очевидным, как и почему (по крайней мере, вы должны добавить небольшое объяснение), и это один из способов, которым вы могли бы получить ответ, отличается от того, как Java получает ответ (и где он задокументирован). - person Bernhard Barker; 27.08.2015

x-- происходит после выполнения инструкции. так что это было как 4*5+4; и после выполнения этого оператора x снова уменьшился до 3.

Вот почему 3 и 24

person Ritesh    schedule 27.08.2015
comment
Я не уверен, что вы неправильно понимаете, что происходит, или просто плохо объясняете это, но, исходя из этого ответа, я бы подумал, что x-- + x-- это 10, хотя на самом деле это 9 (для x = 5). Проверьте другие ответы. - person Bernhard Barker; 27.08.2015
comment
Выбор слов мной был неудачным - person Ritesh; 27.08.2015