February 23, 2014

Date and Calendar Members Are Modifiable


The Date and Calendar classes are mutable classes. If you use them as a class variable and return them via accessor methods ("getters"), the client class can modify them, even if they are private members.


Example

static class AnyClass {

    private Date date;
    private Calendar calendar;

    public static AnyClass getInstance() {
        return new AnyClass(new Date(), Calendar.getInstance());
    }

    public AnyClass(Date date, Calendar calendar) {
        this.date = date;
        this.calendar = calendar;
    }

    public Date getDate() {
        return date; // Member returned
    }

    public Calendar getCalendar() {
        return calendar; // Member returned
    }
}

@Test
public void testDateAndCalendarAreModifiable() throws Exception {

    AnyClass instance = AnyClass.getInstance();

    String dateBefore = instance.getDate().toString();
    instance.getDate().setYear(instance.getDate().getYear() + 1);
    Assert.assertNotEquals(dateBefore, instance.getDate().toString()); // Modified


    String calendarBefore = instance.getCalendar().toString();
    instance.getCalendar().set(Calendar.YEAR, instance.getCalendar().get(Calendar.YEAR) + 1);
    Assert.assertNotEquals(calendarBefore, instance.getCalendar().toString()); // Modified
}

Solution 


The solution is to return only a copy of the internal members to the client classes. In other terms we make our class immutable.

Note: We can use the clone() method here because it is implemented in these classes. If it was not implemented, we would have to create simply new objects with the proper content.

static class ImmutableClass {

    private Date date;
    private Calendar calendar;

    public static ImmutableClass getInstance() {
        return new ImmutableClass(new Date(), Calendar.getInstance());
    }

    public ImmutableClass(Date date, Calendar calendar) {
        this.date = date;
        this.calendar = calendar;
    }

    public Date getDate() {
        return (Date) date.clone(); // Copy returned
    }

    public Calendar getCalendar() {
        return (Calendar) calendar.clone(); // Copy returned
    }
}

@Test
public void testDateIsModifiable() throws Exception {

    ImmutableClass instance = ImmutableClass.getInstance();

    String dateBefore = instance.getDate().toString();
    instance.getDate().setYear(instance.getDate().getYear() + 1);
    Assert.assertEquals(dateBefore, instance.getDate().toString()); // Not modified


    String calendarBefore = instance.getCalendar().toString();
    instance.getCalendar().set(Calendar.YEAR, instance.getCalendar().get(Calendar.YEAR) + 1);
    Assert.assertEquals(calendarBefore, instance.getCalendar().toString()); // Not modified
}

Outlook


Alternative solutions:
  • As an alternative you can use the Joda-Time classes which are immutable.
  • In Java 8 you will have to use the classes from the java.time package, which are also immutable and similar to the Joda-Time.
Defensive programming:
  • As a general pattern you should protect all class members from unwanted modification, as recommended in the book Effective Java (2nd edition), Item 13: Minimize the accessibility of classes and members.
  • You should also protect the (mutable) members of the members too - and so on - to achieve full protection.

Update


The above ImmutableClass is still not immutable. :-) The client class that instantiates it and calls its constructor can keep the references to the Date and Calendar parameters, so it can directly modify the private members of our class.

Solution: you should create a copy (clone) in the setters and constructors too.

No comments :

Post a Comment