在java中使用接口或类型进行变量定义?
ArrayList aList = new ArrayList(); List aList = new ArrayList();
这两者之间有什么区别,哪个更好用,为什么?
List
是接口,其中ArrayList
是类,并且该类实现List
接口。
我更喜欢第二种forms,它更通用,即如果你不使用特定于ArrayList
你可以将它的类型声明为接口List
类型。 使用第二种forms,可以更容易地将实现从ArrayList
更改为实现List
接口的其他类。
编辑:由于许多SO用户评论这两个表单可以是任何接受List
或ArrrayList
。 但是,当我声明方法时,我更喜欢接口:
void showAll(List sl) ...
并使用:
void showAllAS(ArrayList sl) ...
只有当我的方法使用特定于ArrayList
,例如ensureCapacity()
。
响应信息我们应该使用类型List
而不仅仅是List
非常好(当然如果我们不使用古代Java)。
List是一个接口,而ArrayList是该接口的一个实现。
第二个更好,因为这意味着您可以稍后更改ArrayList以用于List的另一个实现,而无需更改应用程序的其余部分。 出于性能原因,或者由于您已选择/将要选择的List实现的行为的其他方面,您可能希望这样做。
自Java 1.5以来,两者都被弃用。
它应该是:
List list = new ArrayList (); // or whatever data type you are using in your list
请阅读Joshua Bloch的Effective Java ,特别是这两个项目:
- 23:不要在新代码中使用原始类型(甚至可以在线获取 )
- 52:通过接口引用对象
顺便说一下,如果你使用Guava ,你有一个工厂方法来构造一个ArrayList,所以你不必重复type参数:
List list = Lists.newArraylist();
在大多数情况下,我更喜欢第二种,因为它表示你没有使用ArrayList api中的任何特定内容,如果你以后需要替换任何其他类型的List而不必更改除第一行之外的任何代码。
所有这些答案都与他们在某处读到的一些教条相同。
变量声明和初始化语句, Type x = new Constructor();
,绝对是实施细节的一部分。 它不是公共API的一部分(除非它是public final
,但List
是可变的,所以这是不合适的)
作为一个实现细节,你是谁试图欺骗你的抽象? 最好保持类型尽可能具体。 它是数组列表还是链表? 它应该是线程安全吗? 选择对于您的实现很重要,您仔细选择了特定的列表。 然后你宣布它只是一个List
,好像它没关系,你不关心?
将其声明为List
的唯一合理理由是我懒得打字 。 这也包含了这样的论点: 如果我需要移动到另一个List impl,我可以少修改一个 。
这个原因只有在变量范围很小的情况下才合法,您可以一目了然地看到它的所有用法。 否则,保留最具体的类型,以便在使用该变量的所有代码中显示其性能和语义特征。
由于类型推断和学说OOP有限,这是一个Java怪癖。 对于局部变量,它主要是风格; 如果您需要子类型的某些特定function,请使用子类型(下面的示例),否则可以使用其中任何一种。
Java风格是使用超类型,甚至在实现主体内强制接口,并与可见类型保持一致( Effective Java 2nd Edition:Item 52:通过接口引用对象)。 在具有更多类型推断的语言中,例如C ++ / C#/ Go /等,您不需要显式声明类型,并且局部变量将具有特定类型。
对于可见类型(public或protected:fields,或参数和方法的返回值),您几乎总是希望使用最通用的类型:接口或抽象类,以提供更好的灵活性( Effective Java :Item 40:Design method signature )。 但是,对于不可见的类型(私有或包私有成员或局部变量),可以使用(它只是样式),有时需要使用更具体的类型,包括具体类。
有关标准指南,请参阅Effective Java ; 个人想法如下。
即使不可见也使用更一般类型的原因是为了减少噪音:你说你只需要更通用的类型。 如果您更改类型,则在不可见的成员(如私有方法)上使用常规类型也可以减少流失。 但是,这不适用于局部变量,它只是改变一行: ConcreteFoo foo = new ConcreteFoo();
to OtherConcreteFoo foo = new OtherConcreteFoo();
。
您需要子类型的情况包括:
- 您只需要在子类型上出现的成员,例如:
- 实现的一些function,例如
ensureCapacity
forArrayList
- (常见于测试代码中)假类的一些成员,如(假设)
FakeFileSystem#createFakeFile
。
- 实现的一些function,例如
- 您依赖于子类型的行为,特别是在超类型方法的覆盖中,例如:
- 具有更一般类型的参数,
- 更具体的返回类型,或
- 抛出更具体或更少的exception类型。
作为最后一个示例,请参阅我应该关闭StringReader吗? : StringReader.html #clut覆盖Reader.html #close并且不会抛出IOException
,因此使用StringReader
而不是Reader
来表示本地变量意味着您不需要处理实际上不会发生的exception,并且显着减少了样板。
如果您使用的是Java 1.4或更早版本,那么我将使用第二个。 最好尽可能一般地声明你的字段,以防你以后需要把它变成别的东西,比如Vector,等等。
从1.5开始,我将使用以下内容
List x = new ArrayList ();
它为您提供了一点类型安全性。 列表’x’可以添加一个String对象,就是这样。 当您从List’x’获得一个项目时,您可以指望一个String将要返回的事实。 这也有助于删除不必要的转换,这可能会使您在6个月后返回时难以阅读代码并尝试记住代码的作用。 如果您尝试添加任何其他对象类型,您的编译器/ IDE将通过显示错误来帮助您记住列表’x’应该进入的类型。
如果要向List添加多个Object类型,则可以添加Annotation以抑制编译错误
@SuppressWarnings("unchecked") List y = new ArrayList();
我知道至少有一种情况,当使用接口声明变量不起作用时。 当你想使用reflection。
我对一些代码进行了错误修复,我将变量声明为Map
并为其分配了一个HashMap
的实例。 此变量用作通过reflection访问的方法调用中的参数。 问题是reflection尝试使用HashMap
签名而不是声明的Map
签名来查找方法。 由于没有使用HashMap
作为参数的方法,因此我无法通过reflection找到方法。
Map map = new HashMap(); public void test(Map m) {...}; Method m = this.getClass().getMethod("test", new Class>[]{map.getClass()});
找不到使用该接口的方法。 如果您使用HashMap
进行另一个版本的测试,那么它将起作用 – 但现在您被迫使用具体类声明您的变量而不是更灵活的接口…