In the previous lesson we looked at immutable classes.
Preventing the compromisation of immutable classes
Whenever you decide to create an immutable class, you need to take steps to ensure the immutability is not compromised, since you may have coded the rest of the application on the strict assumption that the attribute values cannot change. At the moment the immutability of Email could be compromised by creating a subclass which adds a mutable attribute and overrides one of the getter methods:
package com.example.util;
public class CompromisedEmail extends Email {
private String spamAddress;
public CompromisedEmail(String email) {
super(email);
}
public void setSpamAddress(String spamAddress) {
this.spamAddress = spamAddress;
}
@Override
public String getEmail() {
return spamAddress;
}
}
The CompromisedEmail class breaks the immutability of Email in two separate ways:
- By declaring an additional attribute which has a setter method; and
- By overriding the
getEmail()method to return the spam email address instead of the original one
In short, you need to prevent this from happening, and the easiest way to do so is to declare the Email class as final:
public final class Email implements Comparable<Email> {
The final keyword, when applied to the class declaration, means that this class can no longer be sub-classed. If you entered the CompromisedEmail class above then you will find it no longer compiles, and you should delete it.
The final keyword can also be used to prevent specific methods from being overridden:
public final String getEmail() {
// etc.
However, if you mark the class as final there is no need to mark the methods as final since they automatically cannot be overridden since the class cannot be sub-classed in the first place.
You may recall from Section 4 that you defined an interface called Emailable. Because this is another good candidate for a utility you should move it to the Utilities project and then change it to specify that its getEmail() method returns type Email instead of String:
package com.example.util;
public interface Emailable {
public Email getEmail(); // recipient's email address
}
Now that you have an Email utility class in place you can use that as the type instead of String in the ZooKeeper and Visitor classes.
For ZooKeeper, you will need to change the type from String to Email on the instance variable named email, the second argument to the constructor, the argument to the setEmail() method and the return type on the getEmail() method.
For Visitor, you will need to change the type from String to Email on the instance variable named email, the second argument to both constructors, the argument to the setEmail() method and the return type on the getEmail() method.
You may need to fix your import statements when applying the above changes.
You will also need to change the createExampleZooKeepers() and createExampleVisitors() methods in ZooAdministrator to instantiate an Email object which wraps the String:
private void createExampleZooKeepers() {
alice = new ZooKeeper(new Person("Alice Smith", "Some City"),
new Email(""),
new BigDecimal("20000"));
bob = new ZooKeeper(new Person("Bob Jones", "2 The Road"),
new Email(""),
new BigDecimal(22000));
charles = new ZooKeeper(new Person("Charles Green", "3 The Avenue"),
new Email(""),
new BigDecimal(18000));
}
private void createExampleVisitors() {
mary = new Visitor(new Person("Mary Smith"), new Email(""));
peter = new Visitor(new Person("Peter Harris", new Email("));
richard = new Visitor(new Person("Richard York"), new Email(""));
tanya = new Visitor(new Person("Tanya West"), new Email(""));
}
In the next lesson we will look at recommended overrides from the Object class.
Next lesson: 9.3 Recommended overrides from the Object class
Comments