我爱学Java之对象序列化

Java平台允许我们在内存中创建可复用的Java对象,但一般情况下,只有当JVM处于运行时,这些对象才可能存在,当JVM停止运行时,这些对象就不复存在,但在实际应用中,我们需要持久化的保存指定的对象,并在特定的时间重新读取被保存的对象,Java对象序列化就能够帮助我们实现该功能。

在进行Java对象序列化的时候,会把其状态保存为一组字节,在对这些对象反序列化的时候再将这些字节组装成对象。但值得注意的事,对象序列化保存的是对象的”状态“,即它的成员变量,因此类中的静态变量不会被序列化。

除了在持久化对象时会用到对象序列化之外,当使用RMI(远程方法调用),或在网络中传递对象时,都会用到对象序列化,下面我们分别通过文件和MySQL理解Java对象的序列化。

首先定义需要被序列化的类,通过实现Serializable接口:

class Student implements Serializable{

    private static final long serialVersionUID = 1L;

    //多种类型测试
    private int id;
    private String name;
    private Address address;
    private ArrayList<String> course;

    public int getId() {
        return id;
    }
    public void setId(int id) {
        this.id = id;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public Address getAddress() {
        return address;
    }
    public void setAddress(Address address) {
        this.address = address;
    }
    public ArrayList<String> getCourse() {
        return course;
    }
    public void setCourse(ArrayList<String> course) {
        this.course = course;
    }
}
//被引用的类也需要实现Serializable接口,否则会报错
class Address implements Serializable{
    private static final long serialVersionUID = 1L;

    private String street;
    private String road;
    public String getStreet() {
        return street;
    }
    public void setStreet(String street) {
        this.street = street;
    }
    public String getRoad() {
        return road;
    }
    public void setRoad(String road) {
        this.road = road;
    }   
}

接下来,先测试针对MySQL的对象序列化的测试:

先建立数据库表,字段对应为“blog”:

mysql> create table student(
    -> id int auto_increment primary key,     -> stu blob     -> );

接下来,写测试用例:

public static void main(String[] args) throws IOException{

        String url = "jdbc:mysql://localhost:3306/test";
        String user = "root";
        String pass = "root";

        Student student = new Student();
        student.setId(2);
        student.setName("gaoya");

        Address address = new Address();
        address.setStreet("aa");
        address.setRoad("bb");
        student.setAddress(address);

        ArrayList<String> course = new ArrayList<String>();
        course.add("math");
        course.add("english");
        student.setCourse(course);

        try {
            Class.forName("com.mysql.jdbc.Driver");
            Connection conn = DriverManager.getConnection(url, user, pass);
            String sql = "insert into student(stu) values(?)";
            PreparedStatement pre = conn.prepareStatement(sql);
            pre.setObject(1, student);
            pre.executeUpdate();

            sql = "select stu from student";
            pre = conn.prepareStatement(sql);
            ResultSet rs = pre.executeQuery();
            while(rs.next()){
                Blob blob = rs.getBlob(1);
                InputStream is = blob.getBinaryStream();
                BufferedInputStream buff = new BufferedInputStream(is);

                byte[] b = new byte[(int)blob.length()];
                while(buff.read(b) != -1);

                ObjectInputStream obj = new ObjectInputStream(new ByteArrayInputStream(b));
                Student stu = (Student)obj.readObject();

                System.out.println(stu.getName());
                System.out.println(stu.getId());
                System.out.println(stu.getAddress().getRoad() + " " + stu.getAddress().getStreet());
                System.out.println(stu.getCourse());
            ;
            }
        } catch (ClassNotFoundException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (SQLException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }

    }

输出结果:

我爱学Java之对象序列化_第1张图片

再来针对文件的对象序列化

public static void main(String[] args){

        Student student = new Student();
        student.setId(2);
        student.setName("gaoya");

        Address address = new Address();
        address.setStreet("aa");
        address.setRoad("bb");
        student.setAddress(address);

        ArrayList<String> course = new ArrayList<String>();
        course.add("math");
        course.add("english");
        student.setCourse(course);

        try {

            //将对象序列化到文件
            File file = new File("d:\\out.txt");//输出目录
            ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(file));
            out.writeObject(student);
            out.close();

            ObjectInputStream in = new ObjectInputStream(new FileInputStream(file));
            Student stu = (Student)in.readObject();

            System.out.println(stu.getId());
            System.out.println(stu.getName());
            System.out.println(stu.getAddress().getRoad() + " " + stu.getAddress().getStreet());
            System.out.println(stu.getCourse());
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }

    }

输出结果:

再提一下transient关键字,当某个字段被声明为transient后,默认序列化机制就会忽略该字段。此处将Student类中的id字段声明为transient:

private transient String name;

再运行得到的结果如下:

我爱学Java之对象序列化_第2张图片

可见name字段未被序列化;如想再恢复该字段的序列化能力,除了删除transient关键字以外,还有另外一种方式:即在Student类中加两个方法,如下所示:

