Трактуется он так: «взять значение из правой части выражения (часто называемое просто значением) и скопировать его в левую часть (часто называемую именующим выражением)». Значением может быть любая константа, переменная или выражение, но в качестве именующего выражения обязательно должна использоваться именованная переменная (то есть для хранения значения должна выделяться физическая память). Например, вы можете присвоить постоянное значение переменной:
а = 4;
но нельзя присвоить что-либо константе — она не может использоваться в качестве именующего выражения (например, запись 4 = а недопустима).
Для примитивов присвоение выполняется тривиально. Так как примитивный тип хранит данные, а не ссылку на объект, то присвоение сводится к простому копированию данных из одного места в другое. Например, если команда а = b выполняется для примитивных типов, то содержимое b просто копируется в а. Естественно, последующие изменения а никак не отражаются на b. Для программиста именно такое поведение выглядит наиболее логично.
При присвоении объектов все меняется. При выполнении операций с объектом вы в действительности работаете со ссылкой, поэтому присвоение «одного объекта другому» на самом деле означает копирование ссылки из одного места в другое. Это значит, что при выполнении команды c = d для объектов в конечном итоге с и d указывают на один объект, которому изначально соответствовала только ссылка d. Сказанное демонстрирует следующий пример:
//: operators/Assignment.java
// Присвоение объектов имеет ряд хитростей
import static net.mindview.util.Print.*;
class Tank {
int level;
}
public class Assignment {
public static void main(String[] args) {
Tank t1 = new Tank();
Tank t2 = new Tank();
t1.level = 9;
t2.level = 47;
print("1: t1.level: " + t1.level +
", t2.level: " + t2.level);
t1 = t2;
print("2: t1.level: " + t1.level +
", t2.level: " + t2.level);
t1.level = 27;
print("3: t1.level: " + t1.level +
", t2.level: " + t2.level);
}
}
<spoiler text="Output:">
1: t1.level: 9, t2.level: 47
2: t1.level: 47, t2.level: 47
3: t1.level: 27, t2.level: 27
</spoiler> Класс Tank предельно прост, и два его экземпляра (t1 и t2) создаются внутри метода main(). Переменной level для каждого экземпляра придаются различные значения, а затем ссылка t2 присваивается t1, в результате чего t1 изменяется.
Во многих языках программирования можно было ожидать, что t1 и t2 будут независимы все время, но из-за присвоения ссылок изменение объекта t1 отражается на объекте t2! Это происходит из-за того, что t1 и t2 содержат одинаковые ссылки, указывающие на один объект. (Исходная ссылка, которая содержалась в t1 и указывала на объект со значением 9, была перезаписана во время присвоения и фактически потеряна; ее объект будет вскоре удален сборщиком мусора.)
Этот феномен совмещения имен часто называют синонимией (aliasing), и именно она является основным способом работы с объектами в Java. Но что делать, если совмещение имен нежелательно? Тогда можно пропустить присвоение и записать
t1.level = t2.level;
При этом программа сохранит два разных объекта, а не «выбросит» один из них, «привязав» ссылки t1 и t2 к единственному объекту. Вскоре вы поймете, что прямая работа с полями данных внутри объектов противоречит принципам объектно-ориентированной разработки. Впрочем, это непростой вопрос, так что пока вам достаточно запомнить, что присвоение объектов может таить в себе немало сюрпризов.