Java 最终瞬态字段和序列化

Java 最终瞬态字段和序列化,java,serialization,final,Java,Serialization,Final,在Java中序列化后,是否可以将final transient字段设置为任何非默认值?我的用例是一个缓存变量-这就是为什么它是瞬态的。我还有一个习惯,就是创建不会更改的映射字段(即映射的内容已更改,但对象本身保持不变)最终。然而,这些属性似乎是矛盾的——虽然编译器允许这样的组合,但在非序列化之后,我不能将字段设置为除null之外的任何值 我尝试了以下方法,但没有成功: 简单字段初始化(如示例所示):这是我通常所做的,但在非序列化之后初始化似乎不会发生 构造函数中的初始化(我相信这在语义上与上面

在Java中序列化后,是否可以将
final transient
字段设置为任何非默认值?我的用例是一个缓存变量-这就是为什么它是瞬态的。我还有一个习惯,就是创建不会更改的
映射
字段(即映射的内容已更改,但对象本身保持不变)
最终
。然而,这些属性似乎是矛盾的——虽然编译器允许这样的组合,但在非序列化之后,我不能将字段设置为除
null
之外的任何值

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

  • 简单字段初始化(如示例所示):这是我通常所做的,但在非序列化之后初始化似乎不会发生
  • 构造函数中的初始化(我相信这在语义上与上面相同)
  • 无法在
    readObject()
    中指定字段,因为该字段是
    final
在本例中,
cache
仅用于测试

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> ();
    }
}
很不幸,简短的回答是“不”,我经常想要这个。但瞬变不可能是最终的

最后一个字段必须通过直接赋值或在构造函数中初始化。在反序列化过程中,这两个函数都不会被调用,因此必须在反序列化过程中调用的“readObject()”私有方法中设置瞬态的初始值。要使其工作,瞬变必须是非最终的


(严格地说,期末考试只有在第一次被读取时才是最终的,所以有一些黑客可能会在读取前指定一个值,但对我来说这太过分了。)

您可以使用反射更改字段的内容。适用于Java1.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!
    }

}

请记住:

解决此类问题的一般方法是使用“串行代理”(参见有效的Java第二版)。如果需要在不破坏串行兼容性的情况下将其改装为现有的可串行类,则需要进行一些黑客攻击。

是的,通过实现(显然鲜为人知!)
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);
        }
    }
}

五年后,我在谷歌上偶然发现了这篇文章,我发现我最初的答案并不令人满意。另一个解决方案是完全不使用反射,并使用Boann建议的技术

它还利用了
ObjectInputStream 35; readFields()
方法返回的类,根据序列化规范,该类必须在私有
readObject(…)
方法中调用

该解决方案通过将检索到的字段存储在由反序列化过程创建的临时“实例”的临时临时临时临时字段(称为
FinalExample#fields
)中,使字段反序列化显式化。然后反序列化所有对象字段并调用
readResolve(…)
:创建一个新实例,但这次使用构造函数,将临时实例与临时字段一起丢弃。实例使用
GetField
实例显式恢复每个字段;这是检查任何参数的地方,就像检查任何其他构造函数一样。如果构造函数引发异常,它将被转换为
InvalidObjectException
,此对象的反序列化失败

包含的微基准测试确保此解决方案不会比默认的序列化/反序列化慢。事实上,它在我的电脑上:

Problem: 8.598s Solution: 7.818s
下面是代码:

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InvalidObjectException;
import java.io.ObjectInputStream;
import java.io.ObjectInputStream.GetField;
import java.io.ObjectOutputStream;
import java.io.ObjectStreamException;
import java.io.Serializable;

import org.junit.Test;

import static org.junit.Assert.*;

public class FinalSerialization {

    /**
     * Using default serialization, there are problems with transient final
     * fields. This is because internally, ObjectInputStream uses the Unsafe
     * class to create an "instance", without calling a constructor.
     */
    @Test
    public void problem() throws Exception {
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(baos);
        WrongExample x = new WrongExample(1234);
        oos.writeObject(x);
        oos.close();
        ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
        ObjectInputStream ois = new ObjectInputStream(bais);
        WrongExample y = (WrongExample) ois.readObject();
        assertTrue(y.value == 1234);
        // Problem:
        assertFalse(y.ref != null);
        ois.close();
        baos.close();
        bais.close();
    }

    /**
     * Use the readResolve method to construct a new object with the correct
     * finals initialized. Because we now call the constructor explicitly, all
     * finals are properly set up.
     */
    @Test
    public void solution() throws Exception {
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(baos);
        FinalExample x = new FinalExample(1234);
        oos.writeObject(x);
        oos.close();
        ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
        ObjectInputStream ois = new ObjectInputStream(bais);
        FinalExample y = (FinalExample) ois.readObject();
        assertTrue(y.ref != null);
        assertTrue(y.value == 1234);
        ois.close();
        baos.close();
        bais.close();
    }

    /**
     * The solution <em>should not</em> have worse execution time than built-in
     * deserialization.
     */
    @Test
    public void benchmark() throws Exception {
        int TRIALS = 500_000;

        long a = System.currentTimeMillis();
        for (int i = 0; i < TRIALS; i++) {
            problem();
        }
        a = System.currentTimeMillis() - a;

        long b = System.currentTimeMillis();
        for (int i = 0; i < TRIALS; i++) {
            solution();
        }
        b = System.currentTimeMillis() - b;

        System.out.println("Problem: " + a / 1000f + "s Solution: " + b / 1000f + "s");
        assertTrue(b <= a);
    }

    public static class FinalExample implements Serializable {

        private static final long serialVersionUID = 4772085863429354018L;

        public final transient Object ref = new Object();

        public final int value;

