最终瞬态场和序列化

dou*_*lep 59 java serialization final

final transient在Java中进行序列化后,是否可以将字段设置为任何非默认值?我的用例是一个缓存变量 - 这就是它的原因transient.我也习惯于制作Map不会改变的字段(即地图的内容被改变,但是对象本身保持不变)final.但是,这些属性似乎是矛盾的 - 虽然编译器允许这样的组合,但我不能将字段设置为除了反null序列化之外的任何东西.

我尝试了以下内容,没有成功:

  • 简单字段初始化(在示例中显示):这是我通常所做的,但是在反序列化之后似乎没有发生初始化;
  • 在构造函数中初始化(我相信这在语义上与上面相同);
  • 分配字段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> ();
    }
}
Run Code Online (Sandbox Code Playgroud)

输出:

test$X@1a46e30 {}
test$X@190d11 null
Run Code Online (Sandbox Code Playgroud)

mdm*_*dma 34

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

必须通过直接赋值初始值或在构造函数中初始化final字段.在反序列化期间,这些都不会被调用,因此必须在反序列化期间调用的"readObject()"私有方法中设置瞬态的初始值.为了实现这一目标,瞬态必须是非最终的.

(严格来说,决赛只是在他们第一次阅读时才是最终的,所以有些黑客可以在阅读之前分配一个值,但对我来说这是一步太远了.)

  • 实际上答案是错误的.`transient`字段可以是`final`.但是为了使其工作不是默认值(`false` /`0` /`0.0` /`null`),你不仅要实现`readObject()`而且要实现`readResolve()`,或使用*反射*. (14认同)
  • 你的答案"瞬态不能是最终的"是不正确的:请解释Hibernate源代码与`final transient`:https://github.com/hibernate/hibernate-orm/blob/4.3.7.Final/hibernate-core /src/main/java/org/hibernate/internal/SessionFactoryImpl.java (4认同)

Pin*_*juh 16

您可以使用Reflection更改字段的内容.适用于Java 1.5+.它可以工作,因为序列化是在一个线程中执行的.在另一个线程访问同一个对象之后,它不应该更改最终字段(因为内存模型和reflaction中的怪异).

所以,在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!
    }

}
Run Code Online (Sandbox Code Playgroud)

记住:决赛不再是决赛!

  • 好吧,它看起来太乱了,我想在这里放弃`final`会更容易;) (4认同)

Boa*_*ann 14

是的,通过实现(显然鲜为人知的!)readResolve()方法很容易实现.它允许您在反序列化后替换对象.您可以使用它来调用构造函数,以便根据需要初始化替换对象.一个例子:

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

public class test {
    public static void main(String[] args) throws Exception {
        X x = new X();
        x.name = "This data will be serialized";
        x.cache.put("This data", "is transient");
        System.out.println("Before: " + x + " '" + x.name + "' " + x.cache);

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

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

        public X() {} // normal constructor

        private X(X x) { // constructor for deserialization
            // copy the non-transient fields
            this.name = x.name;
        }

        private Object readResolve() {
            // create a new object from the deserialized one
            return new X(this);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

输出 - 保留字符串,但瞬态映射重置为空(但非空!)映射:

Before: test$X@172e0cc 'This data will be serialized' {This data=is transient}
After: test$X@490662 'This data will be serialized' {}
Run Code Online (Sandbox Code Playgroud)


Tom*_*ine 5

这类问题的一般解决方案是使用"串行代理"(参见Effective Java 2nd Ed).如果您需要在不破坏串行兼容性的情况下将其改装为现有的可序列化类,那么您将需要进行一些黑客攻击.