Java基础知识系列—序列化

序列化是将对象的状态转换为字节流;反序列化偏偏相反。换言之,序列化是将Java对象转换为字节的静态流(序列,而后能够将其保存到数据库或经过网络传输。java

序列化与反序列化

序列化和反序列化

序列化过程是独立于实例的,即对象能够在一个平台上序列化并在另外一个平台上反序列化。有资格序列化的类须要实现一个特殊的标记接口Serializable数据库

ObjectInputStream和ObjectOutputStream都是分别扩展java.io.InputStream和java.io.OutputStream的高级类。 ObjectOutputStream能够将对象的基本类型和对象做为字节流写入OutputStream。随后可使用ObjectInputStream读取这些流。网络

ObjectOutputStream提供了writeObject方法能够将可序列化的对象转换为字节的序列(流),一样地,ObjectInputStream提供了readObject方法能够将字节流转换为Java对象。ide

请注意,静态字段属于类(与对象相对)并不会被序列化;另外,也可使用关键字transient忽略字段序列化。测试

用一个Person类来讲明序列化,其源码以下:this

public class Person implements Serializable {
    private static final long serialVersionUID = 1L;
    static String country = "ITALY";
    private int age;
    private String name;
    transient int height;
 
    // getters and setters
}

@Test
public void whenSerializingAndDeserializing_ThenObjectIsTheSame() () throws IOException, ClassNotFoundException { 
    Person person = new Person();
    person.setAge(20);
    person.setName("Joe");
     
    FileOutputStream fileOutputStream
      = new FileOutputStream("yourfile.txt");
    ObjectOutputStream objectOutputStream 
      = new ObjectOutputStream(fileOutputStream);
    objectOutputStream.writeObject(person);
    objectOutputStream.flush();
    objectOutputStream.close();
     
    FileInputStream fileInputStream
      = new FileInputStream("yourfile.txt");
    ObjectInputStream objectInputStream
      = new ObjectInputStream(fileInputStream);
    Person p2 = (Person) objectInputStream.readObject();
    objectInputStream.close(); 
  
    assertTrue(p2.getAge() == p.getAge());
    assertTrue(p2.getName().equals(p.getName()));
}
复制代码

序列化注意事项

继承与构成

当一个类实现了java.io.Serializable接口时,它的全部子类也是可序列化的。相反,当一个对象具备对另外一个对象的引用时,这些对象必须单独实现Serializable接口,不然会引起NotSerializableException异常。spa

public class Person implements Serializable {
    private int age;
    private String name;
    private Address country; // must be serializable too
}
复制代码

Serial Version UID

JVM将版本号与每一个可序列化的类相关联。它用于验证保存和加载的对象具备相同的属性,所以在序列化时兼容。code

若是可序列化的类没有声明serialVersionUID,则JVM将在运行时自动生成一个。可是,强烈建议每一个类声明其serialVersionUID,由于生成的是依赖于编译器的,所以可能会致使意外的InvalidClassExceptions。cdn

Java中的自定义序列化

Java指定了能够序列化对象的默认方式。Java类能够覆盖此默认行为。在尝试序列化具备一些不可序列化属性的对象时,自定义序列化特别有用。对象

一、在类中提供writeObject和readObject方法

能够经过在类中提供两个咱们想要序列化的方法来完成:

private void writeObject(ObjectOutputStream out) throws IOException;

private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException;
复制代码

经过这些方法,咱们能够将那些不可序列化的属性序列化为能够序列化的其余形式:

public class Employee implements Serializable {
    private static final long serialVersionUID = 1L;
    private transient Address address;
    private Person person;
 
    // setters and getters
 
    private void writeObject(ObjectOutputStream oos) throws IOException {
        oos.defaultWriteObject();
        oos.writeObject(address.getHouseNumber());
    }
 
    private void readObject(ObjectInputStream ois) throws ClassNotFoundException, IOException {
        ois.defaultReadObject();
        Integer houseNumber = (Integer) ois.readObject();
        Address a = new Address();
        a.setHouseNumber(houseNumber);
        this.setAddress(a);
    }
}

public class Address {
    private int houseNumber;
 
    // setters and getters
}
复制代码

测试自定义序列化:

@Test
public void whenCustomSerializingAndDeserializing_ThenObjectIsTheSame() throws IOException, ClassNotFoundException {
    Person p = new Person();
    p.setAge(20);
    p.setName("Joe");
 
    Address a = new Address();
    a.setHouseNumber(1);
 
    Employee e = new Employee();
    e.setPerson(p);
    e.setAddress(a);
 
    FileOutputStream fileOutputStream
      = new FileOutputStream("yourfile2.txt");
    ObjectOutputStream objectOutputStream 
      = new ObjectOutputStream(fileOutputStream);
    objectOutputStream.writeObject(e);
    objectOutputStream.flush();
    objectOutputStream.close();
 
    FileInputStream fileInputStream 
      = new FileInputStream("yourfile2.txt");
    ObjectInputStream objectInputStream 
      = new ObjectInputStream(fileInputStream);
    Employee e2 = (Employee) objectInputStream.readObject();
    objectInputStream.close();
 
    assertTrue(
      e2.getPerson().getAge() == e.getPerson().getAge());
    assertTrue(
      e2.getAddress().getHouseNumber() == e.getAddress().getHouseNumber());
}
复制代码

二、实现Externalizable接口

除了Serializable 以外,java中还提供了另外一个序列化接口Externalizable。

Externalizable继承了Serializable,该接口中定义了两个抽象方法:writeExternal()与readExternal()。当使用Externalizable接口来进行序列化与反序列化的时候须要开发人员重写writeExternal()与readExternal()方法。

public class Person implements Externalizable {

    private int age;
    private String name;

    public int getAge() {
        return age;
    }

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

    public String getName() {
        return name;
    }

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

    public Address getCountry() {
        return country;
    }

    public void setCountry(Address country) {
        this.country = country;
    }

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

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

测试代码以下:

ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("yourfile2.txt"));
        Person person = new Person();
        person.setAge(10);
        person.setName("zhang san");
        oos.writeObject(person);

        //Read Obj from file
        File file = new File("yourfile2.txt");
        ObjectInputStream ois =  new ObjectInputStream(new FileInputStream(file));
        Person newInstance = (Person) ois.readObject();
复制代码

值得注意:在使用Externalizable进行序列化的时候,在读取对象时,会调用被序列化类的无参构造器去建立一个新的对象,而后再将被保存对象的字段的值分别填充到新对象中。因此,实现Externalizable接口的类必需要提供一个public的无参的构造器。

参考资料

相关文章
相关标签/搜索