9.1 抽象类和方法
包含抽象方法的类被称为抽象类,如果一个类包含一个或多个抽象方法,该类必须被限定为抽象的(否则,编译器就会报错)。
如果想要继承一个抽象类,那么必须为这个新类定义抽象类中所有的方法,如果不这么做,那么导出类也是抽象类,且编译器会强制我们使用abstract来限定这个类。(不能为抽象类创建任何对象)
9.2接口
一个接口表示"所有实现了该特定接口的类看起来都像这样"。因此接口被用来建立类与类之间的协议。
要让一个类遵循某个特定接口(或是一组接口),需要使用implements关键字,“interface”只是它的外貌,但现在我要声明它是如何工作的。
可以选择在接口中显式的将方法声明为public的,但即使你不这么做,他们也是public的,因此,当要实现一个接口时,在接口中被定义的方法必须是public的,否则,他们将只能得到默认的包访问权限,这样在方法被继承的过程中,其访问权限就被降低了,这是java编译器不允许的。
//: interfaces/music5/Music5.java
// Interfaces.
package interfaces.music5;
import polymorphism.music.Note;
import static net.mindview.util.Print.*;
interface Instrument {
// Compile-time constant:
int VALUE = 5; // static & final
// Cannot have method definitions:
void play(Note n); // Automatically public
void adjust();
}
class Wind implements Instrument {
public void play(Note n) {
print(this + ".play() " + n);
}
public String toString() { return "Wind"; }
public void adjust() { print(this + ".adjust()"); }
}
class Percussion implements Instrument {
public void play(Note n) {
print(this + ".play() " + n);
}
public String toString() { return "Percussion"; }
public void adjust() { print(this + ".adjust()"); }
}
class Stringed implements Instrument {
public void play(Note n) {
print(this + ".play() " + n);
}
public String toString() { return "Stringed"; }
public void adjust() { print(this + ".adjust()"); }
}
class Brass extends Wind {
public String toString() { return "Brass"; }
}
class Woodwind extends Wind {
public String toString() { return "Woodwind"; }
}
public class Music5 {
// Doesn't care about type, so new types
// added to the system still work right:
static void tune(Instrument i) {
// ...
i.play(Note.MIDDLE_C);
}
static void tuneAll(Instrument[] e) {
for(Instrument i : e)
tune(i);
}
public static void main(String[] args) {
// Upcasting during addition to the array:
Instrument[] orchestra = {
new Wind(),
new Percussion(),
new Stringed(),
new Brass(),
new Woodwind()
};
tuneAll(orchestra);
}
} /* Output:
Wind.play() MIDDLE_C
Percussion.play() MIDDLE_C
Stringed.play() MIDDLE_C
Brass.play() MIDDLE_C
Woodwind.play() MIDDLE_C
*///:~
9.4 java的多重继承
导出类组合了具体类和几个接口时,这个具体类必须放在前面,后面跟着的才是接口(否则编译器会报错)。
//: 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(); } b
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
}
} ///:~
Canfight接口与ActionCharacter中的fight()特征签名是一样的,而且,在Hero中没有提供fight的定义,可以扩展接口,但是得到的知识另一个接口,当想要创建对象时,所有的定义首先必须都存在。即使hero没有提供fight的定义,其定义也在ActionCharacter中随之而来,这样使创建Hero对象成为了可能。
上面的例子展示的就是使用接口的原因: 为了能够向上转型为多个基型类(以及由此带来的灵活性),第二个原因却是与使用抽象基类相同:防止客户端程序员创建该类的对象,并确保这仅仅是一个接口,那么何时选择接口或者抽象类呢?如果创建不带任何方法定义和成员变量的基类,就应该选择接口而不是抽象类。
9.5通过继承来扩展接口
可以通过继承在新接口中组合多个接口
//: 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);
}
}
组合不同的接口中中使用相同的方法名通常会造成代码可读性的混乱,请尽量避免这种情况.
9.6 适配接口
接口最吸引人的原因之一就是允许同一个接口有多个不同的实现,在简单的情况下,他的体现形式通常是一个接受接口类型的方法,而该接口的实现和向该方法传递的对象则取决于方法的使用者。
j例如,java的Scanner类的构造器接受的就是一个Readable接口,你会发现readable用作java标准库中其他任何方法的参数,它是单独为scanner创建的,以使Scanner不必将其参数限制为某个特定类,这样Scanner可以作用于更多的类型。
Readable接口只要求实现read()方法,在read()内部,输入内容添加到CharBuffer参数中,或者在没有任何输入时返回-1、
9.7 接口中的域
放入接口中的任何域都自动是static和final的,所以接口成为了一种创建常量组的工具。
//: interfaces/Months.java
// Using interfaces to create groups of constants.
package interfaces;
public interface Months {
int
JANUARY = 1, FEBRUARY = 2, MARCH = 3,
APRIL = 4, MAY = 5, JUNE = 6, JULY = 7,
AUGUST = 8, SEPTEMBER = 9, OCTOBER = 10,
NOVEMBER = 11, DECEMBER = 12;
} ///:~
9.7.1 初始化接口中的域
在接口中定义的域不能是“空final”,但是可以被非常量表达式初始化。
9.9接口与工厂
生成某个遵循接口的对象的典型方法是工厂方法设计模式,这与直接调用构造器不同,我们在工厂对象上调用的是创建方法,而该工厂对象将生成接口的某个实现的对象,理论上,通过这种方式,我们的代码将完全与接口的实现分离,这就是得我们可以透明的将摸个实现替换为另一个实现
//: interfaces/Factories.java
import static net.mindview.util.Print.*;
interface Service1 {
void method1();
void method2();
}
interface ServiceFactory {
Service1 getService();
}
class Implementation1 implements Service1 {
Implementation1() {} // Package access
public void method1() {print("Implementation1 method1");}
public void method2() {print("Implementation1 method2");}
}
class Implementation1Factory implements ServiceFactory {
public Service1 getService() {
return new Implementation1();
}
}
class Implementation2 implements Service1 {
Implementation2() {} // Package access
public void method1() {print("Implementation2 method1");}
public void method2() {print("Implementation2 method2");}
}
class Implementation2Factory implements ServiceFactory {
public Service1 getService() {
return new Implementation2();
}
}
public class Factories {
public static void serviceConsumer(ServiceFactory fact) {
Service1 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
*///:~
9.10总结
任何抽象类都应该是应真正的需求产生的,当必须时,你应该重构接口而不是到处添加额外级别的间接性,并由此带来额外的复杂性。恰当的原则应该是优先选择类而不是接口,从类开始,如果接口的必须性变得非常明确,那么久应该进行重构,接口是一种重要的工具,但是他们容易被滥用。
含有main方法的类中只有static方法可以被直接调用,否则要创建该类的对象,才能对该方法进行调用。