• 问题

    在第39条里介绍到了为了让程序更加安全可靠,需要针对可变域在构造器和访问方法中进行保护性拷贝,例如下面的代码:

    public final static class Period{
        private final Date start;
        private final Date end;
        public Period(Date start, Date end){
            this.start = new Date(start.getTime());
            this.end = new Date(end.getTime());
            if(this.start.compareTo(this.end)>0){
                throw new IllegalArgumentException(start + "after" +end);
            }
        }
        public Date getStart() {
            return new Date(start.getTime());
        }
        public Date getEnd() {
            return new Date(end.getTime());
        }
    
      }
    

    但是如果将这个类进行序列化的时候,就可能这个类会出现不满足start和end的约束关系了,那么,应该怎样保证在序列化的时候也能保障对象的关键约束关系?

  • 答案

    除了构造器构造对象外,反序列化也是一种构造对象的方式,因此,也需要在构造对象的时候进行参数有效性检查以及保护性拷贝。所以在readObject方法也需要确保Period的关键约束不变以及保持它的不可变性:

  private void readObject(ObjectInputStream s)
  throws IOException, ClassNotFoundException {
      s.defaultReadObject();
      // Defensively copy our mutable components
      start = new Date(start.getTime());
      end = new Date(end.getTime());
      // Check that our invariants are satisfied
      if (start.compareTo(end) > 0)
          throw new InvalidObjectException(start +" after "+ end);
      }
  }

并且需要注意的是,保护性拷贝在参数有效性检查的前面,并且不能使用clone方法进行拷贝对象。

  • 结论

    总而言之,每当你编写readObject方法的时候,都要这样想:你正在编写一个公有的构造器,无论给它传递什么样的字节流,它都必须产生一个有效的实例。下面这些经验,有助于编写出更加健壮的readObject方法:

    1. 对于对象引用域必须保持为私有的,要保护性地拷贝这些域中的每个对象。不可变类的可变组件就属于这一类别;
    2. 对于任何约束条件,如果检查失败,则抛出一个InvalidObjectException异常。这些检查动作应该跟在所有的保护性拷贝之后;
    3. 如果整个对象图在被反序列化之后必须进行验证,就应该使用ObjectInputValidation接口;
    4. readObject方法中都不要调用可覆盖的方法,无论是间接的方式还是直接的方式

results matching ""

    No results matching ""