java-Serializable 序列化

java的对象序列化将那些实现了Serializable接口的对象装换成一个字节序列,并能够在以后将这个字节序列完全恢复成原来的对象。这意味着序列化机制能自动弥补不同操作系统之间的差异

public interface Serializable {}可以看到该接口仅是一个标记接口,不包括任何方法。

使用对象流实现序列化


创建一个可序列化的bean,Data类

public class Data implements Serializable{

    private int id;
    private String name;

    public Data(int id, String name) {
        System.out.println("Data()");
        this.id = id;
        this.name = name;
    }

    @Override
    public String toString() {
        return id + " : " + name;
    }
}

对它进行存储和读取

public class Test1 {
    public static void main(String[] args) throws FileNotFoundException, IOException, ClassNotFoundException{
        Data data = new Data(0, "test");
        System.out.println("before: " + data);

        System.out.println("output:");
        ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("test.txt"));
        out.writeObject(data);
        out.close();

        System.out.println("input: ");
        ObjectInputStream in = new ObjectInputStream(new FileInputStream("test.txt"));
        Data data****2 = (Data) in.readObject();
        System.out.println("after: " + data2);
    }
}

可以看到结果:

Data()
before: 0 : test
output:
input: 
after: 0 : test

对Serializable对象进行还原的过程中,没有调用任何构造器,包括默认的构造器,整个对象都是由Inputstream中取得数据恢复过来的。
反序列化恢复对象时,必须保证Java虚拟机能够找到相关的.class文件,否则会引发ClassNotFoundException异常;

序列化的控制


如果不希望对象的某一部分被序列化时,可以对序列化进行控制
- 使用Externalizable接口
- transient关键字
- 实现Serializable接口,并添加名为writeObject(), readObject()方法。

1、使用Externalizable接口

Externalizable接口继承自Serializable接口,并添加了writeExternal()和readExternal()方法

public interface Externalizable extends java.io.Serializable {

    void writeExternal(ObjectOutput out) throws IOException;

    void readExternal(ObjectInput in) throws IOException, ClassNotFoundException;
}

在默认的情况不保存任何字段

public class Dog implements Externalizable{

    private int age;
    private String name;

    public Dog() {
        System.out.println("Dog()");
    }

    public Dog(int age, String name){
        System.out.println("Dog(age, name)");
        this.age = age;
        this.name = name;
    }

    @Override
    public void writeExternal(ObjectOutput out) throws IOException {
        out.writeObject(name);
        out.writeInt(age);
    }

    @Override
    public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
        name = (String) in.readObject();
        age = in.readInt();
    }

    @Override
    public String toString() {
        return "name: " + name + " age: " + age;
    }
}
public class Test2 {
    public static void main(String[] args) throws FileNotFoundException, IOException, ClassNotFoundException {
        Dog dog = new Dog(0, "test");
        System.out.println("before: " + dog);

        System.out.println("output:");
        ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("test.txt"));
        out.writeObject(dog);
        out.close();

        System.out.println("input: ");
        ObjectInputStream in = new ObjectInputStream(new FileInputStream("test.txt"));
        Dog dog2 = (Dog) in.readObject();
        System.out.println("after: " + dog2);
    }
}

结果:

Dog(age, name)
before: name: test age: 0
output:
input: 
Dog()
after: name: test age: 0

可以注意到的是,恢复的时候,会调用默认的构造器,对于Serializable对象而言,对象完全以它存储的二进制位为基础来构造,而不调用构造器,但是对于Externalizable,所有普通的默认构造器都会被调用。

2、transient(瞬时)关键字

如果我们正在操作的是一个Serializable对象,所有的序列化操作都会自动进行,为了控制,可以奖赏transient关键字,会逐个字段地关闭序列化。

将第一个例子中的name加上transient关键字

private transient String name;

结果变成

Data()
before: 0 : test
output:
input: 
after: 0 : null

id域是一般的,会自动序列化,但是name添加了transient关键字,不会进行存储,自动序列化机制也不会尝试去恢复它,所以变成了null

Externalizable对象在默认的情况下是不会保存它的任何字段,所以transient字段只能和Serializable一起用

3、Externalizable的替代方法

可以实现Serializable接口,并添加名为writeObject()和readObject()的方法,一旦对象被序列化或者被反序列化,就会自动的调用这两个方法,而不是使用默认的序列化机制

public class Cat implements Serializable{

    public int age;
    public transient String name;
    public static String home = "catHome";

    public Cat() {
        System.out.println("Cat()");
    }

    @Override
    public String toString() { 
        return "name(transient): " + name + "\nage(not transient): " + age + "\nhome(static): " + home;
    }

