Java 序列化之 Externalizable

作者: jijs | 来源:发表于2018-04-07 12:21 被阅读1173次

    相关文章: Java 序列化 之 Serializable

    JDK中除了提供 Serializable 序列化接口外,还提供了另一个序列化接口Externalizable,使用该接口之后,之前基于Serializable接口的序列化机制就将失效。Externalizable 的序列化机制优先级要高于 Serializable 。

    Externalizable 源码分析


    从源码中,我们可以看到 Externalizable 接口继承了 Serializable 接口。并定义了两个方法 writeExternal 和 readExternal 方法

    Externalizable 示例一

    User 类

    public class User implements Externalizable {
    
        private static final long serialVersionUID = 1318824539146791009L;
        private String userName;
        private transient String password;
    
        public String getUserName() {
            return userName;
        }
        public void setUserName(String userName) {
            this.userName = userName;
        }
        public String getPassword() {
            return password;
        }
        public void setPassword(String password) {
            this.password = password;
        }
        @Override
        public String toString() {
            return "User [userName=" + userName + ", password=" + password + "]";
        }
        @Override
        public void writeExternal(ObjectOutput out) throws IOException {
        }
        @Override
        public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
        }
    }
    

    User 类有两个属性,一个是非 transient 字段的userName 和一个 transient 字段的password,并实现了 Externalizable 接口,writeExternal 和 readExternal 方法都不填写任何逻辑。
    然后下面的代码,来看下序列化和反序列化 User 对象的效果。

    public class Test{
        public static void main(String[] args) throws Exception {
            File file = new File("d:\\a.user");
            ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(file));
            User user1 = new User();
            user1.setUserName("zhangsan");
            user1.setPassword("123456");
            oos.writeObject(user1);
    
            ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file));
            User user2 = (User) ois.readObject();
            System.out.println(user2);
        }
    }
    

    执行结果:
    User [userName=null, password=null]

    示例二

    实现 Externalizable 接口的两个方法的实现,代码如下:

    public class User implements Externalizable {
    
        private static final long serialVersionUID = 1318824539146791009L;
        private String userName;
        private transient String password;
    
        public String getUserName() {
            return userName;
        }
        public void setUserName(String userName) {
            this.userName = userName;
        }
        public String getPassword() {
            return password;
        }
        public void setPassword(String password) {
            this.password = password;
        }
        @Override
        public String toString() {
            return "User [userName=" + userName + ", password=" + password + "]";
        }
        @Override
        public void writeExternal(ObjectOutput out) throws IOException {
            out.writeObject(this.userName);
            out.writeObject(this.password);
        }
        @Override
        public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
            this.userName = in.readObject().toString();
            this.password = in.readObject().toString();
        }
    }
    

    在 writeExternal 方法中写入 userName 和 password 两个数据,在 readExternal 方法中读取反序列化的信息并赋值给 userName 和 password 两个字段。

    执行 Test.main 方法。

    执行结果如下:
    User [userName=zhangsan, password=123456]

    结论一

    • 实现 Externalizable 接口后,序列化的细节需要由开发人员自己实现。由于writeExternal()与readExternal()方法未作任何处理,那么该序列化行为将不会保存/读取任何一个字段。这也就是为什么输出结果中所有字段的值均为空。
    • 实现 Externalizable 接口后,属性字段使用 transient 和不使用没有任何区别。

    示例三

    public class User implements Serializable, Externalizable {
    
        private static final long serialVersionUID = 1318824539146791009L;
        private String userName;
        private transient String password;
    
        public String getUserName() {
            return userName;
        }
        public void setUserName(String userName) {
            this.userName = userName;
        }
        public String getPassword() {
            return password;
        }
        public void setPassword(String password) {
            this.password = password;
        }
        @Override
        public String toString() {
            return "User [userName=" + userName + ", password=" + password + "]";
        }
        private void readObject(ObjectInputStream s) throws Exception {
            s.defaultReadObject();
            this.password = (String) s.readObject();
        }
        private void writeObject(ObjectOutputStream s) throws IOException {
            s.defaultWriteObject();
            s.writeObject("serializable:"+this.password);
        }
        @Override
        public void writeExternal(ObjectOutput out) throws IOException {
            out.writeObject(this.userName);
            out.writeObject("externalizable:"+this.password);
        }
        @Override
        public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
            this.userName = (String)in.readObject();
            this.password = (String)in.readObject();
        }
    }
    

    User 类既实现了 Serializable 接口,又实现了 Externalizable 接口(其实没啥意义,因为 Externalizable 已经继承了 Serializable 接口)。
    在 readObject 和 writeObject 方法中实现了序列化和发序列化。
    在 readExternal 和 writeExternal 方法中也实现了序列化和发序列化。

    当执行 Test.main() 方法,执行的结果如下:

    User [userName=zhangsan, password=externalizable:123456]
    可以看出,执行的是 Externalizable ,而不是 Serializable 。

    结论二

    Externalizable 序列化的优先级比Serializable的优先级高。

    示例四

    public class User implements Externalizable {
    
        private static final long serialVersionUID = 1318824539146791009L;
        private String userName;
        private transient String password;
        
        public User(String userName, String password) {
            super();
            this.userName = userName;
            this.password = password;
        }
        public String getUserName() {
            return userName;
        }
        public void setUserName(String userName) {
            this.userName = userName;
        }
        public String getPassword() {
            return password;
        }
        public void setPassword(String password) {
            this.password = password;
        }
        @Override
        public String toString() {
            return "User [userName=" + userName + ", password=" + password + "]";
        }
        @Override
        public void writeExternal(ObjectOutput out) throws IOException {
            out.writeObject(this.userName);
            out.writeObject("externalizable:"+this.password);
        }
        @Override
        public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
            this.userName = (String)in.readObject();
            this.password = (String)in.readObject();
        }
    }
    

    User 类实现了 Externalizable 接口,并且有一个带参数的构造方法。

    public class Test{
        public static void main(String[] args) throws Exception {
            File file = new File("d:\\a.user");
            ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(file));
            User user1 = new User("zhangsan", "123456");
            oos.writeObject(user1);
    
            System.out.println("-------序列化成功");
            
            ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file));
            User user2 = (User) ois.readObject();
            System.out.println(user2);
            System.out.println("-------反序列化成功");
        }
    }
    

    执行 Test.main() 方法,执行结果:

    -------序列化成功
    Exception in thread "main" java.io.InvalidClassException: cn.com.infcn.serial.external.User; no valid constructor

    发现报错了,序列化成功了,而没有发序列化成功。提示没有有效的构造方法。
    这是因为使用 Externalizable 进行反序列化时,需要有默认的构造方法,通过反射先创建出该类的实例,然后再把解析后的属性值,通过反射赋值。

    结论

    使用 Externalizable 进行序列化时,必须要有默认的构造方法,而Serializable可以没有默认的构造方法。


    个人微信公众号:

    相关文章

      网友评论

      本文标题:Java 序列化之 Externalizable

      本文链接:https://www.haomeiwen.com/subject/oaojhftx.html