java集合Serializable

Serializable 类的源码如下

public interface Serializable {
}

源码的注释

类的可序列化性由实现 java.io.Serializable 接口的类启用。
未实现此接口的类将不会对其任何状态进行序列化或反序列化。
可序列化类的所有子类型本身都是可序列化的。
序列化接口没有方法或字段,仅用于识别可序列化的语义。

为了允许序列化不可序列化类的子类型,子类型可以负责保存和恢复超类型的公共、受保护和(如果可访问)包字段的状态。
仅当它扩展的类具有可访问的无参数构造函数来初始化类的状态时,子类型才可以承担此责任。
如果不是这种情况,则声明类 Serializable 是错误的。将在运行时检测到错误。

在反序列化过程中,不可序列化类的字段将使用类的公共或受保护的无参数构造函数进行初始化。可序列化的子类必须可以访问无参数构造函数。可序列化子类的字段将从流中恢复。

在遍历图时,可能会遇到不支持 Serializable 接口的对象。
在这种情况下,NotSerializableException 将被抛出,并将识别不可序列化对象的类。

在序列化和反序列化过程中需要特殊处理的类必须实现具有这些确切签名的特殊方法:

private void writeObject(java.io.ObjectOutputStream out)  throws IOException
private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException;
private void readObjectNoData() throws ObjectStreamException;

writeObject 方法负责为其特定类写入对象的状态,以便相应的 readObject 方法可以恢复它。
可以通过调用 out.defaultWriteObject 来调用保存 Object 字段的默认机制。
该方法不需要关注属于其超类或子类的状态。
通过使用 writeObject 方法或使用 DataOutput 支持的原始数据类型的方法将各个字段写入 ObjectOutputStream 来保存状态。

readObject 方法负责从流中读取并恢复类字段。
它可以调用 in.defaultReadObject 来调用用于恢复对象的非静态和非瞬态字段的默认机制。 defaultReadObject 方法使用流中的信息将保存在流中的对象的字段分配给当前对象中相应命名的字段。
这可以处理类已经演变为添加新字段的情况。
该方法不需要关注属于其超类或子类的状态。
通过使用 writeObject 方法或使用 DataOutput 支持的原始数据类型的方法将各个字段写入 ObjectOutputStream 来保存状态。

如果序列化流未将给定类列为被反序列化对象的超类,readObjectNoData 方法负责为其特定类初始化对象的状态。
这可能发生在接收方使用与发送方不同版本的反序列化实例类的情况下,并且接收方的版本扩展了发送方版本未扩展的类。
如果序列化流已被篡改,也可能发生这种情况;因此,尽管存在“敌对”或不完整的源流,但 readObjectNoData 对于正确初始化反序列化对象很有用。

在将对象写入流时需要指定要使用的替代对象的可序列化类应使用精确签名实现此特殊方法:

 ANY-ACCESS-MODIFIER Object writeReplace() throws ObjectStreamException;

如果该方法存在并且可以从被序列化对象的类中定义的方法访问,则该 writeReplace 方法由序列化调用。
因此,该方法可以具有私有、受保护和包私有访问
对该方法的子类访问遵循 java 可访问性规则。

十一

当从流中读取实例时需要指定替换的类应该使用精确的签名实现这个特殊方法。

   ANY-ACCESS-MODIFIER Object readResolve() throws ObjectStreamException;

十二

此 readResolve 方法遵循与 writeReplace 相同的调用规则和可访问性规则。

十三

序列化运行时与每个可序列化类关联一个版本号,称为 serialVersionUID,在反序列化期间使用该版本号来验证序列化对象的发送方和接收方是否已加载与序列化兼容的该对象的类。
如果接收者为对象加载了一个类,该对象的 serialVersionUID 与相应发送者的类不同,则反序列化将导致 InvalidClassException。
可序列化的类可以通过声明一个名为“serialVersionUID”的字段来显式声明自己的serialVersionUID,该字段必须是静态的、最终的和long类型

 ANY-ACCESS-MODIFIER static final long serialVersionUID = 42L;

十四

如果可序列化类没有显式声明 serialVersionUID,则序列化运行时将根据类的各个方面为该类计算默认的 serialVersionUID 值,如 Java™ 对象序列化规范中所述。
但是,强烈建议所有可序列化的类都显式声明 serialVersionUID 值,因为默认的 serialVersionUID 计算对类细节高度敏感,这些细节可能因编译器实现而异,因此可能在反序列化期间导致意外的 InvalidClassExceptions。
因此,为了保证在不同的 java 编译器实现中具有一致的 serialVersionUID 值,可序列化的类必须声明一个显式的 serialVersionUID 值。
还强烈建议显式 serialVersionUID 声明尽可能使用private 修饰符 ,因为此类声明仅适用于立即声明的类——serialVersionUID 字段不能用作继承成员。
数组类不能显式声明 serialVersionUID,因此它们始终具有默认计算值,但数组类无需匹配 serialVersionUID 值。

