返回ImmutableMap或Map更好吗?

时间:2016-06-28 23:30:07

标签: java design-patterns immutable-collections

假设我正在编写一个应该返回Map的方法。例如:

EOFError

在考虑了一段时间之后,我决定在创建Map之后没有理由修改它。因此,我想返回ImmutableMap

with open('picklefile', 'rb') as f:
    for obj in iter(pickle.Unpickler(f).load, None):
        ... process an object ...

我应该将返回类型保留为通用Map,还是应该指定我返回一个ImmutableMap?

从一个方面来说,这就是为什么创建接口的原因;隐藏实现细节。
另一方面,如果我将这样离开,其他开发人员可能会错过这个对象是不可变的这一事实。因此,我不会实现不可变对象的主要目标;通过最小化可以更改的对象的数量来使代码更清晰。甚至最糟糕的是,过了一段时间,有人可能会尝试更改此对象,这将导致运行时错误(编译器不会对此发出警告)。

9 个答案:

答案 0 :(得分:68)

  • 如果您正在编写一个面向公众的API,并且不变性是您设计的一个重要方面,我肯定会通过让方法的名称清楚地表明返回的地图是不可变的或通过返回地图的具体类型。在我看来,在javadoc中提到它是不够的。

    由于您显然正在使用Guava实现,我查看了doc并且它是一个抽象类,因此它确实为您提供了实际的具体类型的灵活性。

  • 如果您正在编写内部工具/库,那么返回普通Map会变得更加可接受。人们会知道他们正在调用的代码的内部,或者至少可以轻松访问它。

我的结论是明确是好的,不要把事情搞得一团糟。

答案 1 :(得分:36)

您应该以{{1​​}}作为退货类型。 ImmutableMap包含Map的实施不支持的方法(例如ImmutableMap),并在put中标记为@deprecated

使用弃用的方法将导致编译器警告&当人们尝试使用已弃用的方法时,大多数IDE都会发出警告。

这个高级警告比运行时异常更可取,因为你的第一个暗示是出错了。

答案 2 :(得分:16)

  

另一方面,如果我这样离开,其他开发人员可能会错过这个对象是不可变的事实。

你应该在javadocs中提到它。你知道,开发人员会阅读它们。

  

因此,我不会实现不可变对象的主要目标;制作   通过最小化可以更改的对象数量来更清晰地编码。   甚至最糟糕的是,过了一段时间,有人可能会试图改变这个对象,   这将导致运行时错误(编译器不会发出警告   关于它)。

没有开发人员发布未经测试的代码。当他测试它时,他会抛出异常,他不仅会看到原因,还会看到他试图写入不可变地图的文件和行。

请注意,只有Map本身是不可变的,而不是它包含的对象。

答案 3 :(得分:10)

  

如果我这样离开,其他开发者可能会错过这个对象是不可变的事实

这是事实,但其他开发人员应该测试他们的代码并确保它被覆盖。

尽管如此,您还有2个选项可以解决这个问题:

  • 使用Javadoc

    @return a immutable map
    
  • 选择描述性方法名称

    public Map<String, Integer> getImmutableMap()
    public Map<String, Integer> getUnmodifiableEntries()
    

    对于具体的用例,您甚至可以更好地命名方法。 E.g。

    public Map<String, Integer> getUnmodifiableCountByWords()
    

你还能做什么?!

您可以返回

  • 复制

    private Map<String, Integer> myMap;
    
    public Map<String, Integer> foo() {
      return new HashMap<String, Integer>(myMap);
    }
    

    如果您希望很多客户修改地图并且只要地图只包含一些条目,则应使用此方法。

  • <强> CopyOnWriteMap

    当您需要处理时,通常会使用

    写入集合上的副本 并发性。但是这个概念也会在你的情况下帮助你,因为a CopyOnWriteMap在变异操作上创建内部数据结构的副本(例如,添加,删除)。

    在这种情况下,您需要在地图周围使用一个薄的包装器,它将所有方法调用委托给底层映射,但变量操作除外。如果调用了一个mutative操作,它会创建一个底层映射的副本,所有进一步的调用都将被委托给这个副本。

    如果您希望某些客户端修改地图,则应使用此方法。

    遗憾的是,java没有这样的CopyOnWriteMap。但您可能会找到第三方或自己实施。

最后您应该记住,地图中的元素可能仍然是可变的。

答案 4 :(得分:8)

肯定会返回一个ImmutableMap,理由是:

  • 方法签名(包括返回类型)应该是自我记录的。评论就像客户服务:如果您的客户需要依赖它们,那么您的主要产品就有缺陷。
  • 某些东西是接口还是类只在扩展或实现时才有意义。给定一个实例(对象),99%的客户端代码将无法知道或关心某些东西是接口还是类。我首先假设ImmutableMap是一个接口。只有在我点击链接后才知道它是一个类。

答案 5 :(得分:5)

这取决于班级本身。 Guava的ImmutableMap并不打算成为一个可变类的不可变视图。如果您的类是不可变的并且某些结构基本上是ImmutableMap,那么请返回类型ImmutableMap。但是,如果您的课程是可变的,请不要。如果你有这个:

public ImmutableMap<String, Integer> foo() {
    return ImmutableMap.copyOf(internalMap);
}

番石榴每次都会复制地图。那很慢。但如果internalMap已经是ImmutableMap,那么它就完全正常了。

如果您不限制您的课程返回ImmutableMap,那么您可以改为Collections.unmodifiableMap,如下所示:

public Map<String, Integer> foo() {
    return Collections.unmodifiableMap(internalMap);
}

请注意,这是一个不可变的视图到地图中。如果internalMap发生更改,则Collections.unmodifiableMap(internalMap)的缓存副本也会发生更改。然而,我仍然喜欢吸气剂。

答案 6 :(得分:4)

这不是回答确切的问题,但是仍然值得考虑是否应该返回地图。如果映射是不可变的,那么要提供的主要方法基于get(key):

IPiece

这使API变得更加紧凑。如果实际需要地图,则可以通过提供条目流来留给API的客户端:

IBishop

然后,客户端可以根据需要创建自己的不可变或可变映射,或将条目添加到自己的映射中。如果客户端需要知道该值是否存在,则可以返回optional:

public Integer fooOf(String key) {
    return map.get(key);
}

答案 7 :(得分:-1)

不可变地图是一种地图。所以留下返回类型的Map是可以的。

为确保用户不修改返回的对象,该方法的文档可以描述返回对象的特征。

答案 8 :(得分:-1)

这可以说是一个意见问题,但更好的想法是使用地图类的接口。这个接口不需要明确地说它是不可变的,但是如果你不公开接口中父类的任何setter方法,那么消息将是相同的。

看看以下文章:

andy gibson