为什么要声明一个接口然后在Java中用它实例化一个对象?

我和朋友正在学习Java。 我们今天正在研究接口,我们讨论了如何使用接口。

我朋友给我看的示例代码包含:

IVehicle modeOfTransport1 = new Car(); IVehicle modeOfTransport2 = new Bike(); 

IVehicle是一个在汽车和自行车类中实现的接口。 定义接受IVehicle作为参数的方法时,可以使用接口方法,当运行代码时,上述对象正常工作。 然而,当您按照通常的意愿声明汽车和自行车时,这非常适用:

 Car modeOfTransport1 = new Car(); Bike modeOfTransport2 = new Bike(); 

所以,我的问题是 – 为什么在声明和实例化modeOfTransport对象时,你会使用前一种方法而不是后者? 有关系吗?

那没关系。

真正重要的是在需要在IVehicle上运行的其他接口。 如果他们接受参数并将值返回为IVehicle,那么代码将更容易扩展。

如您所述,这些对象中的任何一个都可以传递给接受IVehicle作为参数的方法。

如果您有后续代码使用了Car或Bike特定的操作,那么将它们声明为Car或Bike将是有利的。 Car and Bike特定操作可用于每个相关对象,并且两者都可以作为IVehicle使用(即可以传递)。

使用接口声明它们是一个很大的优点,这就是所谓的“编码到接口”,而不是“编码到实现” ,这是一个很大的面向对象设计( OOD )原则,这样你就可以声明一个像这样的方法:

 public void (IVehicle myVehicle) 

这将接受任何实现该接口的对象,然后在运行时它将调用这样的实现:

 public void (IVehicle myVehicle) { myVehicle.run() //This calls the implementation for that particular vehicle. } 

要回答原始问题,为什么要使用一个而不是另一个有几个原因:

1)使用接口声明它们意味着您可以稍后将该值替换为实现该接口的任何其他具体类,而不是被锁定到该特定的具体类中

2)您可以通过使用接口声明它们来充分利用多态性,因为每个实现都可以在运行时调用正确的方法。

3)您遵循代码的OOD原则到接口

你真的在问:我应该使用哪种参考类型?

通常,您希望尽可能使用一般的引用类型,以便仍然可以访问所需的行为。 这意味着您的具体类型的任何接口或父类,而不是具体类型本身。 当然,不要把这一点放得太远 – 例如,你当然不希望将所有东西都声明为Object

考虑以下选项:

 Set values1 = new TreeSet(); TreeSet values2 = new TreeSet(); SortedSet values3 = new TreeSet(); 

这三个都是有效的,但一般来说, values1的第一个选项更好,因为你只能访问Set接口的行为,所以以后你可以很容易地交换另一个实现:

 Set values1 = new HashSet(); 

注意使用第二个选项values2 。 它允许您以这样的方式使用TreeSet实现的特定行为,即在不同的Set实现中交换变得更加困难。 只要这是你的目标,这很好。 因此,在您的示例中,仅当您需要访问不在IVehicle界面中的内容IVehicle使用CarBike引用。 请注意,以下情况不起作用:

 TreeSet values2 = new HashSet(); // does not compile! 

有时您需要访问不是最常规类型的方法。 这在第三个选项values3 – 引用比Set更具体,这允许您稍后依赖SortedSet的行为。

 TreeSet values3 = new ConcurrentSkipListSet(); 

有关引用类型的问题不仅适用于声明变量的位置,还适用于必须指定每个参数类型的方法。 幸运的是,“尽可能使用一般参考类型”经验法则也适用于方法参数。

编程到接口而不是实现。

当您编程到接口时,您将编写可以处理任何类型的Vehicle的代码。 因此,未来您的代码无需修改即可与Trains和Planes一起使用。

如果您忽略该界面,那么您将无法使用CArs和Bikes,并且任何新车辆都需要进行额外的代码修改。

这背后的原则是:

打开扩展,关闭到修改。

老实说,你的论点是没有实际意义的。 这里发生的是隐式转换为IVehicle 。 您和您的朋友似乎在争论是否最好立即(根据第一个代码清单)或稍后(根据第二个代码清单调用方法时)。 无论哪种方式,它都会被隐式转换为IVehicle ,所以真正的问题是 – 你需要处理一辆汽车,还是一辆汽车? 如果你需要的只是一个IVehicle,第一种方式是完全没问题的(如果以后你想透明地换掉一辆自行车的话,那就更好了)。 如果您需要在代码中的其他位置将其视为汽车,那么请将其保留为汽车。

因为你并不真正关心实现是什么……只是它的行为是什么。

说你有动物

 interface Animal { String speak(); } class Cat implements Animal { void claw(Furniture f) { /* code here */ } public String speak() { return "Meow!" } } class Dog implements Animal { void water(FireHydrant fh) { /* code here */ } public String speak() { return "Woof!"; } } 

现在你想给你的孩子一个宠物。

 Animal pet = new ...? kid.give(pet); 

然后你会把它拿回来

 Animal pet = kid.getAnimal(); 

你不想去

 pet.claw(favorateChair); 

因为你不知道这个孩子是否有狗。 你不在乎。 你只知道 – 动物 – 被允许说话。 你对他们与家具或消防栓的相互作用一无所知。 你知道动物是为了说话。 它让你的女儿傻笑(或不!)

 kid.react(pet.speak()); 

有了这个,当你制作一条金鱼时,孩子的反应非常蹩脚(事实certificate金鱼不说话!)但是当你放入熊时,反应非常可怕!

如果你说,你不能这样做

 Cat cat = new Cat(); 

因为你限制自己的猫的能力。

声明接口并使用对象实例化它们允许一个称为多态的强大概念。

 List list = new ArrayList(); list.add(new Car()); list.add(new Bike()); for (int i = 0; i < list.size(); ++i) list.get(i).doSomeVehicleAction(); // declared in IVehicle and implemented differently in Car and Bike 

要明确回答这个问题:您将使用接口声明(即使您知道具体类型),以便您可以将多个类型(实现相同的接口)传递给方法或集合; 然后,无论实际类型是什么,都可以调用每种实现类型共有的行为。

井接口是行为,类是它们的实现,所以稍后你会编程只知道行为(接口)。 并且为了利用它,你将实现它们以从中获益。 它主要用于通过告诉用户行为(接口)来隐藏用户的实现细节。

你的直觉是正确的; 变量的类型应尽可能具体。

这与方法返回类型和参数类型不同; API设计人员希望有点抽象,因此API可以更灵活。

变量不是API的一部分。 它们是实施细节。 抽象通常不适用。

使用“IVehicle modeOfTransport1 = new Car();”时,modeOfTransport1无法访问仅由Car拥有的方法。 反正我不知道原因。