【问题标题】:Why would you declare an Interface and then instantiate an object with it in Java?为什么要声明一个接口,然后在 Java 中用它实例化一个对象?
【发布时间】:2011-12-04 17:03:46
【问题描述】:

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

我朋友给我看的示例代码包含以下内容:

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

IVehicle 是在汽车和自行车类中实现的接口。 当定义一个接受 IVehicle 作为参数的方法时,您可以使用接口方法,并且当您运行代码时,上述对象正常工作。但是,当您像通常这样声明汽车和自行车时,这非常有效:

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

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

【问题讨论】:

  • 很多答案都被否决了。投反对票的人,想解释一下原因吗?
  • @stivlo:很多答案根本没有解决问题。
  • 我将解释我的反对意见:这里的每个人似乎都开始解释多态性或接口的目的,这不是 OP 所要求的。 OP 询问是立即转换为接口类型还是推迟转换。
  • @Chris 不,他不是。他特别问“你为什么要使用前一种方法而不是后一种方法”。他甚至没有提到选角。
  • @Chris:多么可怕的逻辑飞跃!拒绝投票的可怕理由。你应该感到羞耻。

标签: java oop interface polymorphism


【解决方案1】:

使用接口声明它们有一个很大的好处,即所谓的“编码到接口”,而不是“编码到实现”是一个很大的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原则的代码到一个接口

【讨论】:

  • 很抱歉,但这根本不能回答问题。发帖人问的是你为什么会做IInterface var = new ConcreteClass();,而不是接口是如何工作的。
  • @Mat 是的,我知道这一点,我正在解释通过使用接口声明它们,您正在对接口而不是实现进行编程,这在上面的示例中可能很有用。
  • 这不是重点,OP 知道接口。当您知道局部变量的具体类时,您没有解决任何关于为什么要将局部变量声明为 IInterface 的问题。您发布的内容在 OP 公开的两种情况下的工作方式完全相同。
【解决方案2】:

那里没关系。

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

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

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

【讨论】:

  • +1 - 在使用IVehicle 的方法的外部 中,变量声明为什么类型并不重要,只要它实现IVehicle。我认为真正看到接口力量的“最佳”例子是依赖注入。
  • @Larry 我相信提倡将它们声明为 IVehicle 而不是 Car 或 Bike 的原因之一是专门避免您提到使用 Car 或 Bike 特定操作的后续代码。在重构以更改车辆类型时,这些事情会造成很大的痛苦,因为您现在已经依赖于特定类型的车辆。这不仅关乎方法的传入和传出,还关乎您作为开发人员的思维框架以及您是否正在编写可维护的代码。
  • 如果稍后在代码中您想要交换存储在modeOfTransport1modeOfTransport2 中的值,那么最好将它们声明为接口实例而不是具体实例.
【解决方案3】:

你真的在问:我应该使用什么引用类型?

一般来说,您希望尽可能使用通用的引用类型,它仍然可以让您访问所需的行为。这意味着您的具体类型的任何接口或父类,而不是具体类型本身。当然,不要过分强调这一点——例如,您当然不想将所有内容都声明为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 的不同实现中进行交换变得更加困难。只要这是你的目标,这很好。因此,在您的示例中,仅当您需要访问不在 IVehicle 接口中的内容时才使用 CarBike 引用。请注意,以下内容不起作用:

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

仍然有些时候您需要访问不是最通用类型的方法。这在第三个选项values3 中进行了说明——引用比Set 更具体,这允许您以后依赖SortedSet 的行为。

TreeSet<String> values3 = new ConcurrentSkipListSet<String>();

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

【讨论】:

  • 我相信这是最好的答案。关键是,当您查看变量声明时,您确切地知道它将如何在该代码中使用。保持类型尽可能通用是一种分析工具和编码指南。对于不需要经常分解其他人编写的代码的人来说,这有点令人困惑,但是一旦它帮助你解决了问题,问题就变成了“你为什么不使用最通用的类​​型)
  • 我当然也这么认为!
【解决方案4】:

编程到接口而不是实现。

当您对接口进行编程时,您将编写可以处理任何类型车辆的代码。因此,将来您的代码无需修改即可与火车和飞机一起使用。

如果您忽略该界面,那么您将被困在汽车和自行车上,任何新的车辆都需要额外的代码修改。

这背后的原理是:

对扩展开放,对修改关闭。

【讨论】:

  • 我无法想象为什么这个(以及这个线程上的许多其他人)获得了两个反对票。它非常清晰简洁,准确地给出了哪些信息对于开发可靠的代码很重要。
  • 我希望看到有用的代码示例而不是流行语,但这绝对不值得两次反对。
  • 许多人都被糟糕的实现所折磨——与滥用接口的团队合作,可能会迫使他们在每个单独的类上作为代码库中团队需求的一部分,而这可能甚至没有那么多保证结构体。这会导致很多挫败感和过度反应。没有什么可做的——在真正获得它之前,他们必须获得更广泛的经验。与此同时,一个好的答案被否决并回到零可能是一个巨大的代表收益,所以不要为作者感到难过:)
