AMT*_*AMT 88 java design-patterns immutable-collections
假设我正在编写一个应该返回Map的方法.例如:
public Map<String, Integer> foo() {
return new HashMap<String, Integer>();
}
Run Code Online (Sandbox Code Playgroud)
在考虑了一段时间之后,我决定在创建Map之后没有理由修改它.因此,我想返回一个ImmutableMap.
public Map<String, Integer> foo() {
return ImmutableMap.of();
}
Run Code Online (Sandbox Code Playgroud)
我应该将返回类型保留为通用Map,还是应该指定我返回一个ImmutableMap?
从一个方面来说,这就是为什么创建接口的原因; 隐藏实现细节.
另一方面,如果我将这样离开,其他开发人员可能会错过这个对象是不可变的这一事实.因此,我不会实现不可变对象的主要目标; 通过最小化可以更改的对象的数量来使代码更清晰.甚至最糟糕的是,过了一段时间,有人可能会尝试更改此对象,这将导致运行时错误(编译器不会对此发出警告).
Dic*_*ici 68
如果你正在编写一个面向公众的API,并且不变性是你设计的一个重要方面,我肯定会通过让方法的名称清楚地表明返回的地图是不可变的或通过返回具体的类型来明确它.地图.在我看来,在javadoc中提到它是不够的.
由于您显然正在使用Guava实现,我查看了doc并且它是一个抽象类,因此它确实为您提供了实际的具体类型的灵活性.
如果您正在编写内部工具/库,那么返回普通文件就更容易接受了Map
.人们会知道他们正在调用的代码的内部,或者至少可以轻松访问它.
我的结论是明确是好的,不要把事情搞得一团糟.
Syn*_*sso 36
你应该有ImmutableMap
你的退货类型.Map
包含ImmutableMap
(例如put
)的实现不支持的方法,并标记@deprecated
在ImmutableMap
.
使用弃用的方法将导致编译器警告,并且大多数IDE将在人们尝试使用已弃用的方法时发出警告.
这个高级警告比运行时异常更可取,因为你的第一个暗示是出错了.
tka*_*usl 16
另一方面,如果我将这样离开,其他开发人员可能会错过这个对象是不可变的这一事实.
你应该在javadocs中提到它.你知道,开发人员会阅读它们.
因此,我不会实现不可变对象的主要目标; 通过最小化可以更改的对象的数量来使代码更清晰.甚至最糟糕的是,过了一段时间,有人可能会尝试更改此对象,这将导致运行时错误(编译器不会对此发出警告).
没有开发人员发布未经测试的代码.当他测试它时,他会抛出异常,他不仅会看到原因,还会看到他试图写入不可变地图的文件和行.
请注意,只有它Map
本身是不可变的,而不是它包含的对象.
Ren*_*ink 10
如果我这样离开,其他开发人员可能会错过这个对象是不可变的这个事实
这是事实,但其他开发人员应该测试他们的代码并确保它被覆盖.
不过,您还有2个选项可以解决此问题:
使用Javadoc
@return a immutable map
Run Code Online (Sandbox Code Playgroud)选择描述性方法名称
public Map<String, Integer> getImmutableMap()
public Map<String, Integer> getUnmodifiableEntries()
Run Code Online (Sandbox Code Playgroud)
对于具体的用例,您甚至可以更好地命名方法.例如
public Map<String, Integer> getUnmodifiableCountByWords()
Run Code Online (Sandbox Code Playgroud)你还能做什么?!
你可以退货
复制
private Map<String, Integer> myMap;
public Map<String, Integer> foo() {
return new HashMap<String, Integer>(myMap);
}
Run Code Online (Sandbox Code Playgroud)
如果您希望许多客户端修改地图并且只要地图只包含少量条目,则应使用此方法.
CopyOnWriteMap
当你必须处理
并发时,通常会使用写集合上的副本.但是这个概念也会在你的情况下帮助你,因为CopyOnWriteMap在一个变异操作上创建了一个内部数据结构的副本(例如add,remove).
在这种情况下,您需要一个围绕地图的薄包装器,它将所有方法调用委托给底层映射,但变量操作除外.如果调用了一个mutative操作,它会创建一个底层映射的副本,所有进一步的调用都将被委托给该副本.
如果您希望某些客户端修改地图,则应使用此方法.
可悲的是java没有这样的CopyOnWriteMap
.但您可能会找到第三方或自己实施.
最后,您应该记住,地图中的元素可能仍然是可变的.
肯定会返回一个ImmutableMap,理由是:
这取决于班级本身.Guava ImmutableMap
并不打算成为一个可变类的不可变视图.如果你的类是不可变的并且有一些基本上是a的结构ImmutableMap
,那么创建返回类型ImmutableMap
.但是,如果您的课程是可变的,请不要.如果你有这个:
public ImmutableMap<String, Integer> foo() {
return ImmutableMap.copyOf(internalMap);
}
Run Code Online (Sandbox Code Playgroud)
番石榴每次都会复制地图.那很慢.但如果internalMap
已经是一个ImmutableMap
,那就完全没问题了.
如果你不限制你的班级返回ImmutableMap
,那么你可以Collections.unmodifiableMap
这样返回:
public Map<String, Integer> foo() {
return Collections.unmodifiableMap(internalMap);
}
Run Code Online (Sandbox Code Playgroud)
请注意,这是一个不可变的地图视图.如果internalMap
更改,则缓存副本也会如此Collections.unmodifiableMap(internalMap)
.然而,我仍然喜欢吸气剂.
这并没有回答确切的问题,但仍然值得考虑是否应该返回地图。如果地图是不可变的,那么提供的主要方法是基于 get(key):
public Integer fooOf(String key) {
return map.get(key);
}
Run Code Online (Sandbox Code Playgroud)
这使得 API 更加紧密。如果实际上需要映射,则可以通过提供条目流将其留给 API 的客户端:
public Stream<Map.Entry<String, Integer>> foos() {
map.entrySet().stream()
}
Run Code Online (Sandbox Code Playgroud)
然后客户端可以根据需要制作自己的不可变或可变映射,或者将条目添加到自己的映射中。如果客户端需要知道该值是否存在,则可以返回 optional 代替:
public Optional<Integer> fooOf(String key) {
return Optional.ofNullable(map.get(key));
}
Run Code Online (Sandbox Code Playgroud)