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