你真的有好好了解过序列化吗:Java序列化实现的原理

前言

在开发过程中经常会对实体进行序列化,但其实我们只是在“只知其然,不知其所以然”的状态,很多时候会有这些问题:

  • 什么是序列化和反序列化?为什么要序列化?
  • 怎么实现序列化?
  • 序列化的原理是什么呢?
  • transient关键字
  • 序列化时应注意什么?

如果你也有这些疑问,不妨看看本文?

(若文章有不正之处,或难以理解的地方,请多多谅解,欢迎指正)

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

Java序列化是指把Java对象转换为字节序列的过程;

Java反序列化是指把字节序列恢复为Java对象的过程;

为什么要序列化?

其实我们的对象不只是存储在内存中,它还需要在传输网络中进行传输,并且保存起来之后下次再加载出来,这时候就需要序列化技术。

  • 一般Java对象的生命周期比Java虚拟机端,而实际开发中如果需要JVM停止后能够继续持有对象,则需要用到序列化技术将对象持久化到磁盘或数据库。
  • 在多个项目进行RPC调用时,需要在网络上传输JavaBean对象,而网络上只允许二进制形式的数据进行传输,这时则需要用到序列化技术。

Java的序列化技术就是把对象转换成一串由二进制字节组成的数组,然后将这二进制数据保存在磁盘或传输网络。而后需要用到这对象时,磁盘或者网络接收者可以通过反序列化得到此对象,达到对象持久化的目的。

怎么实现序列化?

序列化的过程一般会是这样的:

  • 将对象实例相关的类元数据输出
  • 递归地输出类的超类描述,直到没有超类
  • 类元数据输出之后,开始从最顶层的超类输出对象实例的实际数据值
  • 从上至下递归输出实例的数据

所以,如果父类已经序列化了,子类继承之后也可以进行序列化

实现第一步,则需要的先将对象实例相关的类标记为需要序列化。

实现序列化的要求:目标对象实现Serializable接口

我们先创建一个NY类,实现Serializable接口,并生成一个版本号:

public class NY implements Serializable {
    private static final long serialVersionUID = 8891488565683643643L;  //使用idea生成
    private String name;
    private String blogName;
	//省略getter和setter...
    @Override
    public String toString() {
        return "NY{" +
                "name='" + name + '\'' +
                ", blogName='" + blogName + '\'' +
                '}';
    }
}

在这里,Serializable接口的作用只是标识这个类是需要进行序列化的,而且Serializable接口中并没有提供任何方法。而且serialVersionUID序列化版本号的作用是用来区分我们所编写的类的版本,用于反序列化时确定版本。

JDK类库中序列化和反序列化API

  1. java.io.ObjectInputStream:对象输入流

    该类中的readObject()方法从输入流中读取字节序列,然后将字节序列反序列化为一个对象并返回。

  2. java.io.ObjectOutputStream:对象输出流

    该类的writeObject()方法将传入的obj对象进行序列化,把得到的字节序列写入到目标输出流中进行输出。

结合上面的NY类,我们来看看使用JDK类库中的API怎么实现序列化和反序列化:

public class SerializeNY {
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        serializeNY();
        NY ny = deserializeNY();
        System.out.println(ny.toString());
    }

    private static void serializeNY() throws IOException {
        NY ny = new NY();
        ny.setName("NY");
        ny.setBlogName("NYfor2020");
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(new File("D:\\serialable.txt")));
        oos.writeObject(ny);
        System.out.println("NY 对象序列化成功!");
        oos.close();
    }

    private static NY deserializeNY() throws IOException, ClassNotFoundException {
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(new File("D:\\serialable.txt")));
        NY ny = (NY) ois.readObject();
        System.out.println("NY 对象反序列化成功");
        return ny;
    }
}

运行结果为:

NY 对象序列化成功!
NY 对象反序列化成功
NY{name='NY', blogName='NYfor2020'}

可以看到,这整个过程简单来说就是把对象存在磁盘,然后再从磁盘读出来。

但是我们平时看到序列化的实体中的serialVersionUID,为什么有的是1L,有的是一长串数字?

上面我们的提到serialVersionUID作用就是用来区分类的版本,所以无论是1L还是一长串数字,都是用来确认版本的。如果序列化的类版本改变,则在反序列化的时候就会报错。

举个栗子,刚刚我们已经在磁盘中生成了NY对象的序列化文件,如果我们对NY类的serialVersionUID稍作改动,改成:

