JAVA 序列化

本文介绍了Java序列化的基本概念,通过示例展示了如何实现序列化及反序列化。着重讨论了serialVersionUID的作用,指出其在类版本控制中的重要性,并强调了static和transient修饰的字段无法被序列化。此外,还探讨了如何处理transient变量的反序列化问题,通过自定义writeObject和readObject方法来实现。

JAVA 序列化

什么是序列化

序列化:把对象转换为字节序列的过程称为对象的序列化。

反序列化:把字节序列恢复为对象的过程称为对象的反序列化。

​ 再来看看Java中的序列化。

Serializable

public interface Serializable {
}

​ Java中定义了一个空的接口。实现该空接口,即可实现序列化。

​ Java中有ObjectOutputStreamObjectInputStream

先定义一个Student类

public class Student  {

    private String name;
    private int age;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
}

然后写一个测试类

public class Test {
    public static void main(String[] args) {
        Student student = new Student();
        student.setAge(1);
        student.setName("测试");
        //将对象写到文件中
        try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("test"))) {
            oos.writeObject(student);
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
        //从对象中读取文件
        try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream("test"))) {
            Student o = (Student) ois.readObject();
            System.out.println(o.getAge());
            System.out.println(o.getName());
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

执行结果

java.io.NotSerializableException: serial.Student
at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1184)
at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:348)
at serial.Test.main(Test.java:17)
java.io.WriteAbortedException: writing aborted; java.io.NotSerializableException: serial.Student
at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1577)
at java.io.ObjectInputStream.readObject(ObjectInputStream.java:431)
at serial.Test.main(Test.java:25)
Caused by: java.io.NotSerializableException: serial.Student
at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1184)
at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:348)
at serial.Test.main(Test.java:17)

Process finished with exit code 0

可以看到,Student接口没有实现Serializable接口。修改Student类。

public class Student implements Serializable {
	private static final long serialVersionUID = 7583950439381176616L;
    private String name;
    private int age;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
}

再次执行Test类的Main方法

1
测试

可以看到,已经完成了正常的序列化和反序列化过程。

那么serialVersionUID有什么作用呢。

对Student类稍作修改

private static final long serialVersionUID = 1L;

然后修改Test类

public class Test {
    public static void main(String[] args) {
        //从对象中读取文件
        try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream("test"))) {
            Student o = (Student) ois.readObject();
            System.out.println(o.getAge());
            System.out.println(o.getName());
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

运行后

java.io.InvalidClassException: serial.Student; local class incompatible: stream classdesc serialVersionUID = 7583950439381176616, local class serialVersionUID = 1

可以看到,修改了serialVersionUID后,原来已经序列化后的内容,无法再反序列化。

注意事项:

static和transient修饰的字段是不可以被序列化的。

static不被序列化是因为他是属于类的属性,不是对象的属性,那么为什么transient修饰的也不可以被序列化呢?

在Student中添加b属性,并添加get/set方法。

private transient String b;

对应的,也修改Test方法

public static void main(String[] args) {
    Student student = new Student();
    student.setAge(1);
    student.setName("测试");
    student.setB("123");
    //将对象写到文件中
    try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("test"))) {
        oos.writeObject(student);
    } catch (FileNotFoundException e) {
        e.printStackTrace();
    } catch (IOException e) {
        e.printStackTrace();
    }
    //从对象中读取文件
    try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream("test"))) {
        Student o = (Student) ois.readObject();
        System.out.println(o.getAge());
        System.out.println(o.getName());
        System.out.println(o.getB());
    } catch (FileNotFoundException e) {
        e.printStackTrace();
    } catch (IOException e) {
        e.printStackTrace();
    } catch (ClassNotFoundException e) {
        e.printStackTrace();
    }
}

1
测试
null

可以看到transient修饰的属性,拿到是空的。

深入源码ObjectStreamClass#getDefaultSerialFields

/**
     * Returns array of ObjectStreamFields corresponding to all non-static
     * non-transient fields declared by given class.  Each ObjectStreamField
     * contains a Field object for the field it represents.  If no default
     * serializable fields exist, NO_FIELDS is returned.
     */
/**
	返回对应于给定类声明的所有非静态非瞬态字段的ObjectStreamFields数组。每个ObjectStreamField都包含一个代表该字段的Field对象。如果没有默认的可序列化字段存在,则返回NO_FIELDS。
*/
private static ObjectStreamField[] getDefaultSerialFields(Class<?> cl) {
    Field[] clFields = cl.getDeclaredFields();
    ArrayList<ObjectStreamField> list = new ArrayList<>();
    int mask = Modifier.STATIC | Modifier.TRANSIENT;

    for (int i = 0; i < clFields.length; i++) {
        if ((clFields[i].getModifiers() & mask) == 0) {
            list.add(new ObjectStreamField(clFields[i], false, true));
        }
    }
    int size = list.size();
    return (size == 0) ? NO_FIELDS :
        list.toArray(new ObjectStreamField[size]);
}

有没有办法可以实现transient 的反序列化呢?

我们对Student类再次改造

public class Student implements Serializable {

    private static final long serialVersionUID = 7583950439381176616L;
    private String name;
    private int age;
    private transient String b;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getB() {
        return b;
    }

    public void setB(String b) {
        this.b = b;
    }

    private void writeObject(ObjectOutputStream out) throws IOException {
        out.defaultWriteObject();
        out.writeObject(b);
    } 

    private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
        in.defaultReadObject();
        b = (String) in.readObject();
    }
}

可以看到,新增了writeObject方法和readObject方法。在序列化和反序列化的时候,会调用writeObject和readObject方法。

执行Test,

1
测试
123

可以看到,用transient修饰的变量,被反序列化出来。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值