第九章 接口
接口和内部类为我们提供了一种将接口与实现分离的更加结构化的方法。
9.1 抽象类和抽象方法
Java提供一个叫做抽象方法的机制,这个机制是不完整的,仅有声明而没有方法体,抽象方法的语法如下:
abstract void f();
包含抽象方法的类叫做抽象类,如果一个类包含一个或者多个抽象方法,该类必须被限定为抽象的,否则编译器就会报错。 如果一个抽象类不完整,那么当我们试图产生该类的对象时,由于抽象类创建对象时不安全的,所以我们会从编译器那里得到一条出错消息,这样编译器会确保抽象类的纯粹性,我们不必担心会误用它。
如果从一个抽象类继承,并想创建该类的对象,就必须为基类中所有抽象方法提供方法定义没如果不这样做那么导出类便也是抽象类,且编译器会强制我们用abstract关键字来限定这个类。
上一章的Instrument类可以很容易转化成abstract类,既然使某个类成为抽象类并不需要所有方法都是抽象的,所以仅需将某些方法声明为抽象即可,如下图所示:
我们修改一下上面的管弦乐的例子:
package com.example.demo; abstract class Instrument4 { private int i; public abstract void play(Note n); public String what() { return "Instrument4"; } public abstract void adjust(); } class Wind4 extends Instrument4 { public void play(Note n) { System.out.println("Wind4.play() " + n); } public String what() { return "Wind4"; } public void adjust() {} } class Percussion4 extends Instrument4 { public void play(Note n) { System.out.println("Percussion4.play() " + n); } public String what() { return "Percussion4"; } public void adjust() {} } class Stringed4 extends Instrument4 { public void play(Note n) { System.out.println("Stringed4.play() " + n); } public String what() { return "Stringed4"; } public void adjust() {} } class Brass4 extends Wind4 { public void play(Note n) { System.out.println("Brass4.play() " + n); } public void adjust() { System.out.println("Brass.adjust()"); } } class Woodwind4 extends Wind4 { public void play(Note n) { System.out.println("Woodwind4.play() " + n); } public String what() { return "Woodwind"; } } public class Music4 { static void tune(Instrument4 i) { i.play(Note.MIDDLE_C); } static void tuneAll(Instrument4[] e) { for(Instrument4 i : e) tune(i); } public static void main(String[] args) { Instrument4[] orchestra = { new Wind4(), new Percussion4(), new Stringed4(), new Brass4(), new Woodwind4() }; tuneAll(orchestra); } }
输出结果为:
Wind4.play() MIDDLE_C
Percussion4.play() MIDDLE_C
Stringed4.play() MIDDLE_C
Brass4.play() MIDDLE_C
Woodwind4.play() MIDDLE_C
我们可以看出除了基类,实际上没什么改变。
创建抽象类和抽象方法非常有用,因为它们可以使类的抽象性明确起来,并告诉用户和编译器打算怎样来使用它们。
9.2 接口
Interface关键字使抽象的概念更向前迈进了一步,abstract允许我们在类中创建一个或多个没有任何定义的方法,它提供了接口部分,但没有提供任何相应的具体实现,这些实现是由此类的继承者创建的。Interface这个关键字产生一个完全抽象的类,它没有提供任何的具体实现,它允许创建者确定方法名,参数列表和返回类型,但是没有任何方法体,接口只是提供形式,没有任何具体实现。但是Interface不仅仅是一个极度抽象的类,因为它允许人们通过创建一个能够被向上转型为多种基类的类型来实现某种类似多重继变种的特性。
要想创建一个接口,需要用interface关键字来替代class关键字,就像类一样,可以在interface前面加public关键字,如果不添加public则只有包访问权限。接口也可以包含域,但是这些域隐式地是static和final的。
要让一个类遵循某个特定接口需要使用implement关键字,它表示要在这个类中对interface进行具体实现,看起来很像继承:
可以看到,一旦实现了某个接口,其实现就变成了一个普通的类,就可以按常规方式扩展它。可以选择在接口中显示将方法声明为public的,但即使你不这么做它们也是public的,否则的话它们将只能得到默认的包访问权限,这样在方法被继承的过程中,其访问权限就被降低了,这是Java编译器所不允许的。
9.3 完全解耦
只要一个方法操作时类而非接口,那么你就只能使用这个类及其子类,如果你想要将这个方法应用于不在此继承结构中的某个类,那么你就会触霉头了。接口可以在很大程度上放宽这种限制。
创建一个能够根据所传递的参数对象的不同而具有不同行为的方法,被称为策略设计模式,这类方法包含的所要执行的算法中固定不变的部分,而策略包含变化的部分,策略就是传递进去的参数对象,它包含要执行的代码。
9.4 Java 中的多重继承
接口不仅仅只是一种更纯粹形式的抽象类,它的目标比这要高,因为接口是根本没有任何具体实现的,也就是说没有任何与接口相关的存储,因此也就无法阻止多个接口的组合。在C++中,组合多个类的接口的行为被称作多重继承,在Java中你可以执行同样的行为,但是只有一个类可以有具体实现。
在导出类中不强制要求必须有一个是抽象的或具体的基类,如果要从一个非接口的类继承,那么只能从一个类去继承,其余的基元素都必须是接口,需要将所有的接口名都置于implements关键字之后,用都好一一隔开,可以继承任意多个接口,并向上转型为每个接口,因为每一个接口都是一个独立类型。
package com.example.demo; interface CanFight { void fight(); } interface CanSwim { void swim(); } interface CanFly { void fly(); } class ActionCharacter { public void fight() {} } class Hero extends ActionCharacter implements CanFight, CanSwim, CanFly { public void swim() {} public void fly() {} } public class Adventure { public static void t(CanFight x) {x.fight();} public static void u(CanSwim x) {x.swim();} public static void v(CanFly x) {x.fly();} public static void w(ActionCharacter x) {x.fight();} public static void main(String[] args) { Hero h = new Hero(); t(h); u(h); v(h); w(h); } }
可以看到,Hero组合了具体类ActionCharacter和接口CanFight、CanSwim、CanFly ,当通过这种方式将一个具体类和多个接口组合在一起时,这个具体类必须放在前面,后面跟着的才是接口。