抽象类
如果我们只有一个像Instrument这样的抽象类,那么该类的对象几乎没有任何意义,我们创建抽象类是希望通过这个通用接口操纵一系列类。因此,Instrument只是表示了一个接口,没有具体的实现内容,创建一个Instrument对象没有什么意义,并且我们可能还想阻止使用者这样做,通过让Instrument中的所有方都产生错误就可以实现这个目的,但是这样做会讲错误信息延迟到运行时才获得,并且需要在客户端进行可靠、详尽的测试,所以最好在编译时捕获这些问题。
为此,Java提供了一个叫做抽象方法的机制,这种方法是不完整的,仅有声明而没有方法体,如果一个抽象类不完整,那么当我们试图产生该类的对象时,由于为抽象类创建对象时不安全的,所以我们会从编译器那里得到一条出错信息。
接口
interface关键字使抽象的概念更向前迈进了一步,一个接口表示:“所有实现了该特定接口的类看起来都像这样”,因此任何使用某特定接口的代码都知道可以调用该接口的哪些方法,而且仅需知道这些。接口被用来建立类与类之间的协议。
- 接口里的方法默认都是public,毕竟协议是需要公开暴露的。
- 接口里的域默认都是static的,在多接口实现场景可以区分同名域属于哪个接口。
- 接口里的域默认都是final的,如果域可以修改,那么就没有继承意义,接口要的就是协议化,另外一个角度:因为是static的所以一个接口的域只能有一个实例,所以需要用的话只能在接口先初始化,对于实现类对象没有任何修改的意义。
- 因为枚举enum的出现,所以用接口单纯描述域就显得过时了。
多重继承
接口不仅仅只是一种更纯粹形式的抽象类,它的目标比这更高,因为接口是根本没有任何具体实现的,也没有任何与接口相关的存储,因此也就无法阻止多个接口的组合。
在导出类中,不强制要求有一个是抽象的或具体的基类,如果要从一个非接口的类继承,那么只能从一个类去继承,其余的基元素必须使接口。可以继承任意多个接口,并可以向上转型为每个接口,因为每个接口都是一个独立的类型。
interface CanFight {
void fight();
}
interface CanSwim {
void swim();
}
interface CanFly() {
void fly();
}
class ActionCharacter {
public void fight() {}
}
class Hero extends ActionCharater 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);
}
}
复制代码
注意,CanFight接口与ActionCharater类中的fight()方法的特征签名是一样的,而且在Hero中并没有提供fight()的定义,其定义就是来自于ActionCharater,但如果两者返回值类型不同,编译器会执行拒绝。
多重继承是可以对原有类库接口进行适配的重要方式。
接口本身可以进行多重继承。
interface Monster {
void menace();
}
interface DangerousMonster extends Monster {
void destroy();
}
interface Lethal {
void kill();
}
interface Vampire extends DangerousMonster, Lethal {
void drinkBlood();
}
复制代码
嵌套接口
接口可以嵌套在类或者其他接口中。
- 作用于接口的各种规则,特别是所有的接口元素都必须是public,因此嵌套在另一个接口中的接口自动就是public的,而不能声明为private的,除非内部刚好有对应的实现类和对这个private接口的public访问方法。
- 当实现某个接口时,并不需要实现嵌套在其内部的任何接口,此时嵌套接口相当于内部成员变量,未必要被实现,可以单独实现外部接口或者嵌套接口。
我们应该使用接口还是抽象类?恰当的原则应该是优先选择类而不是接口,从类开始,如果接口的必需性变得非常明确,那么就进行重构。