Инкремент
++ увеличивает переменную на единицу, декремент
-- уменьшает. У обоих есть две формы: префиксная (
++x) сначала меняет переменную и возвращает новое значение, постфиксная (
x++) возвращает старое значение и только потом меняет переменную. Отдельной строкой обе формы работают одинаково, разница видна только внутри выражения: при
x = 10 выражение
13 - x++ дает 3, а
13 - ++x дает 2, при этом
x в обоих случаях станет 11 (так это описывает и
учебник Oracle). Ниже разберем обе формы на примерах, типичные ошибки и случаи, где инкремент уместен.
Наверняка вы уже сталкивались с этими
i++ и
++i в циклах. И если честно, первый раз видя это, многие думают примерно так: "Ну ладно, i++ понятно... а вот ++i это что вообще такое? И зачем?".
Давайте разбираться. Тема на самом деле простая, но есть один нюанс, который часто ставит новичков в тупик.
![Абстрактная заставка: строки кода на фоне цветных вертикальных полос от синего к красному]()
Что такое инкремент и декремент простыми словами
Начнем с базы.
Инкремент это когда вы увеличиваете переменную на единицу. Пишется как два плюса:
++.
Декремент, соответственно, это когда уменьшаете на единицу. Два минуса:
--.
Можно представить это так: вы охранник на входе в клуб и считаете гостей. Зашел один человек: щелк на счетчике, плюс один. Это инкремент. Вышел кто-то: минус один, декремент. Все просто, правда?
Вот как это выглядит в коде:
int visitors = 10;
visitors++; // теперь visitors = 11
visitors--; // снова visitors = 10
System.out.println(visitors); // выведет: 10
Да, можно было бы написать
visitors = visitors + 1. Но согласитесь,
visitors++ выглядит куда лаконичнее. Зачем усложнять жизнь?
Префиксная и постфиксная формы: вот тут и начинается веселье
А теперь держитесь! У инкремента и декремента есть две формы записи, и именно здесь обычно начинаются вопросы.
Префиксная форма (prefix) это когда оператор стоит
перед переменной:
++x или
--x.
Постфиксная форма (postfix) это когда оператор идет
после:
x++ или
x--.
Смотрите. Если вы просто увеличиваете переменную, без всяких вычислений, то абсолютно без разницы, какую форму использовать:
int x = 10;
int y = 10;
x++; // x теперь 11
++y; // y тоже 11
System.out.println("x: " + x + ", y: " + y); // выведет: x: 11, y: 11
Результат один и тот же. Но! Когда вы начинаете использовать инкремент
прямо внутри какого-то выражения, вот тут начинается интересное.
Постфиксная форма: используй сейчас, увеличь потом
Постфиксная форма работает по принципу "сначала отдай то, что есть, потом измени".
Помните нашего охранника с табличкой? Представьте: заходит посетитель, второй охранник спрашивает "сколько народу?". Вы показываете табличку с текущей цифрой (допустим, 10), он записывает это число. И только после этого вы меняете цифру на табличке на 11.
int visitors = 10;
int reportedNumber = visitors++;
System.out.println("Сообщили: " + reportedNumber); // выведет: 10
System.out.println("Реально в зале: " + visitors); // выведет: 11
Видите? Сначала значение
visitors (которое было 10) ушло в
reportedNumber. И только потом
visitors увеличился до 11.
Префиксная форма: измени сразу, потом используй
С префиксом все наоборот. Тут правило "сначала измени, а потом работай с новым значением".
Та же ситуация с охранником, но теперь вы сначала меняете цифру на табличке (10 становится 11), а уже потом показываете ее коллеге. Он и записывает 11.
int visitors = 10;
int reportedNumber = ++visitors;
System.out.println("Сообщили: " + reportedNumber); // выведет: 11
System.out.println("Реально в зале: " + visitors); // выведет: 11
Разбираем на живых примерах
Ладно, теория это хорошо. Но давайте посмотрим, как это реально работает в коде с вычислениями:
int x = 10;
int c = 10;
int y = 13 - x++; // постфиксная форма
int z = 13 - ++c; // префиксная форма
System.out.println("y: " + y + ", z: " + z); // выведет: y: 3, z: 2
System.out.println("x: " + x + ", c: " + c); // выведет: x: 11, c: 11
Стоп. И
x, и
c изначально равны 10. Выражения вроде одинаковые. Но результаты разные:
y = 3, а
z = 2! Как так?
Давайте по шагам разберем каждый случай.
Постфиксная форма (x++):
// x = 10
// Шаг 1: берем текущее значение x (оно пока 10) и используем в вычислении
y = 13 - 10; // получается y = 3
// Шаг 2: и только сейчас x увеличивается
x = 11;
Префиксная форма (++c):
// c = 10
// Шаг 1: сразу увеличиваем c
c = 11;
// Шаг 2: теперь используем уже новое значение (11) в вычислении
z = 13 - 11; // получается z = 2
Вот и вся разница! В первом случае мы взяли старое значение для расчета, во втором сначала изменили, потом посчитали.
Все четыре формы в одной таблице
Для наглядности сведем обе формы инкремента и декремента вместе (во всех примерах
x изначально равен 10):
| Форма |
Что возвращает в выражении |
Когда меняется переменная |
Пример |
Итог |
| x++ (постфикс) |
старое значение |
после того, как значение использовано |
y = 13 - x++ |
y = 3, x = 11 |
| ++x (префикс) |
новое значение |
до использования |
z = 13 - ++x |
z = 2, x = 11 |
| x-- (постфикс) |
старое значение |
после того, как значение использовано |
y = 13 - x-- |
y = 3, x = 9 |
| --x (префикс) |
новое значение |
до использования |
z = 13 - --x |
z = 4, x = 9 |
Типичные ошибки (и как не наступать на эти грабли)
Ошибка 1: "Почему i++ не возвращает увеличенное значение?"
Классика! Многие ожидают, что
i++ сразу вернет новое значение. Но нет:
int i = 5;
System.out.println(i++); // выведет 5, а не 6!
System.out.println(i); // а вот теперь будет 6
Если нужно именно увеличенное значение сразу, используйте префикс:
int i = 5;
System.out.println(++i); // выведет 6
Ошибка 2: Сложные выражения с кучей инкрементов
Вот это вообще не делайте, пожалуйста:
int x = 5;
int result = x++ + ++x + x++; // кто вообще это придумал?!
Серьезно, даже опытные разработчики будут минут 10 разбираться, что тут происходит. А еще легко ошибиться. Для справки: операнды вычисляются строго слева направо (
JLS, раздел 15.7), так что при
x = 5 получится 5 + 7 + 7 = 19, а
x станет 8. Если такое вычисление действительно нужно, лучше расписать его по шагам:
int x = 5;
int first = x; // x++ отдает старое значение 5...
x++; // ...и только потом x = 6
x++; // ++x: сначала x = 7...
int second = x; // ...потом отдает новое значение 7
int third = x; // x++ отдает 7...
x++; // ...потом x = 8
int result = first + second + third; // 19, как и в одной строке
Да, получилось длиннее. Зато через месяц, когда вы вернетесь к этому коду, сразу поймете, что к чему.
Ошибка 3: Инкремент в индексах массивов
Еще одна засада: инкремент прямо в индексе массива:
int[] numbers = {10, 20, 30};
int i = 0;
// Что тут вообще произойдет?
numbers[i] = numbers[i++];
Тут можно такого наворотить... Формально в Java (в отличие от C) порядок здесь определен: сначала вычисляется левая часть, то есть индекс
i (пока 0), потом правая,
numbers[0], и только затем
i становится 1 (
JLS, раздел 15.26.1). В итоге
numbers[0] = 10,
i = 1. Но держать это в голове не нужно. Лучше не рисковать и писать явно:
numbers[i] = numbers[i];
i++;
Скучно? Может быть. Зато работает предсказуемо.
А где это вообще применяется?
Нормальные варианты использования:
1. В циклах for, тут это классика жанра:
for (int i = 0; i < 10; i++) {
System.out.println(i);
}
2. Счетчики:
int errorCount = 0;
if (hasError) {
errorCount++;
}
3. Перебор элементов в цикле:
int index = 0;
while (index < array.length) {
process(array[index++]);
}
Когда лучше НЕ использовать:
- Если выражение получается сложным и непонятным, лучше разбить на несколько строк
- Иногда просто
+= 1 будет читаться понятнее
- В современном Java вообще часто можно обойтись
forEach или
Stream API вместо циклов со счетчиками
Коротко о главном
Давайте еще раз пробежимся по основным моментам:
•
++ увеличивает на единицу,
-- уменьшает
•
x++ (постфикс): используем старое значение, потом меняем переменную
•
++x (префикс): сначала меняем переменную, потом используем новое значение
• Если инкремент стоит отдельной строкой, без разницы, какую форму использовать
• Не пишите сложные выражения с кучей инкрементов. Просто не надо.
• Золотое правило: если сами не уверены, что код делает, упростите его
Знаете, инкремент и декремент это штуки простые, но поначалу могут здорово запутать. Особенно эта разница между префиксом и постфиксом. Не переживайте, если с первого раза не все понятно: это нормально!
Лучший способ разобраться: просто больше практиковаться. Попробуйте написать несколько примеров самостоятельно, поэкспериментируйте с разными вариантами. Запустите код, посмотрите что выводится. Очень скоро вы будете использовать эти операторы автоматически, даже не задумываясь.
Удачи в изучении Java!
Вопросы и ответы
Чем ++i отличается от i++?
Обе записи увеличивают
i на единицу. Разница только в том, что возвращает выражение:
++i (префикс) возвращает уже увеличенное значение,
i++ (постфикс) возвращает старое, а переменную увеличивает после. В отдельной строке (
i++; или
++i;) разницы нет, она проявляется внутри выражения: при
i = 5 вызов
System.out.println(i++) печатает 5, а
System.out.println(++i) печатает 6.
Что быстрее: i++ или ++i?
В Java для примитивов разницы нет. Когда результат выражения не используется, javac компилирует обе записи в одинаковый байткод (для локальной переменной
int это одна инструкция
iinc). Выбирайте форму по смыслу, а не ради скорости; в циклах
for принято писать
i++.
К каким типам можно применять ++ и --?
К любой переменной числового типа:
byte,
short,
char,
int,
long,
float,
double, а также к их оберткам вроде
Integer (с распаковкой и обратной упаковкой). К
boolean,
String и
final-переменным нельзя: будет ошибка компиляции. Результат приводится к типу переменной, поэтому
byte b = 127; b++; даст -128 (
JLS, раздел 15.14.2).
Чем x++ отличается от x += 1 и x = x + 1?
Как отдельная инструкция все три увеличивают
x на единицу. Отличия два.
x++ и
++x внутри выражения возвращают старое или новое значение,
x += 1 всегда новое. И
x++ с
x += 1 неявно приводят результат к типу переменной, а
x = x + 1 нет: для
byte b запись
b = b + 1 не скомпилируется без приведения, потому что
b + 1 это уже
int, а
b++ и
b += 1 скомпилируются (
JLS, раздел 15.26.2). Подробнее об
операциях над числами есть отдельная статья.
Что будет, если увеличить Integer.MAX_VALUE на единицу?
Переполнение без исключения:
int x = Integer.MAX_VALUE; x++; даст -2147483648, то есть
Integer.MIN_VALUE. Java не проверяет переполнение ни при
++, ни при обычной арифметике. Если переполнение нужно поймать, используйте
Math.incrementExact(x): он бросит
ArithmeticException.
Почему x++ + ++x + x++ при x = 5 дает 19?
Операнды вычисляются строго слева направо (
JLS, раздел 15.7). Первый
x++ возвращает 5 и делает
x = 6;
++x делает
x = 7 и возвращает 7; второй
x++ возвращает 7 и делает
x = 8. Итого 5 + 7 + 7 = 19, а
x равен 8. Сама спецификация советует не опираться на это правило: в выражении должен быть максимум один побочный эффект.
Читайте также
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