5

I couldn't find any material on Google saying about the use of Cloneable records.

I was thinking of something like this:

record Foo() implements Cloneable {
    public Foo clone() {...}
}

Is it a good thing? Should we avoid it in favor of the future withers?

Arvind Kumar Avinash
  • 71,965
  • 6
  • 74
  • 110
  • 8
    You should avoid Cloneable in general. – khelwood Apr 30 '21 at 13:16
  • Do you have any material explaining why? – Thiago Henrique Hupner Apr 30 '21 at 13:18
  • 3
    [About Java cloneable](https://stackoverflow.com/q/4081858/3890632) – khelwood Apr 30 '21 at 13:19
  • Records are a new Java feature (since Java 14). The only reason to avoid them for now would be because your code would need to run on older Java versions, or because you are using something else (libraries etc.) that don't understand records. That doesn't mean you should avoid using this feature forever. – Jesper Apr 30 '21 at 13:20
  • 4
    It *is* OK to make a record cloneable. You should follow all the usual caveats about why you probably don't want to use clone at all (though this has nothing to do with records), and you must conform to the specification of `java.lang.Record` which constrains the behavior of the constructor+accessors+equals(), but if you can do all that, its OK. – Brian Goetz Apr 30 '21 at 16:13
  • 2
    The standard advice for `Clonable` is "just write a copy constructor instead." That's perfectly good advice for records too. – Brian Goetz May 02 '21 at 23:56

1 Answers1

12

Apart from the fundamental problems of Cloneable there's another strong reason why one shouldn't make a record Cloneable:

Records are inherently immutable. One of the biggest advantages of immutable classes is that one can stop worrying about object identity: any two objects with the same values can be used entirely interchangeable. The JavaDoc has a concept called Value-based Classes that goes a bit further (by disallowing public constructors and making any explicit use of object identity a mistake), but does describe the basic idea well.

Therefore creating a clone() of a record would only ever produce a second object that for all intents and purpose should behave exactly the same as the original and can't ever change to behave differently (since records are immutable).

This suggests that there's no valid reason to clone() a Record.

And just for completeness sake: it is possible (but again, not suggested) to implement Cloneable in a record:

record Foo(String a) implements Cloneable {
  public Foo clone() {
    try {
      return (Foo) super.clone();
    } catch (CloneNotSupportedException e) {
      throw new RuntimeException("this can't happen", e);
    }
  }
}

Running this test code proves that a clone is actually created:

Foo original = new Foo("bar");
Foo clonedFoo = original.clone();
System.out.println(original + " / " + clonedFoo);
System.out.println(System.identityHashCode(original) + " / " + System.identityHashCode(clonedFoo));

produces this output:

Foo[a=bar] / Foo[a=bar]
670700378 / 1190654826
Joachim Sauer
  • 302,674
  • 57
  • 556
  • 614
  • 1
    Thanks. So in practice, it should work just fine, but it wouldn't be of any use. – Thiago Henrique Hupner Apr 30 '21 at 13:54
  • 2
    Yes, I've added a sample demonstrating that it *works*. – Joachim Sauer Apr 30 '21 at 14:01
  • 5
    I think you're (dangerously) confusing records (which are a language feature about classes whose API and representation are transparently related) and primitive classes (nee value types). "Value-based classes" are entirely about classes that are intended to have value semantics; records do not necessarily. – Brian Goetz Apr 30 '21 at 15:50
  • 5
    The claim made here that records have no use at all for `clone()` is overblown. There are all the usual reasons that `clone()` is problematic, but records neither add nor subtract from these. – Brian Goetz Apr 30 '21 at 15:52
  • 2
    The claim that a clone of a record is indistinguishable from the original is still incorrect even if you ignore identity. A clone() method is free to deeply clone components, some of which might be mutable (e.g., arrays.) These are "advanced" uses of records, and might be something you would reasonably discourage in general, but they're not ruled out by the design or implementation. – Brian Goetz Apr 30 '21 at 18:43
  • @BrianGoetz: yes, I agree that I assumed that records would only be used for immutable classes, but they don't necessarily need to be deeply immutable. But I still think that immutable records will be the primary use case and the majority of uses. And personally I'd think using records for non-immutable uses cases is risky as well, since I don't think I'm the only one with that blind spot and/or bias. – Joachim Sauer May 02 '21 at 08:40
  • 5
    @JoachimSauer Full agreement with all of that -- yes immutability is the primary case, and many of the other cases should give users cause to more deeply explore what they're trying to achieve. But the OP asked if there was any reason a record _cannot_ implement `Clonable`. And the simple answer is, the two concepts are compatible, even if the intersection is small. So by all means, say "you can, but it's risky, and you're probably doing something questionable, are you sure you want that?" And perhaps: "LIke with every other class besides arrays, prefer a copy constructor." – Brian Goetz May 02 '21 at 14:50
  • 1
    @BrianGoetz well, records have *stable final fields* which means that when using `super.clone()`, modifying the cloned fields to achieve deep cloning is not possible. Of course, you still can achieve deep cloning by just using the copy constructor (as for final classes, it makes no difference), but then, there is no benefit in implementing `Cloneable`, as that interface does not provide a `clone()` method but only enables the `super.clone()` shallow copying facility. – Holger May 03 '21 at 13:36
  • 2
    @Holger I'm not trying to defend `Clonable`, but the OP asked if there was anything about records that was inconsistent with `Clonable`, and the answer is no. If a copy constructor does the job, it is a better choice, no question. – Brian Goetz May 03 '21 at 13:52
  • 6
    @JoachimSauer and Holger: The reason I'm pushing back here is not that I'm some sort of secret fan of `Clonable`; it's that I'm a rabid anti-fan of making things more complicated than they are. `Clonable` sucks, but its suckage is completely independent of records. `Clonable` records suck _exactly as much_ as `Clonable` classes, no more, and no less. Let's not burden people's perceptions of records with irrelevant coupling to other features. Records are great, `Clonable` sucks, and neither affects the other. That's a simple story. – Brian Goetz May 03 '21 at 13:55
  • 2
    @BrianGoetz that’s understood. I was referring to your sentence “*A clone() method is free to deeply clone components*”. For ordinary classes, `super.clone()` can be used to create a shallow copy of exactly the same type, which is relevant to non-final classes, followed by updating final fields via reflection, to make it a deep copy. This doesn’t work with records, due to the fact that its final fields can not be modified via reflection like with other classes. The only way to deeply copy a record is via constructor. So it’s not exactly the same as with other classes. – Holger May 04 '21 at 07:22