    //需将这两个方法定义为private,序列化的时候会自动被调用
    private void writeObject(ObjectOutputStream out) throws IOException{
        out.defaultWriteObject();//执行默认化序列化机制
        out.writeUTF(name);//将name字段写入ObjectOutputStream中
    }

    private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException{
        in.defaultReadObject();//执行默认反序列化机制
        name = in.readUTF();//将name字段读入ObjectInputStream中
    }

直接在此运行得到结果如图:

还有另一种序列化接口-Externalizable

将之前两个类修改如下:

class Student implements Externalizable{

    private static final long serialVersionUID = 1L;
    //多种类型测试
    private int id;
    private transient String name;
    private Address address;
    private ArrayList<String> course;

    public Student(){
        System.out.println("student");
    }

    public int getId() {
        return id;
    }
    public void setId(int id) {
        this.id = id;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public Address getAddress() {
        return address;
    }
    public void setAddress(Address address) {
        this.address = address;
    }
    public ArrayList<String> getCourse() {
        return course;
    }
    public void setCourse(ArrayList<String> course) {
        this.course = course;
    }

    private void writeObject(ObjectOutputStream out) throws IOException{
        out.defaultWriteObject();//执行默认化序列化机制
        out.writeUTF(name);//将name字段写入ObjectOutputStream中
    }

    private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException{
        in.defaultReadObject();
        name = in.readUTF();
    }
    @Override
    public void writeExternal(ObjectOutput out) throws IOException {
        // TODO Auto-generated method stub

    }
    @Override
    public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
        // TODO Auto-generated method stub

    }
}

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

    private String street;
    private String road;

    public Address(){
        System.out.println("address");
    }

    public String getStreet() {
        return street;
    }
    public void setStreet(String street) {
        this.street = street;
    }
    public String getRoad() {
        return road;
    }
    public void setRoad(String road) {
        this.road = road;
    }

    @Override
    public void writeExternal(ObjectOutput out) throws IOException {
        // TODO Auto-generated method stub

    }
    @Override
    public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
        // TODO Auto-generated method stub

    }   
}

此时在此运行程序,得到结果如下:

从该结果可看出对象中任何一个字段都没有被序列化,但是可以看出调用了类的无参构造器。Externalizable继承于Serializable,当使用该接口时,序列化的细节需要由通过实现writeExternal()与readExternal()方法。另外,若使用Externalizable进行序列化,当读取对象时,会调用被序列化类的无参构造器去创建一个新的对象,然后再将被保存对象的字段的值分别填充到新对象中。这就是为什么在此次序列化过程中类的无参构造器会被调用。由于这个原因,实现Externalizable接口的类必须要提供一个无参的构造器,且它的访问权限为public。
对上述类作进一步的修改,使其能够对name与age字段进行序列化:

@Override
    public void writeExternal(ObjectOutput out) throws IOException {
        // TODO Auto-generated method stub
        out.writeInt(id);
        out.writeObject(name);
        out.writeObject(address);
        out.writeObject(course);

    }
    @Override
    public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
        // TODO Auto-generated method stub
        id = in.readInt();
        name = (String)in.readObject();
        address = (Address) in.readObject();
        course = (ArrayList<String>) in.readObject();

    }

    @Override
    public void writeExternal(ObjectOutput out) throws IOException {
        // TODO Auto-generated method stub
        out.writeObject(street);
        out.writeObject(road);
    }
    @Override
    public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
        // TODO Auto-generated method stub
        street = (String) in.readObject();
        road = (String) in.readObject();
    }   

在运行得到如下结果:

我爱学Java之对象序列化_第3张图片

最后说一下serialVersionUID的作用:

Java的序列化机制是通过在运行时判断类的serialVersionUID来验证版本一致性的,在进行反序列化时,JVM会把传来的字节流中的serialVersionUID与本地相应类的serialVersionUID进行比较,如果相同就认为是一致的,可以进行反序列化,如不同,就会出现序列化版本不一致的异常,如下图:
这里写图片描述

serialVersionUID有两种生成方式:一个是指定一个默认值,比如:private static final long serialVersionUID = 1L; 另一个是根据类名、接口名、成员方法及属性等来生成一个64位的哈希字段,比如:private static final long serialVersionUID = 8107332608515657739L。

在实现序列化的过程当中,如果我们没有显示的指定serialVersionUID,Java序列化机制会根据编译的class自动生成一个serialVersionUID(不同的编译器对于同一个类可能会产生不同的serialVersionUID),如果我们在序列化期间没有对类进行修改,那么无论编译多少次serialVersionUID都不会变,也就是说反序列化的时候不会出现问题,但是我们一旦修改了类的某一部分,反序列化就会出现如上所说的版本不一致的异常。

所以需要序列化的时候,我们最好显示的指定serialVersionUID(但要注意的是,如果使用第一种方式即指定默认值,需要对不同的类指定程不同的值,否则可能会出现把不同项目的相同类名的类当作一样的类去处理),这样当序列化一个类实例的时候,如添加修改删除一个字段,反序列化的时候对于添加和修改的字段会设定对于其类型的默认值,而删除的字段将不设置。

你可能感兴趣的:(java,对象)