Dev Learning Lab

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

Running with the default settings

  1. 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.
  2. (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.
  3. (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

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

Concepts in this lab

What this lab shows

Settings

Code


      
    

Diagram

Run comparison

Try these