接口
- 接口和内部类为我们提供了一种接口和实现分离的更加结构化的方法。而抽象类是普通的类和接口之间的一种中庸之道,接下来先看看抽象类。
抽象类和抽象方法
- 先来说说这个名字,为什么要叫抽象类?也就是说它不是具体的,只是一个概念,而这个概念必须由它的导出类去实现。不一样的导出类会有不同的实现方式。抽象类可以想象是一个空模板,导出类可以再它的基础上自由发挥。由于是一个空的模板,我们无法直接使用抽象类去做一些实际性的操作。
- 但抽象类在Java中并不全是抽象的,他可以有普通的方法和抽象的方法。这个又是一种中庸之道。举个例子,比如说哺乳类,它是一个基类,它可以确定的成员有:四肢五官这些(也许不一定全有,这里只是举个例子);关于方法,可能有胎生,哺乳这些。但是有些方法却不能确定。比如说如何运动(run())。一些哺乳类可能是游,一些是走,一些是爬。在哺乳类中,这个行为是没办法确定的,但是哺乳类总是有这种行为,你又必须去定义。那怎么办呢?好,抽象方法就出来了。抽象方法是一个仅有声明而没有方法体的方法。只要一个类中包含抽象方法,那么这个类也必须声明为抽象类,之所以要定义成抽象类,是因为这个类无法像普通类一样直接使用(因为它有部分不完整)。不过一个抽象类也可以没有抽象方法,这种情况适用于你不想任何人直接使用这个类,但是可以去继承它。
- 抽象类和抽象方法需要用到abstract关键字。抽象方法不能是一个private的,因为这个方法需要重写。具体例子就不给了,很简单。
接口
- 利用interface关键字可以产生一个完全抽象的类,它允许创建者确定方法名、参数列表和返回类型,但是没有任何方法体。
- 接口中可以定义成员,这些成员隐式被声明为static和final,因为接口不能有实例,所以只能是static成员,至于为什么是final的,我想只是人为设计的吧。
- 接口中可以定义方法,接口中的方法访问权限只能是public(如果你不加修饰符,效果不是default而是public)。至于为什么必须是public,书上没有详细说明,我觉得接口的意义就是如果被implements了(因为接口本身可以是default的),那么所有方法都必须实现(如果是default那不是表明只需要同包下实现?这失去了接口的意义),而不限于包或者子类。更不可能是private的,所以只能是public。
- 既然接口中定义的方法都是public的,那我们为一个类实现接口方法的时候,也只能将方法定义成public,这是因为重写方法后不能降低访问权限。
- 关于为什么重写方法后不能降低访问权限,我这里再举个例子。因为可以向上转型(接口也可以)。假设父类中定义了一个public方法,继承的时候声明为protected的。那么问题来了,我在一个无关他们的类的方法中,将子类进行向上转型,那我到底能不能调用这个方法呢?父类的引用告诉我是可以调用的,而子类的protected又告诉我不能调用,这势必会引起混乱,所以重写方法后不能降低访问权限。
完全解耦
- 我们开始说一下接口的好处。只要一个方法操作的是类而非接口,那么你就只能操作这个类及其子类。如果你想要将这个方法应用于不在此继承结构中的类,那该如何做呢?这个情况很常见。比如说戴在手上这个操作,戒指,手镯,手套这些杂七杂八的东西都可以戴在手上。但是我们总不好说戒指和手镯手套之间有什么同类关系吧?此时就需要接口来放宽这种限制。
- 先来看看没有用到接口的例子:
import java.util.Arrays;
public class Apply {
public static String s =
"Disagreement with beliefs is by definition incorrect";
public static void process(Processor p, Object s) {
System.out.println("Using Processor " + p.name());
System.out.println(p.process(s));
}
public static void main(String args[]) {
process(new Upcase(), s);
process(new Downcase(), s);
process(new Splitter(), s);
}
}
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(" "));
}
}
-----------------------------执行结果
Using Processor Upcase
DISAGREEMENT WITH BELIEFS IS BY DEFINITION INCORRECT
Using Processor Downcase
disagreement with beliefs is by definition incorrect
Using Processor Splitter
[Disagreement, with, beliefs, is, by, definition, incorrect]
- 这里再复习一下多态。调用p.name()是调用父类的name(),但是由于getClass()也是动态绑定的方法,因此才没有输出Using Processor Processor,千万不要以为是子类也有这个方法。见下面的例子。
public class Test {
public String className = "Test";
public String getClassName() {
return className;
}
public static void main(String args[]) {
System.out.println(new TestSon().getClassName());
}
}
class TestSon extends Test {
public String className = "TestSon";
}
- Processor例子中,Apply.process()方法可以接收任何类型的Processor,并应用到一个Object对象上。这种被称为策略设计模式。所谓的策略就是传递进去的对象包含要执行的代码。这里Processor对象就是一个策略,在main()中可以看到三种不同类型的策略应用到了String类型的s对象上。
- 现在假设我们发现了一组电子滤波器,它们看起来好像也适用于Apply.process()方法:
public class Waveform {
private static long counter;
private final long id = counter++;
public String toString() {
return "Waveform " + id;
}
}
class Filter {
public String name() {
return getClass().getSimpleName();
}
public Waveform process(Waveform input) {
return input;
}
}
class LowPass extends Filter {
double cutoff;
public LowPass(double cutoff) { this.cutoff = cutoff;}
public Waveform process(Waveform input) {
return input;
}
}
class HighPass extends Filter {
double cutoff;
public HighPass(double cutoff) { this.cutoff = cutoff;}
public Waveform process(Waveform input) {
return input;
}
}
class BandPass extends Filter {
double lowCutoff, highCutoff;
public BandPass(double lowCutoff, double highCutoff) {
this.lowCutoff = lowCutoff;
this.highCutoff = highCutoff;
}
public Waveform process(Waveform input) {
return input;
}
}
- Filter和Processor具有相同的接口元素,但是它并非继承自Processor,因为Filter类的创建者压根不清楚你想要将它用作Processor。这里主要是因为Apply.process()方法和Processor之间的耦合过紧。
- 但是如果Processor是一个接口,那么这些限制就会变得松动。
import java.util.Arrays;
public class Apply {
public static String s =
"Disagreement with beliefs is by definition incorrect";
public static void process(Processor p, Object s) {
System.out.println("Using Processor " + p.name());
System.out.println(p.process(s));
}
public static void main(String args[]) {
process(new Upcase(), s);
process(new Downcase(), s);
process(new Splitter(), s);
}
}
interface Processor {
String name();
Object process(Object input);
}
abstract class StringProcessor implements Processor {
public String name() {
return getClass().getSimpleName();
}
public abstract String process(Object input);
}
class Upcase extends StringProcessor {
public String process(Object input) {
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(" "));
}
}
----输出结果和原来一样
- 如果电子滤波器这个例子是由我们自己定义的,我想大家都会了吧,只要将Filter定义成抽象类,然后让该抽象类实现Processor接口即可(一个name()方法和一个抽象的process方法)。
- 不过像这种电子滤波器一般都是我们无法修改的类库中的类。这个时候,我们需要使用适配器设计模式。其实就是一种代理:
public class FilterAdapter implements Processor {
private 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 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);
}
}
----------------------------运行结果:
Using Processor LowPass
Waveform 0
Using Processor HighPass
Waveform 0
Using Processor BandPass
Waveform 0
- 怎么样,上面这个例子很有意思吧。有了这种适配器模式,我们就可以为两个无法修改的类建立一个调用同名接口的方法了。以下是我的整合代码:
import java.util.Arrays;
public class Apply {
public static void process(ProcessorImpl p, Object s) {
System.out.println("Using Processor " + p.name());
System.out.println(p.process(s));
}
public static void main(String args[]) {
String s = "Disagreement with beliefs is by definition incorrect";
Apply.process(new ProcessorAdapter(new Upcase()), s);
Apply.process(new ProcessorAdapter(new Downcase()), s);
Apply.process(new ProcessorAdapter(new Splitter()), s);
System.out.println("---------------------------------");
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);
}
}
interface ProcessorImpl {
public String name();
public Object process(Object input);
}
class ProcessorAdapter implements ProcessorImpl {
private Processor processor;
public ProcessorAdapter(Processor processor) {
this.processor = processor;
}
public String name() {
return processor.name();
}
public Object process(Object input) {
return processor.process(input);
}
}
class FilterAdapter implements ProcessorImpl {
private 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);
}
}
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(" "));
}
}
class Waveform {
private static long counter;
private final long id = counter++;
public String toString() {
return "Waveform " + id;
}
}
class Filter {
public String name() {
return getClass().getSimpleName();
}
public Waveform process(Waveform input) {
return input;
}
}
class LowPass extends Filter {
double cutoff;
public LowPass(double cutoff) { this.cutoff = cutoff;}
public Waveform process(Waveform input) {
return input;
}
}
class HighPass extends Filter {
double cutoff;
public HighPass(double cutoff) { this.cutoff = cutoff;}
public Waveform process(Waveform input) {
return input;
}
}
class BandPass extends Filter {
double lowCutoff, highCutoff;
public BandPass(double lowCutoff, double highCutoff) {
this.lowCutoff = lowCutoff;
this.highCutoff = highCutoff;
}
public Waveform process(Waveform input) {
return input;
}
}
Java中的多重继承
- Java不存在直接通过extends进行多重继承,但是可以实现多个接口,从某种意义上来说,也算是一种多重继承吧。
public class Adventure {
public static void main(String args[]) {
Hero h = new Hero();
CanFight cf = h;
cf.fight();
CanSwim cw = h;
cw.swim();
CanFly cfly = h;
cfly.fly();
ActionCharacter ac = h;
ac.fight();
}
}
interface CanFight {
void fight();
}
interface CanSwim {
void swim();
}
interface CanFly {
void fly();
}
class ActionCharacter {
public void fight() {}
}
class Hero extends ActionCharacter implements CanFight,CanFly,CanSwim {
public void swim() {}
public void fly() {}
}
- 注意到继承ActionCharacter 类后可以不用在重写fight()方法,如果两个接口中包含同名方法,实现类中只要实现一个就行了,因为接口的意义就是保证实现类具有这个方法。
通过继承来扩展接口
- 接口的继承和普通的类的继承其实相差不大(接口可以不存在继承,但是所有的类除了Object都存在继承)。不过接口支持多重继承。这点也很好理解。其实普通类也可以进行多重继承,不过这种做法在Java看来利大于弊,所以Java就摈弃了这个特性。但是接口相对于类更加纯粹,实现多重继承一点也不复杂。
public class ImplTest implements SonInterface {
public void father() {
System.out.println("father");
}
public void mother() {
System.out.println("mother");
}
public void son() {
System.out.println("son");
}
public static void main(String args[]) {
ImplTest impl = new ImplTest();
SonInterface sif = impl;
sif.son();
sif.father();
sif.mother();
FatherInterface fif = impl;
fif.father();
MotherInterface mif = impl;
mif.mother();
}
}
interface FatherInterface {
void father();
}
interface MotherInterface {
void mother();
}
interface SonInterface extends FatherInterface, MotherInterface {
void son();
}
- 我们可以将一个实现了子接口的对象向上转型为父接口。因为实现了子接口的类必然也实现了父接口,这种转型是绝对没有问题的。不过要注意不要造成混淆,下面的方式是不被允许的:
public class Test {
void f() {}
}
interface I1 {
void f();
}
interface I2 {
int f();
}
- 我们都知道无法在同一个类中定义同名,同参数列表,但返回类型不同的两个方法。因为这两个方法不属于重载,这个在很前面的文章就提到过了。那如果我重新写一个接口继承了I1和I2或者定义一个继承了Test实现了I2的类都会造成这种错误。
适配接口
- 书上是一个关于Scanner类的构造方法传入Readable接口的例子,这里我打算使用另外一个例子:
public Thread(Runnable target) {
init(null, target, "Thread-" + nextThreadNum(), 0);
}
- 上面这个是Thread类的一个构造方法,其中Runnable接口需要实现run()方法,具体见下面例子:
import java.util.Random;
public class Test {
public static void main(String args[]) {
Thread t = new Thread(new MyClass());
t.start();
}
}
class MyClass implements Runnable {
private Random r = new Random();
public int nextInt() {
return r.nextInt();
}
public void run() {
int i = 10;
while (i > 0) {
System.out.println("random " + i-- + ": " + nextInt());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
- 无论我定义了什么类,只要我实现了Runnable 接口,我就可以通过这个类去构造一个Thread类,这样非常的方便。如果这个类是类库的里内,我们同样可以用适配器去解决(其实上面的例子也是适配器,用的是组合的方式,下面用的是继承的方式):
import java.util.Random;
public class Test {
public static void main(String args[]) {
Thread t = new Thread(new RandomAdapter());
t.start();
}
}
class RandomAdapter extends Random implements Runnable {
public void run() {
int i = 10;
while (i > 0) {
System.out.println("random " + i-- + ": " + nextInt());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
接口中的域
- 前面已经说了接口中定义的变量都是static和final的。他们拥有static和final的一切特征,这里就不再复述了。因为在JAVA SE5之前还没有enum,故在较老的代码中还可以见到这种写法。
嵌套接口
- 接口可以定义在类或其他接口中。通常我们定义接口都是public或者default,但是如果是嵌套接口,还可以定义成private(接口中嵌套的接口都是public的,有兴趣的可以试试)。嵌套接口如果不是private的话,那么用法除了类型要加上外部类外,且需要先创建外部对象(如果是接口中嵌套接口无需创建对象,下篇文章还要讲关于内部类的用法)外没有任何区别。如果是private的话,此接口就只能自己使用,也就是说在其他类中,无法向上转型(因为其他类中根本不知道有这么一个接口)。看下面的例子:
public class Test {
private String name;
public Test(String name) {
this.name = name;
}
private interface D {
void f();
}
public class inner implements D {
public void f() {
System.out.println(name);
}
}
public D getD() {
return new inner();
}
public void userD(D d) {
d.f();
}
}
import test.Test.inner;
public class Out {
public static void main(String args[]) {
Test t1 = new Test("t1");
Test t2 = new Test("t2");
((inner) t1.getD()).f();
t1.userD(t2.getD());
}
}
接口与工厂
- 接口是实现多重继承的途径,而生成遵循某个接口的对象的典型方式就是工厂设计模式。这与直接调用构造器不同,我们在工厂对象上调用的是创建方法,而该工厂对象将生成接口的某个实现的对象。
public class FactoriesTest {
public static void serviceConsumer (ServiceFactory sf) {
Service s = sf.getService();
s.method1();
s.method2();
}
public static void main(String args[]) {
serviceConsumer(new Implementation1Factroy());
serviceConsumer(new Implementation2Factroy());
}
}
interface Service {
void method1();
void method2();
}
interface ServiceFactory {
Service getService();
}
class Implementation1 implements Service {
public void method1() {
System.out.println("I1 M1");
}
public void method2() {
System.out.println("I1 M2");
}
}
class Implementation1Factroy implements ServiceFactory {
public Service getService() {
return new Implementation1();
}
}
class Implementation2 implements Service {
public void method1() {
System.out.println("I2 M1");
}
public void method2() {
System.out.println("I2 M2");
}
}
class Implementation2Factroy implements ServiceFactory {
public Service getService() {
return new Implementation2();
}
}
- 我们可以通过调用serviceConsumer ,传入不一样的工厂对象而获得不一样的服务。然后我们就可以拿着返回的对象再做一系列的操作。这个方法的复用性非常的高。也许你会有疑问,为什么要传入工厂,而不直接传入实现了Service的Implementation1或Implementation2对象呢。传入工厂对象连如何生成这些对象的操作都帮我们做好了,何乐而不为呢?如果生成服务对象需要很复杂的操作,这种写法也能使代码更加简洁。在下一章,可以学到更加优雅的工厂实现方式。