Modifying mutable attributes in Java
March 4, 2011
In Java, it is tempting to think that a private attribute cannot be modified by any method outside of the class it is defined in. After all, this is the purpose of the visibility modifiers. However, this is only always true for immutable object attribute. With a mutable object attribute, it is possible to modify the content of the attribute outside of its class.
Consider the following code:
class Trainee {
private String name;
private Module module;
public Trainee(String name, int marks) {
this.name = name;
this.module = new Module(marks);
}
public Module getModule() {
return this.module;
}
public String toString() {
return this.name + " scored " + this.module.getMarks();
}
}
class Module {
private int marks;
public Module(int marks) {
setMarks(marks);
}
public void setMarks(int marks) {
this.marks = marks;
}
public int getMarks() {
return this.marks;
}
}
public class TraineeDriver {
public static void main(String argv[]) {
Trainee trainee = new Trainee("Sam", 40);
System.out.println(trainee);
Module moduleOutside = trainee.getModule();
moduleOutside.setMarks(100);
System.out.println(trainee);
}
}
Will produce the output
Sam scored 40 Sam scored 100
We have successfully modified the module attribute, even though the visibility of module is private!
Wait what?
So how did we manage to modify the module marks? I draw your attention to the following snippet from the above code:
public Module getModule() {
return this.module;
}
Recall that all non-primitive (not int, char, boolean, float etc) “variables” in Java are essentially pointers to objects (aka object references). This is the reason why we cannot compare two Strings with the == operator. (Doing so compares whether the two Strings refer to the same object in memory, and not whether the two Strings are lexicographically equal)
Thus, the getModule method above returns the object reference to the module attribute in the object. With this object reference, we can perform all the public methods of the object. In particular, we can call the setMarks method to modify the marks. And doing so would definitely affect the module attribute in the trainee object, as they are referring to the same Module object!
class Trainee {
private Module module;
public Trainee(String name, int marks) {
// let us assume this.module holds the address 0001
this.module = new Module(marks);
}
public Module getModule() {
// return address 0001
return this.module;
}
}
public class TraineeDriver {
public static void main(String argv[]) {
// moduleOutside holds address 0001!
Module moduleOutside = trainee.getModule();
// We are modifying the object at memory address 0001!
moduleOutside.setMarks(100);
}
}
Okay, so technically, we did not modify the module attribute in the trainee object. The module attribute in the trainee object still holds the address 0001. But since the Module class is a mutable class, we are able to modify the content of its object after creation, as long as we have the address of the object. This, however, is probably not the intended outcome of the programmer. The programmer had expected that the method caller would receive a copy of the object, not the object reference!
Mutable v.s. Immutable
So what are mutable and immutable class? Immutable class means that the content of the object cannot be modified after its creation, and the opposite holds true for mutable object.
For example, consider the following snippet:
class MutableMarks {
private int marks;
public MutableMarks(int marks) {
setMarks(marks);
}
// the following method changes the private attribute marks
public void setMarks(int marks) {
this.marks = marks;
}
}
class ImmutableMarks {
private double marks;
public ImmutableMarks(double marks) {
this.marks = marks;
}
}
The difference between MutableMarks and ImmutableMarks is that MutableMarks has a mutator, setMarks, that allows its user to modify one of its attributes. Examples of immutable classes in Java are String class and Integer class. Examples of mutable classes are the Date class and Stringbuilder class.
We can see that immutable objects would not suffer from this issue.
Workarounds
Naturally, as this behavior is most likely not intended, we would need to modify our program to ensure correct outcome. The first and most obvious solution would be to simply make our Marks class immutable. In fact, it is a good practice to design classes that are immutable, as being immutable immediately brings about a whole host of benefits.
However, if we are unable to modify the nature of the class (say we want to have a Date attribute, which is a mutable), there is another solution, known as defensive copying. The idea is simple; instead of returning the object reference, we simply return a copy of a object with the same content.
public Module getModule() {
int marks = this.module.getMarks();
Module copyModule = new Module(marks);
return copyModule;
// or more simply:
return new Module( this.module.getMarks() );
}
By the doing the above, we can ensure that the module attribute in the object remains unchanged.