In a computer, there is Random Access Memory (RAM). Each unit in RAM can store one byte (eight bits) of data. Each unit is uniquely addressed through its address. In object-oriented programming, such as Apex, there are two kinds of memory: stack and heap.
Stack memory stores function calls and statically allocated variables. Calling a method adds an entry to the stack called the stack frame. When a method returns, the stack removes that frame; if there are no more frames, the program knows it's done running.
Heap memory stores dynamically allocated memory, either from the new
keyword or a library method, such as Trigger.new
data. These values are tracked by the aforementioned "address", in Apex commonly called a "reference." When no more references to that address exist, the garbage collector (GC) can mark that memory as "free."
To conceptualize this better, try this code:
Account a = new Account(Name='Demo');
Account b = new Account(Name='Demo');
System.debug(a == b); // true
System.debug(a === b); // false
What this code is saying is that while the contents of the account records are the same, they are actually two different copies. Modifying a won't modify b.
So, Trigger.new has a reference that points to a List, and each element inside that list has references to each record, and each field inside each record has a literal value or a reference to a value on the heap.
So, even though there is no ID value in the field, the references provided identify each record uniquely, which is how the trigger knows which record to update, even if you assign the record to another variable or list. The references are copied, rather than the entire object.
It's worth noting that =
is always a copy by reference operation. For example:
Object a = 1;
Object b = a;
System.debug(a == b); // true
System.debug(a === b); // true
b = 1;
System.debug(a == b); // true
System.debug(a === b); // false