注:本博客内容是本人在看《Jave编程思想》这本书时从该书上抄录下来的一些片段。这里也强烈建议各位读者去购买这本书进行阅读学习。
接口和内部类为我们提供了一种将接口与实现分离的更加结构化的方法。首先,先学习抽象类,它是普通类与接口之间的一种中庸之道。尽管在构建具有某种为实现方法的类时,你的第一想法可能就是创建接口,但是抽象类仍旧是用于此目的的一种重要而必须的工具。因为你不可能总是使用纯接口。
一、抽象类和抽象方法
抽象类的创建是希望通过这个通用接口操作一系列类,抽象类只是一个接口,没有具体的实现内容,因此创建一个抽象类的对象是没有任何意义的,Java也阻止这样使用。
Java提供一种叫做抽象方法的机制,这种方法是不完整的;仅有声明而没有方法体。如下是抽象方法声明所采用的语法:
abstract void f();
包含抽象方法的类叫做抽象类。如果一个类包含一个或多个抽象方法,该类必须被限定为抽象的。否则,编译器就会报错。
如果一个抽象类不完整,那么当我们尝试产生该类的对象时,编译器会怎么处理呢?由于为抽象类创建对象是不安全的,所以我们会从编译器那里得到一个错误信息。这样,编译器确保抽象类的纯粹性,我们不必担心误用它们。
如果一个抽象类继承,并想创建该新类的对象,那么就必须为基类中所有抽象方法提供方法定义。如果不这样做(可以选择不做),那么导出类便也是抽象类,且编译器将会强制我们用abstract关键字来限定这个类。
我们也可以创建一个没有任何抽象方法的抽象类。考虑这种情况:如果有一个类,让其包含任何abstract方法都显得没有实际意义,而且我们也想要阻止产生这个类的任何对象,那么这时这样做就很有意义了。
既然使某个类成为抽象类并不需要所有的方法都是抽象的,所以仅需要将某些方法声明成抽象的即可。如下图所示:
示例代码如下:
package com.jackson.facade;
enum Node {
MIDDLE_D,
SMALL_L,
LARGE;
}
abstract class Instrument {
private int i;
public abstract void play(Node n);
public String what() {
return "Instrument";
}
public abstract void adjust();
}
class Wind extends Instrument {
@Override
public void play(Node n) {
System.out.println("Wind.play() " + n);
}
@Override
public void adjust() {
System.out.println("Wind.adjust()" );
}
@Override
public String what() {
return "Wind.what()";
}
}
class Percussion extends Instrument {
@Override
public void play(Node n) {
System.out.println("Percussion.play() " + n);
}
@Override
public void adjust() {
System.out.println("Percussion.adjust()");
}
}
class Stringed extends Instrument {
@Override
public void play(Node n) {
System.out.println("Stringed.play() " + n);
}
@Override
public String what() {
return "Stringed";
}
@Override
public void adjust() {
System.out.println("Stringed.adjust()");
}
}
class WoodWind extends Wind {
@Override
public void play(Node n) {
System.out.println("WoodWind.play() " + n);
}
@Override
public String what() {
return "WoodWind";
}
}
class Brass extends Wind {
@Override
public void play(Node n) {
System.out.println("Brass.play() " + n);
}
@Override
public String what() {
return "Brass";
}
}
public class Music {
static void tune(Instrument i) {
i.play(Node.MIDDLE_D);
}
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);
}
}
输出结果:
Wind.play() MIDDLE_D
Percussion.play() MIDDLE_D
Stringed.play() MIDDLE_D
Brass.play() MIDDLE_D
WoodWind.play() MIDDLE_D
创建抽象类和抽象方法非常有用,因为它们可以使类的抽象明确起来,并告诉用户和编译器打算怎样来使用它们。抽象类还是很有用的重构工具,因为它们使得我们可以很容易地将公共方法沿着继承层次结构向上移动。
二、接口
interface关键字使抽象的概念更向前迈进了一步。abstract关键字允许人们在类中创建一个或多个没有任何定义的方法——提供了接口部分,但是没有提供任何相应的具体的实现,这些实现是由此类的继承者创建的。interface这个关键字产生一个完全抽象的类,它根本没有提供任何具体的实现。它允许创建者确定方法名、参数列表和返回类型,但是没有任何方法体。接口提供了形式,而未提供任何具体的实现。
但是,interface不仅仅是一个极度抽象的类,因为它允许人们通过(多实现,实现多个接口)创建一个能够被向上转型为多种基类的类型,来实现某种类似多重继变种的特性。
要想创建一个接口,需要用关键字interface类替代class关键字。就像类一样,可以在interface关键字前面添加public关键字(但仅限于该接口名与其同名的文件中被定义)。如果不添加public关键字,则它只具有包访问权限,这样它就只能在同一个包内使用。接口也可以包含域,但是这些域隐式地是static和final的。
要让一个类遵循某个特定的接口(或者一组接口),需要使用implement关键字,它表示:“interface只是它的外貌,但是现在我要声明它是如何工作的。”除此之外,它看起来还很像继承。如下示例图说明了这一点:
可以选择在接口中显示将方法声明成puplic的,但即使你不这么做,它们也是puplic的。因此,当要实现一个接口时,在接口中被定义的方法必须被为是public的;否则,它们将只能够得到默认的包访问权限,这样在方法被继承的过程中,其可访问权限就被降低了,这是Java编译器所不允许的。示例代码如下:
package com.jackson.facade;
interface InstrumentFace {
// static & final
int VALUE = 5;
public static final int VALUE_1 = 10;
void play(Node n);
String what();
public void adjust();
}
class WindFace implements InstrumentFace {
@Override
public void play(Node n) {
System.out.println("WindFace.play() " + n);
}
@Override
public String what() {
return "WindFace.what()";
}
@Override
public void adjust() {
System.out.println("WindFace.adjust()");
}
}
class PercussionFace implements InstrumentFace {
@Override
public void play(Node n) {
System.out.println("PercussionFace.play() " + n);
}
@Override
public String what() {
return "PercussionFace.what()";
}
@Override
public void adjust() {
System.out.println("PercussionFace.what()");
}
}
class StringedFace implements InstrumentFace {
@Override
public void play(Node n) {
System.out.println("StringedFace.play() " + n);
}
@Override
public String what() {
return "StringedFace.what()";
}
@Override
public void adjust() {
System.out.println("StringedFace.adjust()");
}
}
class BrassFace extends WindFace {
@Override
public String toString() {
return "BrassFace";
}
}
class WoodWindFace extends WindFace {
@Override
public String toString() {
return "WoodWindFace";
}
}
public class Music1 {
static void tune(InstrumentFace i) {
i.play(Node.SMALL_L);
}
static void tuneAll(InstrumentFace [] e) {
for (InstrumentFace i : e) {
tune(i);
}
}
public static void main(String[] args) {
InstrumentFace [] arrs = {
new WindFace(),
new PercussionFace(),
new StringedFace(),
new BrassFace(),
new WoodWindFace()
};
tuneAll(arrs);
}
}
输出结果:
WindFace.play() SMALL_L
PercussionFace.play() SMALL_L
StringedFace.play() SMALL_L
WindFace.play() SMALL_L
WindFace.play() SMALL_L
三、完全解耦
只要一个方法操作的是类而不是接口,那么你就只能使用这个类及其子类。如果你想要将这个方法应用于不在此继承结构中的某个类,那么你就会触霉头了。接口可以在很大程度上放宽这种限制,因此,它使得我们可以编写可复用性更好的代码。
例如,假设有一个Processor类,它有一个name()方法;另外还有一个process()方法,该方法接受输入参数,修改它的值,然后产生输出。这个类作为基类而被继承,用来创建各种不同类型的Processor。在本例中,Processor的子类修改String对象(注意,返回类型可以是协变类型,而非参数类型):
package com.jackson.facade;
import java.util.Arrays;
class Processors {
public String name() {
return getClass().getSimpleName();
}
Object process(Object input) {
return input;
}
}
class UpCase extends Processors {
@Override
String process(Object input) {
return ((String) input).toUpperCase();
}
}
class DownCase extends Processors {
@Override
String process(Object input) {
return ((String)input).toLowerCase();
}
}
class Splitter extends Processors {
@Override
String process(Object input) {
return Arrays.toString(((String)input).split(" "));
}
}
/**
* 功能:
* 描述:
* @author
**/
public class Apply {
public static void process(Processors 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);
}
}
输出结果:
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]
Apply.process()方法可以接受任何类型的Processor,并且将其应用到一个Object对象上,然后打印结果。像本例这样,创建一个能够根据所传递的参数对象的不同而具有不同行为的方法,被称为策略设计模式。这类方法包含所要执行的算法中固定不变的部分,而“策略”包含变化的部分。策略就是传递进去的参数对象,它包含要执行的代码。这里,Processor对象是一个策略,在main()方法中可以看到三种不同类型的策略应用到了String类型的s对象上。
现在假设我们发现了一组电子l波器,它们看起来好像适用于Apply.process()方法:
public class Waveform {
private static long counter;
private final long id = counter ++;
@Override
public String toString() {
return "Waveform " + id;
}
}
public class Filter {
public String name() {
return getClass().getSimpleName();
}
public Waveform process(Waveform input) {
return input;
}
}
public class LowPass extends Filter {
double cutoff;
public LowPass(double cutoff) {
this.cutoff = cutoff;
}
@Override
public Waveform process(Waveform input) {
return input;
}
}
public class HighPass extends Filter {
double cutoff;
public HighPass(double cutoff) {
this.cutoff = cutoff;
}
@Override
public Waveform process(Waveform input) {
return input;
}
}
public class BandPass extends Filter {
double lowCutoff, highCutoff;
public BandPass(double lowCutoff, double highCutoff) {
this.lowCutoff = lowCutoff;
this.highCutoff = highCutoff;
}
@Override
public Waveform process(Waveform input) {
return input;
}
}
Filter与Processor具有相同的接口元素,但是因为它并非继承自Processor——因为Filter类的创建者压根不清楚你想要将它用作Processor——因此你不能将Filter用于Apply.process()方法,即便这样做可以正常运行。这里主要是因为Apply.process()方法和Processor之间的耦合过紧,已经超出了所需要的程度,这就使得应该复用Apply.process()的代码时,复用切禁止了。另外还需要注意的是它们的输入和输出都是Waveform。
但是,如果Processor是一个接口,那么这些限制就会变得松动,使得你可以复用结构该接口的Apply.process()。下面是Processor和Apply的修改版本:
package com.jackson.interfaceprocess;
import java.util.Arrays;
interface Processors {
String name();
Object process(Object input);
}
abstract class StringProcess implements Processors {
@Override
public String name() {
return getClass().getSimpleName();
}
@Override
public Object process(Object input) {
return input;
}
}
class UpCase extends StringProcess {
@Override
public String name() {
return getClass().getSimpleName();
}
@Override
public String process(Object input) {
return ((String) input).toUpperCase();
}
}
class DownCase extends StringProcess {
@Override
public String process(Object input) {
return ((String)input).toLowerCase();
}
}
class Splitter extends StringProcess {
@Override
public String process(Object input) {
return Arrays.toString(((String)input).split(" "));
}
}
/**
* 功能:
* 描述:
* @author
**/
public class Apply {
public static void process(Processors 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);
}
}
输出结果:
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]
但是,你经常碰到的情况是你无法修改你想要使用的类。例如,在上述例子中的电子滤波器的例子中,类库是被发现而非创建的。在这种情况下,可以使用适配器设计模式。适配器中的代码将接受你所拥有的接口,并产生你所需要的接口,就像下面这样:
package com.jackson.interfaceprocess;
import com.jackson.filters.Filter;
import com.jackson.filters.Waveform;
/**
* 功能:
* 描述:
*
* @Author jack
* 2019/12/12下午11:40
**/
public class FilterAdapter implements Processors {
private Filter filter;
public FilterAdapter(Filter filter) {
this.filter = filter;
}
@Override
public String name() {
return filter.name();
}
@Override
public Waveform process(Object input) {
return filter.process((Waveform)input);
}
}
package com.jackson.interfaceprocess;
import com.jackson.filters.BandPass;
import com.jackson.filters.HighPass;
import com.jackson.filters.LowPass;
import com.jackson.filters.Waveform;
/**
* 功能:
* 描述:
*
* @Author jack
* 2019/12/12下午11:44
**/
public class FilterProcessor {
public static void main(String[] args) {
Waveform waveform = new Waveform();
Apply.process(new FilterAdapter(new LowPass(1.0)), waveform);
Apply.process(new FilterAdapter(new HighPass(2.0)), waveform);
Apply.process(new FilterAdapter(new BandPass(3.0, 4.0)), waveform);
}
}
输出结果:
Using Processor LowPass
Waveform 0
Using Processor HighPass
Waveform 0
Using Processor BandPass
Waveform 0
在这种使用适配器的方式中,FilterAdapter的构造器接受你所拥有的接口Filter,然后生成具有你所需要的Processor接口的对象。你可能还注意到,在FilterAdapter类中用到了代理。
将接口从具体实现中解耦使得接口可以应用于多种不同的具体实现,因此代码也就更具可复用性。
四、Java中的多重继承
接口不仅仅只是一种纯粹形式的抽象类,它的目标比这更高。因为接口是根本没有任何具体的实现的——也就是说,没有任何与接口相关的存储;因此,也就无法阻止多个接口的组合。这一点是很有价值的,因为你有时需要去表示“一个X是一个a和一个b以及一个c”。在C++中,组合多个类的接口被称为多重继承。它可能背负很沉重的包袱,因为每个类都有一个具体的实现。在Java中,你可以执行相同的行为,但是只有一个类可以有具体的实现;因此,通过组合多个接口,C++|中的问题是不会在Java中发生的:
在导出类中,不强制要求必须有一个是抽象的或“具体的”(没有任何抽象方法的)基类。如果要从一个非接口的类继承,那么只能从一个类去继承(单继承)。其余的基元素都必须是接口。需要将所有的接口都置于implement关键字之后,用逗号将它们一一隔开。可以继承任意多个接口(多实现),并且可以向上转型为每一个接口,因为每个接口都是一个独立类型。例如:
package com.jackson.interfaces;
interface CanFight {
void fight();
}
interface CanSwim{
void swim();
}
interface CanFly{
void fly();
}
class ActionCharacter {
public void fight() {
System.out.println("ActionCharacter.fight()");
}
}
class Hero extends ActionCharacter implements CanFight, CanSwim, CanFly {
@Override
public void swim() {
System.out.println("hero can swim");
}
@Override
public void fly() {
System.out.println("hero can fly");
}
}
/**
* 功能:
* 描述:
*
* @Author jack
* 2019/12/13上午12:15
**/
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);
}
}
执行结果:
ActionCharacter.fight()
hero can swim
hero can fly
ActionCharacter.fight()
可以看到,Hero组合了具体类ActionCharacter和接口CanFly、CanSwim和CanFly。当通过这种方式将一个具有类和多个接口组合在一起时,这个具体的类必须放在前面,后面跟着的才是接口(否则编译器会报错)。
需要注意的是,CanFight接口和ActionCharacter类中的fight()方法的特征签名是一样的,而且,在Hero中并没有提供figh()的定义。可以扩展接口,但是得到的只是另一个接口。当想要创建对象时,所有的定义首先必须都存在。即使Hero没有显示地提供fight()的定义,其定义也因ActionCharacter而随之而来,这样就使得创建Hero对象成为可能。
该例子展示类使用接口的核心原因:为了能够向上转型为多个基类型(以及由此带来的灵活性)。然而,使用接口的第二个原因却是与使用抽象基类相同:防止客户端程序员创建该类对象,并确保这仅仅是建立一个接口。这就带来一个问题:我们应该使用接口还是抽象类?如果要创建不带任何方法定义和成员变量的基类,那么就应该选择接口而不是抽象类。事实上,如果知道某事物应该成为一个基类,那么第一选择应该使它成为一个接口。
五、通过继承来扩展接口
通过继承,可以很容易在接口中添加新的方法声明,还可以通过继承在新的接口中组合数个接口。这两中情况都可以获得新的接口。例如:
package com.jackson.interfaces;
interface Monster {
void menace();
}
interface DangerousMonster extends Monster {
void destroy();
}
interface Lethal {
void kill();
}
class DragonZilla implements DangerousMonster {
@Override
public void menace() {
}
@Override
public void destroy() {
}
}
interface Vampire extends DangerousMonster, Lethal {
void drinkBlood();
}
class VeryBadVampire implements Vampire {
@Override
public void menace() {
}
@Override
public void destroy() {
}
@Override
public void kill() {
}
@Override
public void drinkBlood() {
}
}
/**
* 功能:
* 描述:
*
* @Author jack
* 2019/12/13上午12:41
**/
public class HorrorShow {
static void u(Monster b) {b.menace();}
static void v(DangerousMonster d) {
d.destroy();
d.menace();
}
static void w (Lethal l) {l.kill();}
public static void main(String[] args) {
DangerousMonster monster = new DragonZilla();
u(monster);
v(monster);
Vampire vampire = new VeryBadVampire();
u(vampire);
v(vampire);
w(vampire);
}
}
需要注意的是,在Vampire中使用的语法仅适用于接口继承。一般情况下,只可以将extends用于单一类(单继承),但是可以引用多个基类接口。就如上面的代码那样,只需要用逗号将接口名一一分隔开来即可。
5.1 组合接口时的名字冲突
在实现多重继承时,可能会碰到一个小的陷阱。在前面的例子中CanFight和ActionCharacter都有一个相同的void fight()方法。这不是问题所在,因为该方法在二者中是相同的。相同的方法不会有什么问题,但是如果他们的签名或者返回类型不一样,又会怎么样呢?看如下例子:
package com.jackson.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 {
@Override
public void f() {
}
@Override
public int f(int i) {
return 1;
}
}
class C3 extends C implements I2 {
@Override
public int f(int i) {
return 1;
}
}
class C4 extends C implements I3 {
@Override
public int f() {
return 1;
}
}
// 编译器报错
class C5 extends C implements I1 {
@Override
public int f() {
return super.f();
}
}
// 编译器报错
interface I4 extends I1, I3 {
@Override
default void f() {
}
}
/**
* 功能:
* 描述:
*
* @Author jack
* 2019/12/13上午12:57
**/
public class NameConflict {
}
此时困难来了,因为覆盖、实现和重载令人不快地搅合在了一起,而且重载方法仅通过返回值类型是区分不开的。在打算组合不同接口中使用相同的方法名通常会造成代码可读性的混乱,请尽量避免这种情况。
六、适配接口
接口最吸引人的原因之一就是运行同一个接口具有不同的具体的实现。接口的一种常见的用法就是策略设计模式,此时你编写一个执行某些操作的方法,而该方法将接受一个同样是你指定的接口。你主要就是要声明:“你可以用任何你想要的对象来调用我的方法,只要你的对象遵循我的接口”。这使得你的方法更加灵活、通用,并更具有可复用性。
例如,Java SE5的Scanner类的构造器接受的就是一个Readable接口。你会发现Readable没有用作Java标准类库中任何方法的参数,它是单独为Scanner创建的,以使的Scanner不必将其参数限制为某个特定的类。通过这种方式,Scanner可以作用于更多的类型。如果你创建了一个新的类,并且想让Scanner可以作用于它,那么你就应该让它成为Readable,就像下面这样:
package com.jackson.interfaces;
import java.io.IOException;
import java.nio.CharBuffer;
import java.util.Random;
import java.util.Scanner;
/**
* 功能:
* 描述:
*
* @Author jack
* 2019/12/13下午10:19
**/
public class RandomWords implements Readable {
private static Random random = new Random(47);
private static final char [] capitals = "ABCDEFGHIJKLMNOPQRSTUVWXYZ".toCharArray();
private static final char [] lows = "abcdefghigklmnopqrstuvwxyz".toCharArray();
private static final char [] vowels = "aeiou".toCharArray();
private int count;
public RandomWords(int count) {
this.count = count;
}
@Override
public int read(CharBuffer cb) throws IOException {
if (count -- == 0) {
//end of input
return -1;
}
cb.append(capitals[random.nextInt(capitals.length)]);
for (int i = 0; i < 4; i++) {
cb.append(vowels[random.nextInt(vowels.length)]);
cb.append(lows[random.nextInt(lows.length)]);
}
cb.append(" ");
return 10;
}
public static void main(String[] args) {
Scanner scanner = new Scanner(new RandomWords(10));
while (scanner.hasNext()) {
System.out.println(scanner.next());
}
}
}
输出信息:
Yazeruyac
Fowenucor
Goeazimom
Raeuuacio
Nuoadesiw
Hageaikux
Ruqicibui
Numasetih
Kuuuuozog
Waqizeyoy
Readab接口只要求实现read方法,在read()内部,将输入内容添加到CharBuffer参数中,或者在没有任何输出的情况时返回-1。
假如你有一个为实现Readab的类,怎样才能让Scanner作用于它呢?下面这个类就是一个例子,它可以产生随机浮点数:
package com.jackson.interfaces;
import java.util.Random;
/**
* 功能:
* 描述:
*
* @Author jack
* 2019/12/13下午10:38
**/
public class RandomDoubles {
private static Random random = new Random(47);
public double next() {
return random.nextDouble();
}
public static void main(String[] args) {
RandomDoubles doubles = new RandomDoubles();
for (int i = 0; i < 10; i++) {
System.out.println(doubles.next());
}
}
}
我们再次使用适配器模式,但是在本例中,被适配的类可以通过继承和实现Readable接口来创建。因此,通过使用interface关键字提供的伪多重继承机制,我们可以生成即使RandomDoubles又是Readab的新类:
package com.jackson.interfaces;
import java.io.IOException;
import java.nio.CharBuffer;
import java.util.Scanner;
/**
* 功能:
* 描述:
*
* @Author jack
* 2019/12/13下午10:42
**/
public class AdapterRandomDoubles extends RandomDoubles implements Readable {
private int counter;
public AdapterRandomDoubles(int counter) {
this.counter = counter;
}
@Override
public int read(CharBuffer cb) throws IOException {
if (counter -- == 0) {
return -1;
}
String result = String.valueOf(next());
cb.append(result);
// 换行
cb.append("\r\n");
return result.length();
}
public static void main(String[] args) {
Scanner scanner = new Scanner(new AdapterRandomDoubles(5));
while (scanner.hasNext()) {
System.out.println(scanner.next());
}
}
}
输出结果:
0.7271157860730044
0.5309454508634242
0.16020656493302599
0.18847866977771732
0.5166020801268457
因为在这种方式中,我们可以在任何现有的类之上添加新的接口,所以这意味着让方法接受接口类型,是一种让任何类都可以对该方法进行适配的方式。这就是使用接口而不是使用类的强大之处。
七、接口中的域
因为放入接口中的任何域都是static和final的,所以接口就成为了一种很便捷的用来创建常量的工具。在Java SE5之前,这是产生与C和C++中的enum(枚举类型)具有相同效果的类型的唯一途径。因此在Java SE5之前的代码中你会看到下面这样的代码:
public interface Constants {
String APP_NAME = "Hello Java";
int MAX_WAIT_TIME = 2000;
}
请注意,Java中标识具有常量初始化值的static final时,会使用大写字母的风格(在一个标识符中用下划线分隔多个单词)。接口中的域自动是public,所以没有显示指明这一点。
有了Java SE 5,你就可以使用更加强大而灵活的enum关键字,因此,使用接口来群组常量已经显得没有意义了。
7.1 初始化接口中的域
在接口中定义的域不可以是“空final”,但是可以被非常量表达式初始化。例如:
public interface RandVals {
Random RANDOM = new Random(47);
int RANDOM_INT = RANDOM.nextInt(10);
long RANDOM_LONG = RANDOM.nextLong() * 10;
float RANDOM_FLOAT = RANDOM.nextFloat() * 10;
double RANDOM_DOUBLE = RANDOM.nextDouble() * 10;
}
既然是static的,它们就可以在类第一次被加载时初始化,这发生在任何域首次被访问时。例如:
package com.jackson.interfaces;
import java.util.Random;
/**
* 功能:
* 描述:
*
* @Author jack
* 2019/12/13下午11:03
**/
interface RandVals {
Random RANDOM = new Random(47);
int RANDOM_INT = RANDOM.nextInt(10);
long RANDOM_LONG = RANDOM.nextLong() * 10;
float RANDOM_FLOAT = RANDOM.nextFloat() * 10;
double RANDOM_DOUBLE = RANDOM.nextDouble() * 10;
}
public class TestRandVals {
public static void main(String[] args) {
System.out.println(RandVals.RANDOM_INT);
System.out.println(RandVals.RANDOM_LONG);
System.out.println(RandVals.RANDOM_FLOAT);
System.out.println(RandVals.RANDOM_DOUBLE);
}
}
输出结果:
8
-32032247016559954
0.534122
1.6020656493302599
当然,这些域不是接口的一部分,它们直接被存储在该接口的静态存储区域内。
八、嵌套接口
接口可以嵌套在类或者其他接口中。这揭示类许多非常有趣的特性:
package com.jackson.interfaces;
class A {
interface B {
void f();
}
public class BImp implements B {
@Override
public void f() {
System.out.println("BImp.f()");
}
}
public interface C {
void f();
}
class CImp implements C {
@Override
public void f() {
System.out.println("CImp.f()");
}
}
private class CImp2 implements C {
@Override
public void f() {
System.out.println("CImp2.f()");
}
}
private interface D {
void f();
}
private class DImp implements D {
@Override
public void f() {
System.out.println("DImp.f()");
}
}
public class DImp2 implements D {
@Override
public void f() {
System.out.println("DImp2.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();
}
public interface H {
void f();
}
void g();
}
/**
* 功能:
* 描述:
**/
public class NestingInterfaces {
public class BImp implements A.B {
@Override
public void f() {
System.out.println("BImp.f()");
}
}
class CImp implements A.C {
@Override
public void f() {
System.out.println("CImp.f()");
}
}
// error
// class DImp implements A.D {
// @Override
// public void f() {
//
// }
// }
class EImp implements E{
@Override
public void g() {
System.out.println("EImp.f()");
}
}
class EGImp implements E.G {
@Override
public void f() {
System.out.println("EGImp.f()");
}
}
class EImp2 implements E {
@Override
public void g() {
System.out.println("EImp2.g()");
}
class EG implements E.G {
@Override
public void f() {
System.out.println("EG.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 otf the interface
// a.getD().f();
// only another A can do anything with getD()
A a2 = new A();
a2.received(a.getD());
}
}
输出结果:
DImp2.f()
在类中嵌套接口的语法是相当显而易见的,就像被嵌套接口一样,可以拥有public和“包访问”两者可见性。作为一中新添加的方式,接口也可以被实现为privare的,就像在A.D中所看到的(相同的语法也同样适用于嵌套接口,也适用于嵌套类)。那么private的嵌套接口能够带来什么好处呢?实现一个private接口只是一种方式,它强制该接口中的方法定义不要添加任何类信息(也就是说,不允许向上转型)。
九、接口与工厂
接口是实现多重继承的途径,而生成遵循某个接口的对象典型方式就是工厂设计模式。这与直接调用构造器不同,我们在工厂对象上调用的是创建方法,而工厂对象将生成接口的某个实现的对象。理论上,通过这种方式,我们的代码将完全与接口的实现分离,这使得我们可以透明地将某个实现替换成另一个实现。例如:
package com.jackson.interfaces;
interface Service {
void method1();
void method2();
}
interface ServiceFactory {
Service getService();
}
class Implementtation1 implements Service {
@Override
public void method1() {
System.out.println("Implementtation1.method1()");
}
@Override
public void method2() {
System.out.println("Implementtation1.method2()");
}
}
class Implementation2 implements Service {
@Override
public void method1() {
System.out.println("Implementation2.method2()");
}
@Override
public void method2() {
System.out.println("Implementation2.method2()");
}
}
class Implementation1Factory implements ServiceFactory {
@Override
public Service getService() {
return new Implementtation1();
}
}
class Implementation2Factory implements ServiceFactory {
@Override
public Service getService() {
return new Implementation2();
}
}
/**
* 功能:
* 描述:
**/
public class Factories {
public static void serviceConsumer(ServiceFactory factory) {
Service service = factory.getService();
service.method1();
service.method2();
}
public static void main(String[] args) {
serviceConsumer(new Implementation1Factory());
serviceConsumer(new Implementation2Factory());
}
}
Implementtation1.method1()
Implementtation1.method2()
Implementation2.method2()
Implementation2.method2()
如果不是工厂方法,你的代码就必须在某处指定要将要创建的Servic的确切类型,以便调用合适的构造器。
为什么要添加这种额外级别的间接性呢?一个常见的原因是想要创建框架:假如你正在创建一个对弈游戏,例如,在相同的棋盘上下国际象棋和西洋跳棋:
package com.jackson.interfaces;
interface Game {
boolean move();
}
interface GameFactory {
Game getGame();
}
class Checkers implements Game {
private int moves = 0;
private static final int MOVES = 3;
@Override
public boolean move() {
System.out.println("Checkers move " + moves);
return ++ moves != MOVES;
}
}
class CheckersFactory implements GameFactory {
@Override
public Game getGame() {
return new Checkers();
}
}
class Chess implements Game {
private int moves = 0;
private static final int MOVES = 4;
@Override
public boolean move() {
System.out.println("Chess moves " + moves);
return moves ++ != MOVES;
}
}
class ChessFactory implements GameFactory {
@Override
public Game getGame() {
return new Chess();
}
}
/**
* 功能:
* 描述:
**/
public class Games {
public static void playGame(GameFactory factory) {
Game game = factory.getGame();
while (game.move()) {
;
}
}
public static void main(String[] args) {
playGame(new CheckersFactory());
playGame(new ChessFactory());
}
}
输出结果:
Checkers move 0
Checkers move 1
Checkers move 2
Chess moves 0
Chess moves 1
Chess moves 2
Chess moves 3
Chess moves 4
如果Games类表示一段复杂的代码,那么这种方式就允许你在不同类型的游戏中复用这段代码。