private static final long serialVersionUID = 8891488565683643643L;  //将末尾的2改成3

再执行一次反序列化方法,运行结果如下:

Exception in thread "main" java.io.InvalidClassException: NY; local class incompatible: stream classdesc serialVersionUID = 8891488565683643642, local class serialVersionUID = 8891488565683643643
	......

至于怎么让idea生成serialVersionUID,则需要在idea设置中改个配置即可:
在这里插入图片描述
之后再使用"Alt+Enter"键即可调出下图选项:
在这里插入图片描述

序列化的原理是什么呢?

既然知道了序列化是怎么使用的,那么序列化的原理是怎么样的呢?

我们用上面的例子来作为探寻序列化原理的入口:

private static void serializeNY() throws IOException {
        NY ny = new NY();
        ny.setName("NY");
        ny.setBlogName("NYfor2020");
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(new File("D:\\serialable.txt")));
        oos.writeObject(ny);
        System.out.println("NY 对象序列化成功!");
        oos.close();
    }
  1. 进入ObjectOutputStream的构造函数
public ObjectOutputStream(OutputStream out) throws IOException {
    //检查是否为ObjectOutputStream的实例
    verifySubclass();
    //bout是底层的数据字节容器
    bout = new BlockDataOutputStream(out);
    handles = new HandleTable(10, (float) 3.00);
    subs = new ReplaceTable(10, (float) 3.00);
    enableOverride = false;
    //写入序列化文件头
    writeStreamHeader();
    //设置文件缓存刷新配置
    bout.setBlockDataMode(true);
    if (extendedDebugInfo) {
    	debugInfoStack = new DebugTraceInfoStack();
    } else {
    	debugInfoStack = null;
    }
}
  • 我们进入**writeStreamHeader()**方法:
protected void writeStreamHeader() throws IOException {
    bout.writeShort(STREAM_MAGIC);
    bout.writeShort(STREAM_VERSION);
}
  • 这个方法是将序列化文件的魔数和版本写入序列化文件头:
/**
* Magic number that is written to the stream header.
*/
final static short STREAM_MAGIC = (short)0xaced;
/**
* Version number that is written to the stream header.
*/
final static short STREAM_VERSION = 5;
  1. 在**writeObject()**方法进行具体的序列化写入操作:
public final void writeObject(Object obj) throws IOException {
    //表示使用writeObjectOverride()方法进行序列化写入,一般为不执行
    if (enableOverride) {
        writeObjectOverride(obj);
        return;
    }
    try {
        //调用writeObject0()方法进行具体的序列化操作
    	writeObject0(obj, false);
    } catch (IOException ex) {
        if (depth == 0) {
        writeFatalException(ex);
        }
    	throw ex;
    }
}
  • 进入**writeObject0()**方法:
    private void writeObject0(Object obj, boolean unshared)
        throws IOException
    {
    	//此处对缓存刷新进行默认配置
        boolean oldMode = bout.setBlockDataMode(false);
        //递归深度
        depth++;
        try {
            // handle previously written and non-replaceable objects
            int h;
            if ((obj = subs.lookup(obj)) == null) {
                writeNull();
                return;
            } else if (!unshared && (h = handles.lookup(obj)) != -1) {
                writeHandle(h);
                return;
            } else if (obj instanceof Class) {
                writeClass((Class) obj, unshared);
                return;
            } else if (obj instanceof ObjectStreamClass) {
                writeClassDesc((ObjectStreamClass) obj, unshared);
                return;
            }

            // check for replacement object
            Object orig = obj;
            //需要序列的对象的Class对象
            Class<?> cl = obj.getClass();
            ObjectStreamClass desc;
            for (;;) {
                // REMIND: skip this check for strings/arrays?
                Class<?> repCl;
                //创建描述c1的ObjectStreamClass对象
                desc = ObjectStreamClass.lookup(cl, true);
                //判断是否有可替换的写方法,一般是没有的
                if (!desc.hasWriteReplaceMethod() ||
                    (obj = desc.invokeWriteReplace(obj)) == null ||
                    (repCl = obj.getClass()) == cl)
                {
                    break;
                }
                cl = repCl;
            }
            //判断此对象是否可以被替换
            if (enableReplace) {
                Object rep = replaceObject(obj);
                if (rep != obj && rep != null) {
                    cl = rep.getClass();
                    desc = ObjectStreamClass.lookup(cl, true);
                }
                obj = rep;
            }

            // if object replaced, run through original checks a second time
            //如果这个obj对象被替换了
            if (obj != orig) {
                subs.assign(orig, obj);
                if (obj == null) {
                    writeNull();
                    return;
                } else if (!unshared && (h = handles.lookup(obj)) != -1) {
                    writeHandle(h);
                    return;
                } else if (obj instanceof Class) {
                    writeClass((Class) obj, unshared);
                    return;
                } else if (obj instanceof ObjectStreamClass) {
                    writeClassDesc((ObjectStreamClass) obj, unshared);
                    return;
                }
            }

            // remaining cases
            //根据实际要写入的类型,进行不同的写入操作
            //其中,String、Array、Enum类型是直接写入操作的
            if (obj instanceof String) {
                writeString((String) obj, unshared);
            } else if (cl.isArray()) {
                writeArray(obj, desc, unshared);
            } else if (obj instanceof Enum) {
                writeEnum((Enum<?>) obj, desc, unshared);
            } else if (obj instanceof Serializable) {
            //实现序列化接口的对象都会执行下面的方法
            //其实在这里就可以看出,Serializable只是一个标记接口,其本身并没有什么意义
                writeOrdinaryObject(obj, desc, unshared);
            } else {
                if (extendedDebugInfo) {
                    throw new NotSerializableException(
                        cl.getName() + "\n" + debugInfoStack.toString());
                } else {
                    throw new NotSerializableException(cl.getName());
                }
            }
        } finally {
        	//此层递归结束
            depth--;
            bout.setBlockDataMode(oldMode);
        }
    }

