return ImmutableMap 还是 Map 更好?

Is it better to return an ImmutableMap or a Map?

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

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

考虑了一会儿,我决定这个地图一旦创建就没有理由再修改了。因此,我想 return 一个 ImmutableMap.

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

我应该将 return 类型保留为通用映射,还是应该指定我正在 returning 一个 ImmutableMap ?

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

On the other hand, if I'll leave it like this, other developers might miss the fact that this object is immutable.

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

Thus, I won't achieve a major goal of immutable objects; to make the code more clear by minimizing the number of objects that can change. Even worst, after a while, someone might try to change this object, and this will result in a runtime error (The compiler will not warn about it).

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

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

不可变地图是地图的一种。所以留下 return 类型的 Map 就可以了。

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

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

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

  • 如果您正在编写一个内部 tool/library,那么只 return 一个普通的 Map 就变得更容易接受了。人们将了解他们正在调用的代码的内部结构,或者至少可以轻松访问它。

我的结论是明确是好的,不要听之任之。

这可以说是一个见仁见智的问题,但这里更好的想法是为地图使用一个界面 class。这个接口不需要明确说明它是不可变的,但是如果你不在接口中公开父 class 的任何 setter 方法,消息将是相同的。

看看下面这篇文章:

andy gibson

绝对 return 一个 ImmutableMap,理由是:

  • 方法签名(包括 return 类型)应该是自文档化的。评论就像客户服务:如果您的客户需要依赖他们,那么您的主要产品就有缺陷。
  • 某个东西是接口还是 class 只有在扩展或实现它时才有意义。给定一个实例(对象),99% 的时间客户端代码不知道或不关心某物是接口还是 class。一开始我假设 ImmutableMap 是一个接口。点了link才发现原来是class.

您的 return 类型应该是 ImmutableMapMap 包含 ImmutableMap 的实现不支持的方法(例如 put),并在 ImmutableMap.

中标记为 @deprecated

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

此高级警告比将运行时异常作为出现问题的第一个提示更可取。

if I'll leave it like this, other developers might miss the fact that this object is immutable

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

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

  • 使用 Javadoc

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

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

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

    public Map<String, Integer> getUnmodifiableCountByWords()
    

你还能做什么?!

你可以return一个

  • 复制

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

    如果您预计会有很多客户端修改地图并且只要地图只包含几个条目,就应该使用这种方法。

  • CopyOnWriteMap

    copy on write collections通常在你必须处理的时候使用
    并发。但是这个概念也会在你的情况下帮助你,因为 CopyOnWriteMap 在变化操作(例如添加、删除)上创建内部数据结构的副本。

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

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

    遗憾的是java没有这样的CopyOnWriteMap。但是你可能会找第三方或者自己实现。

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

这取决于class本身。 Guava 的 ImmutableMap 并不是要成为可变 class 的不可变视图。如果你的 class 是不可变的,并且有一些基本上是 ImmutableMap 的结构,那么将 return 类型设为 ImmutableMap。但是,如果您的 class 是可变的,则不要。如果你有这个:

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

Guava 每次都会复制地图。那很慢。但如果 internalMap 已经是 ImmutableMap,那就完全没问题了。

如果您不将 class 限制为 returning ImmutableMap,那么您可以 return Collections.unmodifiableMap 像这样:

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

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

这并没有回答确切的问题,但仍然值得考虑是否应该返回地图。如果地图是不可变的,那么要提供的主要方法是基于 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));
}