web-dev-qa-db-fra.com

Java Sérialisation avec des parties non sérialisables

J'ai:

class MyClass extends MyClass2 implements Serializable {
  //...
}

Dans MyClass2 est une propriété qui n'est pas sérialisable. Comment puis-je sérialiser (et désérialiser) cet objet?

Correction: MyClass2 n'est bien sûr pas une interface mais une classe.

52
Burkhard

Comme quelqu'un d'autre l'a noté, le chapitre 11 de Josh Bloch Effective Java est une ressource indispensable sur Java Serialization.

Quelques points de ce chapitre pertinents à votre question:

  • en supposant que vous souhaitiez sérialiser l'état du champ non sérialisable dans MyClass2, ce champ doit être accessible à MyClass, soit directement, soit via des getters et setters. MyClass devra implémenter une sérialisation personnalisée en fournissant des méthodes readObject et writeObject.
  • la classe du champ non sérialisable doit avoir une API pour permettre d'obtenir son état (pour l'écriture dans le flux d'objet), puis d'instancier une nouvelle instance avec cet état (lors d'une lecture ultérieure dans le flux d'objet).
  • selon l'article 74 de Java efficace, MyClass2 doit avoir un constructeur sans argument accessible à MyClass, sinon il est impossible pour MyClass d'étendre MyClass2 et d'implémenter Serializable.

J'ai écrit un exemple rapide ci-dessous pour illustrer cela.


class MyClass extends MyClass2 implements Serializable{

  public MyClass(int quantity) {
    setNonSerializableProperty(new NonSerializableClass(quantity));
  }

  private void writeObject(Java.io.ObjectOutputStream out)
  throws IOException{
    // note, here we don't need out.defaultWriteObject(); because
    // MyClass has no other state to serialize
    out.writeInt(super.getNonSerializableProperty().getQuantity());
  }

  private void readObject(Java.io.ObjectInputStream in)
  throws IOException {
    // note, here we don't need in.defaultReadObject();
    // because MyClass has no other state to deserialize
    super.setNonSerializableProperty(new NonSerializableClass(in.readInt()));
  }
}

/* this class must have no-arg constructor accessible to MyClass */
class MyClass2 {

  /* this property must be gettable/settable by MyClass.  It cannot be final, therefore. */
  private NonSerializableClass nonSerializableProperty;

  public void setNonSerializableProperty(NonSerializableClass nonSerializableProperty) {
    this.nonSerializableProperty = nonSerializableProperty;
  }

  public NonSerializableClass getNonSerializableProperty() {
    return nonSerializableProperty;
  }
}

class NonSerializableClass{

  private final int quantity;

  public NonSerializableClass(int quantity){
    this.quantity = quantity;
  }

  public int getQuantity() {
    return quantity;
  }
}
49
Scott Bale

MyClass2 n'est qu'une interface, donc techniquement, elle n'a pas de propriétés, seulement des méthodes. Cela étant dit, si vous avez des variables d'instance qui ne sont pas elles-mêmes sérialisables, le seul moyen que je connaisse pour le contourner est de déclarer ces champs transitoires.

ex:

private transient Foo foo;

Lorsque vous déclarez un champ transitoire, il sera ignoré pendant le processus de sérialisation et de désérialisation. Gardez à l'esprit que lorsque vous désérialisez un objet avec un champ transitoire, la valeur de ce champ sera toujours sa valeur par défaut (généralement nulle).

Notez que vous pouvez également remplacer la méthode readResolve () de votre classe afin d'initialiser les champs transitoires en fonction d'un autre état du système.

35
Mike Deck

Si possible, les parties non sérialisables peuvent être définies comme transitoires

private transient SomeClass myClz;

Sinon, vous pouvez utiliser Kryo . Kryo est un cadre de sérialisation de graphes d'objets rapide et efficace pour Java (par exemple Java sérialisation de Java.awt.Color nécessite 170 octets, Kryo seulement 4 octets), qui peut sérialiser également des objets non sérialisables. Kryo peut également effectuer une copie/clonage automatique profond et superficiel. Il s'agit d'une copie directe d'un objet à un autre, pas object->bytes->object.

Voici un exemple d'utilisation de kryo

Kryo kryo = new Kryo();
// #### Store to disk...
Output output = new Output(new FileOutputStream("file.bin"));
SomeClass someObject = ...
kryo.writeObject(output, someObject);
output.close();
// ### Restore from disk...
Input input = new Input(new FileInputStream("file.bin"));
SomeClass someObject = kryo.readObject(input, SomeClass.class);
input.close();

Les objets sérialisés peuvent également être compressés en enregistrant le sérialiseur exact:

kryo.register(SomeObject.class, new DeflateCompressor(new FieldSerializer(kryo, SomeObject.class)));
15
Radim Burget

Si vous pouvez modifier MyClass2, la façon la plus simple de résoudre ce problème est de déclarer la propriété transitoire.

11
ykaganovich

Vous devrez implémenter writeObject() et readObject() et effectuer une sérialisation/désérialisation manuelle de ces champs. Voir la page javadoc pour Java.io.Serializable pour plus de détails. Effective Java de Josh Bloch contient également de bons chapitres sur l'implémentation d'une sérialisation robuste et sécurisée.

6
sk.

Dépend pourquoi ce membre de MyClass2 n'est pas sérialisable.

S'il y a une bonne raison pour laquelle MyClass2 ne peut pas être représentée sous une forme sérialisée, alors les chances sont bonnes, la même raison s'applique à MyClass, car c'est une sous-classe.

Il peut être possible d'écrire un formulaire sérialisé personnalisé pour MyClass en implémentant readObject et writeObject, de telle manière que l'état des données d'instance MyClass2 dans MyClass puisse être recréé de manière appropriée à partir des données sérialisées. Ce serait la voie à suivre si l'API de MyClass2 est corrigée et que vous ne pouvez pas ajouter Serializable.

Mais vous devez d'abord comprendre pourquoi MyClass2 n'est pas sérialisable, et peut-être le changer.

5
Steve Jessop

Vous pouvez commencer par examiner le mot clé transitoire, qui marque les champs comme ne faisant pas partie de l'état persistant d'un objet.

4
Hank

Plusieurs possibilités sont apparues et je les résume ici:

  • Implémentez writeObject () et readObject () comme sk suggéré
  • déclarer la propriété transitoire et elle ne sera pas sérialisée comme indiqué par hank
  • utilisez XStream comme indiqué par boris-terzic
  • utiliser un proxy série comme indiqué par tom-hawtin-tackline
4
Burkhard

XStream est une excellente bibliothèque pour faire rapidement Java à la sérialisation XML pour tout objet, qu'il soit sérialisable ou non. Même si le format cible XML ne convient pas vous, vous pouvez utiliser le code source pour apprendre à le faire.

3
Boris Terzic

Une approche utile pour sérialiser des instances de classes non sérialisables (ou au moins des sous-classes de) est connue comme un proxy série. Essentiellement, vous implémentez writeReplace pour renvoyer une instance d'une classe sérialisable complètement différente qui implémente readResolve pour renvoyer une copie de l'objet d'origine. J'ai écrit un exemple de sérialisation de Java.awt.BasicStroke sur senet

2