所以我想说我有这个界面:
public interface IBox { public void setSize(int size); public int getSize(); public int getArea(); //...and so on }
我有一个实现它的类:
public class Rectangle implements IBox { private int size; //Methods here }
如果我想使用IBox接口,我实际上无法创建它的实例,方式如下:
public static void main(String args[]) { Ibox myBox=new Ibox(); }
对?所以我实际上必须这样做:
public static void main(String args[]) { Rectangle myBox=new Rectangle(); }
如果这是真的,那么接口的唯一目的是确保实现接口的类如接口所描述的那样在其中获得了正确的方法?或者是否还有其他任何接口用途?
接口是一种使代码更灵活的方法.你做的是这样的:
Ibox myBox=new Rectangle();
然后,如果你决定要使用不同类型的盒子(也许还有另一个库,有更好的盒子),你可以将代码切换到:
Ibox myBox=new OtherKindOfBox();
一旦你习惯了它,你会发现它是一种很棒的(实际上必不可少的)工作方式.
另一个原因是,例如,如果要创建一个框列表并对每个框执行某些操作,但您希望列表包含不同类型的框.在每个盒子上你可以做:
myBox.close()
(假设IBox有一个close()方法),即使myBox的实际类根据迭代中的哪个框而改变.
使接口变得有用的不是 "你可以改变主意并在以后使用不同的实现而只需要改变创建对象的地方"这一事实.这不是问题.
真正的观点已经在名称中:它们定义了一个任何人都可以实现的接口,以使用在该接口上运行的所有代码.最好的例子是java.util.Collections
提供各种有用的方法,这些方法仅在接口上运行,例如sort()
或reverse()
for List
.这里的关键是,这个代码可以用来排序或反向的任何一个实现类List
接口-不仅仅是ArrayList
和LinkedList
,也是班级,你自己写的,这可能在某种程度上人们谁写来实现java.util.Collections
从未想象.
以同样的方式,您可以编写在众所周知的接口或您定义的接口上运行的代码,而其他人可以使用您的代码,而无需请求您支持他们的类.
接口的另一个常见用途是Callbacks.例如,java.swing.table.TableCellRenderer,它允许您影响Swing表如何显示某列中的数据.您实现该接口,将实例传递给JTable
,并且在呈现表的某个时刻,您的代码将被调用以执行其操作.
我读过的众多用途之一就是在没有Java中的多继承使用接口的情况下很难:
class Animal { void walk() { } .... .... //other methods and finally void chew() { } //concentrate on this }
现在,想象一下这样一个案例:
class Reptile extends Animal { //reptile specific code here } //not a problem here
但,
class Bird extends Animal { ...... //other Bird specific code } //now Birds cannot chew so this would a problem in the sense Bird classes can also call chew() method which is unwanted
更好的设计是:
class Animal { void walk() { } .... .... //other methods }
Animal没有chew()方法,而是放在一个接口中:
interface Chewable { void chew(); }
让Reptile类实现这个而不是Birds(因为Birds无法咀嚼):
class Reptile extends Animal implements Chewable { }
和鸟类只是:
class Bird extends Animal { }
接口的目的是多态,即类型替换.例如,给定以下方法:
public void scale(IBox b, int i) { b.setSize(b.getSize() * i); }
调用该scale
方法时,您可以提供实现该IBox
接口的任何类型的值.换句话说,如果Rectangle
和Square
都实现IBox
,可以提供无论是Rectangle
或Square
地方的IBox
预期.
接口允许静态类型语言支持多态.面向对象的纯粹主义者会坚持认为语言应该提供继承,封装,模块化和多态性,以便成为一个功能齐全的面向对象语言.在动态类型 - 或鸭类型 - 语言(如Smalltalk)中,多态性是微不足道的; 然而,在静态类型语言(如Java或C#)中,多态性远非微不足道(事实上,从表面上看,它似乎与强类型的概念不一致.)
让我来证明:
在动态类型(或鸭子类型)语言(如Smalltalk)中,所有变量都是对象的引用(没有更多,仅此而已.)因此,在Smalltalk中,我可以这样做:
|anAnimal| anAnimal := Pig new. anAnimal makeNoise. anAnimal := Cow new. anAnimal makeNoise.
那段代码:
声明一个名为anAnimal的局部变量(请注意,我们不指定变量的TYPE - 所有变量都是对象的引用,不多也不少.)
创建名为"Pig"的类的新实例
将Pig的新实例分配给变量anAnimal.
将信息发送makeNoise
给猪.
使用牛重复整个事情,但将其分配给与Pig相同的确切变量.
相同的Java代码看起来像这样(假设Duck和Cow是Animal的子类:
Animal anAnimal = new Pig(); duck.makeNoise(); anAnimal = new Cow(); cow.makeNoise();
这一切都很好,直到我们介绍类蔬菜.蔬菜与动物有一些相同的行为,但不是全部.例如,动物和蔬菜都可以生长,但显然蔬菜不会产生噪音,动物也无法收获.
在Smalltalk中,我们可以这样写:
|aFarmObject| aFarmObject := Cow new. aFarmObject grow. aFarmObject makeNoise. aFarmObject := Corn new. aFarmObject grow. aFarmObject harvest.
这在Smalltalk中运行得非常好,因为它是鸭子类型(如果它像鸭子一样行走,像鸭子一样嘎嘎叫 - 它是一只鸭子.)在这种情况下,当一条消息被发送到一个对象时,就会执行查找.接收者的方法列表,如果找到匹配的方法,则调用它.如果没有,则抛出某种NoSuchMethodError异常 - 但它都是在运行时完成的.
但在Java中,一种静态类型语言,我们可以为变量分配什么类型?玉米需要从蔬菜中继承,以支持生长,但不能从动物身上继承,因为它不会产生噪音.Cow需要继承Animal以支持makeNoise,但不能继承VEG,因为它不应该实现收获.看起来我们需要多重继承 - 从多个类继承的能力.但是由于弹出的所有边缘情况(当多个并行超类实现相同的方法时会发生什么?等),结果证明这是一个相当困难的语言特性.
接下来的接口......
如果我们制作动物和蔬菜类,每个实施Growable,我们可以宣布我们的牛是动物,我们的玉米是蔬菜.我们还可以宣称动物和蔬菜都是可以生长的.这让我们写这个来增长一切:
Listlist = new ArrayList (); list.add(new Cow()); list.add(new Corn()); list.add(new Pig()); for(Growable g : list) { g.grow(); }
它让我们这样做,发出动物的声音:
Listlist = new ArrayList (); list.add(new Cow()); list.add(new Pig()); for(Animal a : list) { a.makeNoise(); }
duck-typed语言的优点是你可以获得非常好的多态性:所有类都必须提供行为才能提供方法.只要每个人都玩得很好,并且只发送符合定义方法的消息,一切都很好.缺点是下面的错误类型直到运行时才被捕获:
|aFarmObject| aFarmObject := Corn new. aFarmObject makeNoise. // No compiler error - not checked until runtime.
静态类型语言提供了更好的"按合同编程",因为它们将在编译时捕获下面的两种错误:
// Compiler error: Corn cannot be cast to Animal. Animal farmObject = new Corn(); farmObject makeNoise();
-
// Compiler error: Animal doesn't have the harvest message. Animal farmObject = new Cow(); farmObject.harvest();
所以....总结一下:
接口实现允许您指定对象可以执行的操作(交互),而类继承允许您指定应该如何完成(实现).
接口为我们提供了许多"真正的"多态性的好处,而不会牺牲编译器类型检查.
通常接口定义您应该使用的接口(如名称所示;-)).样品
public void foo(List l) { ... do something }
现在你的函数foo
接受ArrayList
s,LinkedList
s,...不仅仅是一种类型.
Java中最重要的是你可以实现多个接口,但你只能扩展一个类!样品:
class Test extends Foo implements Comparable, Serializable, Formattable { ... }可能但是
class Test extends Foo, Bar, Buz { ... }不是!
您上面的代码也可能是:IBox myBox = new Rectangle();
.现在重要的是,myBox仅包含来自IBox的方法/字段,而不包含(可能存在的)其他方法Rectangle
.
你能做到的
Ibox myBox = new Rectangle();
这样你就像Ibox一样使用这个对象而你并不关心它Rectangle
.
我认为你了解接口所做的一切,但你还没有想象接口有用的情况.
如果您在窄范围内实例化,使用和释放对象(例如,在一个方法调用中),则接口实际上不会添加任何内容.就像你提到的那样,具体类是众所周知的.
接口有用的地方是需要在一个地方创建一个对象并返回给可能不关心实现细节的调用者.让我们将您的IBox示例更改为Shape.现在我们可以实现Shape的实现,如Rectangle,Circle,Triangle等.对于每个具体的类,getArea()和getSize()方法的实现将完全不同.
现在你可以使用一个带有各种createShape(params)方法的工厂,这些方法将根据传入的参数返回一个合适的Shape.显然,工厂会知道正在创建什么类型的Shape,但是调用者不会有关心它是圆形还是正方形,等等.
现在,假设您必须对形状执行各种操作.也许您需要按区域对它们进行排序,将它们全部设置为新大小,然后在UI中显示它们.形状都由工厂创建,然后可以非常容易地传递给Sorter,Sizer和Display类.如果您需要在将来的某个时间添加六角形类,除了工厂之外,您不必更改任何内容.没有界面,添加另一个形状会变得非常混乱.
为什么接口??????
它始于一只狗.特别是一只哈巴狗.
哈巴狗有各种各样的行为:
public class Pug { private String name; public Pug(String n) { name = n; } public String getName() { return name; } public String bark() { return "Arf!"; } public boolean hasCurlyTail() { return true; } }
你有一个拉布拉多犬,他也有一套行为.
public class Lab { private String name; public Lab(String n) { name = n; } public String getName() { return name; } public String bark() { return "Woof!"; } public boolean hasCurlyTail() { return false; } }
我们可以制作一些哈巴狗和实验室:
Pug pug = new Pug("Spot"); Lab lab = new Lab("Fido");
我们可以调用他们的行为:
pug.bark() -> "Arf!" lab.bark() -> "Woof!" pug.hasCurlyTail() -> true lab.hasCurlyTail() -> false pug.getName() -> "Spot"
假设我经营一个狗窝,我需要跟踪我所住的所有狗.我需要将我的哈巴狗和拉布拉多存储在不同的数组中:
public class Kennel { Pug[] pugs = new Pug[10]; Lab[] labs = new Lab[10]; public void addPug(Pug p) { ... } public void addLab(Lab l) { ... } public void printDogs() { // Display names of all the dogs } }
但这显然不是最佳的.如果我想要容纳一些贵宾犬,我必须改变我的狗窝定义以添加一系列贵宾犬.事实上,我需要为每种狗提供一个单独的阵列.
洞察力:哈巴狗和拉布拉多犬(和贵宾犬)都是狗的类型,他们有相同的行为.也就是说,我们可以说(为了这个例子的目的)所有的狗都可以吠叫,有一个名字,并且可能有也可能没有卷曲的尾巴.我们可以使用界面来定义所有狗可以做什么,但是将它留给特定类型的狗来实现这些特定的行为.界面说"这里是所有狗都可以做的事情",但没有说明每个行为是如何完成的.
public interface Dog { public String bark(); public String getName(); public boolean hasCurlyTail(); }
然后我稍微改变了Pug和Lab类来实现Dog行为.我们可以说帕格是狗,实验室是狗.
public class Pug implements Dog { // the rest is the same as before } public class Lab implements Dog { // the rest is the same as before }
我仍然可以像以前那样实例化Pugs和Labs,但现在我也有了一种新方法:
Dog d1 = new Pug("Spot"); Dog d2 = new Lab("Fido");
这说d1不仅仅是一只狗,它特别是一只哈巴狗.d2也是狗,特别是实验室.我们可以调用这些行为,它们像以前一样工作:
d1.bark() -> "Arf!" d2.bark() -> "Woof!" d1.hasCurlyTail() -> true d2.hasCurlyTail() -> false d1.getName() -> "Spot"
这里是所有额外工作得到回报的地方.养犬班变得更加简单.我只需要一个数组和一个addDog方法.两者都适用于任何狗的物体; 也就是说,实现Dog接口的对象.
public class Kennel { Dog[] dogs = new Dog[20]; public void addDog(Dog d) { ... } public void printDogs() { // Display names of all the dogs } }
以下是如何使用它:
Kennel k = new Kennel(); Dog d1 = new Pug("Spot"); Dog d2 = new Lab("Fido"); k.addDog(d1); k.addDog(d2); k.printDogs();
最后一个陈述将显示:Spot Fido
通过接口,您可以指定一组行为,实现该接口的所有类将共享.因此,我们可以定义变量和集合(例如数组),这些变量和集合不必事先知道它们将包含哪种特定对象,只是它们将保存实现该接口的对象.