有更多的接口比正确的方法
所以可以说我有这个接口:
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,我实际上不能创build它的一个实例,方式如下:
public static void main(String args[]) { Ibox myBox=new Ibox(); }
对? 所以我实际上必须这样做:
public static void main(String args[]) { Rectangle myBox=new Rectangle(); }
如果这是真的,那么接口的唯一目的是确保实现一个接口的类已经在接口中描述了正确的方法吗? 还是有任何其他使用接口?
接口是一种使你的代码更加灵活的方法。 你做的是这样的:
Ibox myBox=new Rectangle();
然后,如果你决定要使用不同types的盒子(也许还有另外一个库,有更好的盒子),你可以将代码切换到:
Ibox myBox=new OtherKindOfBox();
一旦你习惯了,你会发现这是一个伟大的(实际上是必不可less的)工作方式。
另一个原因是,例如,如果您想创build一个框列表并对每个框执行一些操作,但是您希望列表包含不同types的框。 在每个框中,你可以做:
myBox.close()
(假设IBox有一个close()方法),即使myBox的实际类改变取决于你在迭代中的位置。
使接口有用的原因并不在于“你可以改变主意,稍后使用不同的实现,只需要改变创build对象的地方”。 这是一个非问题。
真正的意义在于:它们定义了一个任何人都可以实现的接口 ,以使用在该接口上运行的所有代码。 最好的例子是java.util.Collections
,它提供了各种有用的方法,这些方法专门用于接口,如List
sort()
或reverse()
。 这里的要点是现在可以使用这段代码对任何实现了List
接口的类进行sorting或者反转 – 不仅仅是ArrayList
和LinkedList
,还有你自己编写的类,这些类可以用编写java.util.Collections
的人来实现java.util.Collections
从未想象。
以同样的方式,你可以编写在众所周知的接口或者你定义的接口上运行的代码,其他人可以使用你的代码,而不必要求你支持他们的类。
接口的另一个常见用途是callback。 例如, java.swing.table.TableCellRenderer ,它允许您影响Swing表在某个列中显示数据的方式。 你实现了这个接口,将一个实例传递给JTable
,并且在表格渲染过程中的某个时候,你的代码会被调用来执行它的工作。
我已经阅读的许多用途之一是在Java中没有多重inheritance使用接口的情况下的困难:
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
更好的devise将是:
class Animal { void walk() { } .... .... //other methods }
动物没有咀嚼()方法,而是被放在一个接口为:
interface Chewable { void chew(); }
并且爬行类实现这个而不是鸟(因为鸟不能咀嚼):
class Reptile extends Animal implements Chewable { }
简单地说:
class Bird extends Animal { }
接口的目的是多态 ,也就是typesreplace 。 例如,给出以下方法:
public void scale(IBox b, int i) { b.setSize(b.getSize() * i); }
当调用scale
方法时,可以提供任何实现IBox
接口的types的值。 换句话说,如果Rectangle
和Square
都实现了IBox
,那么无论IBox
是在哪里,都可以提供Rectangle
或者Square
。
接口允许静态types语言支持多态。 一个面向对象的纯粹主义者会坚持认为,语言应该提供inheritance,封装,模块化和多态性,以便成为一个全function的面向对象的语言。 在dynamictypes或鸭types语言(如Smalltalk)中,多态性是微不足道的; 然而,在静态types语言(如Java或C#)中,多态性远不是微不足道的(事实上,表面上看起来与强types的概念不一致)。
让我来certificate一下:
在一个dynamictypes(或鸭types)的语言(如Smalltalk)中,所有variables都是对象的引用(毫无疑问)。所以,在Smalltalk中,我可以这样做:
|anAnimal| anAnimal := Pig new. anAnimal makeNoise. anAnimal := Cow new. anAnimal makeNoise.
该代码:
- 声明一个名为anAnimal的局部variables(注意,我们不指定variables的types – 所有variables都是对象的引用,不多也不less)。
- 创build一个名为“Pig”的类的新实例
- 将Pig的新实例赋值给variablesanAnimal。
- 将消息
makeNoise
发送给猪。 - 使用母牛重复整个事情,但将其分配给与猪相同的确切variables。
相同的Java代码看起来像这样(假设鸭和牛是动物的子类:
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中是非常好的,因为它是鸭式的(如果它像鸭子一样走路,像鸭子一样嘎嘎叫 – 这是一只鸭子)。在这种情况下,当一个消息被发送到一个对象时,执行查找接收者的方法列表,如果find匹配的方法,则调用它。 如果不是,则引发某种NoSuchMethodErrorexception – 但是这些都是在运行时完成的。
但是在Java中,一种静态types的语言,我们可以分配给我们的variables的types是什么? 玉米需要从蔬菜inheritance,支持成长,但不能从动物inheritance,因为它不会产生噪音。 牛需要inheritance动物来支持makeNoise,但不能从Vegetableinheritance,因为它不应该实现收获。 看起来我们需要多重inheritance – 从多个类inheritance的能力。 但是,事实certificate这是一个非常困难的语言特性,因为所有边界情况都会popup(在多个并行超类实现相同方法时会发生什么情况等)
沿着接口…
如果我们做动物和蔬菜类,每一个实施Growable,我们可以宣布,我们的牛是动物,我们的玉米是蔬菜。 我们也可以宣称动物和蔬菜都可以生长。 这让我们写这个东西来增长一切:
List<Growable> list = new ArrayList<Growable>(); list.add(new Cow()); list.add(new Corn()); list.add(new Pig()); for(Growable g : list) { g.grow(); }
它让我们做到这一点,使动物的噪音:
List<Animal> list = new ArrayList<Animal>(); list.add(new Cow()); list.add(new Pig()); for(Animal a : list) { a.makeNoise(); }
鸭式语言的好处是你可以得到非常好的多态性:所有的类都必须提供行为提供方法。 只要每个人都玩得很好,只发送符合定义方法的消息,一切都很好。 缺点是,下面的错误types不会被捕获,直到运行时:
|aFarmObject| aFarmObject := Corn new. aFarmObject makeNoise. // No compiler error - not checked until runtime.
静态types语言提供了更好的“按合同编程”,因为它们在编译时会捕捉到以下两种错误:
// 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();
所以….总结一下:
-
接口实现允许你指定对象可以做什么(交互),类inheritance可以让你指定应该怎么做(实现)。
-
接口为我们提供了许多“真正”多态的好处,而不牺牲编译器types检查。
通常接口定义你应该使用的接口(如名称所示;-))。 样品
public void foo(List l) { ... do something }
现在你的函数foo
接受ArrayList
, LinkedList
,…不仅是一种types。
Java中最重要的是你可以实现多个接口,但是你只能扩展一个类! 样品:
class Test extends Foo implements Comparable, Serializable, Formattable { ... }
是可能的,但是
class Test extends Foo, Bar, Buz { ... }
不是!
你上面的代码也可以是: IBox myBox = new Rectangle();
。 现在重要的是,myBox只包含来自IBox的方法/字段,而不包含来自Rectangle
的(可能存在的)其他方法。
我认为你了解接口的一切,但是你还没有想象接口是有用的。
如果你正在实例化,在狭窄的范围内使用和释放一个对象(例如,在一个方法调用中),接口并不会真的添加任何东西。 就像你所说的那样,具体的类是已知的。
在接口有用的地方,当一个对象需要被创build一个地方,并返回给一个可能不关心实现细节的调用者。 让我们把你的IBox例子改成一个Shape。 现在我们可以实现Shape,比如Rectangle,Circle,Triangle等。对于每个具体的类,getArea()和getSize()方法的实现将完全不同。
现在,您可以使用具有各种createShape(params)方法的工厂,它将根据传入的参数返回适当的Shape。显然,工厂将知道正在创build的Shape的types,但调用者不会关心它是一个圆圈还是一个方块,等等。
现在,想象一下,您需要在形状上执行各种操作。 也许你需要按区域对它们进行sorting,将它们全部设置为新的大小,然后在UI中显示它们。 这些形状全部由工厂创build,然后可以很容易地传递给Sorter,Sizer和Display类。 如果将来需要添加一个六angular形的类,除了工厂之外,不需要改变任何东西。 没有界面,增加另一个形状就变成了一个非常混乱的过程。
你可以做
Ibox myBox = new Rectangle();
这样你使用这个对象作为Ibox,你不关心它的真正的Rectangle
。
接口
软件工程中有许多情况,对于不同的程序员群体来说,同意一个“契约”来阐明他们的软件如何相互作用是非常重要的。 每个组都应该能够编写自己的代码,而不需要知道如何编写其他组的代码。 一般来说,接口就是这样的合同。
例如,想象一个未来的社会,在这个社会中,电脑控制的机器人汽车在没有操作人员的情况下通过城市街道运送乘客。 汽车制造商编写运行汽车停止的软件(当然是Java),启动,加速,左转,等等。 另一个工业集团,电子制导仪器制造商,使计算机系统接收GPS(全球定位系统)的位置数据和无线传输的交通状况,并使用该信息来驱动汽车。
汽车制造商必须发布一个行业标准接口,详细说明可以调用什么方法使汽车行驶(任何汽车,任何制造商)。 然后指导制造商可以编写调用界面中描述的方法来指挥汽车的软件。 工业组织都不需要知道其他组织的软件是如何实施的。 实际上,每个组织都认为它的软件是高度专有的,并且保留随时修改它的权利,只要它继续遵守公布的界面。
在Collections框架中,一个如何使用接口的很好的例子。 如果你编写一个带List
的函数,那么用户传入一个Vector
或者一个ArrayList
或者一个HashList
或者其他东西并不重要。 而且您可以将该List
传递给任何需要Collection
或Iterable
接口的函数。
这使像Collections.sort(List list)
这样的函数成为可能,无论List
是如何实现的。
这就是为什么Factory Patterns和其他创build模式在Java中如此受欢迎的原因。 没有它们,你是正确的,Java不提供一个开箱即用的机制来简化抽象的实例化。 尽pipe如此,在任何你没有在你的方法中创build一个对象的地方,你都会得到抽象,这应该是你的大部分代码。
另外,我通常鼓励人们不要使用“IRealname”机制来命名接口。 这是一个Windows / COM的东西,把一只脚放在匈牙利符号的坟墓里,实际上并不是必须的(Java已经是强types的,并且接口的整个意义在于使它们尽可能与类types无法区分)。
不要忘记,在以后的日子里,你可以把一个现有的类,并使其实现IBox
,然后它将成为所有你的箱IBox
代码。
如果接口是可命名的,这会变得更清晰。 例如
public interface Saveable { .... public interface Printable { ....
(命名scheme并不总是工作,例如我不确定Boxable
在这里是适当的)
接口的唯一目的是确保实现一个接口的类已经在接口中描述了正确的方法吗? 还是有任何其他使用接口?
我正在用java 8版本引入的界面的新function更新答案。
从oracle文档页面总结界面 :
一个接口声明可以包含
- 方法签名
- 默认方法
- 静态方法
- 常数定义。
唯一具有实现的方法是默认方法和静态方法。
界面的使用 :
- 定义合同
- 链接不相关的类有一个function (例如,实现
Serializable
接口的类除了实现该接口之外,它们之间可能有也可能没有任何关系 - 提供可互换的实施, 如战略模式
- 使用默认方法,可以将新function添加到库的接口,并确保与为这些接口的旧版本编写的代码的二进制兼容性
- 使用静态方法在库中组织帮助器方法(可以在同一个接口中保留特定于接口的静态方法,而不是在单独的类中)
一些相关的SE问题,关于抽象类和接口之间的区别以及用例的例子:
界面和抽象类有什么区别?
我应该如何解释一个接口和一个抽象类的区别?
看一下文档页面,了解java 8中添加的新特性: 默认方法和静态方法 。
接口的目的是抽象的 ,或脱离实现。
如果您在程序中引入抽象,则不关心可能的实现。 你感兴趣的是它可以做什么 ,而不是如何 ,你使用一个interface
来expression这个在Java中。
如果你有CardboardBox和HtmlBox(两者都实现IBox),你可以将它们传递给任何接受IBox的方法。 即使它们是非常不同的,也不是完全可互换的,不关心“打开”或“resize”的方法仍然可以使用你的类(也许是因为他们关心需要多less像素来在屏幕上显示某些东西)。
接口添加到java允许多重inheritance。 虽然Java的开发者意识到拥有多重inheritance是一个“危险的”特性,这就是为什么提出了一个接口的概念。
多重inheritance是危险的,因为你可能有一个像下面这样的类:
class Box{ public int getSize(){ return 0; } public int getArea(){ return 1; } } class Triangle{ public int getSize(){ return 1; } public int getArea(){ return 0; } } class FunckyFigure extends Box, Triable{ // we do not implement the methods we will used the inherited ones }
class Box{ public int getSize(){ return 0; } public int getArea(){ return 1; } } class Triangle{ public int getSize(){ return 1; } public int getArea(){ return 0; } } class FunckyFigure extends Box, Triable{ // we do not implement the methods we will used the inherited ones }
这将是我们使用时应该调用的方法
FunckyFigure.GetArea();
FunckyFigure.GetArea();
所有的问题都是通过接口来解决的,因为你知道你可以扩展接口,并且他们不会有类的方法……编译器很好,告诉你如果你没有实现一个方法,但是我喜欢这样认为一个更有趣的想法的副作用。
为什么接口它从一个狗开始。 特别是一个哈巴狗。
哈巴狗有各种各样的行为:
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"
比方说,我经营一个狗窝,我需要跟踪我住的所有狗。 我需要将我的哈巴狗和拉布拉多放在单独的arrays中 :
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 } }
但是这显然不是最佳的。 如果我也想要一些贵宾犬 ,我必须改变我的养犬定义,增加一些贵宾犬。 事实上,我需要为每一种狗都分开一个arrays。
透视:哈巴狗和拉布拉多犬(和贵宾犬)都是狗的types,他们也有相同的行为。 也就是说,(为了这个例子的目的),所有的狗都可以叫,有个名字,可能有也可能没有curl的尾巴。 我们可以使用一个接口来定义所有的狗都可以做的事情,但是要把它留给特定types的狗来实现这些特定的行为。 界面上说“这里是所有狗都可以做的事情”,但并没有说明每个行为是如何完成的。
public interface Dog { public String bark(); public String getName(); public boolean hasCurlyTail(); }
然后,我稍微改变帕格和实验室类来实现狗的行为。 我们可以说,哈巴狗是狗,实验室是狗。
public class Pug implements Dog { // the rest is the same as before } public class Lab implements Dog { // the rest is the same as before }
我仍然可以像以前那样实例化帕格和实验室,但现在我也得到了一个新的方法来做到这一点:
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"
这是所有额外工作的回报。 犬舍class变得更简单。 我只需要一个数组和一个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
一个接口使您能够指定一组实现该接口的类将共享的一组行为。 因此,我们可以定义variables和集合(比如数组),事先不需要知道他们将要保存什么样的特定对象,只需要保存实现接口的对象。