序列化与反序列化

什么是序列化和反序列化?

序列化(Serializable)是指将内存中存储的对象转变成可以持久化存储在磁盘上的文件(RPC框架protobuf等)或者可以在网络层面传输的结构(XML或者JSON)的过程,反序列化(Deserializable)则是序列化的逆过程。

对象序列化的形式
  • 文件

    使用ObjectOutputStream二进制流的writeObject方法

  • 字符串

    推荐使用jackson工具包

对象序列化与反序列化是否必须实现Serializable接口?
先说结论

   对于序列化为二进制存储,比如文件,是必须实现Serializable接口的;对于XML等网络传输结构存储,则不一定需要实现Serializable接口。

代码实操
  • 对象序列化为文件存储
# step01 定义一个Address类,未实现Serializable接口
public class Address {
  //  private static final long serialVersionUID = 6319966457620802828L;

    private String Name;

    public String getName() {
        return Name;
    }

    public void setName(String name) {
        Name = name;
    }
}

# step02 定义一个测试类,将对象序列化到文件中存储
package com.example.hongchangdemo;

import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;

public class testSerializable {
    public static void main(String[] args) {
        Address address = new Address();
        address.setName("测试地址");
        serializeAddress(address);
    }
    public static void serializeAddress(Address address) {
        FileOutputStream fout = null;
        ObjectOutputStream oos = null;
        try {
            fout = new FileOutputStream("D:\\address.txt");
            oos = new ObjectOutputStream(fout);
            oos.writeObject(address);
            System.out.println("序列化成功");
        } catch (Exception ex) {
            ex.printStackTrace();
        } finally {
            if (fout != null) {
                try {
                    fout.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (oos != null) {
                try {
                    oos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

}


控制台代码执行结果:

未实现序列化接口程序运行结果.png
# step03 Address类,实现 Serializable接口
package com.example.hongchangdemo;

import java.io.Serializable;

public class Address implements Serializable {
    private static final long serialVersionUID = 6319966457620802828L;

    private String Name;

    public String getName() {
        return Name;
    }

    public void setName(String name) {
        Name = name;
    }
}

控制台代码再次执行结果:

实现序列化接口程序运行结果.png
  • 文件反序列化为对象

    # step04 实现反序列化方法 
            public static void deserializeAddress() {
            FileInputStream fin = null;
            ObjectInputStream ois = null;
            try {
                fin = new FileInputStream("D:\\address.txt");
                ois = new ObjectInputStream(fin);
                Address address = (Address) ois.readObject();
                System.out.println("反序列化成功:"+address.toString());
            } catch (Exception ex) {
                ex.printStackTrace();
            } finally {
                if (fin != null) {
                    try {
                        fin.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
                if (ois != null) {
                    try {
                        ois.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    

控制台代码执行结果:

实现序列化接口对象反序列化结果.png
深挖原因

首先我们一起看下Serializable接口

/**
 * Serializability of a class is enabled by the class implementing the
 * java.io.Serializable interface. Classes that do not implement this
 * interface will not have any of their state serialized or
 * deserialized.  All subtypes of a serializable class are themselves
 * serializable.  The serialization interface has no methods or fields
 * and serves only to identify the semantics of being serializable. <p>
 *
 * To allow subtypes of non-serializable classes to be serialized, the
 * subtype may assume responsibility for saving and restoring the
 * state of the supertype's public, protected, and (if accessible)
 * package fields.  The subtype may assume this responsibility only if
 * the class it extends has an accessible no-arg constructor to
 * initialize the class's state.  It is an error to declare a class
 * Serializable if this is not the case.  The error will be detected at
 * runtime. <p>
 *
 * During deserialization, the fields of non-serializable classes will
 * be initialized using the public or protected no-arg constructor of
 * the class.  A no-arg constructor must be accessible to the subclass
 * that is serializable.  The fields of serializable subclasses will
 * be restored from the stream. <p>
 *
 * When traversing a graph, an object may be encountered that does not
 * support the Serializable interface. In this case the
 * NotSerializableException will be thrown and will identify the class
 * of the non-serializable object. <p>
 *
 * Classes that require special handling during the serialization and
 * deserialization process must implement special methods with these exact
 * signatures:
 *
 * <PRE>
 * private void writeObject(java.io.ObjectOutputStream out)
 *     throws IOException
 * private void readObject(java.io.ObjectInputStream in)
 *     throws IOException, ClassNotFoundException;
 * private void readObjectNoData()
 *     throws ObjectStreamException;
 * </PRE>
 *
 * <p>The writeObject method is responsible for writing the state of the
 * object for its particular class so that the corresponding
 * readObject method can restore it.  The default mechanism for saving
 * the Object's fields can be invoked by calling
 * out.defaultWriteObject. The method does not need to concern
 * itself with the state belonging to its superclasses or subclasses.
 * State is saved by writing the individual fields to the
 * ObjectOutputStream using the writeObject method or by using the
 * methods for primitive data types supported by DataOutput.
 *
 * <p>The readObject method is responsible for reading from the stream and
 * restoring the classes fields. It may call in.defaultReadObject to invoke
 * the default mechanism for restoring the object's non-static and
 * non-transient fields.  The defaultReadObject method uses information in
 * the stream to assign the fields of the object saved in the stream with the
 * correspondingly named fields in the current object.  This handles the case
 * when the class has evolved to add new fields. The method does not need to
 * concern itself with the state belonging to its superclasses or subclasses.
 * State is saved by writing the individual fields to the
 * ObjectOutputStream using the writeObject method or by using the
 * methods for primitive data types supported by DataOutput.
 *
 * <p>The readObjectNoData method is responsible for initializing the state of
 * the object for its particular class in the event that the serialization
 * stream does not list the given class as a superclass of the object being
 * deserialized.  This may occur in cases where the receiving party uses a
 * different version of the deserialized instance's class than the sending
 * party, and the receiver's version extends classes that are not extended by
 * the sender's version.  This may also occur if the serialization stream has
 * been tampered; hence, readObjectNoData is useful for initializing
 * deserialized objects properly despite a "hostile" or incomplete source
 * stream.
 *
 * <p>Serializable classes that need to designate an alternative object to be
 * used when writing an object to the stream should implement this
 * special method with the exact signature:
 *
 * <PRE>
 * ANY-ACCESS-MODIFIER Object writeReplace() throws ObjectStreamException;
 * </PRE><p>
 *
 * This writeReplace method is invoked by serialization if the method
 * exists and it would be accessible from a method defined within the
 * class of the object being serialized. Thus, the method can have private,
 * protected and package-private access. Subclass access to this method
 * follows java accessibility rules. <p>
 *
 * Classes that need to designate a replacement when an instance of it
 * is read from the stream should implement this special method with the
 * exact signature.
 *
 * <PRE>
 * ANY-ACCESS-MODIFIER Object readResolve() throws ObjectStreamException;
 * </PRE><p>
 *
 * This readResolve method follows the same invocation rules and
 * accessibility rules as writeReplace.<p>
 *
 * The serialization runtime associates with each serializable class a version
 * number, called a serialVersionUID, which is used during deserialization to
 * verify that the sender and receiver of a serialized object have loaded
 * classes for that object that are compatible with respect to serialization.
 * If the receiver has loaded a class for the object that has a different
 * serialVersionUID than that of the corresponding sender's class, then
 * deserialization will result in an {@link InvalidClassException}.  A
 * serializable class can declare its own serialVersionUID explicitly by
 * declaring a field named <code>"serialVersionUID"</code> that must be static,
 * final, and of type <code>long</code>:
 *
 * <PRE>
 * ANY-ACCESS-MODIFIER static final long serialVersionUID = 42L;
 * </PRE>
 *
 * If a serializable class does not explicitly declare a serialVersionUID, then
 * the serialization runtime will calculate a default serialVersionUID value
 * for that class based on various aspects of the class, as described in the
 * Java(TM) Object Serialization Specification.  However, it is <em>strongly
 * recommended</em> that all serializable classes explicitly declare
 * serialVersionUID values, since the default serialVersionUID computation is
 * highly sensitive to class details that may vary depending on compiler
 * implementations, and can thus result in unexpected
 * <code>InvalidClassException</code>s during deserialization.  Therefore, to
 * guarantee a consistent serialVersionUID value across different java compiler
 * implementations, a serializable class must declare an explicit
 * serialVersionUID value.  It is also strongly advised that explicit
 * serialVersionUID declarations use the <code>private</code> modifier where
 * possible, since such declarations apply only to the immediately declaring
 * class--serialVersionUID fields are not useful as inherited members. Array
 * classes cannot declare an explicit serialVersionUID, so they always have
 * the default computed value, but the requirement for matching
 * serialVersionUID values is waived for array classes.
 *
 * @author  unascribed
 * @see java.io.ObjectOutputStream
 * @see java.io.ObjectInputStream
 * @see java.io.ObjectOutput
 * @see java.io.ObjectInput
 * @see java.io.Externalizable
 * @since   JDK1.1
 */
public interface Serializable {
}

  作为一名资深的搬砖工,小伙伴们肯定坐不住了,what?智商税?,老小子,啥事不干,定义一个空的interface,连个方法都没有?那他是如何控制对象的序列化的呢?静下心来,仔细看上面的注释才发现,原来Serializable接口只是定义了一个规范,标记该对象是可以被序列化,并非是功能实现。那到底是怎么实现对象的序列化的呢?小伙伴们不急,我们继续往下看。

序列化接口注释.png

  大家都知道我们真正序列化对象是调用ObjectOutputStream流的writeObject方法进行序列化到文件中的,试想难道幺蛾子出在这里?接着我们打开writeObject源码看,当看到方法注解时,明白了,原来是否进行对象序列化化的判断是在具体的实现类中,类不实现序列化Serializable接口的话,如果调用writeObject序列化对象,会抛出不可被序列化的异常,至此疑团终于被揭秘了。

    /**
     * Write the specified object to the ObjectOutputStream.  The class of the
     * object, the signature of the class, and the values of the non-transient
     * and non-static fields of the class and all of its supertypes are
     * written.  Default serialization for a class can be overridden using the
     * writeObject and the readObject methods.  Objects referenced by this
     * object are written transitively so that a complete equivalent graph of
     * objects can be reconstructed by an ObjectInputStream.
     *
     * <p>Exceptions are thrown for problems with the OutputStream and for
     * classes that should not be serialized.  All exceptions are fatal to the
     * OutputStream, which is left in an indeterminate state, and it is up to
     * the caller to ignore or recover the stream state.
     *
     * @throws  InvalidClassException Something is wrong with a class used by
     *          serialization.
     * @throws  NotSerializableException Some object to be serialized does not
     *          implement the java.io.Serializable interface.
     * @throws  IOException Any exception thrown by the underlying
     *          OutputStream.
     */
    public final void writeObject(Object obj) throws IOException {
        if (enableOverride) {
            writeObjectOverride(obj);
            return;
        }
        try {
            writeObject0(obj, false);
        } catch (IOException ex) {
            if (depth == 0) {
                writeFatalException(ex);
            }
            throw ex;
        }
    }
serialVersionUID作用

  细心的小伙伴已经发现了,类中定义了一个常量值serialVersionUID,有的常量值为1L,有的是一串看不懂的数字,经过查阅资料得知,SerialVersionUID 适用于java序列化的机制,用这个东西来进行版本一致性的验证,如果是一致的就能实现序列化反序列化,如果不一致就会抛出异常,显示声明serialVersionUID可以避免反序列化出来的对象不一致的问题。这个值其实可以随便写的,只要保证一致就可以正常序列化和反序列化,因为这个值会在序列化时候保存起来,反序列化时校验。

SerialVersionUID有两种显式地生成方式:
 一是默认的1L,比如 private static final long serialVersionUID = 1L;
 二是根据包名,类名,继承关系,非私有的方法和属性,以及参数返回值等诸多因子计算出的,极度复杂生成一个64位的哈希字段.基本上算出来的值是唯一的。
private static final long serialVersionUID = 6319966457620802828L;
serialVersionUID不一致

  序列化与反序列化版本号不一致,反序列化失败,并抛出异常

序列化与反序列化seriaVersionID不一致.png
序列化作用域

  序列化保存的是对象的状态,静态变量属于类加载阶段,所以序列化并不保存静态变量。

transient 关键字

  transient关键字修饰的变量可以不被序列化,也只能修饰变量,不可以修饰类或者方法。transient关键字修饰的变量在进行反序列化时,被设置成初始化值

# step05 Address类新增doorId字段,并且用transient关键字修饰
package com.example.hongchangdemo;

import java.io.Serializable;

public class Address implements Serializable {
    private static final long serialVersionUID = 6319966457620802828L;

    private String Name;

    private transient String doorId;

    public  String getName() {
        return Name;
    }

    public void setName(String name) {
        Name = name;
    }

    public String getDoorId() {
        return doorId;
    }

    public void setDoorId(String doorId) {
        this.doorId = doorId;
    }

    @Override
    public String toString() {
        return "Address{" +
                "Name='" + Name + '\'' +
                ", doorId='" + doorId + '\'' +
                '}';
    }
}


代码运行结果:

transient关键字修饰的对象序列化结果.png
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,012评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,628评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,653评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,485评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,574评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,590评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,596评论 3 414
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,340评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,794评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,102评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,276评论 1 344
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,940评论 5 339
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,583评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,201评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,441评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,173评论 2 366
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,136评论 2 352

推荐阅读更多精彩内容

  • 一、序列化和反序列化的概念 把对象转换为字节序列的过程称为对象的序列化。把字节序列恢复为对象的过程称为对象的反序列...
    叨唧唧的阅读 719评论 0 0
  • 原帖地址:原帖个人网站地址:个人网站简书对markdown的支持太完美了,我竟然可以直接Ctrl C/V过来。 定...
    ryderchan阅读 3,797评论 1 9
  • 序列化与反序列化 把对象以流的方式,写入到文件中保存,叫写对象,也叫对象的序列化。对象中包含的不仅仅是字符,使用字...
    柒_wu7阅读 265评论 0 0
  • “最好的教材就是源码注释,然后是大牛的总结。” 从今天开始写博客,目的很明确,梳理零碎的java知识,总结并记录下...
    蜗牛在北京阅读 851评论 1 1
  • 唠嗑 好久没有写博客了,懈怠了,周一到周五工作,没时间写,周末想着放松放松,也不想写,一拖再拖。 最近意识到正式工...
    _kkk阅读 441评论 0 1