Minborg

Minborg
Minborg

Tuesday, October 21, 2014

Protect Your Immutable Object Invariants in More Complex Java Objects

Background

Duke and Spire Locking in Objects
By using Immutable Objects, which are objects that can not be observed to change once they are created, you gain a number of advantages including inherent thread safety, improved run time efficiency and code robustness. But how do you protect your object's invariants? This post shows some of the fundamental schemes that can be used to ensure that your objects remain immutable. The examples in this post requires Java 8 but it is relatively easy to rewrite them in Java 7 or lower.

Immutable objects are used extensively in the open-source project Speedment that I am contributing to. With Speedment we can view database tables as standard Java 8 streams. Check out Speedment on GitHub.

Simple Immutable Objects

In my Previous post, I talked a lot about how one can create immutable objects using the Builder Pattern. In this post I will use a more simple approach to create the objects, because I want to focus on another aspect of the immutable objects.

Consider the following Object:

public class Author {

    private final String name;
    private final int bornYear;

    public Author(final String name, final int bornYear) {
        this.name = name;
        this.bornYear = bornYear;
    }

    public String getName() {
        return name;
    }

    public int getBornYear() {
        return bornYear;
    }

}

The object's invariants are protected by the final declarations (that make it impossible to write code in the class that directly changes the properties of the Object), by the private declarations (that make sure that no other class can gain access to the fields) and by the fact that there are no setters for the object's properties (it would in fact, not be possible to write setters because the fields are declared final). Now is a good time to mention that your objects can, in theory, be modified anyhow, for example using Java Reflection. However, this is considered "cheating" ...


Now if we run the following test program we get exactly what one would expect.

public class Main {

    public static void main(String[] args) {
        final Author author = new Author("William Shakespeare", 1564);

        System.out.println(author.getName() + " was born in " + author.getBornYear());

    }

}

William Shakespeare was born in 1564

N.B. Even though author is declared final, this does not affect how methods can be called on the object itself. It only says that the object reference variable author can be assigned only once.

Complex Immutable Objects

Some objects contain more complex properties such a Maps, Sets, Lists, Collections and the likes. Consider the following Author object with an added property consisting of a List of the author's works.

import java.util.List;

public class Author {

    private final String name;
    private final int bornYear;
    private final List<String> works;

    public Author(final String name, final int bornYear, final List<String> works) {
        this.name = name;
        this.bornYear = bornYear;
        this.works = works;
    }

    public String getName() {
        return name;
    }

    public int getBornYear() {
        return bornYear;
    }

    public List<String> getWorks() {
        return works;
    }

}

If we run the following test program, we expose a problem with the "immutable" object that really makes it mutable.

import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public class Main {

    public static void main(String[] args) {
        final List<String> works = Stream.of("Hamlet", "Othello", "Macbeth")
              .collect(Collectors.toList());
        final Author author = new Author("William Shakespeare", 1564, works);
        println(author);

        // NOT GOOD! We can add things to the list after the object is created!
        author.getWorks().add("Harry Potter");
        println(author);
    }

    private static void println(final Author author) {
        System.out.println(author.getName() + " was born in " + author.getBornYear()
                + " and wrote " + author.getWorks().stream().collect(Collectors.joining(", ")));
    }

}

William Shakespeare was born in 1564 and wrote Hamlet, Othello, Macbeth
William Shakespeare was born in 1564 and wrote Hamlet, Othello, Macbeth, Harry Potter

The getWorks() method returns a reference to the same List that we used to construct the Author. Because the original list used during construction was writable, we can now change this List, for example we can add "Harry Potter" to William Shakespeare's list of works! Not good! Back to the drawing board!

UnmodifiableList

By using a static method from the Collections class, we can create a view of an existing List that prevents the underlying List from being modified. This is nice and thus we make a new attempt to fix the problem:

import java.util.Collections;
import java.util.List;

public class Author {

