Immutable objects are particularly useful in concurrent applications.
Since they cannot change state, they cannot be corrupted by thread
interference or observed in an inconsistent state.
The following rules define a simple strategy for creating immutable objects.
Lets add all above rules and make something concrete class implementation.
This is the most simple way of making a class mutable.
//No need to make class final here
public class FinalPerson {
private final String name;
private final int age;
private FinalPerson(final String name, final int age) {
super();
this.name = name;
this.age = age;
}
public int getAge() {
return age;
}
public String getName() {
return name;
}
public FinalPerson getFinalPerson(final String name, final int age) {
return new FinalPerson(final String name, final int age);
}
}
Making a class immutable in Java, which includes mutable member
variable.
When an immutable class is implemented, mutable objects passed to or returned
from an immutable object must be properly cloned. Consider the following class declarations: a
DiskDriveInfo class and
a
User class. The
DiskDriveInfo is intended to be immutable. The
User encapsulates which user has shared access to the disk drive. The
User object with shared access is stored as part of the
DiskDriveInfo
object. In the following example, the designer of the class was careful to make
the class
final and all fields
private, and to provide only getter
methods. Is the
DiskDriveInfo class immutable? If not, what needs to
be done to make it so?
class User
{
private String userName;
private String userID;
private int userNode;
User(String name, int node)
{
userName = name;
userNode = node;
}
public void setUserName(String name)
{
userName = name;
}
public void setUserID(String userid)
{
userID = userid;
}
public void setUserNode(int node)
{
userNode = node;
}
public String userName()
{
return userName;
}
}
final class DiskDriveInfo
{
private int driveSize;
private String volumeLabel;
private User driveShare;
DiskDriveInfo(int size, String volLabel, User share)
{
driveSize = size;
volumeLabel = volLabel;
driveShare = share;
}
public int size()
{
return driveSize;
}
public String label()
{
return volumeLabel;
}
public User share()
{
return driveShare;
}
}
The
DiskDriveInfo class is not immutable. Objects of this class can
be changed. Consider the following code that creates a
DiskDriveInfo
object and tests its immutability:
class Test
{
private static final int sizeInMeg = 200;
public static void main(String args[])
{
User share1 = new User("Duke", 10); //1
DiskDriveInfo dd = new DiskDriveInfo(sizeInMeg, "myDrive",
share1); //2
User share = dd.share();
System.out.println("User with shared access is " +
share.userName());
share1.setUserName("Fred"); //3
System.out.println("User with shared access is " +
share.userName());
}
}
If we run the program we will get output like this:
Output
================================================================================
User with shared access is Duke
User with shared access is Fred
What went wrong? This code creates a
User object,
share1, at
//1, with the user name
Duke. A supposedly immutable
DiskDriveInfo
object is created at //2 and is passed a reference to the
User object.
The
DiskDriveInfo object is queried, and the shared owner,
Duke,
is printed. The
User object,
share1, changes its name to
Fred
at //3. When the
DiskDriveInfo object is queried again for the user name,
it discovers that the name changed from
Duke to
Fred.
The problem is that the
DiskDriveInfo constructor receives a reference
to the
User object and does not make a copy, or clone, of this object.
Therefore, the
DiskDriveInfo constructor receives a copy of the reference
to the
User object. Now the
DiskDriveInfo object's
driveShare
field and the local variable,
share1, in
main of class
Test,
reference the same object. Therefore, any changes made through either reference
affect the same object. Figure shows the object layout after the code at //1 is executed.
After the code at //2 is executed, the object layout looks as shown in Figure
Notice that because the reference to the
User object is not cloned,
both the
share1 and
driveShare references share the same
User
object. After the code at //3 is executed, the object layout as shown in Figure.
To correct this problem, the
DiskDriveInfo class must clone any mutable object to which it receives
a reference. It then has a reference to its own copy of the object that cannot
be changed by other code.
The modified
DiskDriveInfo class that supports cloning looks like this:
final class DiskDriveInfo
{
//As before...
DiskDriveInfo(int size, String volLabel, User share)
{
driveSize = size;
volumeLabel = volLabel;
driveShare = (User)share.clone();
}
public User share()
{
return (User)driveShare.clone();
}
}
Because you are cloning the
User object, its definition must change
as well.
class User implements Cloneable
{
//As before...
public Object clone()
{
try {
return super.clone();
}
catch (CloneNotSupportedException e) {
//This should not happen, since this class is Cloneable.
throw new InternalError();
}
}
}
With these changes to the
User object, running the previous test code
produces the correct output:
Output
================================================================================
User with shared access is Duke
User with shared access is Fred
Because the
User object is cloned on the constructor call, the code
that subsequently changes the
User object at //1 has no effect on the
DiskDriveInfo object. The implementation of the immutable
DiskDriveInfo
class is now correct. The object layout looks as shown in Figure