Оператор присваивания в Java
Оператор присваивания = — самый используемый оператор Java: он сохраняет значение выражения в переменной. При этом присваивание в Java — не просто команда, а выражение, которое само имеет значение. Из этого свойства вытекают и цепочки вида x = y = z = 100, и популярные идиомы наподобие чтения файла в цикле while.
Синтаксис оператора присваивания
Общая форма оператора присваивания в языке Java:
переменная = выражение; Сначала полностью вычисляется выражение справа, затем результат записывается в переменную слева. Слева всегда стоит переменная (или элемент массива, поле объекта) — записать 10 = x; нельзя, это ошибка компиляции.
Переменная и выражение должны иметь совместимый тип. В int можно присвоить значение byte или short (расширение выполняется автоматически), а вот присвоить long в int без явного приведения типа компилятор не позволит:
int i = 100;
long l = i; // OK: расширяющее преобразование int -> long
// int k = l; // Ошибка компиляции: требуется приведение (int) l Помимо простого =, в Java есть целое семейство составных операторов присваивания — +=, -=, *=, /=, %= и другие, объединяющих арифметическую или битовую операцию с присваиванием. Это отдельная большая тема; их логические разновидности &=, |=, ^= разобраны в уроке о логических операторах.
Цепочки присваиваний и порядок вычисления
Так как присваивание возвращает значение, присваивания можно объединять в цепочки — это удобный способ инициализировать несколько переменных одним значением:
int x, y, z;
x = y = z = 100; // всем трём переменным присвоено значение 100 Оператор = правоассоциативен, поэтому цепочка вычисляется справа налево: x = (y = (z = 100)). Сначала z получает 100; значением выражения z = 100 является 100, и оно присваивается y; наконец значение выражения y = ... присваивается x.
Важно
Цепочка x = y = z = 100 выполняется справа налево: сначала z = 100, потом y = z, потом x = y. Именно поэтому все переменные в цепочке, кроме самой правой части, обязаны быть уже объявлены, а их типы — совместимы со значением справа.
Обратите внимание: цепочка работает только для присваивания. Объявить несколько переменных с инициализацией одной строкой вида int a = b = 5; нельзя, если b ещё не объявлена, — в объявлении каждая переменная инициализируется отдельно.
Присваивание как выражение
Выражение переменная = значение само имеет значение — то, которое было присвоено. Поэтому присваивание можно использовать внутри другого выражения:
int a;
int b = (a = 5) + 10; // a = 5, b = 15 Классическое практическое применение — чтение данных в цикле, когда присваивание и проверка результата совмещены в одном условии:
String line;
while ((line = reader.readLine()) != null) { // читаем, пока есть строки
System.out.println(line);
} Скобки вокруг line = reader.readLine() обязательны: у != приоритет выше, чем у =, и без скобок компилятор попытался бы присвоить line результат сравнения.
У этой гибкости есть обратная сторона, на которой часто ошибаются новички: перепутанные = и ==. С числами такой код не скомпилируется, но с boolean — вполне:
boolean flag = false;
if (flag = true) { // ПРИСВАИВАНИЕ, а не сравнение! Условие всегда true
System.out.println("Выполнится всегда");
} Здесь вместо оператора сравнения == написан оператор присваивания: flag получает значение true, оно же становится значением всего выражения, и ветка выполняется всегда. Для boolean-переменных безопаснее писать просто if (flag) или if (!flag) — тогда опечатка невозможна.
Примитивы и ссылки: что на самом деле копируется
Оператор = всегда копирует значение, хранящееся в переменной. Для примитивных типов это само число или логическое значение, а для ссылочных типов — ссылка на объект, а не сам объект:
| Тип переменной | Что копируется при b = a | Связаны ли переменные после присваивания |
|---|---|---|
Примитив (int, double, boolean…) | Само значение | Нет: изменение b не влияет на a |
Ссылочный тип (объект, массив, String…) | Ссылка на объект | Да: обе переменные указывают на один объект |
С примитивами всё интуитивно — копия независима:
int a = 10;
int b = a; // b получает копию значения
b = 20;
System.out.println(a); // 10 - переменная a не изменилась Со ссылочными типами картина другая: после присваивания обе переменные указывают на один и тот же объект, и изменение объекта через одну переменную видно через другую:
StringBuilder sb1 = new StringBuilder("Java");
StringBuilder sb2 = sb1; // копируется ссылка, объект один
sb2.append(" 21");
System.out.println(sb1); // Java 21 - изменение видно через sb1! Важно
Присваивание ссылочных типов никогда не копирует объект — только ссылку на него. Если нужна независимая копия объекта или массива, её создают явно: конструктором копирования, методом clone(), Arrays.copyOf() и т. п. Само же присваивание одинаково дёшево для любого типа.
Часто задаваемые вопросы
В каком порядке выполняется цепочка x = y = z = 100?
Справа налево: оператор присваивания правоассоциативен, поэтому запись эквивалентна x = (y = (z = 100)). Сначала z получает 100, затем значение выражения z = 100 присваивается y, и в конце — x. В итоге все три переменные равны 100.
Чем оператор = отличается от ==?
Одиночный = — присваивание: он записывает значение в переменную. Двойной == — сравнение на равенство, возвращающее boolean. Опечатка if (flag = true) с boolean-переменной компилируется и делает условие всегда истинным, поэтому для логических переменных пишите if (flag) без сравнения.
Копируется ли объект при присваивании одной переменной другой?
Нет. Для ссылочных типов оператор = копирует только ссылку, и обе переменные начинают указывать на один и тот же объект: изменение объекта через одну переменную видно через другую. Чтобы получить независимую копию, объект нужно копировать явно — например, конструктором копирования или Arrays.copyOf() для массивов.
Можно ли использовать присваивание внутри условия или другого выражения?
Да: присваивание в Java — выражение, значением которого является присвоенное значение. На этом основан приём чтения в цикле: while ((line = reader.readLine()) != null). Скобки вокруг присваивания обязательны, так как приоритет = ниже, чем у сравнения. Злоупотреблять приёмом не стоит — он снижает читаемость.
Презентацию с видео можно скачать на Patreon.
Видео объяснение
Предпочитаете видеоформат? Посмотрите этот урок с примерами и объяснениями.
Комментарии