如何在Java序列化中使用final关键字与transient修饰符

4

我正在阅读有关 transient 和 final 关键字的内容,发现我们不能将 transient 关键字与 final 关键字一起使用。但是我尝试后却感到困惑,因为在这里它可以正常工作。

import java.io.FileOutputStream;
import java.io.FileInputStream;
import java.io.ObjectOutputStream;
import java.io.ObjectInputStream;
import java.io.Serializable;

public class SerExample{
    public static void main(String... args){
        Student foo = new Student(3,2,"ABC");
        Student koo = new Student(6,4,"DEF");
        try
        {
            FileOutputStream fos = new FileOutputStream("abc.txt");
            ObjectOutputStream oos = new ObjectOutputStream(fos);
            oos.writeObject(foo);
            oos.writeObject(koo);
            oos.close();
            fos.close();
        }
        catch(Exception e){/**/}

        try{
            FileInputStream fis = new FileInputStream("abc.txt");
            ObjectInputStream ois = new ObjectInputStream(fis);
            System.out.println(ois.readObject());
            System.out.println(ois.readObject());
            fis.close();
            ois.close();
        }catch(Exception e){/**/}
    }
}

这里是可序列化的学生类代码:

class Student implements Serializable{
        private transient final int id;
        private transient static int marks;
        private String name;
        public Student(int id, int marks, String name){
            this.id = id;
            this.marks = marks;
            this.name = name;
        }
        public Student(){
            id=0;
        }
        @Override
        public String toString(){
            return (this.name + this.id + this.marks);
        }
    }

使用瞬态关键字的代码输出。

ABC04
DEF04

没有使用transient关键字的输出。
ABC34
DEF64

你能解释一下为什么它正常工作吗?有bug吗?

最后,带有final关键字的瞬态应该有什么行为?

2个回答

7

你的问题有点重复,可以参考以下链接:

一个final字段必须通过直接赋值或构造函数来初始化。在反序列化期间,这些都不会被调用,因此transient的初始值必须在“readObject()”私有方法中设置,在反序列化期间调用该方法。为了使其工作,transient字段必须是非final的。

以及

任何声明为transient的字段都不会被序列化。此外,根据这篇博客文章,字段值甚至不会初始化为默认构造函数设置的值。当transient字段是final时,这就产生了挑战。

关于你测试的结果:

使用transient关键字的代码输出。 ABC04 DEF04
不使用transient关键字的输出。 ABC34 DEF64

transient

显然,transient字段(第四个字符)没有被序列化/反序列化(ABC34->ABC04和DEF64->DEF04)。

static

static字段(第五个字符)也没有被反序列化!这是因为您在同一内存空间中执行操作,静态字段保留在所有实例中。因此,当您将静态字段设置为student时,稍后再反序列化另一个student时,静态字段当然仍具有相同的值!

这也解释了为什么在您的测试中,您首先将静态字段设置为2,然后设置为4,但只打印4。在这种情况下与序列化无关,仅与静态字段行为有关。


3
你的结论是错误的,你的例子并没有起作用。
  1. name字段不是transient,因此正确地存储、恢复和打印。
  2. marks字段被声明为static,因此不是对象状态的一部分,从未被存储或恢复。它始终显示最后写入的值,即4,尽管你的第一个对象已经将其写入了2,因为第二个对象在你的测试开始之前用4覆盖了它。
  3. 只有id字段是transient实例字段,未被存储,因此显示默认值0。当去除transient关键字时,它被存储和恢复,第一个对象显示3,第二个对象显示6

也许如果你在toString()返回的字符串表示中添加空格或标识符,例如
"name="+name+", id="+id+""+", marks="+marks,会对你有所帮助。

为了添加另一个边角案例,产生出人意料的行为,如果你添加一个字段

transient final int foo = 42;

如果您将一个编译时常量存储到类中,您还将体验到在恢复后显示正确值的效果,因为它是编译时常量。因此,任何引用该变量的代码都将不可避免地使用常量值,并且实际上永远不会读取实例字段,因此未恢复的事实不被注意到。但是,当然,这样的常量最好声明为static,以避免浪费内存用于从未读取的实例字段。

另一个令人惊讶的例子可能是在enum中声明transient final字段。它们始终会显示正确的值,因为enum对象的状态从未被存储,但是在反序列化enum值时将解析该enum类型的实际已初始化的常量对象。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接