这一段代码中创建了ObjectStreamClass对象,并根据不同的对象类型来执行不同的写入操作。而在此例子中,对象对应的类实现了Serializable接口,所以下一步会执行writeOrdinaryObject()方法。

  • **writeOrdinaryObject()**是当对象对应的类实现了Serializable接口的时才会被调用:
private void writeOrdinaryObject(Object obj,
                                     ObjectStreamClass desc,
                                     boolean unshared)
        throws IOException
    {
        if (extendedDebugInfo) {
            debugInfoStack.push(
                (depth == 1 ? "root " : "") + "object (class \"" +
                obj.getClass().getName() + "\", " + obj.toString() + ")");
        }
        try {
            desc.checkSerialize();
			//写入Object的标记位符号,表示这是一个新的Object对象
            bout.writeByte(TC_OBJECT);
            //将对类的描述写入
            writeClassDesc(desc, false);
            handles.assign(unshared ? null : obj);
            if (desc.isExternalizable() && !desc.isProxy()) {
                writeExternalData((Externalizable) obj);
            } else {
            	//写入序列化对象具体的实例数据
                writeSerialData(obj, desc);
            }
        } finally {
            if (extendedDebugInfo) {
                debugInfoStack.pop();
            }
        }
    }
  • 接下来是将类的描述写入类元数据中的writeClassDesc()
private void writeClassDesc(ObjectStreamClass desc, boolean unshared)
        throws IOException
    {
        int handle;
        if (desc == null) {
        	//如果desc为null,则写入null
            writeNull();
        } else if (!unshared && (handle = handles.lookup(desc)) != -1) {
            writeHandle(handle);
        } else if (desc.isProxy()) {
            writeProxyDesc(desc, unshared);
        } else {
            writeNonProxyDesc(desc, unshared);
        }
    }
  • 在desc为null时,会执行**writeNull()**方法:
private void writeNull() throws IOException {
	bout.writeByte(TC_NULL);
}
/**
* Null object reference.
*/
final static byte TC_NULL =         (byte)0x70;

可以看到,在writeNull()中,会将表示NULL的标识写入序列中。

  • 那么如果desc不为null时,一般执行**writeNonProxyDesc()**方法:
