10

可能重复:
接口的用途(续)

我不久前才开始学习Java。
我遇到过Interfaces我知道如何使用但仍然不能完全理解它的想法。
据我了解,interfaces通常由类实现,然后必须实现接口中声明的方法。
问题是——究竟有什么意义?将接口中的方法实现为普通的类方法不是更容易吗?使用接口到底有什么好处?

我试过在谷歌上寻找答案。有很多,但我仍然无法理解它的意义。我也阅读了这个问题及其答案,但整个合同只是让它变得更加复杂......

希望有人可以将其简化得足够好!:)
提前谢谢!

4

9 回答 9

12

接口允许您在运行时提供不同的实现、注入依赖项、分离关注点、使用不同的实现进行测试。

只需将接口视为类保证实现的合同。实现接口的具体类是无关紧要的。不知道这是否有帮助。

认为一些代码可能会有所帮助。这并不能解释接口的更多内容,请继续阅读,但我希望这能让您入门。这里的重点是你可以改变实现......

package stack.overflow.example;

public interface IExampleService {
void callExpensiveService();
}

public class TestService implements IExampleService {

@Override
public void callExpensiveService() {
    // This is a mock service, we run this as many 
    // times as we like for free to test our software

}
}

public class ExpensiveService implements IExampleService {
@Override
public void callExpensiveService() {
    // This performs some really expensive service,
    // Ideally this will only happen in the field
    // We'd rather test as much of our software for
    // free if possible.
}
}


public class Main {

/**
 * @param args
 */
public static void main(String[] args) {

    // In a test program I might write
    IExampleService testService = new TestService();
    testService.callExpensiveService();

    // Alternatively, in a real program I might write
    IExampleService testService = new ExpensiveService();
    testService.callExpensiveService();

    // The difference above, is that we can vary the concrete 
    // class which is instantiated. In reality we can use a 
    // service locator, or use dependency injection to determine 
    // at runtime which class to implement.

    // So in the above example my testing can be done for free, but 
    // real world users would still be charged. Point is that the interface
    // provides a contract that we know will always be fulfilled, regardless 
    // of the implementation. 

}

}
于 2012-12-25T21:15:54.017 回答
11

接口可用于许多事情。最常见的用途是多态性依赖注入,您可以在运行时更改依赖关系。例如,假设您有一个名为 Database 的接口,它有一个名为getField(...).

public interface Database 
{
    public void getField(String field);
}

现在假设您在应用程序中使用了两个数据库,具体取决于您的客户端:MySQL 和 PostgreSQL。您将有两个具体的类:

public class MySQL implements Database
{
    // mysql connection specific code
    @Override
    public void getField(String field)
    {
        // retrieve value from MySQL
    }
}

和...

public class PostgreSQL implements Database
{
    // postgre connection specific code
    @Override
    public String getField(String field)
    {
        // retrieve value from Postgre
    }
}

现在根据您的客户偏好,您可以在 main 中实例化 MySQL 或 PostgreSQL 类

 public static void main(String args[])
 {
     if (args[2].equals("mysql"))
         Database db = new MySQL();
     else
         Database db = new PostgreSQL();
 }

 //now get the field
 String foo = db.getField();

或者您可以使用带有 JS 或 Ruby 的 Factory/Abstract Factory 或脚本引擎。

于 2012-12-25T21:23:03.363 回答
4

它们用于实现多态性而不需要使用继承。

于 2012-12-25T21:11:18.357 回答
3

接口的想法是它们指定一个类可以实现的契约。您阅读的另一篇文章主要介绍了这个想法。

仅仅直接从接口实现方法是不够的,因为其他方法可以使用接口类型来要求这些方法存在。

让我们以AutoCloseable为例。唯一的方法是 close 方法,但使用类型来表达“我需要一个可以关闭的参数”的想法比列出所有方法要容易得多:

public void passMeSomethingThatICanClose(AutoCloseable bar) {
    //stuff goes here
    bar.close(); //the compiler knows this call is possible
}

