代码之家  ›  专栏  ›  技术社区  ›  Jonathan Holloway

延迟初始化集合

  •  2
  • Jonathan Holloway  · 技术社区  · 15 年前

    什么是懒惰地初始化一个集合的最好方法,我特别关注Java。我已经看到一些人决定在修改方法中这样做(这看起来有点糟糕),如下所示:

    public void addApple(final Apple apple) {       
    
        if (this.apples == null) {
            apples = new LinkedList<Apple>();
        }
        this.apples.add(apple);
    }
    

    您可以将初始化重构为一个方法,并从添加/删除/更新等调用它…但看起来有点不舒服。人们还通过以下方式来展示收藏品本身,这一事实往往使情况更加复杂:

    public Collection<Apple> getApples() {
        return apples;
    }
    

    这会破坏封装并导致人们直接访问集合。

    延迟初始化的目的纯粹是与性能相关的。

    我很好奇其他人提出了什么方法来解决这个问题。有什么想法吗?

    8 回复  |  直到 15 年前
        1
  •  3
  •   Zack Marrapese    15 年前

    我将懒惰的实例化放入给定函数的getter中。通常我会延迟地实例化一个列表,以避免数据库命中(如果可能的话)。例子:

    public final Collection<Apple> getApples() {
        if (apples == null) {
            // findApples would call the DB, or whatever it needs to do
            apples = findApples();
        return apples;
    }
    
    public void addApple(final Apple apple) {       
        //we are assured that getApples() won't return 
        //null since it's lazily instantiated in the getter
        getApples().add(apple);
    }
    

    这种方法意味着其他函数(比如removeaples())也不需要担心实例化。他们也会调用getapples()。

        2
  •  3
  •   erickson    15 年前

    为了安全地在多线程环境中惰性地初始化成员,您需要一些并发机制来使初始化成为原子的,并对其他线程可见。这项费用在初始化期间支付。 每次访问延迟初始化的成员时。这种持续的支出会严重损害绩效。分析延迟初始化的效果非常重要。根据应用程序的不同,正确的选择会有很大的不同。

        3
  •  1
  •   James Van Boxtel    15 年前

    您可以将初始化重构为一个方法,并从添加/删除/更新中调用它。

    这就是我要做的,但我会使方法私有化/受保护

    protected Collection<Apple> getApples() {
      if (apples == null) {
        apples = new LinkedList<Apple>();
      }
      return apples;
    }
    
        4
  •  1
  •   pjz    15 年前

    对于add case,我会说在构造中初始化它,对于无意中直接公开集合,您可能会考虑:

    public Collection<Apple> getApples() {
        return Collections.unmodifiableCollection(apples);
    }
    

    让人们不要用吸气剂做更多的事。

        5
  •  1
  •   OscarRyz    15 年前

    第二个选项正常:

    初始化到方法中,并从添加/删除/更新调用它

     private Collection<Apple> apples;
     ....
     private final Collection<Apple> getApples() { 
         if( apples == null ) {
              apples = new LinkedList<Apple>();
         }
         return apples;
     }
    
     public final void addApple( Apple a ) { 
          getApples().add( a );
     }
     public final void removeApple( Apple a ) { 
          getApples().remove( a );
     }
    
     public Iterator<Apple> iterator() { 
         return getApples().iterator;
      }
    

    …人们还通过……

    任何惰性初始化策略都无法阻止这种情况。如果不希望底层集合被公开(这是一个非常好的主意),则必须使其私有化,使其成为最终集合(如上面的示例所示),并提供一个迭代器来访问集合的元素,而不是自己传递集合。

    我想你已经有了。

        6
  •  0
  •   RS Conley    15 年前

    首先,您必须决定函数调用的开销是否值得。我猜你担心的是内存占用,而不是原始速度。

    首先我要加上这个。注意,这是班级的私人活动

    private Collection<Apple> myApples() {
      if (this.apples == null) {
          this.apples = new LinkedList<Apple>();
      }
      return this.apples;
    }
    

    现在你的苹果看起来像这样。

    public void addApple(final Apple apple) {       
    
        myApples.add(apple);
    }
    

    以及在类内部使用集合的任何其他内容。

        7
  •  0
  •   Steve Kuo    15 年前

    我将把懒惰的初始化保存在setter中。如果支持集合为空,getter可以简单地返回空集合。

    public Collection<Apple> getApples() {
        return apples==null ? Collections.emptyList() : apples;
    }
    

    这样,如果在不调用setter的情况下调用getter,就永远不会实例化后备集合。

        8
  •  0
  •   Joe W.    15 年前

    将其放入同步块中,或通过其他机制(例如CompareAndSet、并发容器等)使其线程安全:

    if (this.apples == null) {
        apples = new LinkedList<Apple>();
    }
    

    因为就目前的情况而言,如果多个线程同时进入该if块,则会将其击倒。