在java中使用接口或类型进行变量定义?

Use interface or type for variable definition in java?

1
2
3
ArrayList aList = new ArrayList();

List aList = new ArrayList();

这两者有什么区别,哪种更好用,为什么?


列表是接口,而arraylist是该接口的实现。

第二个更好,因为这意味着您可以在以后为列表的另一个实现更改ArrayList,而无需更改应用程序的其余部分。您可能希望这样做是因为性能原因,或者因为您选择/将选择的列表实现行为的其他方面。


两者都是自Java 1.5以来的缩写。

应该是:

1
2
List<String> list = new ArrayList<String>();
// or whatever data type you are using in your list

请阅读Joshua Bloch的有效Java,特别是这两个项目:

  • 23:不要在新代码中使用原始类型(这甚至可以在线获得)
  • 52:通过他们的界面

顺便说一句,如果您使用guava,那么您有一个构造arraylist的工厂方法,这样您就不必重复类型参数:

1
List<String> list = Lists.newArraylist();


List为接口,其中ArrayList为类,该类实现List接口。

我更喜欢第二种形式,这是更一般的形式,即如果您不使用特定于ArrayList的方法,您可以将其类型声明为接口List类型。使用第二种形式,可以更容易地将实现从ArrayList更改为实现List接口的其他类。

编辑:正如许多这样的用户所评论的,这两个表单可以是接受ListArrrayList的任何方法的参数。但当我声明方法时,我更喜欢接口:

1
 void showAll(List <String> sl) ...

用途:

1
 void showAllAS(ArrayList <String> sl) ...

只有当我的方法使用特定于ArrayList的方法时,就像ensureCapacity()一样。

响应信息,我们应该使用类型化的EDCOX1,0,而不是仅仅EDCOX1,12,是非常好的(当然,如果我们不使用古爪哇)。


所有这些答案都是从某个地方读到的教条中背诵出来的。

变量声明和初始化语句Type x = new Constructor();绝对是实现细节的一部分。它不是公共API的一部分(除非它是public final,但List是可变的,所以这是不适当的)

作为一个实现细节,您试图用抽象来愚弄谁?最好保持类型尽可能具体。它是数组列表还是链接列表?它是否应该是线程安全的?选择对于您的实现很重要,您仔细地选择了特定的列表impl。然后你把它当作一个简单的东西来宣布,就好像它无关紧要,你不在乎?

把它声明为List的唯一合法理由是我太懒了,不想打字。这也涵盖了这样一个论点:如果我需要移动到另一个列表impl,我就少了一个地方可以修改。

只有当变量范围很小时,这个原因才是合法的,并且您可以从一眼看到它的所有用法。否则,请保留最具体的类型,以便在使用变量的所有代码中显示其性能和语义特征。


这是一个Java怪癖,由于有限的类型推断和学说OOP。对于局部变量,它主要是样式;如果您需要子类型的某些特定特性,请使用子类型(下面的示例),否则也可以使用。

Java风格是使用超类型,甚至在实现的主体内执行接口,以及与可见类型的一致性(有效Java第二版:项目52:通过它们的接口引用对象)。在具有更多类型推断的语言中,例如c+/c/y//go等,不需要显式地声明类型,并且局部变量将具有特定类型。

对于可见类型(公共或受保护:字段、参数或方法的返回值),您几乎总是希望使用最通用的类型:接口或抽象类,以提供更好的灵活性(有效Java:项目40:仔细设计设计签名)。但是,对于不可见的类型(private或package private成员或局部变量),可以使用(它只是样式),有时还需要使用更具体的类型,包括具体的类。

查看有效的Java标准指南;个人想法如下。

使用更通用的类型(即使不可见)的原因是为了减少噪音:您声明只需要更通用的类型。在不可见的成员(如私有方法)上使用常规类型也可以在更改类型时减少用户流失。但是,这不适用于局部变量,因为它只改变了一行:ConcreteFoo foo = new ConcreteFoo();OtherConcreteFoo foo = new OtherConcreteFoo();

您确实需要子类型的情况包括:

  • 您只需要子类型上存在的成员,例如:
    • 实现的一些特性,如ensureCapacityfor ArrayList
    • (在测试代码中常见)假类的一些成员,如(假设)FakeFileSystem#createFakeFile
  • 您依赖子类型的行为,尤其是在父类型方法的重写中,例如:
    • 具有更一般的参数类型,
    • 更具体的返回类型,或
    • 引发更具体或更少的异常类型。

作为最后一个例子,我应该关闭一个StringReader吗?:stringreader.html close重写reader.html close,不抛出IOException,因此使用StringReader而不是Reader作为局部变量意味着您不需要处理实际不会发生的异常,并显著减少了样板。


在大多数情况下,我更喜欢第二种,因为它表示您没有在arraylist API中使用任何特定的内容,如果以后需要,您可以替换任何其他类型的列表,而不必更改除第一行以外的任何代码。


我知道在用接口声明变量时至少有一种情况不起作用。当你想使用反射。

我在一些代码上做了一个bug修复,在那里我声明了一个变量为Map,并给它分配了一个HashMap的实例。在通过反射访问的方法调用中,此变量用作参数。问题是反射试图找到一个带有HashMap签名而不是声明的Map签名的方法。由于没有使用HashMap作为参数的方法,我无法通过反射找到方法。

1
2
3
4
5
Map<String, Object> map = new HashMap<String, Object>();

public void test(Map<String, Object> m) {...};

Method m = this.getClass().getMethod("test", new Class<?>[]{map.getClass()});

找不到使用接口的方法。如果您制作另一个使用HashMap的测试版本,那么它将工作——但是现在您必须用一个具体的类来声明变量,而不是更灵活的接口……