private void writeNonProxyDesc(ObjectStreamClass desc, boolean unshared)
        throws IOException
    {
    	//类元信息的标记位
    	//表示接下来的数据为Class描述符
        bout.writeByte(TC_CLASSDESC);
        handles.assign(unshared ? null : desc);

        if (protocol == PROTOCOL_VERSION_1) {
            // do not invoke class descriptor write hook with old protocol
            desc.writeNonProxy(this);
        } else {
        	//一般会执行此方法,将类描述写入
            writeClassDescriptor(desc);
        }

        Class<?> cl = desc.forClass();
        bout.setBlockDataMode(true);
        if (cl != null && isCustomSubclass()) {
            ReflectUtil.checkPackageAccess(cl);
        }
        //根据cl的类型进行处理
        annotateClass(cl);
        bout.setBlockDataMode(false);
        //表示对一个object的描述块的结束
        bout.writeByte(TC_ENDBLOCKDATA);//此处会将对象相应的类的父类写入
        writeClassDesc(desc.getSuperDesc(), false);
    }
  • 在上一个方法执行过程中,会执行**writeClassDescriptor()**方法将类的描述写入类元数据中:
protected void writeClassDescriptor(ObjectStreamClass desc)
        throws IOException{
	desc.writeNonProxy(this);
}
  • 在这里我们可以看到,写入类元信息的方法调用了**writeNonProxy()**方法:
    void writeNonProxy(ObjectOutputStream out) throws IOException {
        //写入类名
        out.writeUTF(name);
        //写入serialVersionUID,看!这里显示了序列号的重要性
        out.writeLong(getSerialVersionUID());

        //类的标记
        byte flags = 0;
        if (externalizable) {
            flags |= ObjectStreamConstants.SC_EXTERNALIZABLE;
            int protocol = out.getProtocolVersion();
            if (protocol != ObjectStreamConstants.PROTOCOL_VERSION_1) {
                flags |= ObjectStreamConstants.SC_BLOCK_DATA;
            }
        } else if (serializable) {
            //一般程序会执行到这里,标识类执行序列化
            // final static byte SC_SERIALIZABLE = 0x02;
            flags |= ObjectStreamConstants.SC_SERIALIZABLE;
        }
        if (hasWriteObjectData) {
            // 自定义writeObject方法
            // final static byte SC_WRITE_METHOD = 0x01;
            flags |= ObjectStreamConstants.SC_WRITE_METHOD;
        }
        if (isEnum) {
            //枚举标记
            //final static byte SC_ENUM = 0x10;
            flags |= ObjectStreamConstants.SC_ENUM;
        }
        //将标记写入类元信息中
        out.writeByte(flags);
		
        //写入对象的字段数量
        out.writeShort(fields.length);
        for (int i = 0; i < fields.length; i++) {
            ObjectStreamField f = fields[i];
            //写入字段类型对象的Code,类似标记
            out.writeByte(f.getTypeCode());
            //写入字段的名字
            out.writeUTF(f.getName());
            if (!f.isPrimitive()) {
                //如果是对象或接口,则会写入表示对象的字符串
                out.writeTypeString(f.getTypeString());
            }
        }
    }

这次方法中我们可以看到:

  1. 调用writeUTF()方法将对象所属类的名字写入。
  2. 调用writeLong()方法将类的序列号serialVersionUID写入。
  3. 判断被序列化对象所属类的流类型flag,写入底层字节容器中(占两个字节)。
  4. 写入对象中的所有字段,以及对应的属性

所以直到这个方法的执行,一个对象及其对应类的所有属性和属性值才被序列化。当上述流程完成之后,回到**writeOrdinaryObject()**方法中,继续往下运行:

    private void writeOrdinaryObject(Object obj,ObjectStreamClass desc,boolean unshared)
        throws IOException{
        ...
            writeClassDesc(desc, false);
        	//------------------------
        	//程序会在这里继续往下运行,已将对象的相关序列化数据写入流中了
        	//------------------------
            handles.assign(unshared ? null : obj);
            if (desc.isExternalizable() && !desc.isProxy()) {
                writeExternalData((Externalizable) obj);
            } else {
                //将序列化对象的实例化数据写入
                writeSerialData(obj, desc);
            }
        } finally {
            if (extendedDebugInfo) {
                debugInfoStack.pop();
            }
        }
    }
  • 调用**writeSerialData()**方法将实例化数据写入:
