91

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

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

考虑了一会儿,我决定一旦创建了这个 Map 就没有理由修改它。因此,我想返回一个ImmutableMap

public Map<String, Integer> foo() {
  return ImmutableMap.of();
}

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

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

4

9 回答 9

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

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

  • 如果您正在编写一个内部工具/库,那么只返回一个普通的Map. 人们将了解他们正在调用的代码的内部结构,或者至少可以轻松访问它。

我的结论是明确是好的,不要让事情碰运气。

于 2016-06-28T23:49:58.540 回答
39

你应该有ImmutableMap作为你的返回类型。包含(eg )Map的实现不支持的方法,并在.ImmutableMapput@deprecatedImmutableMap

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

此高级警告优于将运行时异常作为您出现问题的第一个提示。

于 2016-06-29T03:04:55.517 回答
14

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

您应该在 javadocs 中提及这一点。开发人员确实会阅读它们,你知道的。

因此,我不会实现不可变对象的主要目标。通过最小化可以更改的对象的数量来使代码更清晰。更糟糕的是,一段时间后,有人可能会尝试更改此对象,这将导致运行时错误(编译器不会对此发出警告)。

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

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

于 2016-06-28T23:37:40.163 回答
10

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

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

不过,您还有 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. 但是您可能会找到第三方或自己实施。

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

于 2016-06-29T07:25:08.053 回答
8

绝对返回一个 ImmutableMap,理由是:

  • 方法签名(包括返回类型)应该是自记录的。评论就像客户服务:如果您的客户需要依赖他们,那么您的主要产品就有缺陷。
  • 某个东西是接口还是类仅在扩展或实现它时才相关。给定一个实例(对象),99% 的时间客户端代码不会知道或关心某个东西是接口还是类。起初我认为 ImmutableMap 是一个接口。只有在我点击链接后,我才意识到这是一堂课。
于 2016-06-29T03:04:44.270 回答
5

这取决于班级本身。番石榴的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);
}

请注意,这是地图的不可变视图。如果internalMap发生更改,缓存的Collections.unmodifiableMap(internalMap). 但是,我仍然更喜欢吸气剂。

于 2016-06-29T15:00:58.397 回答
5

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

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

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

public Stream<Map.Entry<String, Integer>> foos() {
    map.entrySet().stream()
}

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

public Optional<Integer> fooOf(String key) {
    return Optional.ofNullable(map.get(key));
}
于 2016-07-05T19:08:50.963 回答
-1

这可以说是一个见仁见智的问题,但这里更好的主意是使用地图类的接口。这个接口不需要显式的说它是不可变的,但是如果你不在接口中暴露任何父类的setter方法,消息还是一样的。

看看下面的文章:

安迪吉布森

于 2016-06-29T01:10:58.380 回答
-1

不可变映射是映射的一种。所以保留 Map 的返回类型是可以的。

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

于 2016-06-28T23:37:47.967 回答