九、接口
接口和内部类为我们提供了一种将接口与实现分离的更加结构化的方法。
1.抽象类和抽象方法
抽象类是普通的类与接口之间的一种中庸之道。创建抽象类是希望通过这个通用接口操纵一系列类。
Java提供一个叫做抽象方法的机制,这种方法是不完整的;仅有声明而没有方法体。例:
abstract void f();
包含抽象方法的类叫做抽象类。
如果从一个抽象类继承,并想创建该新类的对象,那么就必须为基类中的所有抽象方法提供方法定义。如果不这样做,那么导出类也是抽象类。
我们可能会创建一个没有任何抽象方法的抽象类。考虑这种情况:如果有一个类,让其包含任何abstract方法都显得没有实际意义,而且我们也想要阻止产生这个类的任何对象,那么这时这样做就很有用了。
2.接口
abstract关键字允许人们在类中创建一个或多个没有任何定义的方法——提供了接口部分,但是没有提供任何相应的具体实现,这些实现是由此类的继承者创建的。interface这个关键字产生一个完全抽象的类,它根本就没有提供任何具体实现。它允许创建者确定方法名、参数列表和返回类型,但是没有任何的方法体。接口只提供了形式,而未提供任何具体实现。
一个接口表示:“所以实现了该特定接口的类看起来都像这样”。接口用来建立类与类之间的协议。
但是,interface不仅仅是一个极度抽象的类,因为它允许人们通过创建一个能够被向上转型为多种基类的类型,来实现某种类似多重继变种的特性。
接口也可以包含域,但是这些域隐式地是static和final的。
要让类遵循某个特定接口,需要使用implements关键字,它表示“interface只是它的外貌,但是现在我要声明它是如何工作的。”
接口中的方法都隐式被定义为public,因此,当要实现一个接口时,在接口中被定义的方法必须被定义为是public的。
3.完全解耦
只要一个方法操作的是类而非接口,那么就只能使用这个类及其子类。如果你想要将这个方法应用于不在此继承结构中的某个类,那么你就会遇到障碍了。接口可以在很大程度上放宽这种限制,因此,它使得我们可以编写可复用性更好的代码。
创建一个能够根据所传递的参数对象的不同而具有不同行为的方法,被称为策略设计模式。这类方法包含所要执行的算法中固定不变的部分,而“策略”包含变化的部分。策略就是传递进去的参数对象,它包含要执行的代码。
适配器设计模式,适配器中的代码将接受你所拥有的接口,并产生你所需要的接口。
4.Java中的多重继承
可以继承任意多个接口,并可以向上转型为每个接口。
使用接口的核心原因:为了能够向上转型为多个基类型(以及由此带来的灵活性)。然而,使用接口的第二个原因却是与使用抽象类相同:防止客户端程序员创建该类的对象,并确保这仅仅是建立一个接口。
如果要创建不带任何方法定义和成员变量的基类,那么就应该选择接口而不是抽象类。事实上,如果知道某事物应该成为一个基类,那么第一选择应该是使它成为一个接口。
5.通过继承来扩展接口
通过继承,可以很容易地在接口中添加新的方法声明。还可以通过继承在新接口中组合数个接口。这两种情况都可以获得新的接口。
①组合接口时的名字冲突
当实现的多个接口中存在相同名字的方法,但它们的签名或返回类型不同,会产生混乱,所以尽量避免这种情况。
6.适配接口
接口最吸引人的原因之一就是允许同一个接口具有多个不同的具体实现。在简单的情况中,它的体现形式通常是一个接受接口类型的方法,而该接口的实现和向该方法传递的对象则取决于方法的使用者。
因此,接口的一种常见用法就是策略设计模式,此时你编写一个执行某些操作的方法,而该方法将接受一个同样是你指定的接口。你主要就是声明:“你可以用任何你想要的对象来调用我的方法,只要你的对象遵循我的接口。”
7.接口中的域
因为放入接口的任何域都自动是static和final的,所以接口就成为了一种很便捷的用来创建常量组的工具。这些域不是接口的一部分,它们的值被存储在该接口的静态存储区域内。
有了Java SE5,使用接口来群组常量已经没有什么意义了,因为我们有了enum关键字。
8.嵌套接口
接口可以嵌套在类中或其他接口中。
class A { interface B { void f(); } public class BImp implements B { public void f() {} } private class BImp2 implements B { public void f() {} } public interface C { void f(); } class CImp implements C { public void f() {} } private class CImp2 implements C { public void f() {} } interface D { void f(); } private class DImp implements D { public void f() {} } public class DImp2 implements D { public void f() {} } public D getD() { return new DImp(); } private D dRef; public void receiveD(D d) { dRef = d; dRef.f(); } } interface E { interface G { void f(); } public interface H { void f(); } void g(); // 不能创建一个private接口 // private interface I {} } public class NestingInterfaces { public class BImp implements A.B { public void f() {} } class CImp implements A.C { public void f() {} } //不能实现私有的接口 //class DImp implements A.D { // public void f() {} // } class EImp implements E { public void g() {} } class EGImp implements E.G { public void f() {} } class EImp2 implements E { public void g() {} class EG implements E.G { public void f() {} } } public static void main(String[] args) { A a = new A(); //无法访问A.D // A.D ad = a.getD(); //只返回A.D //A.DImp di2 = a.getD(); //不能访问接口的成员 //a.getD().f(); A a2 = new A(); a2.receiveD(a.getD()); } }
接口也可以被实现为private的,就像A.D。那么private的嵌套接口能带来什么好处呢?它即可实现为private类,也可实现为public类(A.DImp2)。但是,A.DImp2只能被其自身所使用。你无法说它实现了一个private接口D。因此,实现一个private接口只是一种方式。它可以强制该接口中的定义不要添加任何类型信息(也就是说,不允许向上转型)。
在main()中,数次尝试使用getD()都失败了。只有一种方式能成功,那就是将返回值交给有权使用它的对象。
接口E说明接口彼此之间也可以嵌套。嵌套在另一个接口中的接口自动就是public的。
特别要注意的是,当实现某个接口时,并不需要实现嵌套在其内部的任何接口。而且,private接口不能在定义它的类之外被实现。
9.接口与工厂
接口是实现多重继承的途径,而生成遵循某个接口的对象的典型方式就是工厂方法设计模式。这与直接调用不同,我们在工厂对象上调用的是创建方法,而该工厂对象将生成接口的某个实现的对象。理论上,通过这种方式,我们的代码将完全与接口的实现分离,这就使我们可以透明地将某个实现替换为另一个实现。下面展示了工厂方法的结构:
interface Service { void method1(); void method2(); } interface ServiceFactory { Service getService(); } class Implementation1 implements Service { public void method1() { System.out.println("Implementation1 method1"); } public void method2() { System.out.println("Implementation1 method2"); } } class Implementation1Factory implements ServiceFactory { public Service getService() { return new Implementation1(); } } class Implementation2 implements Service { public void method1() { System.out.println("Implementation2 method1"); } public void method2() { System.out.println("Implementation2 method2"); } } class Implementation2Factory implements ServiceFactory { public Service getService() { return new Implementation2(); } } public class Factories { public static void serviceConsumer (ServiceFactory fact) { Service s = new fact.getService(); s.method1(); s.method2(); } public static void main(String[] args) { serviceConsumer( new Implementation1Factory() ); serviceConsumer( new Implementation2Factory() ); } /*Output Implementation1 method1 Implementation1 method2 Implementation2 method1 Implementation2 method2 */
如果不是用工厂方式,上面代码就必须在某处指定将要创建的Service的确切类型,以便调用合适的构造器。
10.总结
任何抽象性都应该是应真正的需求而产生的。恰当的原则是优先选择类而不是接口。从类开始,如果接口的必须性变得非常明确,那么就进行重构。接口是一种只要的工具,但容易被滥用。