如果我们没有简洁的类型名称,方法就必须明确列出需求,这不是很容易,尤其是当接口“契约”有很多方法时。

这也适用于另一种方式。通过使用特定的关键字“implements AutoCloseable”来表示意图,编译器可以告诉您您是否没有正确实现合同。

public class BrokenCloseable implements AutoCloseable {
    public void colse() {  //ERROR — see the typo?
        //blah
    }    

}

这是一个错误,因为该方法的名称错误。在 Java 中,编译器可以(并且将会)告诉您这一点。但是如果你只负责自己实现这些方法,你就不会出错。相反,您的班级只会默默地“不能”自动关闭。

其他一些语言(Python 是一个很好的例子)不会以这种方式做事——而是按照您在问题中建议的方式做事。这就是所谓的“鸭子打字”(如果它看起来像鸭子,而嘎嘎叫的像鸭子,那就把它当作鸭子对待),它也是一种可行的做事方式,只是与 Java 不同。

于 2012-12-25T21:23:08.247 回答
2

因为“死亡的致命钻石”。让我们考虑一下这种情况:

Parent您用允许多重继承的编程语言编写类(名称:)。该类包含一个实例变量 ( int a;) 和一个方法 ( void hello())。然后创建两个类(Kid1Kid2)。每个类都扩展了Parent class. 然后你覆盖hello();两个Kid1Kid2类中的方法,并为变量分配一些值a,也在两个Kid1Kid2类中。在最后一步中,您创建第 4 个类(如Main)扩展Kid1Kid2类(多重继承)。现在......问题是,哪些hello();方法将Main类继承以及a变量的值。

由于Java中没有多重继承,我们有interfaces......包含抽象方法的抽象类。我们可以实现多个接口,但在某些情况下,我们必须重写所有实现interface包含的方法。

于 2012-12-25T21:21:28.320 回答
2

接口指定功能。在某种程度上,它们提供了多重继承。假设我有一个对列表执行某些操作的函数。但不仅仅是一个列表,任何集合。我可以遍历内容的任何内容。所以我使用接口Iterable。

public int randomMethod(Iterable<Integer> li) {
...
}

现在这适用于 Lists 和 HashSets 以及所有不同的东西,它们以完全不同的方式实现和工作,属于不同的类结构,但都提供了这个基本功能。这与一个大型游戏循环不同,该循环是.update()所有扩展公共类的实体,尽管可以使用接口。

于 2012-12-25T21:14:21.043 回答
2

List<T>界面为例。您只能在某一时刻决定是否要在特定场合使用 anArrayList<T>或 a (或其他相关的东西)。LinkedList<T>

然而,它们都实现了List<T>接口。因此,无论它们如何工作,或者它们是否分层相关,都可以保证它们公开了一组您可以使用的方法,并且您不必在代码中的每一点都知道您的集合对象背后的实现以结束。

于 2012-12-25T21:14:22.550 回答
1

我建议你看看策略模式和组合模式。

于 2012-12-25T21:15:02.250 回答
1

您的问题是在一篇文章中广泛回答的方式,既不会太做作,也不会太笼统而没有用处。因此,我将尝试给出一个有意义的示例,说明接口何时非常有用。

假设您有许多代表各种类型对象的类。可能有十几个,或者任何足够大的数量。而且你在另一个类中有一些函数希望能够订购任意对象。为此,它需要能够比较任何两个对象并确定一个对象是大于还是小于另一个对象。由于每个对象都可以是您的许多类型的类中的任何一种,因此这个比较函数编写起来非常乏味,因为它必须确定每个对象是什么类型,然后通过为每个类调用适当的逻辑来进行比较。

进入界面。此时,您可以让所有类实现一个接口,例如 Comparable,它指定任何实现它的类都将实现一个 Compare 方法。

现在你的命令对象的函数将变得微不足道,因为它可以依赖于它需要比较的任何对象类都具有相同的 Comapre 方法这一事实。

这只是一个例子——而且是一个相当常见的例子。

于 2012-12-25T21:15:40.143 回答