如何在null自由设计中实现List,Set和Map?

在大多数情况下,当你可以返回null / empty对象以避免空值时,它很棒,但是像对象一样集合呢?

在Java中,如果在地图中找不到get(key) key ,则Map返回null

在这种情况下,我能想到避免null的最好方法是返回一个Entry对象,它可以是EmptyEntry ,也可以包含值T

当然我们避免使用null ,但是如果不检查它是否为EmptyEntry ,现在可以有一个类EmptyEntryexception。

有没有更好的方法来避免Mapget(K) null

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

两种可能的方案:

  1. 提供包含(键)function。 如果为不存在的密钥调用get(key),则抛出exception。 缺点是:在contains()重复操作之后调用get(); 没有效率。

  2. function语言在类似情况下使用Maybe。 本文将介绍如何在Java中实现Maybe 。

你可以抛出一个“元素不存在除外”,但exception是昂贵的,应该保留为“exception情况”。 地图中不存在的值几乎不是这样,因此它可能是一个减速带,但通常情况下,它取决于您所处的上下文。

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

编辑:

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

  314 public V get(Object key) { 315 if (key == null) 316 return getForNullKey(); 317 int hash = hash(key.hashCode()); 318 for (Entry 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");} 

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

返回通用Optional类型的实例。

我认为有三种可能性。

  • 返回null或Null对象。
  • 抛出并捕获exception。
  • 或者在调用get之前通过调用containsKey避免此问题。

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

抛出exception。 例如.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. 

参看 @Doug McClean上面 – 这听起来像Scala所说的Option和Haskell称之为Maybe 。 它与你所描述的非常相似,使用EntryEmptyEntry – Scala使用Some作为有效的Entry ,使用None作为EmptyEntry

Daniel Spiewak 对Option有一个很好的介绍 ,包括一个基本的Java实现。 (但是,我可能有一个isNone()方法,而不是他的instanceof None检查,并且可能只有一个None实例 – 因为Javagenerics在运行时被删除,并且它实际上从未包含任何东西,你可以强制转换它,或者有一个“工厂”方法将它投射到你需要的任何None 。)

我想你可以返回一个具有Found的布尔值的对象和一个具有找到的项目或抛出exception的Item。 另一种方法是使用TryGet技术。

我知道您正在寻找null的替代方法,但是替代方案似乎都会导致一些exception情况,这比测试null要贵得多(生成堆栈跟踪等)。

因此,为了保持游戏,在尝试插入无效(即null)值时返回InvalidValueException,或者在错误的get()上返回NoValuePresentException。 (一直希望你可以执行一个简单的空测试)

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

 class MapAdapter implements Map { private Map inner = new HashMap(); private final V nullObject; private MapAdapter(V nullObject) { this.nullObject = nullObject; } public static  Map adapt(Map mapToAdapt, V nullObject) { MapAdapter adapter = new MapAdapter(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实现。

看起来像Maybe和Option是要走的路。

但是也需要模式匹配,以使这个类的用户更简单。 这样,用户不需要使用instanceof和cast,存在实时类转换exception的风险。