    private final String name;
    private final int bornYear;
    private final List<String> works;

    public Author(final String name, final int bornYear, final List<String> works) {
        this.name = name;
        this.bornYear = bornYear;
        this.works = Collections.unmodifiableList(works);
    }

    public String getName() {
        return name;
    }

    public int getBornYear() {
        return bornYear;
    }

    public List<String> getWorks() {
        return works;
    }

}

And here is the corresponding test program:
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public class Main {

    public static void main(String[] args) {
        final List<String> works = Stream.of("Hamlet", "Othello", "Macbeth").collect(Collectors.toList());
        final Author author = new Author("William Shakespeare", 1564, works);
        println(author);

        // We failed again because we can modify the works List 
        // and it reflects in the Author after creation
        works.add("Harry Potter");
        println(author);
        
        // This works though!
        author.getWorks().add("Harry Potter 2");
        
    }

    private static void println(final Author author) {
        System.out.println(author.getName() + " was born in " + author.getBornYear()
                + " and wrote " + author.getWorks().stream().collect(Collectors.joining(", ")));
    }

}

William Shakespeare was born in 1564 and wrote Hamlet, Othello, Macbeth
William Shakespeare was born in 1564 and wrote Hamlet, Othello, Macbeth, Harry Potter
Exception in thread "main" java.lang.UnsupportedOperationException
 at java.util.Collections$UnmodifiableCollection.add(Collections.java:1115)
 at com.blogspot.minborgsjavapot.immutables._4unmod2.Main.main(Main.java:20)

Again we fail, because even though we now cannot change the List by the reference returned by the getWorks() method, we can still use the original List, used during construction of the Author object, to change the works list after the immutable is created. This is a clear violation against the definition of an immutable object (remeber, no observable change shall be detected after an immutable object is created).

Defensive Copying

By employing Defensive Copying we can protect the immutable object's more complex invariants as shown in the example below:

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class Author {

    private final String name;
    private final int bornYear;
    private final List<String> works;

    public Author(final String name, final int bornYear, final List<String> works) {
        this.name = name;
        this.bornYear = bornYear;
        // Now we make a new List that is a copy of the provided works list
        this.works = Collections.unmodifiableList(new ArrayList<>(works));
    }

    public String getName() {
        return name;
    }

    public int getBornYear() {
        return bornYear;
    }

    public List<String> getWorks() {
        return works;
    }

}

Finally, we can not change the List of works after object creation. The price is that we need to make a new internal copy of the list that is provided during object creation. This can sometimes be a good thing, since we can select the implementation of the internal List in a way that it can be more efficient than the original List. For example, if the List only consists of one element, one can create a defensive copy using the Collections.singletonList() that creates a specialized implementation of a List with exactly one element, potentially much more efficient than a general List. If the List is empty, one can use the Collections.emptyList() that is even more efficient.

The Collections class

There are several useful methods in the Collections class with respect to protecting immutable objects including unmodifiableCollection(), unmodifiableList(), unmodifiableMap() and unmodifiableSet() and more. Use them in your immutable classes!

A Final Warning

In the examples above, we had a list of Strings and we draw to our mind that the class String, for good reasons, is immutable. But suppose that we had a List of some mutable objects such as StringBuilders or other Lists. Then we would have to make defensive copies of them too, recursively until we finally arrive at an immutable object...

Tip: If you only work with immutable objects within your immutable objects then you are better off...

Try it!

Take the code for a spin using "git clone https://github.com/minborg/javapot.git"

Remember "To Protect and to Serve"






1 comment:

  1. I don't think this is an ideal design to return a List but make it unmodifiable under the covers. You are effectively misinforming a user of that object about its return types, as there's nothing on your class that would warn a user that the returned list is read-only and is out of bounds.

    Wouldn't it be much simpler and clearer to change List<String> getWorks() to Iterable<String> getWorks()?

    ReplyDelete

Note: Only a member of this blog may post a comment.