通过Collections.unmodifiable *已经用Collections.unmodifiable *包装的实例是多么低效?

我有一些计件工作由不同的自定义(源代码不可用)框架完成,这些框架可以传回Map实例。 不幸的是,这些框架在返回的Map实例中并不一致,这些实例已用Collections.unmodifiableMap包装。 为了确保我的代码中更高程度的不变性(为了更容易的multithreading使用),我在这些框架返回的任何内容上统一调用了Collections.unmodifiableMap。

Map immutableMap = framework.getRecordsByName(); //does this created a nested set of unmodifiableMap wrapper instances? this.immutableField = Collections.unmodifiableMap(immutableMap); . . . Map maybeImmutableMap = framework.getRecordsByName(); //is there some means to get instanceof to work? if (!(maybeImmutableMap instanceof Collections.UnmodifiableMap)) { this.immutableField = Collections.unmodifiableMap(maybeImmutableMap); } 

我意识到我的设计可能会遇到性能问题。 在某些情况下,我正在调用Collections.unmodifiableMap向它传递一个实例,该实例已经被同一个调用的框架包装。 而且我的重新包装可能会在整个实例中引起额外的方法调用。

似乎使用“instanceof Collections.UnmodifiableMap”不起作用。 如果我当前引用的Map实例需要被包装,我找不到任何检测方法(不包括使用这种情况下不是选项的reflection – 方式太慢)。

问题:

    A)Collections.unmodifiableMap()方法是否检查它是否传递了UnmodifiableMap的实例,如果是这样,只返回相同的引用(从而避免在调用方法之前检查)?
    B)为了主动避免接收修改exception,有没有办法查询Map实例(使用reflection除外)来检测它是否可变(或不可变)?
    C)如果A的答案为否,那么JVM / HotSpot是否有一些效率可以消除调用多个方法跳转到核心实例的开销?

尽我所知:

  • A)不。
  • B)不。
  • C)否

番石榴的Immutable*系列没有这个问题。 如果使用本身为ImmutableListlist调用ImmutableList.copyOf(list) ,则返回参数本身。 另外,您可以将它们称为(并使用instanceof检查) Immutable*类型而不是接口,这样可以很容易地知道您是否具有不可变实例。 因此,一种选择是将框架中的结果复制到这些不可变集合中,并在整个代码中使用它们。 (它们还具有真正不可变的优点……不可修改的包装器允许它们包装的原始可变实例在有东西引用时自行变异。)

所有这一切,我不会太担心通过1或2个不可修改的包装层传递方法调用的可能开销,只要你不会以某种方式一次又一次地包装它们。 正如其他人所指出的那样,由于这个原因,你很可能不会注意到性能问题。

将一个不可修改的地图包装到另一个地图时,您不必担心性能问题。 看看UnmodifiableMap类:

 private static class UnmodifiableMap implements Map, Serializable { ... UnmodifiableMap(Map m) { if (m==null) throw new NullPointerException(); this.m = m; } public int size() {return m.size();} ... public V put(K key, V value) { throw new UnsupportedOperationException(); } public V remove(Object key) { throw new UnsupportedOperationException(); } public Set keySet() { if (keySet==null) keySet = unmodifiableSet(m.keySet()); return keySet; } public Set> entrySet() { if (entrySet==null) entrySet = new UnmodifiableEntrySet(m.entrySet()); return entrySet; } ... 

您可以看到此类只是真实地图周围的薄包装。 所有方法(如getSizeisEmpty和其他不影响map状态的方法)都会委托给包装的map实例。 其他影响map状态的方法( putremove )只是抛出UnsupportedOperationException所以几乎没有性能过载。

我对(C)的看法是,热点编译器应该非常擅长内联只返回另一个方法调用结果的小方法。 但我怀疑它可以看到几个级别的间接,例如包含在几个UnmodifiableMaps中的HashMap。 如果您知道您的图书馆有时会返回不可修改的地图,我也认为不会过早优化。 为什么不像这样为Collections.unmodifiableMap创建自己的包装器(未经测试,我只是注意到一个简单的instanceof不起作用,因为集合中的Impl是私有的):

 public class MyCollections { private static final Class UNMODIFIABLE_MAP_CLASS = Collections.unmodifiableMap(new HashMap()).getClass(); public static  Map unmodifiableMap(Map map) { return map.getClass() == UNMODIFIABLE_MAP_CLASS ? map : Collections.unmodifiableMap(map); } } 

在回顾了所有反馈之后,我得出的结论是,无论我做什么,解决方案都将成为某种forms的kludge(具有温和的气味)。 我认为这是因为Collections API中产生不可修改实例的部分不能避免嵌套不可修改的实例,也没有为客户提供“公共”方式来正确避免嵌套。

由于多个类加载器的考虑和通过RMI的序列化,我真正喜欢的一个解决方案(Jorn Horstmann的类参考比较)存在问题。 然而,当我采用他的方法并将其与类名方法的修改(由Eugene Kuleshov推荐)相结合时,我想我会得到尽可能接近的解决方案,这将有助于我的multithreading分布式处理环境。 它有点像这样:

 public class MyCollections { private static final String UNMODIFIABLE_MAP_CLASS_NAME = Collections.unmodifiableMap(new HashMap()).getClass().getName(); public static  Map unmodifiableMap(Map map) { return map.getClass().getName().equals(UNMODIFIABLE_MAP_CLASS_NAME) ? map : Collections.unmodifiableMap(map); } } 

这仍将具有参考比较的所有优点, 假设所有内容都发生在同一个ClassLoader上下文中并且类名的字符串已被正确实现。 并且它在礼貌地保持封装的同时(避免我的代码直接引用类名)。 但是,如果这两个假设不成立,那么评估将回退到标准的字符串比较,假设类名在库的不同版本之间没有变化(这似乎具有非常低的概率)。

这种方法中有什么我遗忘或遗失的吗?

再次感谢大家,感谢您的反馈。 对此,我真的非常感激。

A)没有

B)没有

C)可能,但我希望这取决于JVM的实施。

有问题的装饰器是非常轻的,是否有一些特定的原因,一个额外的方法调用只是使另一个方法调用会导致您的环境中的一些性能问题? 这应该不是任何标准应用程序中的问题,因此除非你有一些非常特殊的应用程序/环境(cpu密集型,实时,移动设备……),否则它应该不是问题。

通常,第一条规则是不对其进行优化,除非确实有必要。 基本上,您需要从应用程序中收集一些性能数据,以查看包装集合是否会导致任何性能损失。

但是如果你真的想检查集合是否是不可修改的,你可以检查“UnmodifiableMap”.equals(Class.getSimpleName())