【问题标题】:How to implement List, Set, and Map in null free design?如何在无空设计中实现 List、Set 和 Map?
【发布时间】:2010-04-07 16:30:14
【问题描述】:

当您在大多数情况下可以返回一个空/空对象以避免空值时,这很棒,但是像 Collection 这样的对象呢?

在 Java 中,如果在映射中找不到 get(key) 中的 keyMap 将返回 null

我能想到的在这种情况下避免nulls 的最佳方法是返回一个Entry<T> 对象,它要么是EmptyEntry<T>,要么包含值T

当然我们避免了null,但是如果你不检查它是否是EmptyEntry<T>,现在你可以有一个类转换异常。

有没有更好的方法来避免Mapget(K)中的nulls?

为了论证,假设这种语言甚至没有null,所以不要说只使用nulls

【问题讨论】:

  • @Sanoj aw,但我一直想成为一名宇航员

标签: java architecture collections null


【解决方案1】:

两种可能的解决方案:

  1. 提供 contains(key) 函数。如果为不存在的键调用 get(key),则抛出异常。缺点是:在 contains() 重复操作之后调用 get();效率不高。

  2. 函数式语言在类似情况下使用 Maybe。本文解释how to implement Maybe in Java

【讨论】:

  • 这会很慢,因为对 Contains 的调用与 Get 的作用几乎相同。
  • 因此是 tryGet 选项。输出为 1. 找到与否 2. 找到结果
  • @Steven - 我同意,感谢您的评论。更新了我的答案。
  • +1 for Maybe,但要意识到它会带你回到你从类型理论术语开始的地方。如果语言允许空值,那么您实际上已经内置了可能(尽管穿的衣服略有不同)。
  • @Joachim:我不确定这是一个明智的优化,因为它会惩罚更常见的模式,即每次使用不同的键调用 get()。我认为更好的答案是提供 TryGet,就像 .NET 集合那样。这将值与是否找到它解耦。
【解决方案2】:

您可以抛出“元素不存在异常”,但异常代价高昂,应保留用于“异常情况”。地图中不存在的值几乎不是这种情况,因此它可能是一个减速带,但像往常一样,这取决于您所处的环境。

无论哪种方式,作为建议,您都应该考虑使用 contains(key) 方法。总是有可能 key 已映射到 null 值,因此 get(key) 将返回 null,即使存在于您的地图中!!!。

编辑:

看了get()的源代码后,我想到了一些东西(记录一下:完全未经测试,当前时间是凌晨01:08,我感冒了!!)

  314       public V get(Object key) {
  315           if (key == null)
  316               return getForNullKey();
  317           int hash = hash(key.hashCode());
  318           for (Entry<K,V> e = table[indexFor(hash, table.length)];
  319                e != null;
  320                e = e.next) {
  321               Object k;
  322               if (e.hash == hash && ((k = e.key) == key || key.equals(k)))
  323                   return e.value;
  324           }
                //This could be instanced by reflection (bad idea?)
  325           return new MappeableElementImpl();
   }

我可以强制 V 实现一些接口,例如 MappeableElement 或类似具有 boolean isUnmappedValue() 方法的接口,然后 get() 方法可以返回该接口的实例。

所以它最终会是这样的:

Element e = map.get(notpresentkey);

if (e.isUnmappedValue()){sysout (notpresentkey  +" is not present");}

【讨论】:

  • 对空值的情况有什么改进?因为在我看来,要得到完全相同的结果,这确实是一种令人费解的方式......
【解决方案3】:

您的问题是由您正在设计的数据结构的一些不寻常的要求或场景引起的,还是您只是在理论化?如果是后者,您可能需要考虑这样一个事实,即 null 在概念上与任何其他引用值都无法区分(例如,对某些表示 null 的最终 obj 的引用),并且异常代价高昂。除非你有特定的顾虑或目标让你问这个问题,否则你真的是在浪费我们的时间。干杯!

【讨论】:

【解决方案4】:

返回一个泛型 Optional&lt;T&gt; 类型的实例。