    public Cat(int age, String name, String home){
        System.out.println("Cat(age, name, home)");
        this.age = age;
        this.name = name;
        this.home = home;
    }

    private void writeObject(ObjectOutputStream stream) throws IOException {
        stream.defaultWriteObject();
        stream.writeObject(name);//明确保存和恢复
    }

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

}

ObjectOutputStream 和ObjectInputStream对象的writeObject()和readObejct()方法调用对象的writeObject()和readObject()方法

这两个方法是private的,那么它们不会是接口的一部分,但其效果就和实现了接口一样。

writeObject()会检查,判断是否拥有自己的writeObject()方法(利用反射),有就使用它。

非transient字段由defaultWriteObject()方法保存(作为第一个操作),transient字段必须在程序中明确保存和恢复。

static值不被序列化


尝试这样的代码

public class Test2 {
    public static void main(String[] args) throws FileNotFoundException, ClassNotFoundException, IOException{
        Cat cat = new Cat(0, "test", "here");
        System.out.println("before: " + cat);

        System.out.println("output:");
        ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("test.txt"));
        out.writeObject(cat);
        out.close();

        System.out.println("input: ");
        ObjectInputStream in = new ObjectInputStream(new FileInputStream("test.txt"));
        Cat cat2 = (Cat) in.readObject();
        System.out.println("after: " + cat2);   
    }
}

输出:

Cat(age, name, home)
before: name(transient): test
age(not transient): 0
home(static): here
output:
input: 
after: name(transient): test
age(not transient): 0
home(static): here

static变量并没有被序列化,所以它并没有被写入流中,读取值时,它不可能在反序列化的文件里找到新的值,而是去全局数据区取值,因为全局数据区的值现在是here,所以读取出来的值就是改变后的值here了。

在另外一个InputStream

public class Test3 {
    public static void main(String[] args) throws FileNotFoundException, IOException, ClassNotFoundException {
        System.out.println("input: ");
        ObjectInputStream in = new ObjectInputStream(new FileInputStream("test.txt"));
        Cat cat2 = (Cat) in.readObject();
        System.out.println("after: " + cat2);   
    }
}

输出:

input: 
after: name(transient): test
age(not transient): 0
home(static): catHome

这时输出的为catHome。因为没有序列化static值,所以上面的here值并没有被写入流中。在另外一个线程读取的时候,它读取到的是全局数据域catHome

深度复制


  • 浅拷贝(Shallow Copy影子克隆):只复制对象的基本类型,对象类型,仍属于原来的引用。
  • 深拷贝(Deep Copy 深度克隆):不仅复制对象的基本类,同时也复制原对象中的对象.完全产生新对象。

Java序列化采用了特殊的序列化算法
- 所有保存到磁盘中的对象都有一个序列化编号;
- 当程序视图序列化一个对象时,程序将先检查该对象是否已经被序列化过,只有该对象从未(在本次虚拟机)中序列化过,系统才会将该对象转换成字节序列并输出;
- 如果某个对象已经序列化过,程序将只是直接输出一个序列化编号,而不是再次重新序列化该对象;

因为这种序列化机制,如果多次序列化同一个对象时,只有第一次序列化时才会把该对象转换成字节流并输出,即使后面对象的域已经改变

将Dog类的toString()改为

    @Override
    public String toString() {
        return super.toString();
    }
public class Test4 {
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        Dog dog = new Dog(1, "candy");
        System.out.println(dog);

        ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("test.txt"));
        out.writeObject(dog);
        out.writeObject(dog);
        out.close();

        ObjectInputStream in = new ObjectInputStream(new FileInputStream("test.txt"));
        Dog dog2 = (Dog) in.readObject();
        System.out.println("after: " + dog2);

        Dog dog3 = (Dog) in.readObject();
        System.out.println("same stream: " + dog3);
    }
}

输出:

com.example.serializable.Dog@15db9742
after: com.example.serializable.Dog@65ab7765
same stream: com.example.serializable.Dog@65ab7765

序列化前后对象的地址不同了,但是内容是一样的,而且对象中包含的引用也相同。通过序列化操作,我们可以实现对任何Serializable对象的”深度复制(deep copy)”——这意味着我们复制的是整个对象网,而不仅仅是基本对象及其引用。

对于同一流的对象,他们的地址是相同,说明他们是同一个对象,但是与其他流的对象地址却不相同。也就说,只要将对象序列化到单一流中,就可以恢复出与我们写出时一样的对象网,而且只要在同一流中,对象都是同一个。

参考:《Thinking in java》
Java 序列化Serializable详解(附详细例子)
Java:对象的序列化

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值