我目前的用例非常简单,无论是可变的还是不可变的Map都可以解决问题。
有一个方法,它接受一个不可变的Map,然后调用第三方API方法,该方法也采用不可变的Map
def doFoo(foo: String = "default", params: Map[String, Any] = Map()) {
val newMap =
if(someCondition) params + ("foo" -> foo) else params
api.doSomething(newMap)
}
有问题的地图通常很小,最多可能有一个嵌入的案例类实例列表,最多几千个条目。因此,再次假设在这种情况下不可变的影响很小(即通过newMap val副本基本上有2个Map实例)。
尽管如此,它还是让我有点尴尬,复制地图只是为了获得一张新地图,上面贴了几个k-> v条目。
我可以为我想要处理的条目变为可变的params.put("bar", bar)
等,然后params.toMap
转换为api调用的不可变,这是一个选项。但后来我必须导入并传递可变映射,与使用Scala的默认不可变映射相比,这有点麻烦。
那么,对于在不可变地图上使用可变地图的合理/良好做法,一般指导原则是什么?
由于
修改 因此,看起来不可变地图上的添加操作接近恒定时间,确认@ dhg和@Nicolas断言没有制作完整副本,这解决了所呈现的具体案例的问题。
答案 0 :(得分:35)
根据不可变的Map实现,添加一些条目可能实际上不会复制整个原始Map。这是不可变数据结构方法的优势之一:Scala将尽可能少地完成复制。
使用List
最容易看到这种行为。如果我有val a = List(1,2,3)
,则该列表存储在内存中。但是,如果我添加了一个额外的元素,例如val b = 0 :: a
,我做会返回一个新的4元素List
,但Scala却不复制了原始列表a
。相反,我们只创建了一个名为b
的新链接,并为其指定了现有列表a
。
您也可以为其他类型的集合设想这样的策略。例如,如果我向Map
添加一个元素,则集合可以简单地包装现有地图,在需要时回退到它,同时提供API就好像它是单个Map
。 / p>
答案 1 :(得分:14)
使用可变对象本身并不坏,它在函数式编程环境中变得很糟糕,在这种环境中,你试图通过保持函数纯和对象不可变来避免副作用。
但是,如果在函数内部创建一个可变对象并修改此对象,如果您不在函数外部释放对该对象的引用,则该函数仍然是纯粹的。可以使用以下代码:
def buildVector( x: Double, y: Double, z: Double ): Vector[Double] = {
val ary = Array.ofDim[Double]( 3 )
ary( 0 ) = x
ary( 1 ) = y
ary( 2 ) = z
ary.toVector
}
现在,我认为这种方法在两种情况下是有用/推荐的:(1)性能,如果创建和修改不可变对象是整个应用程序的瓶颈; (2)代码可读性,因为有时可以更容易地修改复杂的对象(而不是使用镜头,拉链等)。
答案 2 :(得分:5)
除了dhg的回答,您还可以查看performance of the scala collections。如果添加/删除操作不需要线性时间,则必须执行其他操作,而不仅仅是复制整个结构。 (注意反过来不是这样的:它不是因为复制整个结构需要线性时间)
答案 3 :(得分:0)
我喜欢使用collections.maps作为声明的参数类型(输入或返回值)而不是可变或不可变的映射。 Collections映射是不可变的接口,适用于两种类型的实现。使用地图的消费者方法实际上并不需要知道地图实现或其构造方式。 (无论如何,它真的没有任何业务)。
如果您采用从使用它的消费者隐藏地图的特定结构(可变或不可变)的方法,那么您仍然在下游获得基本上不可变的地图。通过使用collection.Map作为不可变接口,您可以完全删除所有" .toMap"消费者编写的使用immutable.Map类型对象的低效率。必须将完全构造的地图转换为另一个地图,只是为了遵守第一个不支持的接口,当你想到它时,这绝对是不必要的开销。
我怀疑在几年后我们将回顾三个独立的接口集(可变映射,不可变映射和集合映射),并意识到99%的时间只需要2个(可变和集合)并且使用(不幸的)默认的不可变映射接口确实为"可扩展语言"增加了许多不必要的开销。