3
votes

Modification des génériques Java sur le champ de classe et la sérialisation

J'ai une question de sérialisation Java qui n'est ni signalée ni rejetée comme problématique dans Java Docs (pour JDK8 ).

Donc, supposons que j'ai une classe qui implémente Serializable

private List<String> values;


4 commentaires

Quelqu'un a peut-être sérialisé une instance avec des instances non String dans la liste. Donc oui. Pourquoi cela devrait-il être mentionné explicitement dans la documentation?


Veuillez vous reporter à stackoverflow .com / questions / 3284979 /… un peu en double


@QingfeiYuan Je ne suis pas d'accord. La réponse renvoie aux mêmes documents d'Oracle que j'ai mentionnés. Rien n'est dit pour ce qui concerne les changements pour les génériques.


@AndyTurner ouais, je vois le point. Ainsi, à l'exécution, la désérialisation échouera lamentablement. Et le contraire? Donc, si je passe de List à List , c'est un supertype? Cela devrait fonctionner correctement?


3 Réponses :


-2
votes

Cela dépend entièrement de vous. Lorsque vous spécifiez un serialVersionUID, vous prenez la responsabilité de le modifier si nécessaire. Si votre système risque de rencontrer différentes versions de cet objet lorsque vous devez le modifier.


0 commentaires

3
votes

Une autre façon de penser à cette opération de sérialisation / désérialisation est que c'est un peu comme un casting.

Pouvez-vous convertir une List en une List code>? Pas directement: le compilateur vous arrêtera, car les génériques sont invariants. Vous pouvez cependant le forcer:

List<String> listOfStrings = listOfCharSequences.stream()
    .map(cs -> Objects.toString(cs, null))
    .collect(toList())

et cela pourrait fonctionner beaucoup de temps, car, String est la CharSequence la plus courante .

Mais il est possible qu'il y ait quelque chose d'autre là-dedans qui soit une CharSequence , mais pas une String code >, par exemple un StringBuilder . Le code ci-dessus planterait avec une ClassCastException.

Donc, si vous pouvez être sûr que tous vos CharSequence sont véritablement String s, vous pouvez effectuer cette modification en toute sécurité. Sinon, vous devez soit vous en prémunir en changeant l'ID de version série; ou, dans le cas de String , vous pouvez simplement invoquer toString () sur tous les éléments:

List<String> list = (List<String>) (List<?>) listOfCharSequences;
String entry = list.get(0);

( String.toString () se renvoie, donc cela ne renverrait aucune nouvelle instance pour les éléments qui sont déjà Strings)


2 commentaires

Je suis d'accord, merci d'avoir clarifié. Pensez-vous que le contraire fonctionnerait à la place? Alors, changer un champ d'un type à son supertype?


@JeanValjean cela dépend si quelque chose exécutant l'ancienne version du code lira un jour une instance écrite par la nouvelle version du code. C'est fondamentalement la même situation que la question d'origine: si quelque chose qui attend une chaîne lit autre chose, il se cassera.



2
votes

Si vous souhaitez apporter une modification incompatible, vous devez modifier le serialVersionUID afin que vous obteniez une erreur appropriée (sinon agréable) au lieu, par exemple, d'une ClassCastException à un point plus loin.

Si vous souhaitez maintenir la compatibilité, conservez le serialVersionUID et ajoutez une méthode readObject personnalisée pour convertir la List code> à List.

Modifier: Pour développer cela, si vous voulez la compatibilité, votre code devrait ressembler à ceci:

    private void readObject(
        ObjectInputStream in
    ) throws IOException, ClassNotFoundException {
        ObjectInputStream.GetField fields = in.readFields();
        @SuppressWarnings("unchecked")
        var oldValues = (List<CharSequence>)fields.get("values", null);
        // Don't bother with nice error message.
        //   NPE if missing or null elements.
        this.values = oldValues.stream().collect(
            ArrayList::new,
            (values, value) -> values.add(value.toString()),
            List::addAll
        );
    }
    // ...

Si cette classe était ajoutée en tant que sous-classe à une autre classe, alors il n'y aurait "aucune donnée" dans les flux sérialisés hérités.

    private static final long serialVersionUID = 1L;

    // Mutable field for Java Serialization, but treat as final.
    private List<String> values = new ArrayList<>();

Si vous voulez que les valeurs soient finales , vous aurez besoin d'un hack dégénéré de Serialization Proxy où l'objet proxy est du même type.

/ p>

Je vais supposer que vous voulez que l'objet de collection soit mutable. Vous ne devriez probablement pas sérialiser les objets mutables, mais c'est comme cela qu'il est habituellement utilisé et ce que donne l'exemple de l'OP.

// final - for one thing I don't want to implement readObjectNoData
public final class JeanObject {

Une alternative au (très fonctionnel ) 3-arg Stream.collect consiste à utiliser Collectors.mapping . Une boucle for à l'ancienne serait moins déroutante, mais ce n'est pas une démonstration sur Internet - vous ressembleriez à quelqu'un qui fait juste des choses.

Collectors.toList code> ne convient probablement pas car, par exemple, l'ajout d'un élément à la liste en aval de la ligne peut entraîner une erreur. Peut-être pas aujourd'hui. Peut-être pas demain. Mais lorsque votre successeur met automatiquement à jour l'implémentation. Et ils vous détesteront pour le reste de leur vie. Voici les documents sur l'API qui parlent:

Il n'y a aucune garantie sur le type, la mutabilité, la sérialisabilité ou thread-safety de la List renvoyée

Qui veut ça?

Modifier: En relisant cette citation, vous ne l'êtes peut-être pas capable de sérialiser votre objet désérialisé si vous utilisez toList...

(Le code se compile mais, comme toujours, n'est pas testé.)


0 commentaires