I want to create an immutable record which has 2 mutable fields Date and a HashMap
public record ImmutableRecord(String name, LocalDate admissionDate, Date dateOfBirth, Map<String, Integer> metaData) {
public ImmutableRecord{
// Date is a mutable field
dateOfBirth = new Date(dateOfBirth.getTime());
// HashMap is a mutable field
Map<String, Integer> tempMap = new HashMap<>();
for(Map.Entry<String, Integer> entry: metaData.entrySet()){
tempMap.put(entry.getKey(), entry.getValue());
}
metaData = tempMap;
//Can I use following instead of above for deep copying the map?
metaData = Map.copyOf(metaData);
}
}
Which of the following approach is correct using forEach to deep clone every field or using Map.copyOf
First of all, in terms of design, generally the responsibility for copying data should fall upon the calling method that instantiates the record. The constructor should not be doing this copying, commonly.
Let’s change the name of this record, as “ImmutableRecord” is redundant, and fails to reflect your problem domain. Apparently you intend to represent students, given the admission date field. So your entire class definition could be:
The modern and immutable
LocalDateclass should always be used instead of the mutable legacy classjava.sql.Dateclass.The calling method should do the copying, as seen further below.
Regarding your attempt to clone the map:
That copying is not a deep clone. You did not copy the content of the keys. Nor did you copy the content of the values. You did create another
Mapobject. But both the old map and the new map have entries holding references to the very same key & value objects.Your copying is effectively the same as merely passing the old map to the constructor of a new map. Both old and new map have entries that point to the same key objects and the same value objects.
Your copying is nearly the same as
Map.copyOf( oldMap )except thatcopyOfproduces an unmodifiable map of some unspecified class implementingMaprather than a modifiableHashMap. But like your code above, both the old and new maps point to the same key objects and the same value objects.So the calling code would look something like this:
The purpose of the records feature is to concisely define a class whose main purpose is to communicate data transparently and immutably. So ideally a record should contain immutable content. That is another reason to prefer
Map.copyOfover a newHashMap.As commented, another reason to prefer
Map.copyOfis that if the passed map is already unmodifiable, that passed map is returned directly. Less memory used, and less CPU used.As commented, one possible limitation to using
Map.copyOfis that nulls are not tolerated in the map reference, in any of the keys, nor in any of the values.Map.copyOfis a “null-free zone”, my own newly coined term.If nulls are tolerable in your scheme, an alternative would be
Collections.unmodifiableMap. And, for defensive programming, make a copy of the map being wrapped byCollections.unmodifiableMap.