如何理解Java中的接口?接口(implement)的实现不同于类(class)的继承(extend),因为方法(method)的实现(implement)不包含在接口中。那么很多人会疑惑,既然接口中的方法没有implement,那么extend(implement)是干什么的呢?这些人实际上混淆了接口的实现和类的扩展——在类的扩展中,子类可以从父类中获取一些实现(implement),从而实现(realize)代码重用。但是接口只是没有用来实现代码重用。为了说明接口的作用,让我们看一个例子。这是一个类(修饰符已被省略)classTiger{voidrun(){//methodbody}voideat(){//methodbody}}你可以看到有2个方法。Java是一种静态类型语言,我们可以使用类名作为参数类型,这是众所周知的。voidfeed(Tigertiger){tiger.eat()}但是如果我们有新的需求怎么办?例如,我们有一个新类classFish{voidswim(){//methodbody}voideat(){//methodbody}}这个Fishclass有一个像Tigerclass一样的eat()方法,我们希望feed方法也适用于Fish对象。当然,我们可以使用方法重载。voidfeed(Tigertiger){tiger.eat()}voidfeed(Fishfish){fish.eat()}但是可以看出,如果这样的类越来越多,我们的工作量会很大。更何况这里的代码基本上是完全重复的。这是什么意思?说明重载方案的扩展性不强,我们需要一劳永逸的方案。理论上,我们可以采用这样的方案来维护一个集合(set)FeedableAnimals,用来记录那些我们希望feedmethod起作用的类。那么我们的feedmethod就是这样写的。(Java不支持这种语法,所以你必须在脑子里做)voidfeed(FeedableAnimialsanimal){animal.eat()}把Tiger,Fish等放到FeedableAnimals中。现在我明确的告诉你,这个FeedableAnimals就是这个界面概念的雏形。现在,我们离真正的界面只有几步之遥了。考虑到我们目前的解决方案,它看起来比原来的要好得多。但仔细一想,还是有不足之处。首先,FeedableAnimals作为一个集合需要维护:如果我们添加一个新的类,比如Bear,那么我们必须修改它的定义来添加这个类Bear。显然,这违反了开闭原则——对修改关闭。其次,假设我们添加了Bear,但是由于疏忽,我们没有确认Bear有eat()方法。假设Bear其实是这个类Bear{voidrun(){//methodbody}voideat(Fishfish){//methodbody}}这时候Bear有一个methodeat(Fish),所以我们的工作人员生成I疏忽了,误以为Bear有一个eat()方法(两者的方法签名(MethodSignature)不一样!),加了进去。当然,我们可以手动浏览FeedableAnimals中的类,一个一个地查看它们是否有正确的方法签名。但是让编译器来做显然更合理,因为保证语法没有错误是编译器的工作。所以在Java中,真正的接口是这样设计的:接口不记录类,相反,类记录接口。接口可以定义抽象方法,抽象方法只包含方法签名。如果类实现了接口,编译器会检查类中是否有对应的方法签名。可见,这样的设计克服了以上两个难点。我们可以回到上面的例子来看一下。定义一个接口FeedableAnimalsinterfaceFeedableAnimals{voideat();}然后feedmethod应该写成这样:,编译器可以保证对于任何实现FeedableAnimals的对象animal,调用animal.eat()都不会导致语法错误(比如animal找不到eat()方法的错误)。这样就从编译器层面排除了feedmethod无法运行的可能性。Bear类应该这样写:那Bear是一个implementinterfaceFeedableAnimals的,所以编译器可以帮我们检查和发现这些错误,从而在编译阶段排除这些错误的可能性。而当以后出现新的类时,我们只需要像Bear这样写,就可以保证feed方法可以自动应用到这些新类的对象上。
