序列化是将对象的状态转换为字节流;反序列化恰恰相反。换言之,序列化是将Java对象转换为字节的静态流(序列,然后可以将其保存到数据库或通过网络传输。
序列化过程是独立于实例的,即对象可以在一个平台上序列化并在另一个平台上反序列化。 有资格序列化的类需要实现一个特殊的标记接口Serializable 。
ObjectInputStream和ObjectOutputStream都是分别扩展java.io.InputStream和java.io.OutputStream的高级类。 ObjectOutputStream可以将对象的基本类型和对象作为字节流写入OutputStream。随后可以使用ObjectInputStream读取这些流。
ObjectOutputStream提供了writeObject方法可以将可序列化的对象转换为字节的序列(流),同样地,ObjectInputStream提供了readObject方法可以将字节流转换为Java对象。
请注意,静态字段属于类(与对象相对)并不会被序列化;另外,也可以使用关键字transient忽略字段序列化。
用一个Person类来说明序列化,其源码如下:
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异常。
public class Person implements Serializable { private int age; private String name; private Address country; // must be serializable too }
JVM将版本号与每个可序列化的类相关联。它用于验证保存和加载的对象具有相同的属性,因此在序列化时兼容。
如果可序列化的类没有声明serialVersionUID,则JVM将在运行时自动生成一个。但是,强烈建议每个类声明其serialVersionUID,因为生成的是依赖于编译器的,因此可能会导致意外的InvalidClassExceptions。
Java指定了可以序列化对象的默认方式。Java类可以覆盖此默认行为。在尝试序列化具有一些不可序列化属性的对象时,自定义序列化特别有用。
可以通过在类中提供两个我们想要序列化的方法来完成:
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()); }
除了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的无参的构造器。