Java Serializable(序列化)

一、什么是Java Serializable(序列化)
为了保存在内存中的各种对象的状态(也就是实例变量,不是方法),并且可以方便地把保存的对象状态再读出来。

二、哪些情况下需要序列化

a)当你想把的内存中的对象状态保存到一个文件中或者数据库中时候;

b)当你想用套接字在网络上传送对象的时候;

 c)当你想通过RMI传输对象的时候


三、如何进行序列化(序列化的方式)
3.1 默认方式:实现Serializable接口

Serializable是Java提供的一个序列化接口,它是一个空接口,起标识作用,标识该类具备了序列化和反序列化的能力。举例如下:

 public class User implements Serializable {
 
    private static final long serialVersionUID = 1L;
    private String name;
    private int age;

    public User(String name, int age) {
        this.name = name;
        this.age = age;
    }

    @Override
    public String toString() {
        return "姓名:" + name + ";年龄:" + age;
    }
}

通过Serializable方式实现对象的序列化,实现起来非常简单,几乎所有工作都被系统自动完成了。如何进行对象的序列化和反序列化也非常简单,只需要采用ObjectOutputStream和ObjectInputStream即可轻松实现:

// 序列化
User user = new User("aronchen", 28);
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("test.txt"));
out.writeObject(user);
out.close();
            
// 反序列化
ObjectInputStream in = new ObjectInputStream(new FileInputStream("test.txt"));
User newUser = (User) in.readObject();
in.close();
上述操作到底发生了什么呢?序列化过程中,user对象中的name和age实例变量的值(aronchen, 28)都被ObjectOutputStream保存在test.txt文件中。反序列化过程中,ObjectInputStream 将test.txt中的字节数据还原为User对象。通过查看test.txt文件内容,发现保存在文件中的内容除了对象的相关信息,还有一些额外的辅助信息,比如字段类型等以便于回复原来的对象。

User类中的serialVersionUID是用来辅助序列化和反序列化的,原则上序列化后的数据中的serialVersionUID只有和当前类的serialVersionUID相同才能够被正常的反序列化。

3.2 自定义序列化和反序列化

重写writeObject(java.io.ObjectOutputStream s)和readObject(java.io.ObjectInputStream s)两个方法即可。它与3.1的最大不同在于:3.1中调用默认的序列化方式:defaultWriteObject和反序列化方式:defaultReadObject。而在该方法中用户可以自由控制序列化与反序列化的字段,而且不用管理父类或者子类中的字段。代码如下:

public class User implements Serializable{

    private static final long serialVersionUID = 1L;
    private String name;
    private int age;

    public User(String name, int age) {
        this.name = name;
        this.age = age;
    }

    private void writeObject(java.io.ObjectOutputStream s)
            throws java.io.IOException{        
	System.out.println("执行序列化动作");
	s.defaultWriteObject();
        s.writeObject(this.name);
        s.writeObject(this.age);
    }

    private void readObject(java.io.ObjectInputStream s)
            throws java.io.IOException, ClassNotFoundException {
        System.out.println("执行反序列化动作");
	s.defaultReadObject();
        this.name = (String) s.readObject();
        this.age = (int) s.readObject();
    }

    @Override
    public String toString() {
        return "姓名:" + name + ";年龄:" + age;
    }
}

在对象序列化的过程中会自动调用writeObject方法,其中的s.defaultWriteObject被调用时,每一个未被标记为transient的实例域都会被序列化;在反序列化的过程中会自动调用readObject方法。

3.3 实现Externalizable接口进行序列化

如果用户希望自己指定序列化的内容,则可以让一个类实现Externalizable接口,此接口定义如下:

public interface Externalizable extends Serializable { 
    // 指定要保存的属性信息,对象序列化时调用
    public void writeExternal(ObjectOutput out) throws IOException ;  
    // 读取被保存的信息,对象反序列化时调用
    public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException ;  
}

具体实现如下:

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

    private String userName;
    private String passWord;
    private int userAge;
    

    public UserInfo(){        
        System.out.println("无参构造函数");
    }

    public UserInfo(String username,String userPass,int userAge) {
        this.userName = username;
        this.passWord = userPass;
        this.userAge = userAge;
    }

    //当序列化对象时,该方法自动调用
    public void writeExternal(ObjectOutput out) throws IOException{        
        System.out.println("执行序列化方法");
        //可以在序列化时写非自身的变量
        Date d = new Date();
        out.writeObject(d);
        //只序列化userName,userPass变量
        out.writeObject(userName);
        out.writeObject(passWord);
    }

    //当反序列化对象时,该方法自动调用
    public void readExternal(ObjectInput in) throws IOException,ClassNotFoundException{        
        System.out.println("执行反序列化方法");
        Date date = (Date)in.readObject();
        Log.e("hello", "序列化时间:" + date);
        this.userName= (String)in.readObject();
        this.passWord = (String)in.readObject();

    }

    public String toString(){
        return "用户名: "+ this.userName+";密码:"+ this.passWord +
                ";年龄:"+ this.userAge;
    }
}
注意:实现Externalizable接口的类一定要定义默认构造函数,因为反序列化时会调用 无参 构造函数,如果未定义,则出现运行时错误:IllegalAccessException。

四、相关注意事项
a)序列化时,只对对象的状态进行保存,而不管对象的方法;
b)当一个父类实现序列化,子类自动实现序列化,不需要显式实现Serializable接口;
c)当一个对象的实例变量引用其他对象,序列化该对象时也把引用对象进行序列化(因此被引用对象所属的类也应该实现序列化);
d)并非所有的对象都可以序列化,至于为什么不可以,有很多原因了,比如:
        1.安全方面的原因,比如一个对象拥有private,public等field,对于一个要传输的对象,比如写到文件,或者进行rmi传输等等,在序列化进行传输的过程中,这个对象的private等域是不受保护的。

        2.资源分配方面的原因,比如socket,thread类,如果可以序列化,进行传输或者保存,也无法对他们进行重新的资源分配,而且,也是没有必要这样实现。

e)  如果超类没有提供可访问的无参构造器,子类也不可能做到可序列化。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值