2

When we create immutable classes using the Immutable objects library, how can we handle mutable members (e.g. j.u.Date)?

NOTE: this is not about the java Date class and totally related to the Immutable Objects java library which will generate some code!

Example:

@Value.Immutable
public interface MyImmutableClass {
    Date creationDateTime();
}

Is there a way to override the getter, so that it returns a copy?

public Date creationDateTime() {
    return new Date(creationDateTime.getTime());
}
TmTron
  • 17,012
  • 10
  • 94
  • 142
  • 2
    You could use an [immutable date class](https://docs.oracle.com/javase/8/docs/api/java/time/LocalDate.html), or, yes, by all means have your getter return a copy. `return new Date(creationDateTime.getTime());` – khelwood Feb 20 '17 at 13:22
  • I cannot use java8 features and it could also affect other classes - not just Date. – TmTron Feb 20 '17 at 13:23
  • See also http://stackoverflow.com/questions/7082553/java-util-date-clone-or-copy-to-not-expose-internal-reference – khelwood Feb 20 '17 at 13:27
  • @khelwood that is not helpful, since it is not at all about the Immutable Objects library – TmTron Feb 20 '17 at 13:31

3 Answers3

1

Is there a way to override the getter, so that it returns a copy?

Pretty much like you've written it there:

public Date creationDateTime() {
    return new Date(creationDateTime.getTime());
}

(like khelwood pointed out in the comments above).

However, if you want to avoid accidental mutation of creationDateTime inside your class, consider just storing the millis as a final long:

private final creationDateTimeMillis;

public Date creationDateTime() {
    return new Date(creationDateTimeMillis);
}

Whereas you can call Date.setTime() even if the Date is final, thus mutating the internal state, you can't reassign creationDateTimeMillis.

Community
  • 1
  • 1
Andy Turner
  • 137,514
  • 11
  • 162
  • 243
  • Well, I know that I must copy it somehow: The question is "How to tell the Immutables annotation processor to do this?" – TmTron Feb 20 '17 at 13:28
1

You can make the generated method protected and serve the field only from a cloning getter method:

@Value.Immutable
public abstract class WrapMutable {
    protected abstract Date timestamp();

    public Date getTimestamp() {
        return new Date(timestamp().getTime());
    }
}

All usage of the field is then through its copy-getter, while the timestamp() method is only used to define the setter in the builder:

WrapMutable obj = ImmutableWrapMutable.builder().timestamp(new Date()).build();
System.out.println(obj.getTimestamp());
System.out.println(obj.timestamp()); // Error: timestamp() has protected access in WrapMutable
Henrik Aasted Sørensen
  • 6,966
  • 11
  • 51
  • 60
  • That's not really immutable. Just create a subclass of `WrapMutable`, and you can add a method to call `timestamp()` nefariously. You need to have the `timestamp` property store/return something immutable, like a `long`. – Andy Turner Feb 20 '17 at 17:32
  • 1
    @AndyTurner: You have read the question in the context of the [Immutables framework](http://immutables.github.io/). Its purpose is not to guard against malicious developers, which can be very helpful during testing, but rather to deliver POJO instances on which you can make ton of assumptions, e.g. that collections are initialized, that fields are set, that Optionals are non-null etc. If you work with the framework it will eliminate a lot of boilerplate, and help you reduce the amount of defensive coding required. – Henrik Aasted Sørensen Feb 20 '17 at 19:15
1

From my question in the Immutables issue tracker I learned that the cleanest way to handle mutable objects is to use a custom encoding annotation.

I've created a little open source project tmtron-immutables encoding to create such an annotation for the java.util.Date class. This should be a good starting point for anyone who wants to create a custom encoding.

Then you can directly use your mutable class (e.g. java.util.Date) as attribute and still get the same immutability guarantee as for immutable attributes (like String, long, etc.)

@Value.Immutable
@DateEncodingEnabled
public interface ValueObject {
    Date creationDate();
}

The @DateEncodingEnabled annotation will make sure that only the immutable long value of the Date object is stored in the ImmutableValueObject class.

TmTron
  • 17,012
  • 10
  • 94
  • 142