简体   繁体   中英

Java generics change on class field and serialization

I have a Java serialization question that is neither reported nor negated as problematic in Java Docs (for JDK8 ).

So, suppose I have a class that implements Serializable

private static final long serialVersionUID = 1L;

private List<CharSequence> values;

If I change values to

private List<String> values;

should I change the serialVersionUID ?

Another way to think of this serialization/deserialization operation is that it's a bit like casting.

Can you cast a List<CharSequence> to a List<String> ? Not directly: the compiler will stop you, because generics are invariant. You can force it, however:

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

and this might work a lot of the time, because, String is the most common CharSequence .

But it's possible that there's something else in there which is a CharSequence , but not a String , for example a StringBuilder . The code above would crash with a ClassCastException .

So, if you can be sure that all of your CharSequence s are genuinely String s, it's safe to make this change. Otherwise, you should either guard against it by changing the serial version id; or, in the case of String , you could simply invoke toString() on all of the elements:

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

( String.toString() returns itself, so this would return no new instance for elements that are already String s)

If you want to make an incompatible change you should change the serialVersionUID so the you get an appropriate (if not nice) error instead of, say, a ClassCastException at some point further on.

If you want to maintain compatibility, keep the serialVersionUID and add a custom readObject method to convert the List<CharSequence> to List<String> .

Edit: To expand on that, if you want compatibility, your code should look something like this:

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

If this class was added as a subclass to another class then there would be "no data" in legacy serialised streams.

    private static final long serialVersionUID = 1L;

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

If you want values to be final you will need a degenerate Serialisation Proxy hack where the proxy object is of the same type.

I am going to assume you want the collection object to be mutable. You probably shouldn't serialise mutable objects, but that's how it's usually used and how the OP's example feels.

    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
        );
    }
    // ...

An alternative to the (very functional-looking ) 3-arg Stream.collect is to use Collectors.mapping . An old-fashioned posh for loop would be less confusing all round, but that's not showing off on the internet - you'd look like someone who just gets stuff done.

Collectors.toList is probably not suitable as, say, adding an elements to the list someway down the line may result in an error. Maybe not today. Maybe not tomorrow. But when your successor's automatic updates the implementation. And they will hate you for the rest of their life. Here's the API docs talking:

There are no guarantees on the type, mutability, serializability, or thread-safety of the List returned

Who wants that?

Edit: Reading that quote again, you might not be able to serialise your deserialised object if you use toList ...

(Code compiles but, as ever, is untested.)

It is completely up to you. When you are specifying a serialVersionUID you are taking over the responsibility to change it when needed. If your system is in risk of encountering various versions of this object when you need to change it.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM