A reference is not an object copy
In int[] a = {4, 8}; int[] b = a;, there are two variables and one array. Assignment copies the reference value. If b[0] becomes 9, reading a[0] also yields 9. If b then receives new int[]{1, 2}, only b points to another array: a[0] remains 9 and b[0] becomes 1. Draw variables and objects separately when interpreting this kind of snippet.
Method arguments are also passed by value. When that value is a reference, the parameter and caller can initially reach the same object. A method can change that object’s elements through the parameter; assigning a new reference to the parameter does not reassign the caller’s variable. Simply saying that an array is "passed by reference" hides this distinction and leads to wrong predictions about update methods.
final and mutability boundaries
final int[] limits = {2, 5}; prevents another assignment to limits after initialization. It does not prevent limits[0] = 3 or a change through another alias. A fixed reference and an immutable object are different properties. Nor is there an automatic lock against concurrent element writes; coordination is covered in its own module.
String has an immutability contract. Calling code.strip() produces a text result without changing the original string. With String code = " ab ";, discarding the result retains the spaces in code. Discarding a method return value is permitted. When you need the result, store it or pass it to the next operation. Removing spaces must respect the input contract; an identifier may require rejecting spaces rather than silently normalizing them.
Equality needs an absence policy
== between references asks whether both denote the same object or both are null. Two new String("READY") creations produce distinct references with equal contents: == is false and equals is true. Avoid using object identity as a substitute for text comparison.
Objects.equals(a, b) accepts null references and treats two null values as equal. That is useful for comparing optional fields, but it does not decide the business rule. For an update requiring both an incoming and stored identifier to be present, two null values cannot authorize the match. Check presence, then content equality. This match does not replace user authorization or prove that the record is still current; those are other operation conditions. The same mechanism can be correct for an optional form field and incomplete for an identity check.
The actual array type still matters
Arrays have a fixed length after creation and indices from 0 through length - 1. A reference variable does not contain those elements; it points to the array. Assigning a String[] to an Object[] reference is permitted. This does not transform the object into an array capable of storing any Object.
In Object[] values = new String[2]; values[0] = 12;, boxing the int value into Integer allows assignment under the static Object type. During the write, the actual array type still requires String or null, so ArrayStoreException occurs. There is no automatic conversion of the number to "12". During diagnosis, distinguish a compilation error, an invalid index and an incompatible component: each requires different evidence. Changing the variable type does not remove restrictions of the created object.
Guided application: an independent snapshot
A component receives an int[][] matrix, stores it and returns it later. Its contract requires original-input changes not to alter stored state, and changes to one returned result not to affect future reads. There is no concurrency; rows may differ in length and may be null. Identity between rows is not part of the contract.
Cloning an int[] creates independent storage for primitive values. Cloning an int[][] creates only a new outer array: references to rows remain shared. Thus copying only received.clone() on input and internal.clone() on output is insufficient. Copy the outer array and every non-null row at both boundaries. Preserve null as null. The policy must include output, because returning the internal matrix would expose the same objects again. If mutable leaf objects are introduced later, revisit this solution; it is not a universal deep-copy recipe.
Text: code unit, code point and meaning
String uses UTF-16. length counts char code units, and charAt returns one unit. Supplementary code point U+1F642 occupies a surrogate pair: a string containing only that point has length 2. codePointAt(0) combines the valid pair and returns the integer code-point value.
This matters when defining text limits and positions in an integration. A limit in bytes, UTF-16 units or code points is not the same rule. Code points also do not necessarily correspond to the visual symbols a person counts. In this lesson, the decision is to identify the unit required by the contract and avoid splitting a pair’s two units. Do not infer a presentation policy from length; make the unit explicit in validation messages and contracts between components.
static int[][] snapshot(int[][] input) {
java.util.Objects.requireNonNull(input);
int[][] result = input.clone();
for (int i = 0; i < result.length; i++) {
if (result[i] != null) result[i] = result[i].clone();
}
return result;
}Read the method as a class member. For {{2}, {7}}, the outer clone initially retains both references; each row clone separates the primitive values. The component must use the same policy when receiving and returning the matrix.
This example assumes no concurrent access and rejects a null outer matrix.
Common pitfalls
Confusing reassignment with mutation; treating final as immutability; accepting two missing identifiers because they compare equal; calling an outer clone a deep copy; counting code points with length.
Related topics: Types, conversions and validation · Objects, interfaces and records · Collections, generics and keys · Concurrency, state and cancellation
Distinguish variables from objects, define equality and absence through the contract, and copy every mutable level crossing an ownership boundary.
Reference: JLS 25: Arrays · Java SE 25; no preview features