我和朋友正在学习Java。我们今天正在研究接口,我们讨论了如何使用接口。
我朋友给我看的示例代码包含:
IVehicle modeOfTransport1 = new Car();
IVehicle modeOfTransport2 = new Bike();
其中 IVehicle 是一个在汽车和自行车类中实现的界面。 定义接受 IVehicle 作为参数的方法时,可以使用接口方法,并且在运行代码时,上述对象可以正常工作。然而,当您按照通常的意愿宣布汽车和自行车时,这非常适用:
Car modeOfTransport1 = new Car();
Bike modeOfTransport2 = new Bike();
所以,我的问题是 - 在声明和实例化 modeOfTransport 对象时,为什么要使用前一种方法而不是后一种?这有关系吗?
答案 0 :(得分:13)
使用接口声明它们有很大的好处,这就是所谓的“编码到接口”而不是“编码到实现”是一个很大的Object Oriented Design (OOD
)原则,这样你可以声明一个这样的方法:
public void (IVehicle myVehicle)
这将接受实现该接口的任何对象,然后在运行时它将调用这样的实现:
public void (IVehicle myVehicle)
{
myVehicle.run() //This calls the implementation for that particular vehicle.
}
要回答原来的问题,为什么要使用其中一个原因有几个原因:
1)使用接口声明它们意味着您可以稍后将该值替换为实现该接口的任何其他具体类,而不是被锁定到该特定的具体类
2)您可以通过使用接口声明多态来充分利用多态性,因为每个实现都可以在运行时调用正确的方法。
3)您遵循代码的OOD原则到接口
答案 1 :(得分:9)
那里没关系。
真正重要的是在需要在IVehicle上运行的其他界面。如果他们接受参数并将值返回为IVehicle,那么代码将更容易扩展。
如您所述,这些对象中的任何一个都可以传递给接受IVehicle作为参数的方法。
如果您的后续代码使用了所使用的Car或Bike特定操作,那么将它们声明为Car或Bike将是有利的。 Car and Bike特定操作可用于每个相关对象,并且两者都可以作为IVehicle使用(即可以传递)。
答案 2 :(得分:5)
您真的在问:我应该使用哪种参考类型?
通常,您希望尽可能使用一般的引用类型,这样您仍然可以访问所需的行为。这意味着您的具体类型的任何接口或父类,而不是具体类型本身。当然,不要过分强调这一点 - 例如,您当然不希望将所有内容都声明为Object
!
考虑以下选项:
Set<String> values1 = new TreeSet<String>();
TreeSet<String> values2 = new TreeSet<String>();
SortedSet<String> values3 = new TreeSet<String>();
这三个都是有效的,但通常values1
的第一个选项更好,因为你只能访问Set
接口的行为,所以以后你可以很容易地交换另一个实现:
Set<String> values1 = new HashSet<String>();
小心使用第二个选项values2
。它允许您使用TreeSet
实现的特定行为,以便在Set
的不同实现中交换变得更加困难。只要这是你的目标,这很好。因此,在您的示例中,仅当您需要访问不在Car
界面中的内容时,才使用Bike
或IVehicle
引用。请注意,以下情况不起作用:
TreeSet<String> values2 = new HashSet<String>(); // does not compile!
有时您需要访问不是最常规类型的方法。第三个选项values3
中说明了这一点 - 该引用比Set
更具体,这使您可以在以后依赖SortedSet
的行为。
TreeSet<String> values3 = new ConcurrentSkipListSet<String>();
关于引用类型的问题不仅适用于声明变量的地方,还适用于必须指定每个参数类型的方法。幸运的是,“尽可能使用一般参考类型”经验法则也适用于方法参数。
答案 3 :(得分:3)
因为你并不关心实现是什么......只是它的行为是什么。
说你有动物
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());
有了这个,当你制作一条金鱼时,孩子的反应非常蹩脚(事实证明金鱼不会说话!)但是当你放入一只熊时,反应非常可怕!
如果你说
,你就不能这样做Cat cat = new Cat();
因为你限制自己的猫的能力。
答案 4 :(得分:3)
编程到接口而不是实现。
当您编程到接口时,您将编写可以处理任何类型的Vehicle的代码。因此,未来您的代码无需修改即可与Trains和Planes一起使用。
如果您忽略该界面,那么您将无法使用CArs和Bikes,并且任何新车辆都需要进行额外的代码修改。
这背后的原则是:
打开扩展程序,关闭修改。
答案 5 :(得分:2)
老实说,你的论点是没有实际意义的。这里发生的是隐式转换为IVehicle
。您和您的朋友似乎在争论是否最好立即(根据第一个代码清单)或稍后(根据第二个代码清单调用方法时)。无论哪种方式,它都会被隐式转换为IVehicle
,所以真正的问题是 - 你需要处理一辆汽车,还是一辆汽车?如果你需要的只是一个IVehicle,第一种方式就完全没问题了(如果以后你想要透明地换掉一辆自行车的话,那就更好了)。如果您需要在代码中的其他位置将其视为汽车,则只需将其作为汽车保留。
答案 6 :(得分:1)
声明接口并使用对象实例化它们可以实现一个名为polymorphism的强大概念。
List<IVehicle> list = new ArrayList<IVehicle>();
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
明确回答问题:您将使用接口声明(即使您知道具体类型),以便您可以将多种类型(实现相同的接口)传递给方法或集合;然后,无论实际类型是什么,都可以调用每种实现类型共有的行为。
答案 7 :(得分:1)
井接口是行为,类是它们的实现,所以稍后当你编程只知道行为(接口)的时候会有几次。并且为了利用它,你将实现它们以从中获益。它主要用于通过告诉用户行为(接口)来隐藏用户的实现细节。
答案 8 :(得分:0)
你的直觉是正确的;变量的类型应尽可能具体。
这与方法返回类型和参数类型不同; API设计人员希望有点抽象,因此API可以更灵活。
变量不是API的一部分。它们是实现细节。抽象通常不适用。
答案 9 :(得分:-2)
使用“IVehicle modeOfTransport1 = new Car();”时,modeOfTransport1无法访问仅由Car拥有的方法。我不知道原因。