How transient works with final in Serialization Ja

2019-04-12 14:22发布

问题:

I was reading about transient and final keyword and I found the answer that we can't use transient keyword with final keyword. I tried and got confused because here it working fine.

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){/**/}
    }
}

Here is the Serializable Student class Code:

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

Code output with transient keyword.

ABC04
DEF04

Output without transient keyword.

ABC34
DEF64

Can you explain why it's working fine? is there a bug?

At the end what should be behavior of transient with final keyword?

回答1:

Your question is somewhat a duplicate of this:

  • final-transient-fields-and-serialization
  • a-transient-final-field-used-as-a-lock-is-null

A final field must be initialized either by direct assignment of an initial value or in the constructor. During deserialization, neither of these are invoked, so initial values for transients must be set in the 'readObject()' private method that's invoked during deserialization. And for that to work, the transients must be non-final.

and

Any field that is declared transient is not serialized. Moreover, according to this blog post, field values are not even initialized to the values that would be set by a default constructor. This creates a challenge when a transient field is final.

As for the results of your test:

Code output with transient keyword. ABC04 DEF04
Output without transient keyword. ABC34 DEF64

transient

Clearly, the transient field (4th character) is not being serialized/deserialized (ABC34->ABC04 and DEF64->DEF04)

static

The static field (5th char) is also not being deserialized! It's simply because your are performing the operation in the same memory space, and the static field remains across all instances. So when you set the static field on student, and later deserialize another student, of course the static field still has the same value!

This also explain why in your test you first set the static field to 2 and then 4, but only 4 gets printed. Nothing to do with serialization in this case, simply static field behavior.



回答2:

Your conclusion that your example works, is wrong.

  1. The field name is not transient, therefore correctly stored, restored and printed.
  2. The field marks is declared static, hence not part of the object’s state and never stored nor restored. It always shows the last written value, 4, despite your first object has written 2 to it, because the second object overwrites it with 4 before your test even starts.
  3. Only the field id is a transient instance field which is not stored, thus, showing the default value 0. When removing the transient keyword, it gets stored and restored, showing 3 for the first and 6 for the second object.

Maybe it would help you, if you add spacing or even the identifiers like
"name="+name+", id="+id+""+", marks="+marks in the string representation
returned by toString().

To add another corner case, producing counter-intuitive behavior, if you add a field

transient final int foo = 42;

to your class, you will also experience it to show the correct value after restoring, because it is a compile-time constant. So any code referencing this variable will invariably use the constant value and never actually read the instance field, so the fact that it wasn’t restored stays unnoticed. But, of course, such a constant is better declared static to avoid wasting memory for a never-read instance field.

Another perhaps surprising example would be declaring transient final fields in an enum. They will always appear to show the correct value, because the state of enum objects is never stored, but the actual, already initialized, constant objects of that enum type are resolved when deserializing an enum value.