开发者

Scala 2.8 mutable.Set null handling

Using scala, 2.8:

import scala.collection.mutable
import mutable.MultiMap
val m = new mutable.HashMap[String, mutable.Set[String]] with MultiMap[String, String]
m.addBinding("key", null)
m exists { _._2 contains null }

prints false

m exists { _._2开发者_StackOverflow isEmpty }

prints false

m("key").size

prints 1

How do I find the first key (or any) key that was added via an addBinding call with a value of null?


In Scala 2.7.7, you get a NullPointer straight away when adding null:


scala> val s = new scala.collection.mutable.HashSet[String]
s: scala.collection.mutable.HashSet[String] = Set()

scala> s += null
java.lang.NullPointerException
    at scala.collection.mutable.FlatHashTable$class.elemHashCode(FlatHashTable.scala:144)
    at scala.collection.mutable.HashSet.elemHashCode(HashSet.scala:31)
    at scala.collection.mutable.FlatHashTable$class.addEntry(FlatHashTable.scala:66)
    at scala.collection.mutable.HashSet.addEntry(HashSet.scala:31)
    at scala.collection.mutable.HashSet.$plus$eq(HashSet.s...

Using Scala 2.8.0.RC7, that is no longer the case. However, as Randall has observed, the behaviour is not entirely consistent either:


scala> import scala.collection.mutable.HashSet
import scala.collection.mutable.HashSet

scala> val s = new HashSet[String]
s: scala.collection.mutable.HashSet[String] = Set()

scala> s += null
res0: s.type = Set()

scala> s += null
res1: s.type = Set()

scala> s.size
res2: Int = 2

scala> s.head
java.util.NoSuchElementException: next on empty iterator
    at scala.collection.Iterator$$anon$3.next(Iterator.scala:29)
    at scala.collection.Iterator$$anon$3.next(Iterator.scala:27)
    at scala.collection.mutable.FlatHashTable$$anon$1.next(FlatHashTable.scala:176)
    at scala.collection.IterableLike$class.head(IterableLike.scala:102)
    at scala.collection.mutable.HashSet.head(HashSet.scala:38)
    at .(:8)

Most of the work when adding an entry is done in FlatHashTable When you add an entery, addEntry is invoked, which looks like this:


 /** The actual hash table.
 */
 @transient protected var table: Array[AnyRef] = new Array(initialCapacity)

def addEntry(elem: A) : Boolean = {
  var h = index(elemHashCode(elem))
  var entry = table(h)
  while (null != entry) {
  if (entry == elem) return false
      h = (h + 1) % table.length
      entry = table(h)
 }
 table(h) = elem.asInstanceOf[AnyRef]
 tableSize = tableSize + 1
 if (tableSize >= threshold) growTable()
    true
 }

null.asInstanceOf[AnyRef] just gives you back null, and the elemHashCode(elem) returns zero if the elem is null. So in this case, the element with index zero is 'initialized' with null (which is the value it already had), but the tableSize is increased. This explains the results that are seen in the REPL.

If you call head on the Set, ultimately the iterator in FlatHashTable is called, as can be seen from the stacktrace. This is implemented as follows:


iterator = new Iterator[A] {
  private var i = 0
  def hasNext: Boolean = {
    while (i < table.length && (null == table(i))) i += 1
    i < table.length
  }
  def next(): A =
    if (hasNext) { i += 1; table(i - 1).asInstanceOf[A] }
    else Iterator.empty.next
}

hasNext will return false, since the element that we stored in the Array is null, therefore, the call to next will call Iterator.empty.next, which throws an exception. Since adding null to a HashSet is not handled consistently in Scala 2.8, this indeed looks like a bug. Also, as already has been remarked, it provides another good reason to stay away from using null where possible.

Edit Since I couldn't find a ticket for this, I added it.


Perhaps this is a bug, but it is what I observe in Scala 2.8.RC3:

scala> import scala.collection.mutable.HashSet
import scala.collection.mutable.HashSet

scala> val hs1 = new HashSet[String]
hs1: scala.collection.mutable.HashSet[String] = Set()

scala> hs1 += null
res0: hs1.type = Set()

scala> hs1.size
res1: Int = 1

scala> hs1.contains(null)
res2: Boolean = false
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