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 方法,消息将是相同的。
看看下面这篇文章:
绝对 return 一个 ImmutableMap,理由是:
- 方法签名(包括 return 类型)应该是自文档化的。评论就像客户服务:如果您的客户需要依赖他们,那么您的主要产品就有缺陷。
- 某个东西是接口还是 class 只有在扩展或实现它时才有意义。给定一个实例(对象),99% 的时间客户端代码不知道或不关心某物是接口还是 class。一开始我假设 ImmutableMap 是一个接口。点了link才发现原来是class.
您的 return 类型应该是 ImmutableMap
。 Map
包含 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));
}
假设我正在编写一个应该 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 方法,消息将是相同的。
看看下面这篇文章:
绝对 return 一个 ImmutableMap,理由是:
- 方法签名(包括 return 类型)应该是自文档化的。评论就像客户服务:如果您的客户需要依赖他们,那么您的主要产品就有缺陷。
- 某个东西是接口还是 class 只有在扩展或实现它时才有意义。给定一个实例(对象),99% 的时间客户端代码不知道或不关心某物是接口还是 class。一开始我假设 ImmutableMap 是一个接口。点了link才发现原来是class.
您的 return 类型应该是 ImmutableMap
。 Map
包含 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));
}