接口和内部类为我们提供了一种将接口与实现分离的更加结构化的方法。
这种机制在编程语言中并不通用。例如,C++对这些概念只有间接的支持。Java中存在语言关键字这个事实表明人们认为这些思想是很重要的,以至于要提供对它们的直接支持。
首先我们将学习抽象类,它是普通类与接口之间的一种中庸之道。尽管构建具有某些未实现方法的类时,你的第一想法可能是创建接口,但是抽象类仍旧是用于此目的的一种重要而必须的工具,因为你不可能总是使用纯接口。
9.1 抽象类和抽象方法
Java提供一个叫做抽象方法的机制,这种方法是不完整的:仅有声明而没有方法体。下面是抽象方法声明所采用的语法:
abstract void f( );
包含抽象方法的类叫做抽象类。如果一个类包含一个或者多个抽象方法,该类必须被限定为抽象的。(否则,编译器就会报错。)
如果一个抽象类不完整,那么当我们试图产生该类的对象时,编译器会怎样处理呢?由于为抽象类创建对象时不安全的,所以我们会从编译器那里得到一条出错消息。这样,编译器会确保抽象类的纯粹性,我们不必担心误用它。
如果从一个抽象类继承,并想创建该新类的对象,那么必须为基类中的所有抽象方法提供方法定义。如果不这样做(可以选择不做),那么导出类便也是抽象类,且编译器将会强制我们用abstract关键字来限定这个类、
我们也可能会创建一个没有任何抽象方法的抽象类。考虑这种情况:如果有一个了,让其包含任何abstract方法方法都显得没有实际意义,而且我们也想要阻止产生这个类的任何对象,那么这时这样做就很有用了。
即使使某个非抽象类成为抽象类并不需要所有的方法都是抽象的,所以仅需将某些方法声明为抽象的即可。如下图所示:
下面是修改过的“管弦乐器”的例子,其中采用了抽象类和抽象方法:
//: interfaces/music4/Music4.java // Abstract classes and methods. packageinterfaces.music4; importpolymorphism.music.Note; importstaticnet.mindview.util.Print.*; abstractclassInstrument { privateinti; // Storage allocated for each publicabstractvoidplay(Noten); publicStringwhat() { return"Instrument"; } publicabstractvoidadjust(); } classWindextendsInstrument { publicvoidplay(Noten) { print("Wind.play() "+n); } publicStringwhat() { return"Wind"; } publicvoidadjust() {} } classPercussionextendsInstrument { publicvoidplay(Noten) { print("Percussion.play() "+n); } publicStringwhat() { return"Percussion"; } publicvoidadjust() {} } classStringedextendsInstrument { publicvoidplay(Noten) { print("Stringed.play() "+n); } publicStringwhat() { return"Stringed"; } publicvoidadjust() {} } classBrassextendsWind { publicvoidplay(Noten) { print("Brass.play() "+n); } publicvoidadjust() { print("Brass.adjust()"); } } classWoodwindextendsWind { publicvoidplay(Noten) { print("Woodwind.play() "+n); } publicStringwhat() { return"Woodwind"; } } publicclassMusic4 { // Doesn’t care about type, so new types // added to the system still work right: staticvoidtune(Instrumenti) { // ... i.play(Note.MIDDLE_C); } staticvoidtuneAll(Instrument[] e) { for(Instrumenti : e) tune(i); } publicstaticvoidmain(String[] args) { // Upcasting during addition to the array: Instrument[] orchestra= { newWind(), newPercussion(), newStringed(), newBrass(), newWoodwind() }; tuneAll(orchestra); } }
/* Output:
Wind.play() MIDDLE_C
Percussion.play() MIDDLE_C
Stringed.play() MIDDLE_C
Brass.play() MIDDLE_C
Woodwind.play() MIDDLE_C
*///:~
我们看到除了基类,实际上并没有什么改变。
创建抽象类和抽象方法非常有用,因为他们可以使类的抽象性明确起来,并告诉用户和编译器打算怎样来使用它们。抽象类还是很有用的重构工具,因为它们使得我们可以很容易地将公共方法沿着继承层次结构向上移动。
9.2 接口
interface关键字使抽象的概念更向前迈进了一步。abstract关键字允许人们在类中创建一个或者多个没有任何定义的方法——提供了接口部分,但是没有提供任何相应的具体实现,这些实现是由此类的继承者创建的。interface这个关键字产生一个完全抽象的类,它根本就没有提供任何具体的实现(新版本的JDK允许接口拥有默认实现)。它允许创建者确定方法名、参数列表和返回类型,但是没有任何方法体。接口只提供形式,而为提供任何具体的实现。
一个接口表示:“所有实现了该特定接口的类看起来都像这样”。因此,任何使用某特定接口的代码都知道可以调用该接口的哪些方法,而且仅需知道这些。因此,接口被用来建立类与类之间的协议。(某些面向对象编程语言使用关键字protocol来完成这一功能。)
但是,interface不仅仅是一个极度抽象的类,因为它允许人们通过创建一个能够被相送转型为多种基类的类型,来实现某种类似多重继承的特性。
要想创建一个接口,需要使用interface关键字来替代class关键字。就像一个类一样,可以在interface关键字前面添加public关键字(但仅限于该接口在与其同名的文件中被定义)。如果不添加public关键字,则它只具有包访问权限,这样它就只能在同一个包内可用。接口也可以包含域,但是这些域隐式地是static和final的。
要让一个类遵循某个特定接口或者一组接口,需要使用implements关键字,它表示:“interface只是它的外貌,但是现在我要声明它是如何工作的。”除此以外,它看起来还很像继承。“乐器”示例的图说明了这一点:
可以从Woodwind和Brass类中看到,一旦实现了某个接口,其实现就变成了一个普通的类,就可以按常规方式扩展它。
可以选择在接口中显式地将方法声明为public的,但即使你不这么做,它们也是public的。因此,当要实现一个接口时,在接口中被定义的方法必须被定义为public的;否则它们将只能得到默认的包访问权限,这样在方法被继承的过程中,其可访问权限就被降低了,这是Java编译器所不允许的。
//: interfaces/music5/Music5.java // Interfaces. packageinterfaces.music5; importpolymorphism.music.Note; importstaticnet.mindview.util.Print.*; interfaceInstrument { // Compile-time constant: intVALUE=5; // static & final // Cannot have method definitions: voidplay(Noten); // Automatically public voidadjust(); } classWindimplementsInstrument { publicvoidplay(Noten) { print(this+".play() "+n); } publicStringtoString() { return"Wind"; } publicvoidadjust() { print(this+".adjust()"); } } classPercussionimplementsInstrument { publicvoidplay(Noten) { print(this+".play() "+n); } publicStringtoString() { return"Percussion"; } publicvoidadjust() { print(this+".adjust()"); } } classStringedimplementsInstrument { publicvoidplay(Noten) { print(this+".play() "+n); } publicStringtoString() { return"Stringed"; } publicvoidadjust() { print(this+".adjust()"); } } classBrassextendsWind { publicStringtoString() { return"Brass"; } } classWoodwindextendsWind { publicStringtoString() { return"Woodwind"; } } publicclassMusic5 { // Doesn’t care about type, so new types // added to the system still work right: staticvoidtune(Instrumenti) { // ... i.play(Note.MIDDLE_C); } staticvoidtuneAll(Instrument[] e) { for(Instrumenti : e) tune(i); } publicstaticvoidmain(String[] args) { // Upcasting during addition to the array: Instrument[] orchestra= { newWind(), newPercussion(), newStringed(), newBrass(), newWoodwind() }; tuneAll(orchestra); } }
/* Output:
Wind.play() MIDDLE_C
Percussion.play() MIDDLE_C
Stringed.play() MIDDLE_C
Brass.play() MIDDLE_C
Woodwind.play() MIDDLE_C
*///:~