Search code examples
javajaxbxmladapter

unmarshal synchronized map


I'm using JAXB to save objects to xml files.

@XmlRootElement(name="jaxbobjt")
@XmlAccessorType(XmlAccessType.FIELD)
public class SomeJAXBObject
{

  @XmlElementWrapper(name="myEntry")
  private Map<Integer, AnotherJAXBObject> map = Collections.synchronizedMap(new LinkedHashMap<Integer, AnotherJAXBObject>());
}

Note the fact that I'm using a synchronizedMap(...) wrapper.

The above results in the following xml:

<jaxbobjt>
  <map>
    <myEntry>
      <key>key</key>
      <value>value</value>
    </myEntry>
  </map>
</jaxbobjt>

Actually I thought that I would need an XmlAdapter to get this working. But to my surprise this marshals and unmarshals fine. Tests revealed that it correctly uses a java.util.Collections$SynchronizedMap containing a LinkedHashMap$Entry object.

So, if I understand correctly. JAXB's unmarshaller, just instantiates my object using the constructor. Since there's already an instance for the map after instantiation of the object, it does not instantiate the map itself. It uses the putAll I assume ?

I'm just trying to get a deeper understanding of what is going on. It would be nice of somebody could give me some more background information about this. Are my assumptions correct ?

If I am correct, I assume the following implementation would have failed:

@XmlRootElement(name="jaxbobjt")
@XmlAccessorType(XmlAccessType.FIELD)
public class SomeJAXBObject
{
  // no instance yet.
  @XmlElementWrapper(name="myEntry")
  private Map<Integer, AnotherJAXBObject> map = null;

  public synchronized void addObject(Integer i, AnotherJAXBObject obj)
  {
    // instantiates map on-the-fly.
    if (map == null) map = Collections.synchronizedMap(new LinkedHashMap<Integer, AnotherJAXBObject>());
    map.put(i, obj);
  }
}

Solution

  • The strategy used by JAXB is to create container classes only when it is necessary. For anything that is bound to a List, JAXB's xjc creates

    protected List<Foo> foos;
    public List<Foo> getFoos(){
        if( foos == null ) foos = new ArrayList<>();
        return foos;
    }
    

    and thus, unmarshalling another Foo to be added to this list, does essentially

    parent.getFoos().add( foo );
    

    As for maps: presumably the working version of your class SomeJAXBObject contains a getMap method, and that'll work the same way. Setters for lists and maps aren't necessary, and they'll not be used if present. A put method in the parent class isn't expected either; if present it'll not be used because JAXB wouldn't have a way of knowing what it does.