Java对象序列化

1.介绍

序列化是将对象的状态转换为字节流;反序列化恰恰相反。换句话说,序列化是将Java对象转换成静态字节流(序列),然后可以保存到数据库、文件或通过网络传输。

在这里插入图片描述

2.序列化和反序列化

序列化过程是独立于实例的,即对象可以在一个平台上序列化,在另一个平台上反序列化。符合序列化条件的类需要实现可序列化的特殊标记接口Serializable。

ObjectInputStream和ObjectOutputStream都是分别继承java.io.InputStream和java.io.OutputStream的高级类。ObjectOutputStream可以将对象的基本类型和对象图作为字节流写入OutputStream。随后可以使用ObjectInputStream读取这些流。

ObjectOutputStream中最重要的方法是:

public final void writeObject(Object o) throws IOException;

它接收一个可序列化的对象并将其转换为字节序列(流)。同样,ObjectInputStream中最重要的方法是:

public final Object readObject() 
  throws IOException, ClassNotFoundException;

它可以读取字节流并将其转换回Java对象。然后可以将其转换回原始对象。

让我们用一个Person类来序列化。注意,静态字段属于一个类(与对象相对),并且不可序列化。另外,请注意,我们可以使用关键字transient在序列化期间忽略某些不想序列化的字段:

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
}

下面的测试显示了将Person类型的对象保存到本地文件,然后将此值读回的示例:

@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()));
}

我们使用ObjectOutputStream将此对象的状态保存到使用FileOutputStream的文件中。文件“yourfile.txt”是在项目目录中创建的。然后使用FileInputStream加载此文件。ObjectInputStream将此流提取并将其转换为名为p2的新对象。

最后,我们测试加载对象的状态,并且它与原始对象的状态相匹配。

请注意,加载的对象必须显式转换为Person类型。

3.Java序列化注意事项

3.1 继承与构成

当一个类实现java.io.Serializable接口时,它的所有子类也都是可序列化的。相反,当一个对象引用另一个对象时,这些对象必须分别实现Serializable接口,否则会抛出NotSerializableException:

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

如果可序列化对象中的某个字段由对象数组组成,则所有这些对象也必须可序列化,否则将引发NotSerializableException。

3.2 版本号UID

JVM将版本号(long)与每个可序列化的类相关联。它用于验证保存和加载的对象是否具有相同的属性,从而在序列化时兼容。

这个数字可以由大多数ide自动生成,并且基于类名、属性和相关的访问修饰符。任何更改都会导致不同的数字,并可能导致InvalidClassException。

如果可序列化类没有声明serialVersionUID,JVM将在运行时自动生成一个。但是,强烈建议每个类声明其serialVersionUID,因为生成的类依赖于编译器,因此可能会导致意外的InvalidClassExceptions。

3.3 自定义序列化

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());
}

在这段代码中,我们将看到如何通过使用自定义序列化来序列化地址来保存一些不可序列化的属性。注意,我们必须将不可序列化的属性标记为transient,以避免NotSerializableException。

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值