【解决方案5】:

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

假设你有一只动物

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();

因为您将自己限制在猫的能力范围内。

【讨论】:

  • 很好奇这个(以及其他许多人)是如何获得如此多的反对票的。老实说,似乎有点奇怪。
【解决方案6】:

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

【讨论】:

  • 我不能代表 OP 的朋友,但我相信开发人员应该尽可能避免“需要像对待汽车一样对待它”。通过实例化为Vehicle 而不是Car,您会迫使人们根据Vehicle 来思考事物。这样,当您将 Car 替换为 Plane 时,这肯定会在您的应用程序生命周期中的某个时间发生,您无需(重复,否)重构。
  • 引用爱因斯坦的话:“让事情尽可能简单,但不要更简单。”尽可能多地使用界面,但信不信由你,有时您确实需要像对待汽车一样对待汽车。如果是这样,那么接口就不合适了。
  • 这与其说是争论,不如说是试图弄清楚我们在做什么。我的伙伴想通了上面的代码,我们只是想了解每个选项的含义。
  • @Chris 有时候,是的。但应该避免那些时间。将汽车仅视为汽车而不是车辆,您会获得什么优势?如果您确实有时将汽车视为汽车而不是车辆,那么您可能在设计中犯了错误。
  • @glowcoder:汽车有引擎。自行车没有。说“让对象自己管理”很诱人,但是来吧——引擎多久自我修复一次?不,一个好的设计仍然允许外部对象知道Car ojbects(可能是CarMechanic 对象)。模块化和接口都很好,但您仍然需要提供专业化服务。
【解决方案7】:

声明接口并用对象实例化它们允许一个强大的概念,称为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

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

【讨论】:

  • 好的,有人可以解释为什么我被否决了吗?
  • 我不是反对者之一,但我怀疑他们是受您最后一段的激励。您声称要回答 OP 的问题,这就是为什么 local variables 可能被声明为接口对象而不是具体对象的原因。你的理由——这样你就可以将多种类型传递给一个方法或集合——没有意义。声明为Car 的变量可以传递给接受IVehicle 的方法或集合。完全没有要求将其声明为IVehicle
【解决方案8】:

好吧,接口是行为,类是它们的实现,所以以后会有好几次你只知道行为(接口)的情况下进行编程。并利用它,您将实施它们以从中受益。它基本上用于通过仅告诉用户行为(接口)来向用户隐藏实现细节。

【讨论】:

    【解决方案9】:

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

    这不同于方法返回类型和参数类型; API 设计者希望稍微抽象一点,以便 API 更加灵活。

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

    【讨论】:

    • “抽象通常不适用”——抽象通常适用于局部变量。这实际上取决于如何使用变量。例如,如果稍后在代码中有理由交换存储在 modeOfTransport1modeOfTransport2 中的引用,则需要将它们声明为接口实例而不是具体实例。
    【解决方案10】:

    使用“IVehicle modeOfTransport1 = new Car();”,只有 Car 拥有的方法不能被 modeOfTransport1 访问。反正我也不知道是什么原因。

    【讨论】:

    • 这是错误的。创建的对象仍然是Car 的一个实例。存储在modeOfTransport1 中的引用可以转换为Car(如果您碰巧知道IVehicle 的这个特定实例实际上是Car)并且所有Car 特定的实例方法都是然后可调用。
    • 没有强制转换,如原始问题中所问,您可以调用特定于汽车的实例方法吗?
    • 不,不是没有演员表,但这不是我的意思。我在批评您声称 OP 的代码将 “仅实例化接口中的方法,而不是 Car 自己的方法......它们可能导致不同的内存使用。” 也许这不是你的意思,但是您显然是在说 new Car() 的行为(或可能行为)不同,具体取决于赋值语句左侧的内容,而这根本不是真的。
    • 是的。这应该是我的假设。这是我能想到的唯一可能。我在想如果汽车的对象被实例化,为什么我们不能称它为自己的方法。你知道如何测试这两种对象创建方式的内存使用情况吗?此外,“演员”在内部做什么?我是Java初学者。感谢您的帮助。
    • 您可以使用instanceof 来测试是否可以将引用强制转换为特定的类或接口。当您声明IVehicle thing = new Car() 时,您不能调用Car 特定方法的原因是您或者编译器不知道 thing 恰好是@987654332 @(即使是)因为它没有被声明为汽车。但是,您可以使用if (thing instanceof Car) { /* cast to Car and call car-specific methods */ }。请注意,将引用变量转换为另一种类型不会改变被引用对象本身,只会改变引用。
    猜你喜欢
    • 2019-12-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-10-08
    • 1970-01-01
    • 2011-08-11
    • 1970-01-01
    相关资源
    最近更新 更多