IO流之对象序列化(Serializable接口和Externalizable接口)

Java对象序列化

所有分布式应用常常需要跨平台,跨网络,因此要求所有传的签署,返回值都必须实现序列化。

定义:对象的序列化是指将一个Java对象写入IO流中,反序列化是指从IO流中恢复改Java对象。

实现:
实现如下两个接口之一的类才能被序列化:
(1)Serializable
(2)Externalizable
—序列化:ObjectOutputStream代表对象输出流,它的writeObject(Object obj)方法可对参数指定的obj对象进行序列化,把得到的字节序列写道一个目标输出流中。
—反序列化:ObjectInputStream代表对象输入流,它的readObject()方法从一个源输出流中读取字节序列,再把他们反序列化为一个对象,并将其返回。

使用writeObject()和readObject()方法的对象必须已经被序列化。


一般序列化

示例:
要被序列化的对象:

public class Person implements Serializable {  

    private String name = null;  

    private Integer age = null;   

    public Person(){
        System.out.println("无参构造");
    }
    public Person(String name, Integer age) {  
        this.name = name;  
        this.age = age;  
    }  
    //getter setter方法省略...
    @Override 
    public String toString() {  
        return "[" + name + ", " + age+"]";  
    }  
} 

如果有另一个类持有一个Person类的引用,只有Person类是可序列化的,这个类才是可序列化的

MySerilizable 是一个简单的序列化程序,它先将一个Person对象保存到文件person.txt中,然后再从该文件中读出被存储的Person对象,并打印该对象。

ublic class MySerilizable {

    public static void main(String[] args) throws Exception {  
        File file = new File("person.txt");  

        //序列化持久化对象
        ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(file));  
        Person person = new Person("Peter", 27);  
        out.writeObject(person);  
        out.close();  

        //反序列化,并得到对象
        ObjectInputStream in = new ObjectInputStream(new FileInputStream(file));  
        Object newPerson = in.readObject(); // 没有强制转换到Person类型,也可以强制转换到Person,这里主要是因为Person类里改写了toString方法才没有强制转换类型  
        in.close();  
        System.out.println(newPerson);  
    }  
}

选择序列化

transient

当某个对象进行序列化时,系统会自动把该对象的所有实例变量依次进行序列化,如果某个实例变量引用到另一个对象,则被引用的对象也会被序列化;如果被引用的对象的实例变量也引用了其他对象,则被引用的对象也会被序列化,这种情况被称为递归序列化。

为了避免这些情况,特别是某些实例变量是敏感信息时,例如银行账户信息等。这时不希望系统将该实例变量进行序列化,该怎么办呢?
通过在实例变量面前使用transient关键字修饰,就可以指定Java序列化时无需理会该实例变量。

public class Person
    implements java.io.Serializable
{
    private String name;
    private transient int age;
    // 注意此处没有提供无参数的构造器!
    public Person(String name , int age)
    {
        System.out.println("有参数的构造器");
        this.name = name;
        this.age = age;
    }
    // 省略name与age的setter和getter方法

    // name的setter和getter方法
    public void setName(String name)
    {
        this.name = name;
    }
    public String getName()
    {
        return this.name;
    }

    // age的setter和getter方法
    public void setAge(int age)
    {
        this.age = age;
    }
    public int getAge()
    {
        return this.age;
    }
}

对Person的age变量加了transient修饰符,便不会被序列化,因此下面程序中输出值为0

public class TransientTest
{
    public static void main(String[] args)
    {
        try(
            // 创建一个ObjectOutputStream输出流
            ObjectOutputStream oos = new ObjectOutputStream(
                new FileOutputStream("transient.txt"));
            // 创建一个ObjectInputStream输入流
            ObjectInputStream ois = new ObjectInputStream(
                new FileInputStream("transient.txt")))
        {
            Person per = new Person("孙悟空", 500);
            // 系统会per对象转换字节序列并输出
            oos.writeObject(per);
            Person p = (Person)ois.readObject();
            System.out.println(p.getAge());
        }
        catch (Exception ex)
        {
            ex.printStackTrace();
        }
    }
}

writeObject()与readObject()方法

