Java对象序列化总结

含义

对象序列化(Serialize)指将一个Java对象写入IO流中,反序列化(Deserialize)是从IO流中恢复该对象.

目标

目标是将对象保存在磁盘中,或允许在网络中传输对象,对象序列化机制允许把内存中的Java对象转换成平台无关的二进制流,从而实现对象的保存或者传输.并且其他程序获取该二进制流之后,也可以将其反序列化为原来的对象.

需实现接口

Serializable

该接口没有任何方法,只是表明该类可以被序列化

public interface Serializable {
}

Externalizable

public interface Externalizable extends java.io.Serializable {

void writeExternal(ObjectOutput out) throws IOException;
     void writeExternal(ObjectOutput out) throws IOException;
ClassNotFoundException;
}

序列化实例

实现序列化和反序列化

创建写入的对象,只有一个字段

class Student implements Serializable { 
    private String name;
    public Student() {
    super();
    // TODO Auto-generated constructor stub
    }
    public String getName() {
    return name;
    }
    public void setName(String name) {
    this.name = name;
    }
    }

创建两个对象

    Student studentWrite1 = new Student();
    studentWrite1.setName("LiNing");
    Student studentWrite2 = new Student();
    studentWrite2.setName("zhaosi");

执行序列化
使用的是IO流中的ObjectOutputStream.
注意最后需要把输出流关闭.
这里序列化了两个对象

