最终瞬态字段和序列化

2022-08-31 14:49:17

在 Java 中序列化后,是否可以将字段设置为任何非默认值?我的用例是一个缓存变量 - 这就是为什么它是.我也有一个习惯,即制作不会更改的字段(即地图的内容已更改,但对象本身保持不变)。但是,这些属性似乎是矛盾的 - 虽然编译器允许这样的组合,但除了取消序列化之后,我无法将字段设置为任何内容。final transienttransientMapfinalnull

我尝试了以下方法,但没有成功:

  • 简单的字段初始化(如示例所示):这是我通常执行的操作,但在取消序列化后初始化似乎没有发生;
  • 构造函数中的初始化(我相信这在语义上与上述相同);
  • 无法将字段分配到 — 中,因为该字段为 。readObject()final

在示例中仅用于测试。cachepublic

import java.io.*;
import java.util.*;

public class test
{
    public static void main (String[] args) throws Exception
    {
        X  x = new X ();
        System.out.println (x + " " + x.cache);

        ByteArrayOutputStream  buffer = new ByteArrayOutputStream ();
        new ObjectOutputStream (buffer).writeObject (x);
        x = (X) new ObjectInputStream (new ByteArrayInputStream (buffer.toByteArray ())).readObject ();
        System.out.println (x + " " + x.cache);
    }

    public static class X implements Serializable
    {
        public final transient Map <Object, Object>  cache = new HashMap <Object, Object> ();
    }
}

输出:

test$X@1a46e30 {}
test$X@190d11 null

答案 1

不幸的是,简短的答案是“不” - 我经常想要这个。但瞬态不可能是最终的。

必须通过直接赋值或在构造函数中初始化最终字段。在反序列化期间,这两个都不会被调用,因此必须在反序列化期间调用的'readObject()'私有方法中设置瞬态的初始值。要做到这一点,瞬变必须是非最终的。

(严格来说,决赛只是在第一次被读取时才是最终的,所以有一些黑客可能会在读取之前分配一个值,但对我来说,这太过分了。


答案 2

您可以使用反射更改字段的内容。适用于 Java 1.5+。它将起作用,因为序列化是在单个线程中执行的。在另一个线程访问同一对象后,它不应该更改最终字段(因为内存模型和引用中的奇怪之处)。

因此,在 中,您可以执行类似于此示例的操作:readObject()

import java.lang.reflect.Field;

public class FinalTransient {

    private final transient Object a = null;

    public static void main(String... args) throws Exception {
        FinalTransient b = new FinalTransient();

        System.out.println("First: " + b.a); // e.g. after serialization

        Field f = b.getClass().getDeclaredField("a");
        f.setAccessible(true);
        f.set(b, 6); // e.g. putting back your cache

        System.out.println("Second: " + b.a); // wow: it has a value!
    }

}

请记住:决赛不再是决赛了!