miércoles, 18 de agosto de 2010

TreeSet y java.io.NotSerializableException

Después de un periodo sabático, un truco para solucionar un problemilla habitual por culpa de como está montado el JDK, sin ser realmente culpa de nadie... El problema en sí aparece al tratar de serializar un TreeSet, o cualquier otra colección ordenada, en el cual hemos especificado un Comparator creado como instancia anónima. En código:
static Comparator miComparador =
    new Comparator()
    {
      @Override
      public int compare(Object arg0, Object arg1)
      {
        //...
        return valorAdecuado;
      }
    };

  public static void main(String[] arg)
    throws Exception
    {
      Set miSetOrdenado = new TreeSet(miComparador);
      // ... rellenamos el Set
      ByteArrayOutputStream theBOS = new ByteArrayOutputStream();
      ObjectOutputStream theOOS = new ObjectOutputStream(theBOS);
      theOOS.writeObject(miSetOrdenado);
      theOOS.close();
    }
Si intentamos ejecutar este código, nos saltará una excepción:
java.io.NotSerializableException: test.App$1

Después de volverse tonto mirando por que el los elementos que uno pone en el TreeSet no son serializables, para normalmente descubrir que sí lo son, al final resulta que el problema es el Comparator. Entonces buscamos por Internet y las recomendaciones más habituales son hacer el campo static o transient, implementar Serializable... pero resulta que el Comparator lo tenemos declarado como static, por lo tanto no debería afectar puesto que la clase App sería Serializable... ¿o no? La cuestión sin embargo es... ¿cual es realmente la instancia que no es Serializable?

Para hacer la historia breve, el problema es que la instancia no Serializable es el TreeSet y que el campo que habría que marcar como transient es donde se guarda el Comparator de la clase TreeSet, cosa imposible, así que sólo nos queda declarar que el Comparator implementa Serializable, pero... ¿Cómo lo hacemos si es una instancia anónima? La solución pasa por no usar una instancia de clase anónima y hacer una clase interna, la cual sí podemos declarar que es Serializable y luego crear una instancia. Modificando ligeramente el código:
static class MiClaseComparadora
    implements Comparator, Serializable
  {
    @Override
    public int compare(Object arg0, Object arg1)
    {
      //...
      return valorAdecuado;
    }
  }
  static Comparator miComparador = new MiClaseComparadora();

  public static void main(String[] arg)
    throws Exception
    {
      Set miSetOrdenado = new TreeSet(miComparador);
      // ... rellenamos el Set
      ByteArrayOutputStream theBOS = new ByteArrayOutputStream();
      ObjectOutputStream theOOS = new ObjectOutputStream(theBOS);
      theOOS.writeObject(miSetOrdenado);
      theOOS.close();
    }
Y listo. Un detalle tonto que puede impedir que podamos serializar algunos objetos necesarios para nuestra aplicación y cuyo diagnóstico no es sencillo.

En mi caso, necesitaba almacenar en OSCache una lista ordenada de elementos para no tener que re-ordenarlos cada vez y al pasar la cache de memoria a disco... zas!

Espero que esto le evite a alguien tener que dar tantas vueltas por Internet como a mí, para acabar sin encontrar una solucón clara y concisa.

Happy coding!
EJ

2 comentarios:

  1. A mi me da error a pesar de que ya hice la clase serializable y todos sus atributos son extendidos y serializables también, me imagino que ArrayList ya es serializable.
    public class Data implements Serializable {
    MiDefaultListModel model = new MiDefaultListModel();
    ArrayList jtabs = new ArrayList();
    }

    ResponderEliminar
  2. Hola,

    Sin ver el stacktrace del error que da y teniendo en cuenta que tu clase Data no es o extiende un TreeSet, no puedo adivinar cual es el problema.

    Así a primera vista, podría ser un problema con la clase MiDefaultListModel o con los objeto que se introduzcan dentro del ArrayList, ya que no sólo ha de serlo la clase, si no también todos los elementos que contenga.

    Haría falta más información para un diagnóstico más preciso.

    Happy Coding!
    EJ

    ResponderEliminar