创建具有可变字段的不可变记录

问题描述 投票:0回答:2

我想创建一个不可变记录,它有 2 个可变字段 Date 和一个 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);
    }

}

以下哪种方法是正确的,使用 forEach 深度克隆每个字段或使用 Map.copyOf

java record java-16 java-record
2个回答
2
投票

首先,从设计上来说,通常复制数据的责任应该落在实例化记录的调用方法上。通常,构造函数不应该进行此复制。 让我们更改该记录的名称,因为“ImmutableRecord”是多余的,并且无法反映您的问题域。考虑到入学日期字段,显然您打算代表学生。所以你的整个类定义可能是:

public record Student ( String name, LocalDate admitted, LocalDate dateOfBirth, Map<String, Integer> metaData ) {}

应始终使用现代且不可变的 
LocalDate

类,而不是可变的遗留类

java.sql.Date
类。
调用方法应该进行复制,如下所示。

关于您尝试克隆地图:

Map<String, Integer> tempMap = new HashMap<>(); for(Map.Entry<String, Integer> entry: metaData.entrySet()){ tempMap.put(entry.getKey(), entry.getValue()); }

这种复制
不是

深度克隆。您没有复制密钥的内容。您也没有复制值的内容。您确实创建了另一个 Map 对象。但旧映射和新映射都有包含相同键和值对象的

引用
的条目。 您的复制实际上与仅将旧地图传递给新地图的构造函数相同。新旧映射都有指向相同键对象和相同值对象的条目。

Map < String , Integer > metaData = new HashMap <> ( oldMetaData ) ;

您的复制几乎与 
Map.copyOf( oldMap )

相同,只是

copyOf
生成某个实现
Map
的未指定类的不可修改映射,而不是可修改的
HashMap
。但就像上面的代码一样,新旧映射都指向相同的键对象和相同的值对象。
所以调用代码看起来像这样:

Student someStudent = new Student( "Alice" , … ) ; … Student twinStudent = new Student( "Bob" , LocalDate.of( 2021, Month.MARCH, 23 ), someStudent.dateOfBirth, Map.copyOf( someStudent.metaData ) ) ;

记录
功能的目的是简洁地定义一个类,其主要目的是透明且不可变地通信数据。因此理想情况下,记录应该包含不可变的内容。这是更喜欢

Map.copyOf 而不是新 HashMap

 的另一个原因。
正如
评论

,更喜欢

Map.copyOf的另一个原因是,如果传递的映射已经不可修改,则直接返回传递的映射。使用的内存更少,CPU 的使用也更少。

正如
注释的

,使用

Map.copyOf的一个可能的限制是,在地图引用、任何键或任何值中,不能

容忍空值。 
Map.copyOf 是“无空区”,我自己新创造的术语。
如果您的方案中可以容忍 null,则替代方案是 

Collections.unmodifiableMap

。并且,对于防御性编程,请复制由

Collections.unmodifiableMap
包裹的地图。
metaData = Collections.unmodifiableMap( new HashMap<>( oldMetaData ) ) ;

最好将设计决策视为二元的……即它是否是不可变的?

-1
投票
就你而言,你有一些可变的东西。既然如此,只需使用 Lombok

@Data

来构建它即可。

此外您还可以:

添加@Builder为您提供流畅的构建器

    添加@With为您提供复制变异器
© www.soinside.com 2019 - 2024. All rights reserved.