private void writeSerialData(Object obj, ObjectStreamClass desc)
        throws IOException
    {
    	//获取序列化对象的数据布局ClassDataSlot
        ObjectStreamClass.ClassDataSlot[] slots = desc.getClassDataLayout();
        for (int i = 0; i < slots.length; i++) {
            ObjectStreamClass slotDesc = slots[i].desc;
            //如果序列化对象实现了自己的writeObject()方法,则进入if代码块,否则进入else代码块,执行默认的写入方法
            if (slotDesc.hasWriteObjectMethod()) {
                PutFieldImpl oldPut = curPut;
                curPut = null;
                SerialCallbackContext oldContext = curContext;

                if (extendedDebugInfo) {
                    debugInfoStack.push(
                        "custom writeObject data (class \"" +
                        slotDesc.getName() + "\")");
                }
                try {
                    curContext = new SerialCallbackContext(obj, slotDesc);
                    bout.setBlockDataMode(true);
                    slotDesc.invokeWriteObject(obj, this);
                    bout.setBlockDataMode(false);
                    bout.writeByte(TC_ENDBLOCKDATA);
                } finally {
                    curContext.setUsed();
                    curContext = oldContext;
                    if (extendedDebugInfo) {
                        debugInfoStack.pop();
                    }
                }

                curPut = oldPut;
            } else {
            	//一般执行这个方法,默认的写入实例数据
                defaultWriteFields(obj, slotDesc);
            }
        }
    }
  • 当执行到**defaultWriteFields()**方法时,会将实例数据写入:
    private void defaultWriteFields(Object obj, ObjectStreamClass desc)
        throws IOException
    {
        Class<?> cl = desc.forClass();
        if (cl != null && obj != null && !cl.isInstance(obj)) {
            throw new ClassCastException();
        }
		
		//检查是否可以使用默认的序列化
        desc.checkDefaultSerialize();
		
        int primDataSize = desc.getPrimDataSize();
        if (primVals == null || primVals.length < primDataSize) {
            primVals = new byte[primDataSize];
        }
        //获取对象中基本类型的实例数据,并将其放到primVals数组中
        desc.getPrimFieldValues(obj, primVals);
        //将对象基本类型的实例数据,写入底层的字节缓冲流
        bout.write(primVals, 0, primDataSize, false);
		
		//获取类对应的引用类型的字段对象
        ObjectStreamField[] fields = desc.getFields(false);
        Object[] objVals = new Object[desc.getNumObjFields()];
        int numPrimFields = fields.length - objVals.length;
        //对象的字段对象值
        desc.getObjFieldValues(obj, objVals);
        //将对应的对象类型字段保存到objVals数组中
        for (int i = 0; i < objVals.length; i++) {
            if (extendedDebugInfo) {
                debugInfoStack.push(
                    "field (class \"" + desc.getName() + "\", name: \"" +
                    fields[numPrimFields + i].getName() + "\", type: \"" +
                    fields[numPrimFields + i].getType() + "\")");
            }
            try {
            	//对序列化对象中引用类型的字段,调用writeObject0()写入对应的数据
                writeObject0(objVals[i],
                             fields[numPrimFields + i].isUnshared());
            } finally {
                if (extendedDebugInfo) {
                    debugInfoStack.pop();
                }
            }
        }
    }

在执行完上述方法之后,程序将会回到writeNonProxyDesc()方法中,并且在writeClassDesc()中会将对象对应的类的父类信息进行写入:

private void writeNonProxyDesc(ObjectStreamClass desc, boolean unshared)
        throws IOException
    {
    	...
        	//一般会执行此方法,将类描述写入
            writeClassDescriptor(desc);
        }

        Class<?> cl = desc.forClass();
        bout.setBlockDataMode(true);
        if (cl != null && isCustomSubclass()) {
            ReflectUtil.checkPackageAccess(cl);
        }
        //根据cl的类型进行处理
        annotateClass(cl);
        bout.setBlockDataMode(false);
        //表示对一个object的描述块的结束
        bout.writeByte(TC_ENDBLOCKDATA);//此处会将对象相应的类的父类写入
        writeClassDesc(desc.getSuperDesc(), false);
    }

至此,我们可以知道,整个序列化的过程其实就是一个递归写入的过程。

将上面的过程进行简化,可以总结为这幅图:
在这里插入图片描述

transient关键字

在有些时候,我们并不想将一些敏感信息序列化,如密码等,这个时候就需要transient关键字来标注属性为非序列化属性。

transient关键字的使用

将上面的NY类中的name属性稍作修改:

private transient String name;

当我们再次运行SerializeNY类中的main()方法时,运行结果如下:

NY 对象序列化成功!
NY 对象反序列化成功
NY{name='null', blogName='NYfor2020'}

我们可以看到,name属性为null,说明反序列化时根本没有从文件中获取到信息。

