A股上市公司传智教育(股票代码 003032)旗下技术交流社区北京昌平校区

 找回密码
 加入黑马

QQ登录

只需一步,快速开始

© 西早boy 中级黑马   /  2019-11-23 17:16  /  793 人查看  /  0 人回复  /   0 人收藏 转载请遵从CC协议 禁止商业使用本文

↓↓↓↓↓↓序列化后的对象调用List.of ↓↓↓↓↓↓
*********************ListN<E> 的源码分析*********************


           static final class ListN<E> extends AbstractImmutableList<E>
           implements Serializable {
               ...省略部分代码....

              ListN(E... input) {
                  // copy and check manually to avoid TOCTOU
                   @SuppressWarnings("unchecked")
                   E[] tmp = (E[])new Object[input.length]; // implicit nullcheck of input
                   for (int i = 0; i < input.length; i++) {
                      tmp[i] = Objects.requireNonNull(input[i]);
                   }
                   elements = tmp;
               }

                ...省略部分代码....

               1. 如果一个序列化类(implements Serializable 的类)中含有Object writeReplace()方法,
               那么实际序列化的对象将是作为writeReplace方法返回值的对象,
               而且序列化过程的依据是实际被序列化对象的序列化实现。

               2. "CollSer.IMM_LIST = 1"

               3. 获取一个与List
               private Object writeReplace() {
                   // 调用CollSer
                  return new CollSer(CollSer.IMM_LIST, elements);
               }
           }
           
         

            ↓↓↓ CollSer 的源码分析↓↓↓

           final class CollSer implements Serializable {
           private static final long serialVersionUID = 6309168927139932177L;

                   static final int IMM_LIST = 1;
                   static final int IMM_SET = 2;
                   static final int IMM_MAP = 3;

                   ...省略部分代码....

                   private final int tag;


                   !!!敲黑板,元素不可改变的原因!!!
                   transient解析:
                   当对象被序列化时(写入字节序列到目标文件)时,
                   transient阻止实例中那些用此关键字声明的变量持久化
                   ;当对象被反序列化时(从源文件读取字节序列进行重构),
                   这样的实例变量值不会被持久化和恢复。

                   例如
                      当反序列化对象——数据流(例如,文件)可能不存在时,
                   原因是你的对象中存在类型为java.io.InputStream的变量,
                   序列化时这些变量引用的输入流无法被打开。

                   作用
                       是因为array里面不是所有的元素都有数据,
                       因为容量的问题,array里面有一些元素是空的,
                       这种是没有必要序列化的。

                       ArrayList的序列化和反序列化依赖writeObject和readObject方法来实现。
                       可以避免序列化空的元素。
                   private transient Object[] array;

                   CollSer(int t, Object... a) {
                       tag = t;
                       array = a;
                   }

                   //因为对象流的写入内存的过程中,它会通过反射的形式,调用private的writeObject方法
                   private void writeObject(ObjectOutputStream oos) throws IOException {
                       oos.defaultWriteObject();
                       oos.writeInt(array.length);
                       for (int i = 0; i < array.length; i++) {
                          oos.writeObject(array[i]);
                       }
                   }

                   //因为对象流的读取内存的过程中,它会通过反射的形式,调用private的readObject方法
                   private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
                       ois.defaultReadObject();
                       int len = ois.readInt();

                       if (len < 0) {
                           throw new InvalidObjectException("negative length " + len);
                       }

                       SharedSecrets.getJavaObjectInputStreamAccess().checkArray(ois, Object[].class, len);
                       Object[] a = new Object[len];
                       for (int i = 0; i < len; i++) {
                          a[i] = ois.readObject();
                       }

                       array = a;
                   }

                   private Object readResolve() throws ObjectStreamException {
                       ...省略部分代码....
                       1.对象序列化概念:
                       一般来说, 一个类实现了 Serializable接口,
                       我们就可以把它往内存地写再从内存里读出而"组装"成一个跟原来一模一样的对象.

                       2.问题:
                       不过当序列化遇到单例时,这里边就有了个问题:
                       从内存读出而组装的对象破坏了单例的规则.
                       单例是要求一个JVM中只有一个类对象的,
                       而现在通过反序列化,一个新的对象克隆了出来.

                       3.readResolve在序列化中的作用:
                       当把目标对象(通过getInstance方法获得的那个单例对象)序列化后再从内存中读出时,
                       就有一个全新但跟原来一样的对象存在了.那怎么来维护单例模式呢?

                       这就要用到readResolve方法了


                       4.readResolve维护过程
                       JVM从内存中反序列化地"组装"一个新对象时,就会自动调用这个 readResolve方法来返回我们指定好的对象了,
                       单例规则也就得到了保证


                      //使用低阶8位表示“种类”
                      //忽略高阶24位
                       switch (tag & 0xff) {
                          // 当前数组转集合就是这里返回一个新的数组,调用了List.of的单个参数方法
                          //static <E> List<E> of(E e1)
                           case IMM_LIST:
                              return List.of(array);
                           case IMM_SET:
                              return Set.of(array);
                           case IMM_MAP:
                               if (array.length == 0) {
                                  return ImmutableCollections.emptyMap();
                               } else if (array.length == 2) {
                                   return new ImmutableCollections.Map1<>(array[0], array[1]);
                           } else {
                                  return new ImmutableCollections.MapN<>(array);
                           }
                           default:
                           throw new InvalidObjectException(String.format("invalid flags 0x%x", tag));
                       }

                   }

                   ↓↓↓↓现在回调List.of(E)的 "单个参数构造函数"↓↓↓↓注意,上面的是List.of(E......)的 "可变参数构造函数"

                   //又回到List.of(E e),其中e1 是  新数组
                   static <E> List<E> of(E e1) {
                      return new ImmutableCollections.List12<>(e1);
                   }

                   ↓↓↓↓ImmutableCollections.List12<>(e1)详解↓↓↓↓
                   static final class List12<E> extends AbstractImmutableList<E>
                   implements Serializable {

                       @Stable
                       private final E e0;

                       @Stable
                       private final E e1;

                       List12(E e0) {
                           // 校验是否为 null,不是就是报错,是就赋值
                           this.e0 = Objects.requireNonNull(e0);
                           //  对象序列化的时候返回特定CollSer对象
                           this.e1 = null;
                       }

                   ...省略部分代码....

                   private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
                      throw new InvalidObjectException("not serial proxy");
                   }

                   // 在将对象写入流时,需要指定要使用的替代对象的可序列化类应实现具有确切签名的特殊方法:
                   // ANY-ACCESS-MODIFIER Object writeReplace() throws ObjectStreamException;
                   如果该方法存在并且可以通过在正在序列化的对象的类中定义的方法来访问该writeReplace方法,
                   则通过序列化来调用该writeReplace方法。
                   因此,该方法可以具有私有,受保护和包私有访问。 子
                   类访问此方法遵循java可访问性规则。

                   //在这里的作用:
                      虽然writeObject() 试图序列化一个原本的List.of(E......) 对象,
                      但是,调用了List.of()定义的writeObject方法,并且在反序列化过程中,
                   得到了一个List.of(E)对象,并且调用了List.of(E)定义的readObject方法。
                   整个过程与People的writeObject/readObject无关。

                   //所以:  这个方法表示取消序列化 ,直接把对象返回到static <E> List<E> of(E... elements) 这个函数里面
                   private Object writeReplace() {
                       if (e1 == null) {
                          // 上面构造函数设置返回该对象,设置了e1为空
                           return new CollSer(CollSer.IMM_LIST, e0);
                       } else {
                           return new CollSer(CollSer.IMM_LIST, e0, e1);
                       }
                   }
                   }


           }

0 个回复

您需要登录后才可以回帖 登录 | 加入黑马