"编程到接口"的最佳实践是否适用于局部变量?

Mat*_*att 8 java generics collections interface

我被告知对局部变量的接口进行编程是没用的,不应该这样做,因为它只会伤害性能并且没有任何好处.

public void foo() {
    ArrayList<Integer> numbers = new ArrayList<Integer>();
    // do list-y stuff with numbers
}
Run Code Online (Sandbox Code Playgroud)

代替

public void foo() {
    List<Integer> numbers = new ArrayList<Integer>();
    // do list-y stuff with numbers
}
Run Code Online (Sandbox Code Playgroud)

我觉得性能命中率可以忽略不计,但可以肯定的是,使用ArrayList的List语义并没有太大的收获.是否有充分理由采取这种或那种方式?

Tim*_*ote 14

以这堂课为例:

public class Tmp {
    public static void main(String[] args) {
        ArrayList<Integer> numbers = new ArrayList<Integer>();
        numbers.add(1);
    }
}
Run Code Online (Sandbox Code Playgroud)

它归结为:

Compiled from "Tmp.java"
public class Tmp extends java.lang.Object{
public Tmp();
  Code:
   0:   aload_0
   1:   invokespecial   #1; //Method java/lang/Object."<init>":()V
   4:   return

public static void main(java.lang.String[]);
  Code:
   0:   new #2; //class java/util/ArrayList
   3:   dup
   4:   invokespecial   #3; //Method java/util/ArrayList."<init>":()V
   7:   astore_1
   8:   aload_1
   9:   iconst_1
   10:  invokestatic    #4; //Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
   13:  invokevirtual   #5; //Method java/util/ArrayList.add:(Ljava/lang/Object;)Z
   16:  pop
   17:  return

}
Run Code Online (Sandbox Code Playgroud)

虽然这堂课:

public class Tmp {
    public static void main(String[] args) {
        List<Integer> numbers = new ArrayList<Integer>();
        numbers.add(1);
    }
}
Run Code Online (Sandbox Code Playgroud)

编译到这个:

Compiled from "Tmp.java"
public class Tmp extends java.lang.Object{
public Tmp();
  Code:
   0:   aload_0
   1:   invokespecial   #1; //Method java/lang/Object."<init>":()V
   4:   return

public static void main(java.lang.String[]);
  Code:
   0:   new #2; //class java/util/ArrayList
   3:   dup
   4:   invokespecial   #3; //Method java/util/ArrayList."<init>":()V
   7:   astore_1
   8:   aload_1
   9:   iconst_1
   10:  invokestatic    #4; //Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
   13:  invokeinterface #5,  2; //InterfaceMethod java/util/List.add:(Ljava/lang/Object;)Z
   18:  pop
   19:  return

}
Run Code Online (Sandbox Code Playgroud)

你会看到唯一的区别是第一个(使用an ArrayList)进行调用invokevirtual而另一个(使用List用途)invokeinterface. invokeinterface实际上是一个慢于invokevirtual(〜38%根据这个家伙)的头发.这显然是由于JVM在搜索具体类的虚方法表与接口的方法表时可以进行的优化.所以你说的其实是真的.接口调用比具体的类调用速度较慢.

但是,你必须考虑我们正在谈论的速度.对于100,000,000次调用,实际差异为.03秒.所以你必须进行大量的调用才能真正显着降低速度.

另一方面,正如@ChinBoon指出的那样,编码到接口使得使用代码的人更容易,特别是如果你的代码返回某种类型List.因此,在绝大多数情况下,编码的容易程度远远超过相对性能费用.


在阅读了@ MattQuigley的评论并在驱动器回家后对其进行了思考后添加了

这一切意味着这不是你应该担心的太多.任何表现增益或惩罚都可能非常小.

请记住,使用返回类型的接口和方法参数是一个非常好的主意.这允许您和使用您的代码的任何人使用List最适合他们需求的任何实现.我的例子还表明,如果你碰巧使用一种返回的方法List,99%的时间,你最好不要将它转换为具体的类,以获得性能提升.施法所需的时间可能超过表现的增益.

话虽这么说,这个例子也表明,对于一个局部变量,你确实最好使用一个具体的类而不是一个接口.如果你使用的唯一方法是on方法List,那么你可以切换出没有副作用的实现类.此外,如果需要,您可以访问特定于实现的方法.此外,还有一个轻微的性能提升.

TL;博士

始终在方法上使用返回类型和参数的接口.对局部变量使用具体类是个好主意.它提供了较小的性能提升,如果您使用的唯一方法是在接口上,则切换实现没有成本.最后,你不要太担心它.(除了返回类型和参数之外.这很重要.)


aio*_*obe 12

"编程到接口"的最佳实践是否适用于局部变量?

尽管这种变量的范围比一个成员变量的范围较小,大部分 利益编码对接口,当涉及到成员变量也适用于本地变量.

除非您需要特定于ArrayList的方法,否则请转到List.

我觉得性能命中率可以忽略不计,但可以肯定的是,使用ArrayList的List语义并没有太大的收获

我从未听说过调用接口方法比调用类类型的方法要慢.

使用ArrayList而不是List静态类型听起来像是对我的过早优化.像往常一样,在您对程序进行整体分析之前,优先考虑可读性和可维护性.

  • @aioobe我觉得你误解了.我知道为什么对一般的接口进行编码是个好主意.我所说的是你在答案中写道,出于同样的原因它也更好地作为局部变量而没有解释原因.我相信人们认为没有理解_why_会更好,只因为它在其他地方完成了.一个是不保护自己不会在未来改变实现,因为它是一个局部变量并且很容易改变.由于它是一个局部变量,我没有看到"更灵活"的论点.它不能换掉测试 - 它是本地的! (4认同)
  • @aioobe你说它更好,因为它更灵活,没有解释为什么使用List over ArrayList更灵活.小心解释一下?记住,我们这里只讨论局部变量. (2认同)

Mat*_*ley 5

TLDR:方法调用在接口/抽象类慢,几乎没有任何障碍,在稍后的时间点,这使得大多数参数的模拟点改变局部变量的实现.

这里有很多答案盲目地说将局部变量声明为接口而不理解为什么.首先,该方法调用慢-约3倍,因为我在测试中召回.第add一个代码块中的方法是第二个代码块的3倍.您可以通过一个测量for循环中经过时间的简单测试来自行测试.

List<Integer> numbers = new ArrayList<Integer>();
numbers.add(1); // slower

ArrayList<Integer> numbers = new ArrayList<Integer>();
numbers.add(1); // faster
Run Code Online (Sandbox Code Playgroud)

其次,问题是关于局部变量,而不是一般界面的面向对象设计原则.有一个好的OO原因,公共类方法将返回接口或抽象类(List)而不是实现(ArrayList).你可以在其他地方看到这些原因,但这个问题与此无关 - 它是关于局部变量的.99.999%的时间你永远不会将该局部变量的实现从ArrayLista 更改为a Vector.但是,在实际执行的.001%的情况下,这样做没有任何障碍 - 您只需将单词Vector复制并粘贴到单词ArrayList上即可.每个人似乎都认为这种改变很难.不是.

第三,a LinkedList与a 不是一回事ArrayList.您选择一个或另一个是有原因的(访问/追加/插入/删除所需的时间).将其声明为ArrayList是一种肯定,可以让您知道此局部变量用于快速随机访问.它是过早优化的论点是愚蠢的 - 只要它被实例化,它就会以某种方式进行优化.