The labs on this site need JavaScript to run. The same content is written out below.
Learn › Java › Classes and objects
Immutability and defensive copies
The field is final, but its value changed
An Order object holds its list of items in a final field. final means only that the field cannot be made to refer to a different list. It does not prevent the contents of that list from changing. Making a copy instead of storing or returning the original list is called a defensive copy. This lab shows whether outside code can change the contents of the order, depending on whether the constructor and the getter make a copy.
Checked against real execution · The 6 combinations were run on JDK 21 and checked against the results
Concepts in this lab
- Immutable object
- This is an object whose state does not change after it is created. Because its value never changes, it is safe for several threads to use at the same time, you can pass it to other code without worrying that it will be changed, and it is well suited for caching or for use as a key in a hash collection. Declare the fields private final, provide no methods that change the state, and make the class final so that a subclass cannot change its behavior. If a field refers to a mutable object (such as a List or a Date), the constructor and the getters pass copies in and out (defensive copying) so that outside code cannot change the internals.
- Defensive copy
- When an object has a field that refers to a mutable object, such as a list, an array, or a Date, this means storing a copy of what was passed in rather than the original, and returning a copy of what it holds rather than the original. If the originals are stored and returned, outside code and this object refer to the same list, and changing that list from outside changes the internal state of this object. If copies are exchanged, adding or removing elements in the copy does not affect the internal state (if the element objects themselves are mutable, they must be protected separately). When returning, instead of a copy you can also return an unmodifiable view such as Collections.unmodifiableList, but a view changes when the underlying list changes.
- final
- This keyword means that something can no longer be changed. On a variable, it means the value cannot be assigned again; on a method, it means a subclass cannot override it; on a class, it means the class cannot be extended. On a reference variable, it only prevents the variable from pointing to another object; the internal state of that object can still change.
- Encapsulation
- This means bundling an object's state (fields) together with the methods that work on that state, and hiding the internal implementation so that outside code cannot access it directly. If you make the fields private and let values change only through public methods, the methods can reject invalid values, and you can change the internal structure without affecting outside code.
- record
- This is a syntax, which became a standard feature in Java 16, for compactly declaring a (shallowly) immutable class whose purpose is to hold data. You write only the list of fields, and the constructor, accessor methods, equals, hashCode, and toString are generated automatically. All fields are final, but if a field refers to a mutable object (such as a List), the contents of that object can change. It is used to define DTOs and value objects.
Try these
- Default settings — No defense at all. The internal state is changed from outside through two paths.
- Copy only in the constructor — Changing the original list has no effect, but the internal state can still be changed through the getter.
- Copy on both sides — There is no way to change the internal state from outside.
- The getter returns an unmodifiable view — An attempt to change it throws an exception.
- A view only, with no copy in the constructor — The internal state can still be changed through the original list.
Running with the default settings
- An Order is created from the src list ["keyboard", "mouse"]. The constructor stored the list it received in the field as it is. The variable src and order.items refer to the same single list object on the heap.
- (1) src.add("monitor"): Outside code adds an element to the original list. It is the same object, so the order now has 3 items inside as well. The state of the Order changed even though not a single method of Order was called. Declaring items as final does not prevent this.
- (2) order.getItems().add("chair"): The getter returned the internal list itself. Adding to that list is the same as adding to the inside of the order. The order now has 4 items inside.
Result of this run
- final only prevents a variable from being made to refer to a different object. It does not prevent the contents of that object from changing. To make an object immutable, you must make sure that the mutable objects its fields refer to (lists, arrays, Date, and so on) are not shared with outside code.
- The internal state was changed from outside. You must copy the list when you receive it (in the constructor), and when you hand it out (in the getter) you must also copy it or wrap it so that it cannot be modified. Making such copies is called defensive copying.
- The same is true of a record. Its fields are final, but if a field holds a mutable object such as a list, the contents of that object can still change.
Code
class Order {
private final List<String> items; // final: cannot be replaced with another list
Order(List<String> items) {
this.items = items; // stores the list it received as it is
}
List<String> getItems() {
return items; // exposes the internal list itself
}
}
List<String> src = new ArrayList<>(List.of("keyboard", "mouse"));
Order order = new Order(src);
src.add("monitor"); // (1) modifies the original list
order.getItems().add("chair"); // (2) modifies the list returned by the getter
Assumptions of this model
The results of the 6 combinations (the number of items inside and the exception) were checked against the results of running the same code on JDK 21 on this PC. In this case the elements of the list (String) are immutable, so a shallow copy is enough. If the elements themselves were mutable objects, the elements would also have to be copied to separate the two completely.
Previous lab: Inner classes and anonymous classes · Next lab: Strategy pattern