抽象类和抽象方法
在第8章中所有的“乐器”的例子中,基类Instrument中的方法往往是“哑”的,若要调用这些方法,就会出现一些错误。这是因为Instrument类的目的是为它所有的导出类创建一个通用接口。
在那些实例中,建立这个通用接口的唯一理由是,不用的子类可以用不同的方式表示此接口。通用接口建立起一种基本形式,依次表示所有的导出类的共同部分,另一种说法是将Instrument类称为抽象基类,或者简称抽象类。
抽象方法:仅有声明没有方法体,abstract修饰的方法。
抽象类:
1)包含抽象方法的类,必须限定为抽象类。
2)abstract修饰的类。抽象类不能被实例化。
3)抽象类可以没有抽象方法。
4)如果继承一个抽象类,就必须实现所有的抽象方法。如果不实现,那么导出类也是抽象类,编译器会强制用abstract关键字来限定这个类。
第8章Instrument类可以很容易转化为abstract类。既然某个类成为抽象类并不需要所有的方法都是抽象的,所以仅需将某些方法声明为抽象的即可。
abstract class Instrument {
private int i; // Storage allocated for each
public abstract void play(Note n);
public String what() { return "Instrument"; }
public abstract void adjust();
}
class Wind extends Instrument {
public void play(Note n) {
System.out.println("Wind.play() " + n);
}
public String what() { return "Wind"; }
public void adjust() {}
}
class Percussion extends Instrument {
public void play(Note n) {
System.out.println("Percussion.play() " + n);
}
public String what() { return "Percussion"; }
public void adjust() {}
}
class Stringed extends Instrument {
public void play(Note n) {
System.out.println("Stringed.play() " + n);
}
public String what() { return "Stringed"; }
public void adjust() {}
}
class Brass extends Wind {
public void play(Note n) {
System.out.println("Brass.play() " + n);
}
public void adjust() { System.out.println("Brass.adjust()"); }
}
class Woodwind extends Wind {
public void play(Note n) {
System.out.println("Woodwind.play() " + n);
}
public String what() { return "Woodwind"; }
}
public class Music4 {
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) {
Instrument[] orchestra = {
new Wind(),
new Percussion(),
new Stringed(),
new Brass(),
new Woodwind()
};
tuneAll(orchestra);
}
}
除了基类,实际上没有改变。
创建抽象类和抽象方法非常有用,因为它们可以使类的抽象性明确起来,并告诉用户和编译器打算怎么使用他们
接口
interface关键字使抽象的概念更向前买进了 一步,abstract关键字允许人们在类中创建一个或多个没有任何方法体的方法,这些实现由继承者实现。interface则是一个完全抽象的类,任何方法都没有方法体。
一个接口表示‘所有实现了该特定类接口的类都看起来像这样’
使用implement关键字实现接口,一单实现了某个接口,其实现就变成了一个普通类。
interface Instrument {
int VALUE = 5;
void play(Note n);
void adjust();
}
class Wind implements Instrument {
public void play(Note n) {
System.out.println(this + ".play() " + n);
}
public String toString() { return "Wind"; }
public void adjust() { System.out.println(this + ".adjust()"); }
}
class Percussion implements Instrument {
public void play(Note n) {
System.out.println(this + ".play() " + n);
}
public String toString() { return "Percussion"; }
public void adjust() { System.out.println(this + ".adjust()"); }
}
class Stringed implements Instrument {
public void play(Note n) {
System.out.println(this + ".play() " + n);
}
public String toString() { return "Stringed"; }
public void adjust() { System.out.println(this + ".adjust()"); }
}
class Brass extends Wind {
public String toString() { return "Brass"; }
}
class Woodwind extends Wind {
public String toString() { return "Woodwind"; }
}
public class Music5 {
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) {
Instrument[] orchestra = {
new Wind(),
new Percussion(),
new Stringed(),
new Brass(),
new Woodwind()
};
tuneAll(orchestra);
}
}
完全解耦
只要一个方法让操作的是类而非接口,那么你就只能使用这个类及其子类。接口可以在很大程度上放宽这种限制。
例如,假设有一个Processor类,他有一个name()方法,另外还有一个process()方法,该方法接受输入参数,修改它的值,然后产生输出。这个类作为基类被扩展,用来创建各种不同的类型的Prcocessor. 在本例中,Processor的子类将修改String对象。(返回类型可以是协变参数,而非参数类型)
class Processor {
public String name() {
return getClass().getSimpleName();
}
Object process(Object input) {
return input;
}
}
class Upcase extends Processor {
String process(Object input) {
return ((String)input).toUpperCase();
}
}
class Downcase extends Processor {
String process(Object input) {
return ((String)input).toLowerCase();
}
}
class Splitter extends Processor {
String process(Object input) {
return Arrays.toString(((String)input).split(" "));
}
}
public class Apply {
public static void process(Processor p, Object s) {
System.out.println("Using Processor " + p.name());
System.out.println(p.process(s));
}
public static String s = "Disagreement with beliefs is by definition incorrect";
public static void main(String[] args) {
process(new Upcase(), s);
process(new Downcase(), s);
process(new Splitter(), s);
}
}
Apply.process()方法可以接受任何类型的Processor,并将其应用到一个Object对象上,然后打印结果。想本例这样,创建一个能够根据所传递带参数对象的不同而具有不同的行为的方法,被称为策略设计模式,这类方法包含所要执行的算法中固定的部分,而“策略”包含变化的部分。策略就是传递进去的参数对象,它包含要执行的代码。这里,Processor对象就是一个策略,在main()方法中可以看到三种不同类型的策略应用到了String类型的s对象上。
现在假设我们发现了一组电子滤波器,他们看起来好像适用于Apply.process()方法:
public class Filter {
public String name() {
return getClass().getSimpleName();
}
public Waveform process(Waveform input) {
return input;
}
}
public class Waveform {
private static long counter;
private final long id = counter++;
public String toString() {
return "Waveform " + id;
}
}
public class LowPass extends Filter {
double cutoff;
public LowPass(double cutoff) {
this.cutoff = cutoff;
}
public Waveform process(Waveform input) {
return input; // Dummy processing
}
}
public class HighPass extends Filter {
double cutoff;
public HighPass(double cutoff) {
this.cutoff = cutoff;
}
public Waveform process(Waveform input) {
return input;
}
}
public class BandPass extends Filter {
double lowCutoff, highCutoff;
public BandPass(double lowCut, double highCut) {
lowCutoff = lowCut;
highCutoff = highCut;
}
public Waveform process(Waveform input) {
return input;
}
}
Filter和Processor具有相同的元素,但是因为它并不是继承自Processor----因为不能将Filter用于Apply.processor()方法,这里主要是因为Apply.Processor()方法和Processor之间的耦合过紧,但是如果Process是一个接口,那么这些限制就会变得松动,下面是修改版:
public interface Processor {
String name();
Object process(Object input);
}
public class Apply {
public static void process(Processor p, Object s) {
System.out.println("Using Processor " + p.name());
System.out.println(p.process(s));
}
}
复用代码的第一种方式是客户端程序员遵循该接口来编写他们自己的类,就像下面这样
public abstract class StringProcessor implements Processor{
public String name() {
return getClass().getSimpleName();
}
public abstract String process(Object input);
public static String s = "If she weighs the same as a duck, she's made of wood";
public static void main(String[] args) {
Apply.process(new Upcase(), s);
Apply.process(new Downcase(), s);
Apply.process(new Splitter(), s);
}
}
class Upcase extends StringProcessor {
public String process(Object input) { // Covariant return
return ((String)input).toUpperCase();
}
}
class Downcase extends StringProcessor {
public String process(Object input) {
return ((String)input).toLowerCase();
}
}
class Splitter extends StringProcessor {
public String process(Object input) {
return Arrays.toString(((String)input).split(" "));
}
}
但是,你经常碰到的情况是你无法修改你要使用的类。例如,在电子滤波器的例子中,类库是被发现而非创建的,在这种情况下,可以使用适配器模式。适配器中的代码将接受你所有拥有的接口,并产生你所需要的接口,就想下面这样:
class FilterAdapter implements Processor {
Filter filter;
public FilterAdapter(Filter filter) {
this.filter = filter;
}
public String name() {
return filter.name();
}
public Waveform process(Object input) {
return filter.process((Waveform)input);
}
}
public class FilterProcessor {
public static void main(String[] args) {
Waveform w = new Waveform();
Apply.process(new FilterAdapter(new LowPass(1.0)), w);
Apply.process(new FilterAdapter(new HighPass(2.0)), w);
Apply.process( new FilterAdapter(new BandPass(3.0, 4.0)), w);
}
}
在使用适配器的方式中,FilterAdapter的构造器接受你所拥有的接口Filter,然后生成具有你所需要的Processor接口的对象。你可能注意到了,在FilterAdapter类中用到了代理。
java中的接口多重继承
这个标题为书中的标题,其实就是说了继承的同时可以实现多个接口
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
}
}
通过继承来扩展接口
在java中类是单继承的,但是接口是可以多继承的
interface Monster {
void menace();
}
interface DangerousMonster extends Monster {
void destroy();
}
interface Lethal {
void kill();
}
interface Vampire extends DangerousMonster, Lethal {
void drinkBlood();
}
适配接口
接口最吸引人的原因之一就是允许同一个接口具有多个不同具体实现。在简单的情况中,它的体现形式通常是一个接受接口类型的方法,而该接口的实现和该方法传递的对象则取决于方法的使用者
public class RandomWords implements Readable {
private static Random rand = new Random(47);
private static final char[] capitals = "ABCDEFGHIJKLMNOPQRSTUVWXYZ".toCharArray();
private static final char[] lowers = "abcdefghijklmnopqrstuvwxyz".toCharArray();
private static final char[] vowels = "aeiou".toCharArray();
private int count;
public RandomWords(int count) {
this.count = count;
}
public int read(CharBuffer cb) {
if(count-- == 0)
return -1;
cb.append(capitals[rand.nextInt(capitals.length)]);
for(int i = 0; i < 4; i++) {
cb.append(vowels[rand.nextInt(vowels.length)]);
cb.append(lowers[rand.nextInt(lowers.length)]);
}
cb.append(" ");
return 10;
}
public static void main(String[] args) {
Scanner s = new Scanner(new RandomWords(10));
while(s.hasNext())
System.out.println(s.next());
}
}
假设你还有一个未实现的Readable的类,怎么才能让Scanner作用于它呢,下面这个类就是一个例子,它可以产生随机浮点数
public class RandomDoubles {
private static Random rand = new Random(47);
public double next() {
return rand.nextDouble();
}
public static void main(String[] args) {
RandomDoubles rd = new RandomDoubles();
for(int i = 0; i < 7; i ++)
System.out.print(rd.next() + " ");
}
}
我们再次使用了适配器模式,但是在本例中,被适配的类可以通过继承和实现Readable接口来创建。因此,通过使用interface关键字提供的伪多重继承机制,我们可以生成即是RandomDoubles又是Readable的新类。
public class AdaptedRandomDoubles extends RandomDoubles implements Readable {
private int count;
public AdaptedRandomDoubles(int count) {
this.count = count;
}
public int read(CharBuffer cb) {
if(count-- == 0)
return -1;
String result = Double.toString(next()) + " ";
cb.append(result);
return result.length();
}
public static void main(String[] args) {
Scanner s = new Scanner(new AdaptedRandomDoubles(7));
while(s.hasNextDouble())
System.out.print(s.nextDouble() + " ");
}
}
嵌套接口
接口可以嵌套在类或其他的接口中。这揭示了许多非常有趣的特性。
class A {
interface B {
void f();
}
public class BImp implements B {
public void f() {}
}
private class BImp2 implements B {
public void f() {}
}
public interface C {
void f();
}
class CImp implements C {
public void f() {}
}
private class CImp2 implements C {
public void f() {}
}
private interface D {
void f();
}
private class DImp implements D {
public void f() {}
}
public class DImp2 implements D {
public void f() {}
}
public D getD() {
return new DImp2();
}
private D dRef;
public void receiveD(D d) {
dRef = d;
dRef.f();
}
}
interface E {
interface G {
void f();
}
// Redundant "public":
public interface H {
void f();
}
void g();
// Cannot be private within an interface:
//! private interface I {}
}
public class NestingInterfaces {
public class BImp implements A.B {
public void f() {}
}
class CImp implements A.C {
public void f() {}
}
class EImp implements E {
public void g() {}
}
class EGImp implements E.G {
public void f() {}
}
class EImp2 implements E {
public void g() {}
class EG implements G {
public void f() {}
}
}
public static void main(String[] args) {
A a = new A();
// Can't access A.D:
//! A.D ad = a.getD();
// Doesn't return anything but A.D:
//! A.DImp2 di2 = a.getD();
// Cannot access a member of the interface:
//! a.getD().f();
// Only another A can do anything with getD():
A a2 = new A();
a2.receiveD(a.getD());
}
}
接口与工厂
接口是实现多重继承的途径,而生成遵循某个接口的对象的经典方式就是工厂方式设计模式。这与直接调用构造器不同,我们在工厂对象上调用的是创建方法,而该工厂对象将生成接口的某个实现的对象家,理论上,通过这种方式,我们的代码将完全与接口的实现分离,这就使得我们可以透明的将某个实现替换为另一个实现,下面的实例展示工厂方法的结构
interface Service {
void method1();
void method2();
}
interface ServiceFactory {
Service getService();
}
class Implementation1 implements Service {
Implementation1() {} // Package access
public void method1() {
System.out.println("Implementation1 method1");
}
public void method2() {
System.out.println("Implementation1 method2");
}
}
class Implementation1Factory implements ServiceFactory {
public Service getService() {
return new Implementation1();
}
}
class Implementation2 implements Service {
Implementation2() {} // Package access
public void method1() {
System.out.println("Implementation2 method1");
}
public void method2() {
System.out.println("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());
}
}
如果不是工厂方法,你的代码就必须在某处指定将要创建的Service的确切类型,以便调用合适的构造器,为什么要添加这种额外级别的间接性呢?一个常见的原因就是要创建框架:假设正在创一个对弈游戏系统,例如,在相同的棋盘上下国际象棋和西洋跳棋:
interface Game {
boolean move();
}
interface GameFactory {
Game getGame();
}
class Checkers implements Game {
private int moves = 0;
private static final int MOVES = 3;
public boolean move() {
System.out.println("Checkers move " + moves);
return ++moves != MOVES;
}
}
class CheckersFactory implements GameFactory {
public Game getGame() {
return new Checkers();
}
}
class Chess implements Game {
private int moves = 0;
private static final int MOVES = 4;
public boolean move() {
System.out.println("Chess move " + moves);
return ++moves != MOVES;
}
}
class ChessFactory implements GameFactory {
public Game getGame() {
return new Chess();
}
}
public class Games {
public static void playGame(GameFactory factory) {
Game s = factory.getGame();
while(s.move());
}
public static void main(String[] args) {
playGame(new CheckersFactory());
playGame(new ChessFactory());
}
}
这种方式就允许你在不同类型的游戏中复用这段代码,你可以想象一些能够从这个模式中收益的更加精巧的游戏。
在下一章中,你将看到另一种更加优雅的工厂实现方式,那就是使用匿名类。