Practical Java Praxis 63: Define and Implement Immutable Classes Judiciously
This content is excerpted from Peter's book, Practical Java, (Addison-Wesley, 2000).
Immutable objects can be a valuable and necessary construct in object-oriented programming. Sometimes you want to prohibit an object from ever being changed. By definition, an immutable object is an object and any object it references that does not change after construction. The object is, therefore, immutable for its lifetime. Immutable classes are commonly used to represent strings, colors, and numeric values.
Immutable objects provide a valuable service. Because they guarantee that their state cannot change after construction, they are inherently thread-safe. Thread concurrency issues are relevant when one thread can change data while another thread is reading the same data. Because an immutable object never changes its data, synchronizing access to it is not needed.
Depending on the design of the immutable object, this lack of synchronization can have enormous performance benefits. However, any performance gains achieved by immutable objects can be negated by the extra code that you sometimes must implement to support them. For example, implementing immutable objects often requires you to implement cloning, which can be expensive. Cloning is discussed in detail in the second article in this series.
While immutability is a property of an object, it must be coded explicitly. There is no keyword in Java to specify immutability. However, several aspects of a class's definition and implementation enable immutability:
-
Declare all data in the class private.
-
Provide only getter methods. No setter methods are allowed.
-
Declare the class final.
-
Clone mutable objects before returning a reference to them from a getter method (see the second article in this series).
-
Clone objects provided to the constructor that are references to mutable objects (see the second article in this series).
-
Set all data contained in the class in the constructor.
Because an immutable object cannot be changed, all data must be declared private. If it is not, the data—and, therefore, the object—can be changed.
No setter methods are allowed because they change class data. In addition, the class must be declared final, to prevent its being subclassed. A subclass could provide setter methods or override one of the getter methods and return a value not consistent with the base class.
Furthermore, before a reference to any mutable object may be passed to the constructor or returned from a getter method, the object must be cloned first. If it is not, immutability can be lost (see the second article in this series). Because of these restrictions, all data relevant to the class's immutability must be set by the class constructor. Consider the following immutable class:
final class PinNumbers { private String acctOwner; private int checkingAcctPin; private int savingsAcctPin; PinNumbers(String owner, int cPin, int sPin) { acctOwner = owner; checkingAcctPin = cPin; savingsAcctPin = sPin; } public String accountOwner() { return acctOwner; } public int checkingPin() { return checkingAcctPin; } public int savingsPin() { return savingsAcctPin; } //... }
This class is declared final to prevent subclassing. All of its data is declared private, and it provides only getter methods to access that data. Furthermore, all of the data is set by the constructor. These attributes ensure that an object of this class cannot change after it is created. You must also be sure that none of the methods for the class changes the internal data of the class and thereby breaks immutability.
This class does not have to clone any data because the only data types that it receives in the constructor, or returns from its methods, are primitive types and object references to immutable objects. Primitive types are not objects, and, therefore, cloning makes no sense for them. The String class is immutable, so there is no need to clone it. The second article in this series discusses the details of cloning mutable objects.
About the Author
Peter Haggar is a senior software engineer with IBM in Research Triangle Park, North Carolina, and the author of the best-selling book Practical Java, published by Addison-Wesley. Having worked on development tools, class libraries, and operating systems, he has a broad range of programming experience. At IBM, Peter works on emerging Java technology and, most recently, on real-time Java. He is also a frequent technical speaker on Java technology at numerous industry conferences. Peter received his bachelor of science degree in computer science from Clarkson University. He can be contacted at haggar@us.ibm.com.