Java泛型更改类字段和序列化

时间:2019-06-14 14:46:44

标签: java serialization

我有一个Java序列化问题,在Java Docs中(JDK8)既没有报告也没有否认它有问题。

因此,假设我有一个实现Serializable

的类
private static final long serialVersionUID = 1L;

private List<CharSequence> values;

如果我将values更改为

private List<String> values;

我应该更改serialVersionUID吗?

3 个答案:

答案 0 :(得分:4)

考虑此序列化/反序列化操作的另一种方法是,它有点像强制转换。

您可以将List<CharSequence>投射到List<String>吗?不直接:编译器会阻止您,因为泛型是不变的。您可以强制执行此操作,

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

并且此可能经常工作,因为String是最常见的CharSequence

但是其中可能还有其他CharSequence,而不是String,例如StringBuilder。上面的代码将以ClassCastException崩溃。

因此,如果您可以确定所有CharSequence都是真实的String,则可以安全地进行此更改。否则,您应该通过更改串行版本ID来防止它;或者或者,对于String,您可以简单地在所有元素上调用toString()

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

({String.toString()返回自身,因此对于已经String s的元素,这将不返回新实例)

答案 1 :(得分:2)

如果您要进行不兼容的更改,则应更改serialVersionUID,这样您将得到一个适当的(如果不是很好的)错误,而不是某个以后的ClassCastException。 / p>

如果要保持兼容性,请保留serialVersionUID并添加自定义readObject方法,以将List<CharSequence>转换为List<String>

编辑:要对此进行扩展,如果需要兼容性,您的代码应类似于以下内容:

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

如果将此类作为子类添加到另一个类,则旧序列化流中将“没有数据”。

    private static final long serialVersionUID = 1L;

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

如果您希望values成为final,则需要一个退化的序列化代理 hack,其中代理对象的类型相同。

我将假设您希望集合对象是可变的。您可能不应该序列化可变对象,但这就是通常使用的对象以及OP的示例的感受。

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

(非常具有功能外观的)3-arg Stream.collect的替代方法是使用Collectors.mapping。老式的豪华for loop不会造成混乱,但这在Internet上并没有炫耀-您看起来就像是刚刚完成工作的人。

Collectors.toList可能不适合,例如,在行的某个位置向列表中添加元素可能会导致错误。也许不是今天。也许不是明天。但是当您的后继者自动更新实现时。他们将一生都讨厌您。 API文档讨论如下:

  

不保证类型,可变性,可序列化性或   返回的List的线程安全性

谁想要的?

编辑:再次阅读该报价,如果您使用toList ...

,则可能无法序列化反序列化的对象。

(代码可以编译,但是仍然未经测试。)

答案 2 :(得分:-2)

完全取决于您。当您指定serialVersionUID时,您将负责在需要时进行更改。 如果您的系统在需要更改此对象时有遇到不同版本的风险。