transient关键字的特点

  • 变量一旦被transient修饰,则不再是对象持久化的一部分了,而且变量内容在反序列化时也不能获得。

  • transient关键字只能修饰变量,而不能修饰方法和类,而且本地变量是不能被transient修饰的,如果变量是类变量,则需要该类也实现Serializable接口。

  • 一个静态变量不管是否被transient修饰,都不会被序列化。

    关于这一点,可能会有读者感到疑惑。举个栗子,如果用static修饰NY类中的name:

    private static String name;
    

    运行SerializeNY类中的main程序,可以看到运行结果:

    NY 对象序列化成功!
    NY 对象反序列化成功
    NY{name='NY', blogName='NYfor2020'}
    

    嘶…这是翻车了吗?并没有,因为这里出现的name值是当前JVM中对应的static变量值,这个值是JVM中的而不是反序列化得出的

    不信?我们来改变一下SerializeNY类中的**serializeNY()**函数:

        private static void serializeNY() throws IOException {
            NY ny = new NY();
            ny.setName("NY");
            ny.setBlogName("NYfor2020");
            ny.setTest("12");
            ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(new File("D:\\serialable.txt")));
            oos.writeObject(ny);
            System.out.println("NY 对象序列化成功!");
            System.out.println(ny.toString());
            oos.close();
            ny.setName("hey, NY");
        }
    
    

    笔者在NY对象被序列化之后,改变了NY对象的name值。运行结果为:

    NY 对象序列化成功!
    NY{name='NY', blogName='NYfor2020'}
    NY 对象反序列化成功
    NY{name='hey, NY', blogName='NYfor2020'}
    

transient修饰的变量真的就不能被序列化了吗?

举个栗子:

public class ExternalizableTest implements Externalizable {

    private transient String content = "即使被transient修饰,我也会序列化";

    @Override
    public void writeExternal(ObjectOutput out) throws IOException {
        out.writeObject(content);
    }

    @Override
    public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
        content = (String)in.readObject();
    }

    public static void main(String[] args) throws IOException, ClassNotFoundException {
        ExternalizableTest et = new ExternalizableTest();
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(new File("D:\\externalizable.txt")));
        oos.writeObject(et);
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(new File("D:\\externalizable.txt")));
        et = (ExternalizableTest) ois.readObject();
        System.out.println(et.content);
        oos.close();
        ois.close();
    }
}

运行结果为:

即使被transient修饰,我也会序列化

我们可以看到,content变量在被transient修饰的情况下,还是被序列化了。因为在Java中,对象序列化可以通过实现两种接口来实现:

  • 如果实现的是Serializable接口,则所有信息(不包括被static、transient修饰的变量信息)的序列化将自动进行。
  • 如果实现的是Externalizable接口,则不会进行自动序列化,需要开发者在writeExternal()方法中手工指定需要序列化的变量,与是否被transient修饰无关。

序列化注意事项

  • 序列化对象必须实现序列化接口Serializable。
  • 序列化对象中的属性如果也有对象的话,其对象需要实现序列化接口。
  • 类的对象序列化后,类的序列号不能轻易更改,否则反序列化会失败。
  • 类的对象序列化后,类的属性增加或删除不会影响序列化,只是值会丢失。
  • 如果父类序列化,子类会继承父类的序列化;如果父类没序列化,子类序列化了,子类中的属性能正常序列化,但父类的属性会丢失,不能序列化。
  • 用Java序列化的二进制字节数据只能由Java反序列化,如果要转换成其他语言反序列化,则需要先转换成Json/XML通用格式的数据。
  • 如果某个字段不想序列化,在该字段前加上transient关键字即可。(咳咳,下一篇就是写这个了,敬请关注~)

结语

第一次写关于JDK实现原理的文章,还是觉得有点难度的,但是这对于源码分析能力还是有点提升的。在这个过程中最好多打断点,多调试。

如果本文对你的学习有帮助,请给一个赞吧,这会是我最大的动力~

参考资料:

序列化和反序列化

序列化和反序列化的详解

Java 之 Serializable 序列化和反序列化的概念,作用的通俗易懂的解释

Java中序列化实现原理研究

关于Java序列化你应该知道的一切

本文已授权发布在微信公众号:Java后端。

posted @ 2020-03-17 17:47  NYfor2018  阅读(342)  评论(0编辑  收藏  举报