        private transient GetField fields;

        public FinalExample(int value) {
            this.value = value;
        }

        private FinalExample(GetField fields) throws IOException {
            // assign fields
            value = fields.get("value", 0);
        }

        private void readObject(ObjectInputStream stream) throws IOException,
                ClassNotFoundException {
            fields = stream.readFields();
        }

        private Object readResolve() throws ObjectStreamException {
            try {
                return new FinalExample(fields);
            } catch (IOException ex) {
                throw new InvalidObjectException(ex.getMessage());
            }
        }

    }

    public static class WrongExample implements Serializable {

        private static final long serialVersionUID = 4772085863429354018L;

        public final transient Object ref = new Object();

        public final int value;

        public WrongExample(int value) {
            this.value = value;
        }

    }

}
import java.io.ByteArrayInputStream;
导入java.io.ByteArrayOutputStream;
导入java.io.IOException;
导入java.io.InvalidObject异常;
导入java.io.ObjectInputStream;
导入java.io.ObjectInputStream.GetField;
导入java.io.ObjectOutputStream;
导入java.io.ObjectStreamException;
导入java.io.Serializable;
导入org.junit.Test;
导入静态org.junit.Assert.*;
公共类金融市场化{
/**
*使用默认序列化时,瞬态final会出现问题
*这是因为ObjectInputStream在内部使用了不安全的
*类创建“实例”,而不调用构造函数。
*/
@试验
public void problem()引发异常{
ByteArrayOutputStream bas=新的ByteArrayOutputStream();
ObjectOutputStream oos=新的ObjectOutputStream(BAS);
错误示例x=新错误示例(1234);
oos.writeObject(x);
oos.close();
ByteArrayInputStream bais=新的ByteArrayInputStream(baos.toByteArray());
ObjectInputStream ois=新ObjectInputStream(BAI);
错误示例y=(错误示例)ois.readObject();
assertTrue(y.value==1234);
//问题:
assertFalse(y.ref!=null);
ois.close();
baos.close();
bais.close();
}
/**
*使用readResolve方法构造具有正确
*因为我们现在显式调用构造函数
*决赛安排妥当。
*/
@试验
public void解决方案()引发异常{
ByteArrayOutputStream bas=新的ByteArrayOutputStream();
ObjectOutputStream oos=新的ObjectOutputStream(BAS);
FinalExample x=新的FinalExample(1234);
oos.writeObject(x);
oos.close();
ByteArrayInputStream bais=新的ByteArrayInputStream(baos.toByteArray());
ObjectInputStream ois=新ObjectInputStream(BAI);
最终样本y=(Fi
Problem: 8.598s Solution: 7.818s
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InvalidObjectException;
import java.io.ObjectInputStream;
import java.io.ObjectInputStream.GetField;
import java.io.ObjectOutputStream;
import java.io.ObjectStreamException;
import java.io.Serializable;

import org.junit.Test;

import static org.junit.Assert.*;

public class FinalSerialization {

    /**
     * Using default serialization, there are problems with transient final
     * fields. This is because internally, ObjectInputStream uses the Unsafe
     * class to create an "instance", without calling a constructor.
     */
    @Test
    public void problem() throws Exception {
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(baos);
        WrongExample x = new WrongExample(1234);
        oos.writeObject(x);
        oos.close();
        ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
        ObjectInputStream ois = new ObjectInputStream(bais);
        WrongExample y = (WrongExample) ois.readObject();
        assertTrue(y.value == 1234);
        // Problem:
        assertFalse(y.ref != null);
        ois.close();
        baos.close();
        bais.close();
    }

    /**
     * Use the readResolve method to construct a new object with the correct
     * finals initialized. Because we now call the constructor explicitly, all
     * finals are properly set up.
     */
    @Test
    public void solution() throws Exception {
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(baos);
        FinalExample x = new FinalExample(1234);
        oos.writeObject(x);
        oos.close();
        ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
        ObjectInputStream ois = new ObjectInputStream(bais);
        FinalExample y = (FinalExample) ois.readObject();
        assertTrue(y.ref != null);
        assertTrue(y.value == 1234);
        ois.close();
        baos.close();
        bais.close();
    }

    /**
     * The solution <em>should not</em> have worse execution time than built-in
     * deserialization.
     */
    @Test
    public void benchmark() throws Exception {
        int TRIALS = 500_000;

        long a = System.currentTimeMillis();
        for (int i = 0; i < TRIALS; i++) {
            problem();
        }
        a = System.currentTimeMillis() - a;

        long b = System.currentTimeMillis();
        for (int i = 0; i < TRIALS; i++) {
            solution();
        }
        b = System.currentTimeMillis() - b;

        System.out.println("Problem: " + a / 1000f + "s Solution: " + b / 1000f + "s");
        assertTrue(b <= a);
    }

    public static class FinalExample implements Serializable {

        private static final long serialVersionUID = 4772085863429354018L;

        public final transient Object ref = new Object();

        public final int value;

        private transient GetField fields;

        public FinalExample(int value) {
            this.value = value;
        }

        private FinalExample(GetField fields) throws IOException {
            // assign fields
            value = fields.get("value", 0);
        }

        private void readObject(ObjectInputStream stream) throws IOException,
                ClassNotFoundException {
            fields = stream.readFields();
        }

        private Object readResolve() throws ObjectStreamException {
            try {
                return new FinalExample(fields);
            } catch (IOException ex) {
                throw new InvalidObjectException(ex.getMessage());
            }
        }

    }

    public static class WrongExample implements Serializable {

        private static final long serialVersionUID = 4772085863429354018L;

        public final transient Object ref = new Object();

        public final int value;

        public WrongExample(int value) {
            this.value = value;
        }

    }

}