使用transient组织序列化虽然简单,但被她修饰的属性被完全隔离在序列化机制之外,导致了在反序列化时无法获得该属性的值,而通过在需要的序列化对象的Java类加入writeObject()与readObject()的方法可以控制如何序列化各属性,甚至不完全序列化某些属性。

例如:如果我们想要上面的Person类里的name属性在序列化后存在文件里不让别人知道具体是什么(加密),我们就可在Person类里加如下代码:

public class Person
    implements java.io.Serializable
{
    private String name;
    private int age;
    // 注意此处没有提供无参数的构造器!
    public Person(String name , int age)
    {
        System.out.println("有参数的构造器");
        this.name = name;
        this.age = age;
    }
    // 省略name与age的setter和getter方法

    // name的setter和getter方法
    public void setName(String name)
    {
        this.name = name;
    }
    public String getName()
    {
        return this.name;
    }

    // age的setter和getter方法
    public void setAge(int age)
    {
        this.age = age;
    }
    public int getAge()
    {
        return this.age;
    }

    private void writeObject(java.io.ObjectOutputStream out)
        throws IOException
    {
        // 将name实例变量的值反转后写入二进制流
        out.writeObject(new StringBuffer(name).reverse());
        out.writeInt(age);
    }
    private void readObject(java.io.ObjectInputStream in)
        throws IOException, ClassNotFoundException
    {
        // 将读取的字符串反转后赋给name实例变量
        this.name = ((StringBuffer)in.readObject()).reverse()
            .toString();
        this.age = in.readInt();
    }
}

Externalizable接口

Java还提供了另一种序列化机制,这种序列化方式完全由程序员决定存储和恢复对象的数据。要实现改目标,Java类必须实现Externalizable接口,该接口里定义了如下两个方法。

void readExternal(ObjectInput in):需要序列化的类实现readExternal()方法来实现反序列化。
void writeExternal(ObjectOutput out):需要序列化的类实现writeExternal()方法来保存对象的状态值。

下面程序中的Person实现了java.io.Externalizable接口,该Person类还实现了readExternal(),writeExternal()两个方法,这两个方法除了方法签名和readObject(),writeObject()两个方法的方法签名有所不同之外,其方法体完全一样。

需要注意的是实现Externalizable接口的类必须实现无参构造方法,否则反序列化会失败。

public class Teacher implements Externalizable{

    private String name;
    private Integer age;

    public Teacher(){
        System.out.println("无参构造");
    }

    public Teacher(String name,Integer age){
        System.out.println("有参构造");
        this.name = name;
        this.age = age;
    }

    //setter、getter方法省略

    @Override
    public void writeExternal(ObjectOutput out) throws IOException {
        out.writeObject(new StringBuffer(name).reverse()); //将name简单加密
        //out.writeInt(age);  //注掉这句后,age属性将不能被序化
    }

    @Override
    public void readExternal(ObjectInput in) throws IOException,
            ClassNotFoundException {
        name = ((StringBuffer) in.readObject()).reverse().toString();
        //age = in.readInt();  
    }

    @Override 
    public String toString() {  
        return "[" + name + ", " + age+ "]";  
    } 

}
public class MySerilizable {

    public static void main(String[] args) throws Exception {  
        File file = new File("person.txt");  

        //序列化持久化对象
        ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(file));  
        Teacher person = new Teacher("Peter", 27);  
        out.writeObject(person);  
        out.close();  

        //反序列化,并得到对象
        ObjectInputStream in = new ObjectInputStream(new FileInputStream(file));  
        Object newPerson = in.readObject(); // 没有强制转换到Person类型  
        in.close();  
        System.out.println(newPerson);  

    }  
}

Serializable和Externalizable 的比较

实现Serializable接口的需要序列化的类可以没有无参构造器,但实现Externalizable接口的需要序列化的类必须有无参构造器,否在在反序列化时会失败。
—实现Serializable接口的类自动存储必要信息,而实现Externalizable接口的类由程序员决定存储哪些信息。
—实现Serializable接口的类易于实现,只需实现该接口即可,无需任何代码支持。而实现Externalizable接口的类必须为writeExternal(),readExternal()方法提供实现
— 实现Serializable接口的类性能略差,实现Externalizable的类性能略好。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值