help with serializing a HashMap

A

Aryeh.Friedman

I have the following class that attempts to save a
HashMap<T1,ArrayList<T2>>

public class Roles
{
public Roles()
{
contributers=new
HashMap<Contributer,ArrayList<Role>>();
}
public void addRole(Contributer contributer,Role role)
{
ArrayList<Role> r=contributers.get(contributer);
if(r==null) {
r=new ArrayList<Role>();
} else r=contributers.get(contributer);
r.add(role);
contributers.put(contributer,r);
}
public ArrayList<Role> getRoles(Contributer contributer)
{
return contributers.get(contributer);
}
public void save()
{
FileUtils.serialize("contributers",contributers);
}
public void load()
{

contributers=FileUtils.deserialize("contributers",contributers.getClass());
}
public void reset()
{
contributers=new
HashMap<Contributer,ArrayList<Role>>();
}
public String toString()
{
String s=new String();
s+=contributers;
return s;
}
private HashMap<Contributer,ArrayList<Role>> contributers;
}

When I run it against the following test code I get the output below:
public class RolesTest extends TestCase
{
public void setUp()
{
mc=new MockContributer();
mr=new MockRole();
mrl=new ArrayList<MockRole>();
mcl=new ArrayList<MockContributer>();
mrl.add(mr);
mcl.add(mc);
}
public void testAdd() {
Roles roles=new Roles();
roles.addRole(mc,mr);
System.out.println(roles.getRoles(mc));
roles.save();
roles.reset();
roles.load();
System.out.println(roles.getRoles(mc));
}
private MockContributer mc;
private MockRole mr;
private ArrayList<MockContributer> mcl;
private ArrayList<MockRole> mrl;
}

Output:

[Name: mock role]
null

For reference here are the MockContributer, MockRole, Contributer,
Role, FileUtils and IOUtils (which FIleUtils.[de]serialize calls on):

public class MockContributer extends Contributer
{
public MockContributer()
{
super("aryeh");
}
private static final long serialVersionUID=0;
}

public class MockRole extends Role
{
public MockRole()
{
super("mock role");
}
private static final long serialVersionUID=0;
}

public abstract class Contributer implements Serializable
{
public Contributer(String id)
{
this.id=id;
}
public String getId()
{
return id;
}
public String toString()
{
String s=new String();

s+="authorId: "+id;

return s;
}
private String id;
}

public abstract class Role implements Serializable
{
public Role(String name)
{
this.name=name;
}
public String getName()
{
return name;
}
public String toString()
{
String s=new String();
s+="Name: "+name;
return s;
}

private static final long serialVersionUID=0;
private String name;
}

public class FileUtils
{
public static void serialize(String fname,Serializable obj)
{
FileOutputStream out=null;
try {
out=new FileOutputStream(fname);
} catch(FileNotFoundException e) {
throw new FileError(e);
}
IOUtils.serialize(out,obj);
try {
out.close();
} catch(IOException e) {
throw new FileError(e);
}
}
public static <T extends Serializable> T deserialize(String
fname,Class<? extends T> c)
{
FileInputStream in=null;
T obj=null;
try {
in=new FileInputStream(fname);
} catch(FileNotFoundException e) {
throw new FileError(e);
}
obj=IOUtils.deserialize(in,c);
try {
in.close();
} catch(IOException e) {
throw new FileError(e);
}
return obj;
}
}

public class IOUtils
{
public static void serialize(OutputStream out,Serializable obj)
{
try {
ObjectOutputStream stream=new
ObjectOutputStream(out);
stream.writeObject(obj);
} catch(IOException e) {
throw new IOError(e);
}
}
public static <T> T deserialize(InputStream in,Class<? extends
T> c)
{
try {
ObjectInputStream stream=new
ObjectInputStream(in);
T obj=c.cast(stream.readObject());
return obj;
} catch(IOException e) {
throw new IOError(e);
} catch(ClassNotFoundException e) {
throw new IOError(e);
}
}
}
 
Z

zero

I have the following class that attempts to save a
HashMap<T1,ArrayList<T2>>

<code snipped>

The first step when debugging should always be to exactly pinpoint the
problem. Print out the HashMap before you serialize it, after you
serialize it, before you deserialize, and after you deserialize. Before
you deserialize should be empty, all the rest should be the same. This
will help you see where the problem occurs: when adding items to the Map,
when serializing, or when deserializing. Run tests that show the data in
every stage of the program, that way you can be sure when it goes wrong.

I suspect the problem is when adding items. You'll probably need to
implement boolean equals(Object o) and int hashCode() in the Contributer
class.
 
A

Aryeh.Friedman

Actually no here is the test I was using (removed the two middle prints
to make the output/bug a little clearer):

public void testAdd() {
Roles roles=new Roles();
roles.addRole(mc,mr);
System.out.println(roles.getRoles(mc));
System.out.println(roles);
roles.save();
System.out.println(roles);
roles.reset();
System.out.println(roles);
roles.load();
System.out.println(roles);
System.out.println(roles.getRoles(mc));
}


Output:

[Name: mock role]
{authorId: aryeh=[Name: mock role]}
{authorId: aryeh=[Name: mock role]}
{}
{authorId: aryeh=[Name: mock role]}
null
 
Z

zero

Actually no here is the test I was using (removed the two middle
prints to make the output/bug a little clearer):

public void testAdd() {
Roles roles=new Roles();
roles.addRole(mc,mr);
System.out.println(roles.getRoles(mc));
System.out.println(roles);
roles.save();
System.out.println(roles);
roles.reset();
System.out.println(roles);
roles.load();
System.out.println(roles);
System.out.println(roles.getRoles(mc));
}


Output:

[Name: mock role]
{authorId: aryeh=[Name: mock role]}
{authorId: aryeh=[Name: mock role]}
{}
{authorId: aryeh=[Name: mock role]}
null

Ok, your problem is not with the serialization. As you can see on the
second to last line in your output, the HashMap gets read fine. Your
problem is that the rules.getRoles(mc) returns null.

Why?

To figure that out you first have to consider how objects are saved in a
HashMap. The hashMap uses the key's hashCode method to determine where
to put the object in its internal table. In your case, that's
MockContributer's hashCode function. However, you didn't implement
hashCode, so it's using the function inherited from Object. Object's
JavaDoc says "... typically implemented by converting the internal
address of the object into an integer"

So, what happens is that the object gets saved in a location determined
by its address. You then save the Map to disk, and delete the reference
to it. When you reload the Map, it - and all of its elements - have a
different address, and thus a different hashCode. You then try to get
back an object from the Map, but you're using an old reference - and an
old hashCode. The table entry in the Map corresponding to this old
hashCode is now empty, so you're getting a null object.

And the solution? If you don't actually need to use old references to
get items from the deserialized Map, you could ignore this problem, and
instead for example use an Iterator to get all the elements in the Map.
However, this may lead to problems later on, and is probably not
optimally performant.

The other, and better, solution would be to override the hashCode()
function of Contributer. This could be as simple as having an int
argument in Contributer. Other people here recently gave me some great
hints on how to write a good hashCode method. Have a look at:

http://groups.google.be/group/comp.lang.java.programmer/browse_thread/th
read/32c74ea4ecc8e31f/6d7e56f773d37aa6
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,969
Messages
2,570,161
Members
46,708
Latest member
SherleneF1

Latest Threads

Top