读Java编程思想随笔の接口
接口和抽象类为我们提供了一种将接口和实现分离的更加结构化的方法。
这种机制在编程语言中并不通用。例如,C++对这些概念只有间接的支持。在Java中存在语言关键字这个事实表明人们认为这些思想是很重要的,以至于要提供对它们的直接支持。
首先,我们来学习抽象类,它是普通的类与接口之间的中庸之道。尽管在构建具有某些未实现的方法的类时,你的第一想法可能是创建接口,但是抽象类仍旧是用于此目的重要而必须的工具。因为你不可能总是使用纯接口。
抽象类,如果一个类包含一个或多个抽象方法,该类必须被限定为抽象的(否则编译通不过)。但是在抽象类中,并不是要求所有方法都是抽象方法。
例如下面结构图:
在Instrument抽象类中,有play()和adjust()的两个抽象方法,也有what()的普通方法。
1 /** 2 * @author yxm 3 * @date 2015/11/4. 4 * @company 中国奇奇科技有限公司 5 * @description abstract示例 6 */ 7 public class Music4 { 8 static void tune(Instrument i){ 9 i.play(Note.MIDDLE_C); 10 } 11 static void tuneAll(Instrument[] e){ 12 for (Instrument i :e){ 13 tune(i); 14 } 15 } 16 public static void main(String[] args) { 17 Instrument[] orchestra = {new Wind(),new Percussion(),new Stringed(),new Brass(),new Woodwind()}; 18 tuneAll(orchestra); 19 } 20 } 21 abstract class Instrument{ 22 private int i; 23 public abstract void play(Note e); 24 public String what(){ 25 return "Instrument"; 26 } 27 public abstract void adjust(); 28 } 29 class Wind extends Instrument { 30 public void play(Note n){ 31 System.out.println("Wind.play()"+n); 32 } 33 public String what(){ 34 return "Wind"; 35 } 36 public void adjust(){ 37 38 } 39 } 40 class Percussion extends Instrument { 41 public void play(Note n){ 42 System.out.println("Percussion.play()"+n); 43 } 44 public String what(){ 45 return "Percussion"; 46 } 47 public void adjust(){ 48 49 } 50 } 51 class Stringed extends Instrument { 52 public void play(Note n){ 53 System.out.println("Stringed.play()"+n); 54 } 55 public String what(){ 56 return "Stringed"; 57 } 58 public void adjust(){ 59 60 } 61 } 62 class Brass extends Wind{ 63 public void play(Note n){ 64 System.out.println("Brass.play()"+n); 65 } 66 public void adjust(){ 67 System.out.println("Brass.adjust()"); 68 } 69 } 70 class Woodwind extends Wind{ 71 public void play(Note n){ 72 System.out.println("Woodwind.play()"+n); 73 } 74 public String what(){ 75 return "Woodwind"; 76 } 77 } 78 //Wind.play()MIDDLE_C 79 //Percussion.play()MIDDLE_C 80 //Stringed.play()MIDDLE_C 81 //Brass.play()MIDDLE_C 82 //Woodwind.play()MIDDLE_C
接口
interface关键字使抽象的概念更向前迈进了一步。abstract关键字使人们类中创建一个或多个没有任何定义的方法--提供了接口部分,但是没有提供任何相应的具体实现,这些实现是由此类的继承者创建的。interface关键字创建了一个完全抽象的类,它根本没有提供任何具体实现。它允许创建者创建方法名、参数列表和返回类型,但是没有方法体。接口只提供了形式,而未提供任何具体实现。
但是,interface并不仅仅是一个极度抽象的类,因为它允许人们通过创建一个能够被向上转型为多种基类的类型,来实现某种类似多重继变种的特性。
接口中也包含域,但是这些域隐式地被声明为public static final类型。
这张图非常直白的讲明了抽象和接口在被继承或实现的使用时的差别。而且,我们也可以从Woodwind和Brass类中看到,一旦实现了某个接口,其实现就变成了一个普通的类,就可以按照常规方式扩展它。
1 /** 2 * @author yxm 3 * @date 2015/11/4. 4 * @company 中国奇奇科技有限公司 5 * @description 接口使用 6 */ 7 public class Music5 { 8 static void tune(com.qiqi.interfaces.interfaces.Instrument i){ 9 i.play(Note.MIDDLE_C); 10 } 11 static void tuneAll(com.qiqi.interfaces.interfaces.Instrument[] e){ 12 for (com.qiqi.interfaces.interfaces.Instrument i :e){ 13 tune(i); 14 } 15 } 16 public static void main(String[] args) { 17 //upcasting during addition to array 18 Instrument[] orchestra = {new Wind(),new Percussion(),new Stringed(),new Brass(),new Woodwind()}; 19 tuneAll(orchestra); 20 } 21 } 22 interface Instrument{ 23 int VALUE = 5; 24 void play(Note e);//Automatically public 25 void adjust(); 26 } 27 class Wind implements Instrument{ 28 29 @Override 30 public void play(Note e) { 31 System.out.println(this+".play()"+e); 32 } 33 34 @Override 35 public void adjust() { 36 System.out.println(this+".adjust()"); 37 } 38 39 @Override 40 public String toString() { 41 return "Wind"; 42 } 43 } 44 class Percussion implements Instrument{ 45 46 @Override 47 public void play(Note e) { 48 System.out.println(this+".play()"+e); 49 } 50 51 @Override 52 public void adjust() { 53 System.out.println(this+".adjust()"); 54 } 55 56 @Override 57 public String toString() { 58 return "Precussion"; 59 } 60 } 61 class Stringed implements Instrument{ 62 63 @Override 64 public void play(Note e) { 65 System.out.println(this+".play()"+e); 66 } 67 68 @Override 69 public void adjust() { 70 System.out.println(this+".adjust()"); 71 } 72 73 @Override 74 public String toString() { 75 return "Stringed"; 76 } 77 } 78 class Brass extends Wind{ 79 @Override 80 public String toString() { 81 return "Brass"; 82 } 83 } 84 class Woodwind extends Wind{ 85 @Override 86 public String toString() { 87 return "Woodwind"; 88 } 89 }
完全解耦
只要一个方法操作的是类而非接口,那么你就只能使用这个类及其子类。如果你想要将这个方法应用于不在此继承结构中的某个类,那么你就会触霉头了。接口可以在很大程度上放宽这种限制,因此,它可以使得我们编写可复用性更好的代码。
基类作为参数列表
1 /** 2 * @author yxm 3 * @date 2015/11/5 20:17 4 * @company 中国奇奇科技有限公司 5 * @description 完全解耦 基类作为参数列表 6 */ 7 public class Apply { 8 public static String s = "Disagreement with beliefs is by definition incorrect"; 9 public static void main(String[] args) { 10 process(new Upcase(),s); 11 process(new Downcase(),s); 12 process(new Splitter(),s); 13 } 14 public static void process(Processor p,Object s){ 15 System.out.println("Using Processor "+p.name()); 16 System.out.println(p.process(s)); 17 } 18 } 19 class Processor{ 20 public String name(){ 21 return getClass().getSimpleName(); 22 } 23 Object process(Object input){ 24 return input; 25 } 26 } 27 class Upcase extends Processor{ 28 String process(Object input){ 29 return ((String)input).toUpperCase(); 30 } 31 } 32 class Downcase extends Processor{ 33 String process(Object input){ 34 return ((String)input).toLowerCase(); 35 } 36 } 37 class Splitter extends Processor{ 38 String process(Object input){ 39 return Arrays.toString(((String)input).split(" ")); 40 } 41 } 42 //Using Processor Upcase 43 //DISAGREEMENT WITH BELIEFS IS BY DEFINITION INCORRECT 44 //Using Processor Downcase 45 //disagreement with beliefs is by definition incorrect 46 //Using Processor Splitter 47 //[Disagreement, with, beliefs, is, by, definition, incorrect]
接口作为参数列表
1 public interface Processor { 2 String name(); 3 Object process(Object input); 4 5 }
1 public class Apply { 2 public static void process(Processor p,Object s){ 3 System.out.println("Using Processor "+p.name()); 4 System.out.println(p.process(s)); 5 } 6 }
1 public abstract class StringProcessor implements Processor { 2 @Override 3 public String name() { 4 return getClass().getSimpleName(); 5 } 6 public abstract String process(Object input); 7 public static String s = ""; 8 public static void main(String[] args) { 9 Apply.process(new Upcase(),s); 10 Apply.process(new Downcase(),s); 11 Apply.process(new Splitter(),s); 12 } 13 } 14 class Upcase extends StringProcessor{ 15 public String process(Object input){ 16 return ((String)input).toUpperCase(); 17 } 18 } 19 class Downcase extends StringProcessor{ 20 public String process(Object input){ 21 return ((String)input).toLowerCase(); 22 } 23 } 24 class Splitter extends StringProcessor{ 25 public String process(Object input){ 26 return Arrays.toString(((String) input).split(" ")); 27 } 28 }
多重继承
接口不仅仅是一种更纯粹形式的抽象类,它的目标比这要高。因为接口是根本没有任何具体实现的---也就是说,没有任何与接口相关的存储;因此,也就无法阻止多个接口的组合。这一点很有价值,因为你有时需要去表示"一个x是一个a和一个b或者一个c"。我们可以去继承任意多个接口,并可以向上转型为每个接口,因为每个接口都是一个独立类型。
1 /** 2 * @author yxm 3 * @date 2015/11/7 12:27 4 * @company 中国奇奇科技有限公司 5 * @description 多重继承 6 */ 7 public class Adventure { 8 public static void t(CanFight x){ 9 x.fight(); 10 } 11 public static void u(CanSwim x){ 12 x.swim(); 13 } 14 public static void v(CanFly x){ 15 x.fly(); 16 } 17 public static void w(ActionCharacter x){ 18 x.fight(); 19 } 20 public static void main(String[] args) { 21 Hero h = new Hero(); 22 } 23 24 } 25 interface CanFight{ 26 void fight(); 27 } 28 interface CanSwim{ 29 void swim(); 30 } 31 interface CanFly{ 32 void fly(); 33 } 34 class ActionCharacter{ 35 public void fight(){} 36 } 37 class Hero extends ActionCharacter implements CanFight,CanSwim,CanFly{ 38 public void swim(){} 39 public void fly(){} 40 }
上面的例子展示了使用接口的核心原因:为了能够向上转型为多个基类型(以及由此而带来的灵活性)。然而,使用接口的第二个原因却是与使用抽象基类相同:防止客户端程序员创建该类的对象,并确保这仅仅是建立了一个接口。这就带来了一个问题:我们应该使用接口还是抽象类?如果要创建不带任何方法定义和成员变量的基类,那么就应该选择接口而不是抽象类。事实上,如果知道某事物应该成为一个基类,那么第一选择应该是使他成为一个接口。
通过继承来扩展接口
通过继承,可以很容易的在接口中添加新的方法声明,还可以通过继承在新接口中组合数个接口。这两种情况都可以获得新的接口。
1 /** 2 * @author yxm 3 * @date 2015/11/7 12:33 4 * @company 中国奇奇科技有限公司 5 * @description 通过继承来扩展接口 6 */ 7 public class HorrorShow { 8 static void u(Monster b){ 9 b.menace(); 10 } 11 static void v(DangerousMonster d){ 12 d.menace(); 13 d.destroy(); 14 } 15 static void w(Lethal l){ 16 l.kill(); 17 } 18 public static void main(String[] args) { 19 DangerousMonster dang = new DragonZilla();// 接口 = new 实现类() 20 u(dang); 21 v(dang); 22 Vampire vlad = new VeryBadVampire(); 23 u(vlad); 24 v(vlad); 25 w(vlad); 26 } 27 } 28 interface Monster{ 29 void menace(); 30 } 31 interface DangerousMonster extends Monster{ 32 void destroy(); 33 } 34 interface Lethal{ 35 void kill(); 36 } 37 class DragonZilla implements DangerousMonster{ 38 public void menace(){} 39 public void destroy(){} 40 } 41 interface Vampire extends DangerousMonster,Lethal{//接口继承可以继承两个接口,三观尽毁 42 void drinkBlood(); 43 } 44 class VeryBadVampire implements Vampire{//如果要实现,就必须实现Vampire接口和Vampire继承结构中基类 45 public void menace(){} 46 public void destroy(){} 47 public void kill(){} 48 public void drinkBlood(){} 49 }
适配接口
接口最吸引人的原因之一就是允许同一个接口具有多个不同的具体实现。在简单的情况中,它的体现形式通常是接受接口类型的方法,而该接口的实现和向该方法传递的对象则取决于方法的使用者。
因此,接口一种常见的用法就是上面提到的策略设计模式,此时你编写一个执行某些操作的方法,而该方法接受一个同样是你指定的接口。你主要就是要声明:“你可以用任何你想要的对象来调用我的方法,只要你的对象遵循我的接口。”这使得你的方法更加灵活、通用,并更具有复用性。
例如,在Java SE5中的Scanner类的构造器接受的就是一个Readable接口。你会发现Readable没有用作Java标准类库其他任何方法的参数,它是单独为Scanner创建的,以使得Scanner不必将参数限定为某个特定类。通过这种方式,Scanner可以作用于更多的类型。如果你创建了一个新的类,并且想让Scanner作用于它,那么你就应该实现Readable接口。
1 public class RandomWords implements Readable{ 2 private static Random rand = new Random(47); 3 private static final char [] capitals = "ABCDEFGHIJKLMNOPQRSTUVWXYZ".toCharArray(); 4 private static final char [] lowers = "abcdefghijklmnopqrstuvwxyz".toCharArray(); 5 private static final char [] vowels = "ieaou".toCharArray(); 6 private int count; 7 public RandomWords(int count){ 8 this.count = count; 9 } 10 @Override 11 public int read(CharBuffer cb) throws IOException { 12 if(count--==0) 13 return -1; 14 cb.append(capitals[rand.nextInt(capitals.length)]); 15 for (int i = 0;i < 4; i++){ 16 cb.append(vowels[rand.nextInt(vowels.length)]); 17 cb.append(lowers[rand.nextInt(lowers.length)]); 18 } 19 cb.append(" "); 20 return 10; 21 } 22 23 public static void main(String[] args) { 24 Scanner s = new Scanner(new RandomWords(10)); 25 while (s.hasNext()) 26 System.out.println(s.next()); 27 } 28 } 29 //Yizeruyic 30 //Fowenucor 31 //Goeizamom 32 //Rieuuicao 33 //Nuoidesaw 34 //Higeaakux 35 //Ruqacabui 36 //Numisetah 37 //Kuuuuozog 38 //Wiqazeyoy
初始化接口中的域
接口中的域自动默认为public static final。
在接口中定义的域不能是空final,但是可以被非常量表达式初始化。
既然域是static的,它们就可以在类第一次加载时被初始化,这发生在任何域首次被访问时。
当然,这些域不是接口的一部分,它们的值被存储在该接口的静态存储区域内。
1 public interface RandVals { 2 Random RAND = new Random(47); 3 int RANDOM_INT = RAND.nextInt(10); 4 long RANDOM_LONG = RAND.nextLong()*10; 5 float RANDOM_FLOAT = RAND.nextFloat()*10; 6 double RANDOM_DOUBLE = RAND.nextDouble()*10; 7 }
1 public class TestRandVals { 2 public static void main(String[] args) { 3 System.out.println(RandVals.RANDOM_INT); 4 System.out.println(RandVals.RANDOM_LONG); 5 System.out.println(RandVals.RANDOM_DOUBLE); 6 System.out.println(RandVals.RANDOM_FLOAT); 7 } 8 } 9 // 8 10 // -32032247016559954 11 // 1.6020656493302599 12 // 0.534122