假设我正在编写一个应该返回地图的方法。例如:
public Map<String, Integer> foo() {
return new HashMap<String, Integer>();
}
考虑了一段时间后,我决定没有理由在创建此地图后修改。因此,我想返回一个immutableMap。
public Map<String, Integer> foo() {
return ImmutableMap.of();
}
我应该将返回类型作为通用地图留下,还是应该指定我要返回ImmutableMap?
从一侧,这就是为什么创建接口的原因;隐藏实现细节。
另一方面,如果我这样留下,其他开发人员可能会错过这个物体是不可变的事实。因此,我将无法实现不变物体的主要目标。通过最大程度地减少可以更改的对象数量来使代码更清楚。甚至一段时间后,有人可能会尝试更改此对象,这将导致运行时错误(编译器不会警告它)。
如果您正在编写面向公共的API并且不变性是您设计的重要方面,那么我肯定会通过使用该方法的名称清楚地表明返回的地图是不可变的,或者通过返回地图的混凝土类型。在我看来,在Javadoc中提及它是不够的。
由于您显然正在使用Guava实现,因此我查看了DOC,它是一个抽象类,因此它确实为您提供了一些实际的,具体类型的灵活性。
如果您正在编写内部工具/库,则仅返回普通的Map
就变得更加可接受。人们会知道他们正在调用的代码的内部或至少可以轻松访问它。
我的结论是,明确是好的,不要让事情偶然。
您应该将ImmutableMap
作为您的返回类型。Map
包含实现ImmutableMap
(例如put
)不支持的方法,并在ImmutableMap
中标记为@deprecated
。
使用不推荐的方法将导致编译器警告&amp;当人们尝试使用弃用的方法时,大多数ID会警告。
此高级警告比让运行时异常是您的第一个提示。
另一方面,如果我这样留下,其他开发人员可能会错过这样的事实。
您应该在Javadocs中提到这一点。开发人员确实阅读了它们,您知道。
因此,我将无法实现不变对象的主要目标;做 通过最小化可以更改的对象数量来最大程度清晰的代码。 甚至一段时间后,有人可能会试图改变这个对象, 这将导致运行时错误(编译器不会警告 关于它)。
没有开发人员未经测试的代码发布。当他确实测试它时,他会得到一个例外,不仅看到原因,而且还在尝试写入不变的地图的文件和行。
确实注意,只有Map
本身将是不可变的,而不是它包含的对象。
如果我这样留下来,其他开发人员可能会错过这个对象是不变的事实
是的,但是其他开发人员应测试其代码并确保其涵盖。
尽管如此,您还有2个解决此问题的选择:
-
使用Javadoc
@return a immutable map
-
选择描述性方法名称
public Map<String, Integer> getImmutableMap() public Map<String, Integer> getUnmodifiableEntries()
对于具体用例,您甚至可以更好地命名方法。例如
public Map<String, Integer> getUnmodifiableCountByWords()
你还能做什么?!
您可以返回
-
复制
private Map<String, Integer> myMap; public Map<String, Integer> foo() { return new HashMap<String, Integer>(myMap); }
如果您希望很多客户会修改地图,并且只要地图只包含一些条目。
,应使用此方法。
CopyOnWriteMap
在您必须处理
时,通常会使用写作集合的副本并发。但是这个概念也将在您的情况下为您提供帮助,因为CopyOnWriteMap在突变操作上创建了内部数据结构的副本(例如,添加,删除)。在这种情况下,您需要围绕地图上的薄包装,除突变操作外,将所有方法调用委托给基础地图。如果调用了突变操作,它将创建基础地图的副本,所有其他调用将委派给此副本。
如果您希望某些客户会修改地图,则应使用此方法。
可悲的是,Java没有这样的
CopyOnWriteMap
。但是您可能会找到第三方或自己实施。
终于应该记住,地图中的元素可能仍然可变。
肯定会返回一个不可能的理由,理由为:
- 方法签名(包括返回类型)应为自我记录。评论就像客户服务:如果您的客户需要依靠它们,那么您的主要产品就有缺陷。
- 在扩展或实施时,某物是接口还是类是相关的。给定一个实例(对象),客户端代码的99%不知道或关心某物是接口还是类。我起初以为ImmutableMap是一个接口。只有在我单击链接后,我才意识到这是一个类。
它取决于类本身。Guava的ImmutableMap
并不是要成为可变级别的不变的视图。如果您的班级是不可变的,并且具有基本上是ImmutableMap
的某些结构,则进行返回类型ImmutableMap
。但是,如果您的班级可变,请不要。如果有:
public ImmutableMap<String, Integer> foo() {
return ImmutableMap.copyOf(internalMap);
}
Guava每次都会复制地图。那很慢。但是,如果internalMap
已经是ImmutableMap
,那就完全可以。
如果您不将课程限制为返回ImmutableMap
,则可以像这样返回Collections.unmodifiableMap
:
public Map<String, Integer> foo() {
return Collections.unmodifiableMap(internalMap);
}
请注意,这是地图中的不变 View 。如果internalMap
发生了变化,则Collections.unmodifiableMap(internalMap)
的缓存副本也会发生变化。但是,我仍然更喜欢Getters。。如果地图是不可变的,则提供的主要方法基于GET(键):
public Integer fooOf(String key) {
return map.get(key);
}
这使API更加紧密。如果实际需要地图,则可以通过提供一系列条目来保留API的客户端:
public Stream<Map.Entry<String, Integer>> foos() {
map.entrySet().stream()
}
然后,客户可以根据需要制作自己的不变或可变的地图,或将条目添加到自己的地图中。如果客户需要知道该值是否存在,则可以返回可选的:
public Optional<Integer> fooOf(String key) {
return Optional.ofNullable(map.get(key));
}
不可变的地图是一种地图。因此,离开地图的返回类型是可以的。
为了确保用户不修改返回的对象,该方法的文档可以描述返回对象的特征。
这可以说是一个意见问题,但是这里更好的想法是使用映射类使用接口。此界面不需要明确说明它是不变的,但是如果您不公开接口中的父类的任何设置器方法,则该消息将相同。
看以下文章:
安迪·吉布森