十五 注释部分参阅类

public class ObjectOutputStream
    extends OutputStream implements ObjectOutput, ObjectStreamConstants
{}

public class ObjectInputStream
    extends InputStream implements ObjectInput, ObjectStreamConstants
{}

public interface ObjectOutput extends DataOutput, AutoCloseable {}

public interface ObjectInput extends DataInput, AutoCloseable {}

public interface Externalizable extends java.io.Serializable {}

那么什么是序列化呢?

序列化是将对象状态转换为可保持或传输的格式的过程。与序列化相对的是反序列化,它将流转换为对象。这两个过程结合起来,可以轻松地存储和传输数据。

如何理解输入流InputStream和OutputStream

我的个人理解是把stream换成内存,输入流代表进入内存的数据,输出流代表把内存中的数据输出。
举个栗子,从文件中读取内容到内存中,数据进入内存,file -> 内存,用inputStream。
将内存数据写入文件,数据从内存中出来,内存->file,用outputStream。

我们来试图序列化一个对象并写入到文件中

类A实现了序列化接口,类B未实现序列化接口,使用类A可以顺利的将对象写入以及读取。,

public class UseSerializable {

    public static void main(String[] args) throws Exception {
        UseSerializable.writeFile();
        UseSerializable.readFile();
    }

    public static void readFile() throws FileNotFoundException, IOException, ClassNotFoundException {
        File file = new File("d:\\file01.txt");
        FileInputStream fileInputStream = new FileInputStream(file);
        ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream);
        A a = (A) objectInputStream.readObject();
        System.out.println(a.toString());
        objectInputStream.close();
        fileInputStream.close();
    }

    public static void writeFile() throws FileNotFoundException, IOException {
        File file = new File("d:\\file01.txt");
        FileOutputStream fileOutputStream = new FileOutputStream(file);
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream);
        A a = new A(1, 2, 3);
        // B b = new B(1, 2);
        objectOutputStream.writeObject(a);
        objectOutputStream.flush();
        System.out.println("写入成功");
        objectOutputStream.close();
        fileOutputStream.close();
    }
}

class A implements Serializable {
    private static final long serialVersionUID = 1L;

    private int a;
    private int b;
    private transient int c;

    public A(int a, int b, int c) {
        this.a = a;
        this.b = b;
        this.c = c;
    }

    @Override
    public String toString() {
        return "A{" +
                "a=" + a +
                ", b=" + b +
                ", c=" + c +
                '}';
    }
}

class B {
    private int a;
    private int b;

    public B(int a, int b) {
        this.a = a;
        this.b = b;
    }
}

当我们试图序列化B类的时候,发生了报错 NotSerializableException
在这里插入图片描述
ObjectOutputStream类部分源码如下,可以看到,如果没有实现Serializable接口的话,在将对象写入流的过程也就是序列化的过程中会报错。

ObjectOutputStream类writeObject0方法部分源码如下

// remaining cases
            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) {
                writeOrdinaryObject(obj, desc, unshared);
            } else {
                if (extendedDebugInfo) {
                    throw new NotSerializableException(
                        cl.getName() + "\n" + debugInfoStack.toString());
                } else {
                    throw new NotSerializableException(cl.getName());
                }
            }

模拟serialVersionUID不一致的问题

首先以类A的uid=1,写入文件,然后将类A的UID改成2,执行读取文件。

在这里插入图片描述
java.io.ObjectStreamClass.initNonProxy 类中部分源码如下,可以看到比较源UID和现UID的代码,如果不一致则会抛出InvalidClassException异常

if (model.serializable == osc.serializable &&
                    !cl.isArray() &&
                    suid != osc.getSerialVersionUID()) {
                throw new InvalidClassException(osc.name,
                        "local class incompatible: " +
                                "stream classdesc serialVersionUID = " + suid +
                                ", local class serialVersionUID = " +
                                osc.getSerialVersionUID());
            }

断点调试,可以知道原先的uid为1,而修改后uid为2,所以报异常。
在这里插入图片描述

ObjectInputStream类中readObject方法调用过程梳理

待完善

transient关键字

对c属性加该关键字后,测试反序列化。
在这里插入图片描述
可以看到c属性并没有获取到值3,
在这里插入图片描述

