【问题标题】:Why should the interface for a Java class be preferred?为什么首选 Java 类的接口?
【发布时间】:2010-09-13 22:21:35
【问题描述】:

PMD 会报告以下违规行为:

ArrayList<Object> list = new ArrayList<Object>();

违规是“避免使用像 'ArrayList' 这样的实现类型;改用接口”。

以下行将纠正违规行为:

List<Object> list = new ArrayList<Object>();

为什么要使用带有List 的后者而不是ArrayList

【问题讨论】:

标签: java collections interface


【解决方案1】:

在具体类型上使用接口是良好封装和松散耦合代码的关键。

在编写自己的 API 时遵循这种做法甚至是一个好主意。如果这样做,您稍后会发现向代码中添加单元测试(使用 Mocking 技术)以及在将来需要时更改底层实现会更容易。

这里有一个关于这个主题的good article

希望对你有帮助!

【讨论】:

  • 那么使用“Collection”而不是“List”怎么样,所以我们在抽象上更进一步......?
【解决方案2】:

这是首选,因为您将代码与列表的实现分离。使用该接口,您可以轻松地将实现(在本例中为 ArrayList)更改为另一个列表实现,而无需更改任何其余代码,只要它仅使用 List 中定义的方法即可。

【讨论】:

    【解决方案3】:

    总的来说,我同意将接口与实现分离是一件好事,并且会使您的代码更易于维护。

    但是,您必须考虑一些例外情况。通过接口访问对象会增加一个额外的间接层,这会使您的代码变慢。

    出于兴趣,我进行了一个实验,该实验生成了对 100 万长度的 ArrayList 的 100 亿次顺序访问。在我的 2.4Ghz MacBook 上,通过 List 接口访问 ArrayList 平均需要 2.10 秒,而在声明 ArrayList 类型时平均需要 1.67 秒。

    如果您正在处理大型列表、深入内部循环或经常调用的函数,那么这是需要考虑的事情。

    【讨论】:

    • @Owen:+5 有见地的回复:性能差异......非常出乎意料
    • 然而,这个答案表明开销可能非常小:stackoverflow.com/questions/890687/…
    • 哇! 100 亿次访问需要 0.5 秒,即 1 次接口访问比类访问慢半纳秒!这当然是永远不要使用接口的理由。
    【解决方案4】:

    ArrayList 和 LinkedList 是 List 的两种实现,List 是项的有序集合。从逻辑上讲,使用 ArrayList 或 LinkedList 并不重要,因此您不应将类型限制为那样。

    这与说,Collection 和 List 形成对比,它们是不同的东西(List 意味着排序,Collection 不是)。

    【讨论】:

      【解决方案5】:

      为什么要用List而不是ArrayList呢?

      这是一个很好的做法:编程接口而不是实现

      通过将ArrayList 替换为List,您可以在以后根据您的业务用例更改List 的实现,如下所示。

      List<Object> list = new  LinkedList<Object>(); 
      /* Doubly-linked list implementation of the List and Deque interfaces. 
       Implements all optional list operations, and permits all elements (including null).*/
      

      List<Object> list = new  CopyOnWriteArrayList<Object>(); 
      /* A thread-safe variant of ArrayList in which all mutative operations
       (add, set, and so on) are implemented by making a fresh copy of the underlying array.*/
      

      List<Object> list = new  Stack<Object>(); 
      
      /* The Stack class represents a last-in-first-out (LIFO) stack of objects.*/
      

      其他一些List 的具体实现。

      List 接口定义了合约,List 的具体实现可以改变。这样,接口和实现是松耦合的。

      相关的 SE 问题:

      What does it mean to "program to an interface"?

      【讨论】:

      • 那么使用“Collection”而不是“List”怎么样,例如可以使用List或Set
      【解决方案6】:

      即使对于局部变量,在具体类上使用接口也会有所帮助。您最终可能会调用接口之外的方法,然后在必要时很难更改 List 的实现。 此外,最好在声明中使用最不具体的类或接口。如果元素顺序无关紧要,请使用 Collection 而不是 List。这为您的代码提供了最大的灵活性。

      【讨论】:

        【解决方案7】:

        您的类/接口的属性应该通过接口公开,因为它为您的类提供了使用的行为契约,而与实现无关。

        不过……

        在局部变量声明中,这样做没有什么意义:

        public void someMethod() {
        List theList = new ArrayList();
        //do stuff with the list
        }
        

        如果它是一个局部变量,只需使用类型。它仍然可以隐式向上转换为其适当的接口,并且您的方法应该希望接受接口类型作为其参数,但是对于局部变量,将实现类型用作容器是完全有意义的,以防万一您确实需要实现-具体功能。

        【讨论】:

          【解决方案8】:

          一般而言,对于您的代码行,打扰接口是没有意义的。但是,如果我们谈论 API,那是有充分理由的。我上了小班

          class Counter {
              static int sizeOf(List<?> items) {
                  return items.size();
              }
          }
          

          在这种情况下,需要使用接口。因为我想计算所有可能的实现的大小,包括我自己的自定义。 class MyList extends AbstractList&lt;String&gt;...

          【讨论】:

          • 如果你对像 ArrayList 这样的实现进行编码,那么即使你可以用接口方法做同样的事情,你也很有可能使用实现方法。这会将您的代码绑定到实现 - 使以后在实现之间切换变得更加困难。
          【解决方案9】:

          界面向最终用户公开。一个类可以实现多个接口。暴露于特定接口的用户可以访问在该特定接口中定义的某些特定行为。

          一个接口也有多个实现。基于场景的系统将与不同的场景(接口的实现)一起工作。

          如果您需要更多解释,请告诉我。

          【讨论】:

            【解决方案10】:

            接口在调试器视图中的表示通常比具体类更好。

            【讨论】:

              猜你喜欢
              • 2016-10-18
              • 1970-01-01
              • 2012-11-06
              • 2017-09-10
              • 1970-01-01
              • 1970-01-01
              • 2011-03-14
              相关资源
              最近更新 更多