【9】接口
【9.1】抽象类和抽象方法
1)抽象方法:仅有方法声明而没有方法体;由abstract 修饰的方法;
2)抽象类:如果一个类包含一个或多个抽象方法, 则该类声明为抽象类,由 abstract修饰;
3)当然了,也可以创建一个没有抽象方法但被abstract修饰的抽象类;
【荔枝】抽象类和抽象方法
1)interface接口:产生了一个完全抽象的类,没有提供任何具体实现;
2)接口被用来建立类与类之间的协议;
3)接口与抽象类的区别:接口中的方法都必须是抽象方法,即只有方法声明没有方法体;而抽象类中的方法即可以是抽象方法也可以是带有方法体的方法;
4)接口中的字段:默认被 static 和 final 修饰;访问权限默认且必须为 public;
5)接口中的方法:其访问权限是public,即便你不使用public修饰,其访问权限默认且必须为 public;
6)接口的包可见性:
【9.3】完全解耦
【荔枝】 策略模式:创建一个能够根据所传递的参数对象的不同而具有不同行为的方法;
(干货——策略模式的定义和荔枝)
这里的话,Processor对象就是一个策略;以上荔枝中有3个不同类型的策略应用到对字符串的处理;
【9.4】java中的多重继承
1)普通类的单继承和接口的多继承: 从 一个非接口类继承,只能继承一个类;其余的基类必须是接口;需要把所有的接口都放置在 implements 关键字之后,用逗号隔开它们;
2)可以继承多个接口,并将其转型为每个接口;
【荔枝】java多重继承
分析)第二个原因:防止客户端程序员创建该类的对象,并确保这仅仅是建立一个接口;
【问题】到底是使用接口还是抽象类? 接口是首选;因为灵活;
【9.5】通过继承来扩展接口
1)扩展接口: 新接口中添加新方法声明;通过继承在新接口中组合多个接口;
【荔枝】扩展接口
1)当继承的父类和实现的接口的方法签名或返回类型不同,会发生声明呢?
补充)也有可能是: 两个父接口的方法签名或返回类型不同; 如 :
(干货——编码注意事项,避免不同接口中使用相同的方法名)
【9.6】适配接口
1)接口最吸引人的原因:允许同一个接口具有多个不同的具体实现;接口的常见用法是策略设计模式;
2)如果创建一个新类,并让 Scanner作用于它,让该类去实现 Readable 接口;
【荔枝】Readable接口的实现类,并让Scanner作用于该类
1)接口是一种便捷的用来创建常量组的工具: 因为接口中的任何域都自动是 public static final ;
【荔枝】接口是一种便捷的用来创建常量组的工具
【9.7.1】初始化接口中的域
1)接口中的域不能是空final,但是可以被非常量表达式来初始化;
【荔枝】使用非常量表达式来初始化接口域
注意1)嵌套接口可以拥有 public 和包可见性;
注意2)实体内中可以嵌套private接口;而接口中不可以嵌套private接口;(干货)
注意3)实现一个private接口: 那么它可以强制该接口中的方法定义不要添加任何类型信息,即无法向上转型;
【9.9】接口与工厂
1)接口是实现多重继承的途径: 生成遵循某个接口的对象的典型方式是 工厂方法设计模式;(干货——基于接口的工厂方法设计模式)
【荔枝】基于接口的工厂方法结构
【9.1】抽象类和抽象方法
1)抽象方法:仅有方法声明而没有方法体;由abstract 修饰的方法;
2)抽象类:如果一个类包含一个或多个抽象方法, 则该类声明为抽象类,由 abstract修饰;
3)当然了,也可以创建一个没有抽象方法但被abstract修饰的抽象类;
【荔枝】抽象类和抽象方法
/*public enum Note {
MIDDLE_C, C_SHARP, B_FLAT;
}
*/
//荔枝 抽象类和抽象方法
// 抽象类
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) {
print("Wind.play() " + n);
}
public String what() {
return "Wind";
}
public void adjust() {
}
}
class Percussion extends Instrument { // 抽象类子类
public void play(Note n) {
print("Percussion.play() " + n);
}
public String what() {
return "Percussion";
}
public void adjust() {
}
}
class Stringed extends Instrument { // 抽象类子类
public void play(Note n) {
print("Stringed.play() " + n);
}
public String what() {
return "Stringed";
}
public void adjust() {
}
}
class Brass extends Wind { // 抽象类子类
public void play(Note n) {
print("Brass.play() " + n);
}
public void adjust() {
print("Brass.adjust()");
}
}
class Woodwind extends Wind { // 抽象类子类
public void play(Note n) {
print("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);
}
}
/*
Wind.play() MIDDLE_C
Percussion.play() MIDDLE_C
Stringed.play() MIDDLE_C
Brass.play() MIDDLE_C
Woodwind.play() MIDDLE_C
*/
【9.2】接口1)interface接口:产生了一个完全抽象的类,没有提供任何具体实现;
2)接口被用来建立类与类之间的协议;
3)接口与抽象类的区别:接口中的方法都必须是抽象方法,即只有方法声明没有方法体;而抽象类中的方法即可以是抽象方法也可以是带有方法体的方法;
4)接口中的字段:默认被 static 和 final 修饰;访问权限默认且必须为 public;
5)接口中的方法:其访问权限是public,即便你不使用public修饰,其访问权限默认且必须为 public;
6)接口的包可见性:
// 接口
interface Instrument {
// Compile-time constant:
int VALUE = 5; // static & final
// Cannot have method definitions:
void play(Note n); // Automatically public
void adjust();
}
【荔枝】接口
// 荔枝:接口
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 {
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);
}
}
/*
Wind.play() MIDDLE_C
Percussion.play() MIDDLE_C
Stringed.play() MIDDLE_C
Brass.play() MIDDLE_C
Woodwind.play() MIDDLE_C
*/
分析)在tune() 方法中,无法判断 Instrument 是一个普通类,抽象类,还是一个接口;
【9.3】完全解耦
【荔枝】 策略模式:创建一个能够根据所传递的参数对象的不同而具有不同行为的方法;
(干货——策略模式的定义和荔枝)
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) {
print("=== Using Processor " + p.name());
print(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]
*/
分析)这类方法如process方法包含所要执行的算法中固定不变的部分,而策略包含变化的部分;策略就是传进去的参数,它包含要执行的代码;
这里的话,Processor对象就是一个策略;以上荔枝中有3个不同类型的策略应用到对字符串的处理;
【9.4】java中的多重继承
1)普通类的单继承和接口的多继承: 从 一个非接口类继承,只能继承一个类;其余的基类必须是接口;需要把所有的接口都放置在 implements 关键字之后,用逗号隔开它们;
2)可以继承多个接口,并将其转型为每个接口;
【荔枝】java多重继承
interface CanFight { void fight(); }
interface CanSwim { void swim(); }
interface CanFly { void fly(); }
interface CanClimb { void climb(); }
// 接口多重继承
interface MyInterface extends CanFight, CanSwim, CanFly, CanClimb {
}
class ActionCharacter {
public void fight() { }
}
// 【荔枝】 java多重继承-普通类的单继承和接口的多继承
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
}
}
分析)使用接口的核心原因:为了能够向上转型为多个基类型,由此带来的灵活性;
分析)第二个原因:防止客户端程序员创建该类的对象,并确保这仅仅是建立一个接口;
【问题】到底是使用接口还是抽象类? 接口是首选;因为灵活;
【9.5】通过继承来扩展接口
1)扩展接口: 新接口中添加新方法声明;通过继承在新接口中组合多个接口;
【荔枝】扩展接口
// 【荔枝】扩展接口
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.5.1】组合接口时的名字冲突1)当继承的父类和实现的接口的方法签名或返回类型不同,会发生声明呢?
补充)也有可能是: 两个父接口的方法签名或返回类型不同; 如 :
class A extends B implements C {
}
class B{
void f1();
}
class C {
int f1();
}
【荔枝】组合接口时的名字冲突
(干货——编码注意事项,避免不同接口中使用相同的方法名)
// 【荔枝】组合接口时的名字冲突(已修正)
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; }
}
注意:尽量避免不同接口中使用相同的方法名,以免造成可读性混乱;
【9.6】适配接口
1)接口最吸引人的原因:允许同一个接口具有多个不同的具体实现;接口的常见用法是策略设计模式;
2)如果创建一个新类,并让 Scanner作用于它,让该类去实现 Readable 接口;
【荔枝】Readable接口的实现类,并让Scanner作用于该类
// 荔枝-Readable接口的实现类,并让Scanner作用于该类
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;
}
@Override
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());
}
}
/*
Yazeruyac
Fowenucor
Goeazimom
Raeuuacio
Nuoadesiw
Hageaikux
Ruqicibui
Numasetih
Kuuuuozog
Waqizeyoy
*/
// Readable 接口
public interface Readable {
public int read(java.nio.CharBuffer cb) throws IOException;
}
【荔枝】通过使用interface关键字提供的伪多重继承机制,可以生成既是 RandomDoubles 又是 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.println(s.nextDouble() + " ");
}
}
/*
0.7271157860730044
0.5309454508634242
0.16020656493302599
0.18847866977771732
0.5166020801268457
0.2678662084200585
0.2613610344283964
*/
【9.7】接口中的域1)接口是一种便捷的用来创建常量组的工具: 因为接口中的任何域都自动是 public static final ;
【荔枝】接口是一种便捷的用来创建常量组的工具
// 荔枝-接口是一种便捷的用来创建常量组的工具
// 因为接口中的任何域都自动是 public static final
public class Months {
int JANUARY = 1, FEBRUARY = 2, MARCH = 3,
APIRL = 4, MAY = 5, JUNE = 6, JULY = 7,
AUGUST = 8, SEPTEMBER = 9, OCTOBER = 10,
NOVEMBER = 11, DECEMBER = 12;
}
注意) java中标识具有常量初始化值的static final时,会使用大写字母(用下划线分隔多个单词);不过java se5后,建议使用 enum 关键字来定义枚举变量;
【9.7.1】初始化接口中的域
1)接口中的域不能是空final,但是可以被非常量表达式来初始化;
【荔枝】使用非常量表达式来初始化接口域
// 【荔枝】使用非常量表达式来初始化接口域
public interface RandVals {
Random RAND = new Random(47);
int RANDOM_INT = RAND.nextInt(10);
long RANDOM_LONG = RAND.nextLong() * 10;
float RANDOM_FLOAT = RAND.nextLong() * 10;
double RANDOM_DOUBLE = RAND.nextDouble() * 10;
}
2)这些static域会在类第一次加载时被初始化,荔枝如下:
// 荔枝-测试接口static域在类第一次加载时被初始化
public class TestRandVals {
public static void main(String[] args) {
print(RandVals.RANDOM_INT);
print(RandVals.RANDOM_LONG);
print(RandVals.RANDOM_FLOAT);
print(RandVals.RANDOM_DOUBLE);
}
}
/*
8
-32032247016559954
-8.5939291E18
5.779976127815049
*/
【注意】这些域不是接口的一部分,其值存储在接口的静态存储区域内; 即jvm内存模型中的方法区:线程公有,用于存储编译后的 类信息(Class类),常量,静态变量 和 即时编译后的数据;
【9.8】嵌套接口(接口中再定义另一个接口)
1)接口嵌套荔枝:// 荔枝-接口嵌套荔枝
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 {} // 普通类中嵌套一个接口(可以为private访问权限)
}
interface E {
interface G { void f(); } // 类中嵌套一个接口
// Redundant "public":
public interface H { void f(); } // 类中嵌套一个接口
void g();
// Cannot be private within an interface: (注意:不可以为private访问权限)
// ! 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 E.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();
}
} // /:~
【注意】注意1)嵌套接口可以拥有 public 和包可见性;
注意2)实体内中可以嵌套private接口;而接口中不可以嵌套private接口;(干货)
注意3)实现一个private接口: 那么它可以强制该接口中的方法定义不要添加任何类型信息,即无法向上转型;
【9.9】接口与工厂
1)接口是实现多重继承的途径: 生成遵循某个接口的对象的典型方式是 工厂方法设计模式;(干货——基于接口的工厂方法设计模式)
【荔枝】基于接口的工厂方法结构
// 荔枝-基于接口的工厂方法结构
interface Service { void method1(); void method2(); } // 普通接口
interface ServiceFactory { Service getService(); } // 工厂接口
class Implementation1 implements Service { // 实现普通接口的普通类
Implementation1() {}
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() {}
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());
serviceConsumer(new Implementation2Factory());
}
}
/*
Implementation1 method1
Implementation1 method2
Implementation2 method1
Implementation2 method2
*/
【荔枝】利用接口提高代码复用率
// 【荔枝】利用接口提高代码复用率
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() {
print("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() {
print("Chess move " + moves);
return ++moves != MOVES;
}
}
class ChessFactory implements GameFactory { // 实现工厂接口的工厂类
public Game getGame() {
return new Chess();
}
}
// 工厂1 和 工厂2 复用代码
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()); // 工厂1
playGame(new ChessFactory()); // 工厂2
}
}
/*
Checkers move 0
Checkers move 1
Checkers move 2
Chess move 0
Chess move 1
Chess move 2
Chess move 3
*/
分析)如 Games 类表示一段复杂代码,则这种方式允许你在不同类型的游戏中复用这段代码;提高代码复用率;