探究不同类型的值transient后生成的默认值

 A a = new A(1, (short) 2, 3L, (byte) 4, '5', 6.1f, 7.1D, true, "9");
 
class A implements Serializable {
    private static final long serialVersionUID = 1L;

    private transient int a;
    private transient short b;
    private transient long c;
    private transient byte e;
    private transient char f;
    private transient float g;
    private transient double h;
    private transient boolean i;
    private transient String j;

    public A(int a, short b, long c, byte e, char f, float g, double h, boolean i, String j) {
        this.a = a;
        this.b = b;
        this.c = c;
        this.e = e;
        this.f = f;
        this.g = g;
        this.h = h;
        this.i = i;
        this.j = j;
    }

    @Override
    public String toString() {
        return "A{" +
                "a=" + a +
                ", b=" + b +
                ", c=" + c +
                ", e=" + e +
                ", f=" + f +
                ", g=" + g +
                ", h=" + h +
                ", i=" + i +
                ", j='" + j + '\'' +
                '}';
    }
}

A{a=0, b=0, c=0, e=0, f= , g=0.0, h=0.0, i=false, j='null'}

类中不同修饰符属性的序列化问题

定义类B,有四种修饰符变量,以及静态变量b1

class B implements Serializable {

    private static final long serialVersionUID = 5734311923841349094L;

    private int a1;
    protected int a2;
    int a3;
    public int a4;

    static int b1;

    public B(int a1, int a2, int a3, int a4, int b1) {
        this.a1 = a1;
        this.a2 = a2;
        this.a3 = a3;
        this.a4 = a4;
        B.b1 = b1;
    }

    @Override
    public String toString() {
        return "B{" +
                "a1=" + a1 +
                ", a2=" + a2 +
                ", a3=" + a3 +
                ", a4=" + a4 +
                ", b1=" + b1 +
                '}';
    }
}

执行后发现,均可以全部写入,证明反序列化和访问修饰符以及静态变量无关。
在这里插入图片描述

序列化中的继承问题

1 父类实现序列化,子类的情况

根据源码注释第一部分第三条可知,

可序列化类的所有子类型本身都是可序列化的。

类B见上面代码段,a1是私有权限,子类中不可见。

class C extends B {

    private static final long serialVersionUID = -3933467757956971835L;

    public C(int a1, int a2, int a3, int a4, int b1) {
        super(a1, a2, a3, a4, b1);
    }

    @Override
    public String toString() {
        return "C{" +
                "a2=" + a2 +
                ", a3=" + a3 +
                ", a4=" + a4 +
                ", b1=" + b1 +
                '}';
    }
}

成功序列化,父类实现了该接口后,子类无需实现接口也能拥有序列化能力。
在这里插入图片描述

2 子类实现了序列化,父类的情况

父类想序列化和反序列化只能实现接口。

子类序列化调用父类情况

定义父类E和子类F,其中F实现了序列化

class E  {

    int a;

    public E(int a) {
        System.out.println("有参构造");
        this.a = a;
    }

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

class F extends E implements Serializable {

    private static final long serialVersionUID = 4056348573025517780L;

    public F(int a) {
        super(a);
    }

    @Override
    public String toString() {
        return "F{" +
                "a=" + a +
                '}';
    }
}

进行子类F的序列化以及反序列化之后输出结果如下。
在这里插入图片描述

将类E实现序列化后发现
在这里插入图片描述
进行子类F的序列化以及反序列化之后输出结果如下。
在这里插入图片描述
这个时候我们将父类中的无参构造去掉
在这里插入图片描述
发现报错信息如下。
在这里插入图片描述
在这里插入图片描述

翻译一下 InvalidClassException 这个异常类的场景发现

  1. 当序列化运行时检测到类的以下问题之一时引发。
  2. 类的串行版本与从流中读取的类描述符的版本不匹配
  3. 该类包含未知数据类型
  4. 该类没有可访问的无参数构造函数

这里可知是第四种,没有可访问的无参构造函数。

at java.io.ObjectStreamClass$ExceptionInfo.newInvalidClassException
在这里插入图片描述

at java.io.ObjectStreamClass.checkDeserialize
在这里插入图片描述
at java.io.ObjectInputStream.readOrdinaryObject
在这里插入图片描述
at java.io.ObjectInputStream.readObject0
在这里插入图片描述
at java.io.ObjectInputStream.readObject
在这里插入图片描述

idea中对于 Uid的设置

建议将第一点设为警告,而第二点设为错误,来规范编程思想和对序列化的理解。
在这里插入图片描述

参考文章

Serializable

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值