【讨论】:

    【解决方案5】:

    在我看来,有三种可能性。

    • 返回 null 或 Null 对象。
    • 抛出并捕获异常。
    • 或在致电get 之前致电containsKey 避免此问题。

    如果您担心 Null 对象是错误的类型,那么您可以设计您的地图以接受 NullObjectFactory,它会为您正在处理的内容创建一个正确类型的特殊 Null 对象(如 @ 987654321@)。这样您就可以从 Map 中get,而不必检查它是否包含密钥,不必检查它是否返回 null,也不必捕获任何异常。

    【讨论】:

      【解决方案6】:

      抛出异常。例如 .NET 的 KeyNotFoundException、Java 的 ArrayIndexOutOfBoundsException 和 Python 的 KeyError

      众所周知,异常是针对特殊情况的,因此用户应该在查找密钥之前检查它是否存在。

      if collection.contains(key):
          return collection.get(key);
      

      不好

      try:
          return collection.get(key);
      catch KeyError:
          pass # Don't care.
      

      【讨论】:

      • Tom 说得对:必须为非异常情况捕获异常是很慢的。
      • 你的好例子和坏例子在很大程度上取决于具体情况。是否首先检查密钥是否存在的选择取决于许多因素。例如,您可能不想先检查是否: - 您正在优化一个紧凑的循环以提高速度,其中 - 很少有键不存在(即键的数量有限)
      • 我要重复我的建议:TryGet 之类的东西在这里是理想的。
      【解决方案7】:

      参照。上面的@Doug McClean——这听起来像是 Scala 调用的 Option 和 Haskell 调用的 Maybe。这很像你所描述的,EntryEmptyEntry - Scala 使用Some 来表示有效的Entrys 和None 来表示EmptyEntry

      Daniel Spiewak 拥有a good intro to Option,包括一个基本的 Java 实现。 (不过,我可能会有一个isNone() 方法,而不是他的instanceof None 检查,并且可能只有一个None 实例——因为Java 泛型在运行时被删除,它实际上不包含任何东西,你可以将其转换为您需要的任何None&lt;T&gt;。)

      【讨论】:

        【解决方案8】:

        我想您可以返回一个具有 Found 布尔值的对象和一个具有已找到项目或引发异常的 Item。另一种方法是使用 TryGet 技术。

        【讨论】:

          【解决方案9】:

          我了解您正在寻找 null 的替代方案,但这些替代方案似乎都会导致一些异常情况,这比测试 null 更昂贵(生成堆栈跟踪等)。

          因此,要继续游戏,请在尝试插入无效(即 null)值时返回 InvalidValueException,或在错误 get() 时返回 NoValuePresentException。 (一直希望您可以执行一个简单的空测试)

          【讨论】:

            【解决方案10】:

            从概念上讲,这是一个大问题。一种有用的场景是创建将所有调用委托给底层映射对象的适配器。对于此适配器,将需要指定 null 对象的参数。例如:

            class MapAdapter<K,V> implements Map<K,V> {
                private Map<K,V> inner = new HashMap<K,V>();
                private final V nullObject;
            
                private MapAdapter(V nullObject) {
                    this.nullObject = nullObject;
                }
            
                public static <K,V> Map<K,V> adapt(Map<K,V> mapToAdapt, V nullObject) {
                    MapAdapter<K,V> adapter = new MapAdapter<K,V>(nullObject);
                    adapter.inner.addAll(mapToAdapt);
                    return adapter;
                }
            
            
                //Here comes implementation of methods delegating to inner.
            
            
               public V get(K key) {
                   if (inner.containsKey(key)) {
                       return inner.get(key);
                   }
                   return nullObject;
               }
            }
            

            很多工作,但它允许通用 NullSafe 实现。

            【讨论】:

              【解决方案11】:

              看起来可能和选项是要走的路。

              但也需要模式匹配以使该类的用户更简单。这样,用户就不需要使用 instanceof 和 cast 来冒实时类转换异常的风险。

              【讨论】:

              • 既然如此,也许最好不要实现Map——相反,实现一个类似地图的接口,让您而不是get(),传入一个键和一个值处理程序,同样用于迭代。
              • @David:恕我直言,这种基于委托的答案听起来比 TryGet 更复杂、更慢。
              • @Steven Sudit:你能详细说明一下吗?我希望性能在最坏的情况下是相同的,但也许我误解了你的 tryGet() 方法。它对我来说似乎也没有更复杂,但这可能是因为我习惯了更函数式的编程风格。
              • @David:可以肯定的是,传递一个委托比使用 TryGet 慢,但它不一定很多慢,所以速度不是真正的问题。复杂性是。也许是因为我不是来自函数式编程风格的背景,但是设置回调而不只是检查返回值的想法听起来像 Rube Goldbergish。我看不出有什么比if (map.tryGet(key, out value)) 更简单的了。也许委托方法的一个例子可能会有所帮助。
              • @Steven Sudit:完成:stackoverflow.com/questions/2681415/… :)
              猜你喜欢
              • 1970-01-01
              • 2011-07-02
              • 1970-01-01
              • 1970-01-01
              • 2018-06-19
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多