【问题标题】:Lazily Initialization of a Collection集合的延迟初始化
【发布时间】:2009-04-20 17:44:31
【问题描述】:

延迟初始化集合的最佳方法是什么,我专门研究 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;
}

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

延迟初始化的目的纯粹是为了性能。

我很想知道其他人为此提出的方法是什么。有什么想法吗?

【问题讨论】:

    标签: java collections


    【解决方案1】:

    我将惰性实例化放入给定函数的 getter 中。通常我会懒惰地实例化一个列表,以尽可能避免 DB 命中。示例:

    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);
    }
    

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

    【讨论】:

    • getApples() 在这里应该是final。至少,应该清楚地记录 getApples() 的非标准“自用”应该给子类一个正确的机会。
    • @erickson 如果 getApples() 是最终结果,那意味着您不能调用 getApples().add(apple),对吗?还是我在使用 final 时错了?
    • 不,这意味着子类无法覆盖该方法。 (将 Collections.unmodifiableList 用于不可修改的列表。)
    • 啊...那么是的,那是有道理的。更新我的例子。
    • 对了,如果子类重写了getApples(),它必须确保调用super.getApples(),否则私有成员变量“apples”永远无法初始化。
    【解决方案2】:

    为了在多线程环境中安全地延迟初始化成员,您需要一些并发机制来使初始化原子化并且对其他线程可见。每次访问延迟初始化的成员时,都会在初始化期间支付此费用。这种持续的费用可能会严重影响性能。分析延迟初始化的效果非常重要。正确的选择会因应用而异。

    【讨论】:

      【解决方案3】:

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

      这就是我会做的,但我会将方法设为私有/受保护

      protected Collection<Apple> getApples() {
        if (apples == null) {
          apples = new LinkedList<Apple>();
        }
        return apples;
      }
      

      【讨论】:

      • 在某些情况下(例如,当 JSF 应用程序中的前端需要集合时)使这个受保护可能不是一个选项。
      • 我会将其设为私有(受保护是邪恶的)。
      【解决方案4】:

      对于 add 的情况,我会说在你的构造函数中初始化它,至于不经意间直接暴露集合,你可以考虑:

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

      防止人们使用 getter 做更多的事情。

      【讨论】:

        【解决方案5】:

        第二个选项还可以:

        初始化成一个方法并从add/delete/update调用它

         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】:

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

          首先我会添加这个。注意它是类私有的

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

          现在你的 addApples 看起来像这样。

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

          以及在您的班级内部使用该集合的任何其他内容。

          【讨论】:

          • myApples() 应该被称为 getApples()
          • 我喜欢区分私有属性和公共属性。所以五年后,我可以阅读该部分并知道它访问了私有财产。约定就是约定。不要盲目跟风。
          【解决方案7】:

          我会在 setter 中保留惰性初始化。如果支持集合为空,getter 可以简单地返回一个空集合。

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

          以这种方式,如果在调用 getter 而没有调用 setter 的情况下,则永远不会实例化支持集合。

          【讨论】:

            【解决方案8】:

            把它放在一个同步块中,或者通过其他一些机制(例如 compareAndSet、并发容器等)使其成为线程安全的:

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

            因为就目前而言,如果多个线程同时进入该 if 块,您将破坏苹果。

            【讨论】:

              猜你喜欢
              • 2020-05-10
              • 1970-01-01
              • 1970-01-01
              • 2014-12-19
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多