public static void serialize(Object obj,String file) {
        System.out.println("执行序列化......");
        ObjectOutputStream outputStream = null;
        try {
            //创建对象输出流
            outputStream =  new ObjectOutputStream(new FileOutputStream(file,true));
            //将对象写入输出流
            outputStream.writeObject(obj);

            System.out.println("执行序列化完成......");
        } catch (FileNotFoundException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        finally {
            try {
                if(outputStream != null) {
                    outputStream.close();
                }
            } catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
    }

执行反序列化
使用的是IO流中的ObjectInputStream.
注意最后需要把输入流关闭.

public static Object deserialize(String file) {
        System.out.println("执行反序列化......");
        ObjectInputStream inputStream = null ;
        Object obj = null;
        try {

            FileInputStream fis = new FileInputStream(file);
            inputStream = new ObjectInputStream(fis);
            obj = inputStream.readObject();

        } catch (FileNotFoundException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        finally {
            try {
                if(inputStream != null) {
                    inputStream.close();
                }
            } catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            return obj;
        }
    }

测试

    public static void main(String[] args) {
    //创建对象
    Student studentWrite1 = new Student();
    studentWrite1.setName("LiNing");

    String file = "serialize.txt";
    //序列化对象1
    SerializeUtils.serialize(studentWrite1, file);
    //读取对象
    Student studentRead1 =  (Student)SerializeUtils.deserialize(file);
    System.out.println("学生1:" + studentRead1.getName());
}

输出

执行构造器
执行序列化......
执行序列化完成......
执行反序列化......
学生1:xxxx

1.反序列化读取的仅仅是Java对象,而不是Java类.进行反序列化时,必须提提供该对象所属类的class文件,否则将会引发ClassNotFoundExcepyion.
2.如果向文件中使用序列化机制写入多个对象,使用反序列化机制恢复对象时必须按实际写入的顺序读取.

父类序列化

当父类没有实现Serializable时,其能够被序列化么?

1.父类没有无参构造器,父类没有实现Serializable接口
创建一个父类

class Person{   
    Integer age;
    public Person(Integer age) {
        super();
        this.age = age;
    }
}

Student类继承Person类

class Student extends Person implements Serializable {

    static final long serialVersionUID = 1L;

    private String name;    
    public Student(Integer age,String name) {
        super(age);
        this.name = name;
        System.out.println("执行构造器");
    }
    public String getName() {
        return name;
    }
    public String toString() {
        return "name = " + name 
                + "  age = " + age;
    }   
}

测试

public static void main(String[] args) {
    String file = "serialize.txt";
    //序列化对象1
    SerializeUtils.serialize(new Student(18, "lining"), file);
    //读取对象
    Student studentRead1 =  (Student)SerializeUtils.deserialize(file);
    System.out.println("学生1:" + studentRead1.toString());
}

反序列化时将会报错:java.io.InvalidClassException: org.serialize1.Student; no valid constructor

2.父类没有无参构造器,父类实现Serializable接口

class Person implements Serializable{   
    Integer age;
    public Person(Integer age) {
        super();
        this.age = age;
        System.out.println("Person执行有参构造器");
    }
}

输出

Person执行有参构造器
Student执行构造器
执行序列化......
执行序列化完成......
执行反序列化......
学生1:name = lining  age = 18

说明当父类实现Serializable接口,子类反序列化时就不会有问题

3.父类有无参构造器,父类没有实现Serializable接口

class Person {  
    Integer age;
    public Person() {
        System.out.println("Person执行无参构造器");
    }
    public Person(Integer age) {
        super();
        this.age = age;
        System.out.println("Person执行有参构造器");
    }
}

输出

Person执行有参构造器
Student执行构造器
执行序列化......
执行序列化完成......
执行反序列化......
Person执行无参构造器
学生1:name = lining  age = null

注意到执行反序列化时,调用了父类的无参构造器,并且父类的age属性为null.

总结:
1.父类没有无参构造器,父类没有实现Serializable接口
 反序列化时将会报错:java.io.InvalidClassException: org.serialize1.Student; no valid constructor
2.父类有无参构造器,父类实现Serializable接口
 父类实现Serializable接口,子类反序列化时就不会有问题
3.父类有无参构造器,父类没有实现Serializable接口
 执行反序列化时,调用了父类的无参构造器,并且父类的属性为null

对象引用的序列化

创建一个新类City

class City{
    Integer code;
    public City(Integer code) {
        super();
        this.code = code;
    }
}
Student 使用City作为其引用属性
class Student extends Person implements Serializable {

    static final long serialVersionUID = 1L;
    City city = new City(9527);
}

执行反序列化将会报异常:java.io.NotSerializableException: org.serialize1.City
让City实现Serializable,执行结果正常.

class City implements Serializable{
    Integer code;
    public City(Integer code) {
        super();
        this.code = code;
    }
}

使用transient防止属性被序列化

给name属性加上transient关键字

transient private String name;  

执行代码
可以看到name为null,说明其没有被序列化.

Person执行有参构造器
Student执行构造器
执行序列化......
执行序列化完成......
执行反序列化......
学生1:name = null  age = 18  city code = 9527

类属性序列化

被stati修饰的类属性,在序列化时并没有被序列化.
1.继承接口Externalizable
2.重写readExternal和writeExternal
3.序列化和反序列化操作方式和上述一样

扩展接口Externalizable实现序列化

class Student1  implements Externalizable {

    static final long serialVersionUID = 1L;

     private String name;   
     private int age;



    public Student1() {
        super();
        // TODO Auto-generated constructor stub
    }
    public Student1(Integer age,String name) {
        this.name = name;
        this.age = age; 
        System.out.println("Student执行构造器");
    }
    public String getName() {
        return name;
    }
    public String toString() {
        return "name = " + name 
                + "  age = " + age;

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

从以上可以看出,扩展接口Externalizable实现序列化非常的复杂,如果属性很多,那么写起来就会非常的麻烦.因此不建议使用该方式.

序列化UID

  根据前面的描述,当执行反序列化时,环境中必须提供带对象所对应的class文件,否讲将会报异常.但是两个不同的环境,由于项目文件地更改(比如属性或者方法的修改),有可能会导致class文件不一样.
  Java序列化提供了一个类型为private static final long的 serialVersionUID属性.该属性用于标识Java序列化的版本,也就是说一个类升级后,只要它的serialVersionUID没有更改,那么序列化机制会将其当作同一个序列化版本.
 

class Student1  implements Externalizable { 
    private static final long serialVersionUID = 1L;
}

  不显示定义serialVersionUID的问题是,不利于程序在不同的JVM之间的移植,因为不同的JVM计算serialVersionUID并不一定是一样的.
  

总结

Externalizable和Serializable区别

实现Serializable接口实现Externalizable接口
系统自动存储必要信息程序员决定存储哪些信息
Java内建支持,易于实现,只需实现接口即可,无需其他代码支持仅仅提供两个空方法,实现该接口必须重写这两个方法
性能略差性能略高

Externalizable编程复杂,不推荐使用.

序列化注意事项

1.对象的类名,属性(包括基本属性,数组,对其他对象的引用(也需要实现Serializable接口))都会被序列化;
2.方法,static属性,transient属性(瞬态属性)都不会被序列化.
3.反序列化必须有序列化对象的class文件.
4.当通过文件,网络来读取序列化对象时,读取的顺序必须和写入的顺序一致.

序列化ID

  为了保证程序在不同JVM之间的正常移植或者防止类文件升级导致的反序列化失败,应当对需要被序列化的类添加serialVersionUID属性.
  由于static属性,transient属性(瞬态属性)都不会被序列化,因此当对其进行修改时,不影响反序列化结果.类定义可以不更新serialVersionUID值.
  非静态非瞬态属性修改:

旧类新类结果
包含同名属性,但是类型不一样反序列化失败,类定义应该更新serialVersionUID值
新类比旧类的属性少反序列化没有问题,旧类多出的属性被忽略,类定义可以不更新serialVersionUID值
新类比旧类的属性多反序列化没有问题,新类多出的属性被设置为null(引用类型)或0(基本类型),类定义可以不更新serialVersionUID值
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值