Adam Burley
Adam Burley

Reputation: 6069

How should I read and write an object containing a List in java?

I had an object

public class Foo implements Serializable {
    private List<Bar> barList;
    // getters and setters
}

(Bar also implements Serializable.)

FindBugs threw a violation SE_BAD_FIELD. Which is fair enough, because List is not transient or Serializable. However, I want to serialize this list! So I'm going to need to write writeObject and readObject myself.

I had a look at the ArrayList implementation of writeObject in the JDK, which is fairly straightforward but also a little more complicated than I thought. Here it is:

private void writeObject(java.io.ObjectOutputStream s)
    throws java.io.IOException{
    // Write out element count, and any hidden stuff
    int expectedModCount = modCount;
    s.defaultWriteObject();

    // Write out size as capacity for behavioural compatibility with clone()
    s.writeInt(size);

    // Write out all elements in the proper order.
    for (int i=0; i<size; i++) {
        s.writeObject(elementData[i]);
    }

    if (modCount != expectedModCount) {
        throw new ConcurrentModificationException();
    }
}

I had expected it would just do the part called "Write out all elements in the proper order" above. But it seems to be a bit more complex than that. Do I need to copy the stuff into my implementation of writeObject inside Foo?

Thanks!

Upvotes: 0

Views: 115

Answers (2)

isk
isk

Reputation: 11

If barList is private and doesn't need to be set verbatim, a quick solution is to declare it as some serializable type like private ArrayList<Bar> barList and modify it with something like:

void addBar(Bar bar) { barList.add(bar); }
void setBarList(List<Bar> bars) { barList = new ArrayList(bars); }

But if you have to write serialization yourself for some reason, you need to write the size and each object (use Externalizable, it's faster than just Serializable). Based on example from Oracle:

class Foo implements Externalizable {
  List<Bar> barList;

  public void writeExternal(ObjectOutput out) throws IOException {
    if (barList == null) {
      out.writeInt(-1);
      return;
    }
    out.writeInt(barList.size());
    for (Bar bar : barList) {
      out.writeObject(bar);
    }
  }

  public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
    int size = in.readInt();
    if (size >= 0) {
      barList = new ArrayList<>();
      for (int i = 0; i < size; i++) {
        barList.add((Bar) in.readObject());
      }
    }
  }
}

Note that readExternal() still uses ArrayList. If you want to preserve the class of the list, you can also write class name in writeExternal() and instantiate it using reflection in readExternal(). That would require a no-args constructor in that class though.

Upvotes: 1

JB Nizet
JB Nizet

Reputation: 691933

FindBugs is warning you, because the actual concrete List might not be Serializable. That doesn't mean it isn't. If you're absolutely sure that the actual list is indeed, at runtime, serializable (as ArrayList, LinkedList and many other List implementations are), then you don't need to do anything.

A good way to ensure about that is to avoid accepting any List as argument from the outside:

// ArrayList is serializable
private List<Bar> barList = new ArrayList<>();

public void setBarList(List<Bar> barList) { 
    // barList stays an ArrayList, still serializable
    this.barList.clear();
    this.barList.addAll(barList);
}

Upvotes: 0

Related Questions