抽象类和抽象方法:
- 抽象类是普通的类和接口之间的一种中庸之道。我们创建抽象类是希望通过这个通用接口操纵一系列类。抽象类中方法不一定都是抽象方法。我们可能会创建一个没有任何抽象方法的抽象类。
- 创建抽象类和抽象方法非常有用,因为它们可以使类的抽象性明确起来,并告诉用户和编译器打算怎样来使用它们。抽象类还是很有用的重构工具,因为它们使得我们可以很容易将公共方法沿着继承层次结构向上移动。
接口:
- 接口是极度抽象的类,它没有提供任何具体实现,它允许创建者确定方法名,返回类型,参数列表,但是没有任何方法体。接口也可以包含域,但是这些域隐式的是static和final的。
- 接口中的方法即使你不显式地将方法声明成public,它们也是public的。在接口中被定义的方法必须是public的,否则,它们将只能得到默认的包访问权限,这样在方法继承的过程中,其可访问权限就被降低了,这是Java编译器所不允许的。
- 可以同时实现多个接口,也可以继承和实现接口同时进行,如下例:
上例中,虽然Hero虽然没有实现fight()方法,但是由于继承的基类中有fight()方法的定义,所以在创建hero的对象时,fight()定义也就随基类而来。//: interfaces/Adventure.java // Multiple interfaces. 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); // Treat it as a CanFight u(h); // Treat it as a CanSwim v(h); // Treat it as a CanFly w(h); // Treat it as an ActionCharacter } } ///:~
- 上面这个例子展示了使用接口的核心原因:为了能够向上转型为多个基类型(以及由此而带来的灵活性)。使用接口的第二个原因与使用抽象基类相同:防止客户端程序员创建该类的对象,并确保这仅仅是建立一个接口。
- 关于接口和抽象类的选择:如果要创建不带任何方法定义和成员变量的基类,那么就应该选择接口而不是抽象类。事实上,如果知道某事物应该成为一个基类,那么第一选择应该是使它成为一个接口。
使用继承扩展接口:
通过继承,可以很容易在接口中添加新的方法声明,还可以通过继承在新接口中组合数个接口。这两种情况都可以获得新的接口。看下例:
//: interfaces/HorrorShow.java
// Extending an interface with inheritance.
interface Monster {
void menace();
}
interface DangerousMonster extends Monster {
void destroy();
}
interface Lethal {
void kill();
}
class DragonZilla implements DangerousMonster {
public void menace() {}
public void destroy() {}
}
interface Vampire extends DangerousMonster, Lethal {
void drinkBlood();
}
class VeryBadVampire implements Vampire {
public void menace() {}
public void destroy() {}
public void kill() {}
public void drinkBlood() {}
}
public class HorrorShow {
static void u(Monster b) { b.menace(); }
static void v(DangerousMonster d) {
d.menace();
d.destroy();
}
static void w(Lethal l) { l.kill(); }
public static void main(String[] args) {
DangerousMonster barney = new DragonZilla();
u(barney);
v(barney);
Vampire vlad = new VeryBadVampire();
u(vlad);
v(vlad);
w(vlad);
}
} ///:~
在上例中,Vampire使用的语法仅适用于接口继承。一般情况下,只可以将extends用于单一类,但是可以引用多个基类接口。就像所看到的,只需用逗号将接口名一一分隔开即可。
在实现多重继承时,如果在打算组合的不同接口中使用相同的方法名通常会造成代码可读性的混乱,如下例:
//: interfaces/InterfaceCollision.java
package interfaces;
interface I1 { void f(); }
interface I2 { int f(int i); }
interface I3 { int f(); }
class C { public int f() { return 1; } }
class C2 implements I1, I2 {
public void f() {}
public int f(int i) { return 1; } // overloaded
}
class C3 extends C implements I2 {
public int f(int i) { return 1; } // overloaded
}
class C4 extends C implements I3 {
// Identical, no problem:
public int f() { return 1; }
}
// Methods differ only by return type:
//! class C5 extends C implements I1 {}
//! interface I4 extends I1, I3 {} ///:~
上例中的最后两行在编译时会出错,因为重载方法仅通过返回类型时区分不开的。
接口和工厂:
接口是实现多重继承的途径,而生成遵循某个接口的对象的典型方式就是工厂方法设计模式。这与直接调用构造器不同,我们在工厂对象上调用的是创建方法,而该工厂对象将生成接口的某个实现对象。理论上,通过这种方式,我们的代码将完全与接口的实现分离。这就使得我们可以透明的将,某个实现替换为另一个实现,下例展示了工厂方法的结构:
//: interfaces/Factories.java
import static net.mindview.util.Print.*;
interface Service {
void method1();
void method2();
}
interface ServiceFactory {
Service getService();
}
class Implementation1 implements Service {
Implementation1() {} // Package access
public void method1() {print("Implementation1 method1");}
public void method2() {print("Implementation1 method2");}
}
class Implementation1Factory implements ServiceFactory {
public Service getService() {
return new Implementation1();
}
}
class Implementation2 implements Service {
Implementation2() {} // Package access
public void method1() {print("Implementation2 method1");}
public void method2() {print("Implementation2 method2");}
}
class Implementation2Factory implements ServiceFactory {
public Service getService() {
return new Implementation2();
}
}
public class Factories {
public static void serviceConsumer(ServiceFactory fact) {
Service s = fact.getService();
s.method1();
s.method2();
}
public static void main(String[] args) {
serviceConsumer(new Implementation1Factory());
// Implementations are completely interchangeable:
serviceConsumer(new Implementation2Factory());
}
} /* Output:
Implementation1 method1
Implementation1 method2
Implementation2 method1
Implementation2 method2
*///:~
看了上例我们可能会有疑问,对于接口和实现类,例中都额外又添加了一个factory类,那么为什么我们想要添加这种额外级别的间接性呢?一个常见的原因是想要创建框架。