Инкремент постфикса и префикса с лямбда-выражениями

Этот вопрос определенно не повторяется, потому что я имею в виду не общую оценку после/прекремента, а конкретно использование приращения в лямбда-выражениях, что доказуемо отличается. Каждый может подтвердить, что код будет возвращать нули только там, где вы обычно ожидаете увеличения значения переменной. Ответ на этот вопрос не опубликован.

Я хорошо осведомлен о различных моментах оценки и приращения как пост-, так и префиксного приращения. Тем не менее, я не понимаю, почему это возвращает только нули:

Stream.iterate(0, e -> e++)
            .limit(10)
            .forEach(System.out::println);

Проверяя это, я вижу, что e инициализируется как 0 во время первой итерации, а затем увеличивается. Таким образом, вторая итерация должна дать e = 1, но, по-видимому, это не так. Что мне не хватает?

Кроме того, если я реверсирую инкремент, он работает, как и предполагалось, перечисляя все числа от 0 до 9:

Stream.iterate(0, e -> ++e)
            .limit(10)
            .forEach(System.out::println);

person AdHominem    schedule 22.04.2016    source источник
comment
Похоже, вы пропустили, что e++ возвращает предыдущее значение e, когда ++e возвращает обновленное значение. Таким образом, e -> e++ фактически эквивалентно e -> e.   -  person Tunaki    schedule 22.04.2016
comment
Я не думаю, что это дубликат статьи Как работают операторы постинкремента и преинкремента? Проблема AdHominem не в том, что он/она не понимает, что e++ оценивается как старое значение e; это то, что он / она не понимает, почему это то, что он оценивает, а не то, какое значение имеет e впоследствии, это важно.   -  person Gareth McCaughan    schedule 22.04.2016
comment
@GarethMcCaughan Связанный вопрос объясняет это. Например, этот ответ stackoverflow.com/a/2371162/1743880   -  person Tunaki    schedule 22.04.2016
comment
Хотя на самом деле вы бы использовали IntStream.range(0,10).   -  person Brian Goetz    schedule 22.04.2016
comment
@Tunaki Нет, это действительно так. Я думаю, что проблема заключается в ментальной модели AdHominem либо в том, как работает iterate, либо в том, как работают лямбда-выражения, и ни один из них не задается связанным вопросом и не решается ни одним из его ответов.   -  person Gareth McCaughan    schedule 22.04.2016
comment
Что ж, если вы запустите приведенный выше код, любой может подтвердить, что он возвращает нули и что вы ожидаете от оператора посткремента, не происходит — переменная остается прежней.   -  person AdHominem    schedule 23.04.2016
comment
@AdHominem Смотрите мой первый комментарий, в котором я объяснил, почему он всегда возвращает 0. e -> e++ на самом деле то же самое, что e -> e, потому что e++ возвращает предыдущее значение e в соответствии с правилами постфиксного оператора, описанными в связанном вопросе. И Stream.iterate(0, e -> e) может быть только потоком нулей.   -  person Tunaki    schedule 23.04.2016
comment
Я знаю, но это не объясняет, почему значение никогда не увеличивается. Согласно документам: result++; and ++result; will both end in result being incremented by one. Так почему нет приращения?   -  person AdHominem    schedule 23.04.2016


Ответы (1)


Основная проблема заключается в том, что вы используете выражения, предназначенные для побочного эффекта, который не имеет никакого эффекта. Хорошие IDE, компиляторы или инструменты аудита должны выдавать предупреждение об этом.

Лямбда-выражение вида e -> expression имеет параметр с именем e. Это не следует путать с переменной кучи. Выражения e++ и ++e будут модифицировать параметр, который не имеет эффекта, который будет длиться дольше, чем вычисление одной функции. Это не отличается, например. такой метод, как

static int myFunction(int e) {
    return e++; // or ++e
}

Модификация e не имеет постоянного эффекта, единственное, что имеет значение, это то, какое значение в конечном итоге будет возвращено, поэтому e -> ++e эквивалентно e -> e+1, а e -> e++ эквивалентно e -> e, и поэтому вы должны использовать либо e -> e+1, либо e -> e, чтобы подчеркнуть, что будет возвращено. без выполнения отвлекающего побочного эффекта для переменной параметра.

Но, как упоминалось в комментариях, в этом конкретном случае вы должны использовать IntStream.range(0, 10) вместо Stream.iterate(0, e -> e+1).limit(10). Мало того, что IntStream.range четко задокументирует намерение, в текущей реализации это дает преимущество в производительности для многих потоковых операций.

person Holger    schedule 25.04.2016