Tim
Tim

Reputation: 13258

Scala - mutable HashMap -> write to file / read from file -> HashMap null

I have this Scala code running on an Android device:

// create Map
val myMap1 = new HashMap[Int, String]()
myMap1.put(1, "a")

// write it to file
val outStream = context.openFileOutput("test.txt", Context.MODE_PRIVATE)
val ostream = new ObjectOutputStream(outStream)
ostream.writeObject(myMap1)
ostream.close

// read from file
val inStream = context.openFileInput("test.txt")
val istream = new ObjectInputStream(inStream)
val myMap2 = (istream.readObject).asInstanceOf[HashMap[Int, String]]
istream.close

// java.lang.NullPointerException accessing myMap2
if (myMap2.contains(1)) { println("yes") } else { println("no") }

I create a mutable.HashMap and write it to a file, read it and then the HashMap is null. Why is myMap2 null and do not have any contents? Below is a screenshot of the debug session.

Screenshot of Eclipse IDE - debug session

Full stacktrace:

java.lang.NullPointerException
  at scala.collection.mutable.HashTable$class.index(HashTable.scala:353)
  at scala.collection.mutable.HashMap.index(HashMap.scala:39)
  at scala.collection.mutable.HashTable$class.findEntry(HashTable.scala:130)
  at scala.collection.mutable.HashMap.findEntry(HashMap.scala:39)
  at scala.collection.mutable.HashMap.contains(HashMap.scala:60)
  at com.test.mytest.bean.MyItem$.read(MyItem.scala:74)
  at com.test.mytest.bean.MyItem$.add(MyItem.scala:93)
  at com.test.mytest.frag.MyFragment.onClick(MyFragment.scala:114)
  at android.view.View.performClick(View.java:4084)
  at android.view.View$PerformClick.run(View.java:16966)
  at android.os.Handler.handleCallback(Handler.java:615)
  at android.os.Handler.dispatchMessage(Handler.java:92)
  at android.os.Looper.loop(Looper.java:137)
  at android.app.ActivityThread.main(ActivityThread.java:4745)
  at java.lang.reflect.Method.invokeNative(Native Method)
  at java.lang.reflect.Method.invoke(Method.java:511)
  at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:786)
  at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:553)
  at dalvik.system.NativeStart.main(Native Method)

Upvotes: 2

Views: 974

Answers (1)

Régis Jean-Gilles
Régis Jean-Gilles

Reputation: 32719

The reason is that HashTable contains a protected field table that is declared as "transient":

@transient protected var table: Array[HashEntry[A, Entry]]

So it is never written to the file, and when deserializing keeps its default null value.

However, HashMap defines readObject and writeObject to explictly handle the serialization (I have not taken the time to dive in the whole code but most certainly this is supposed to handle the reading/writing of the table field, among others) so it appears that it should work anyway.

Now, let me make an educated guess here: you are on Android, so most certainly you are using a tool like proguard to remove all the unneeded code (and shrink the code you do need via renamings). The problem then is that readObject and writeObject are private, so depending on your proguard configuration, proguard might assume that the methods are never accessed and thus remove them altogether. Thus at runtime, only the standard serialization is used and the custom serialization that is supposed to handle table never happens.

What you should do is modify your proguard configuration so as to keep the readObject and writeObject methods. The standard proguard documentation even has an example of this here (search for "readObject"): http://proguard.sourceforge.net/index.html#manual/examples.html

Upvotes: 3

Related Questions