简述
接口是Java中比较重要的一个特性,为我们提供了一种将接口和实现分离的更加结构化的方法。此外,接口还可以用来实现多重继承。在了解接口之前,我们先学习一下抽象类。
抽象类和抽象方法
抽象类是指在某个类中存在抽象方法的类,不管是抽象方法一个还是多个。所谓抽象方法,就是指在类中,仅有方法声明,但没有方法体。包含抽象方法的类,必须被定义为抽象的,否则编译会报错。我们创建抽象类主要是希望创建一些通用的类,通过这个通用类去操纵一系列类。因此创建一个抽象类的对象没有意义,并且我们可能还想阻止使用者这么去做。当我们尝试去为一个抽象类创建对象时,编译器将会给出出错消息。此外,也可以创建一个不包含任何方法的抽象类。
1 //创建一个简单的抽象类 《Thinking in java》 2 abstract class Instrument 3 { 4 public abstract void play(Node n); 5 public String what(){return "Instrument";} 6 public abstract void adjust(); 7 }
接下来,以一个例子讲述抽象类
1 package interfaces.music4; 2 //《Thinking in Java》 3 abstract class Instrument 4 { 5 private int i; 6 public abstract void play(); 7 public String what() 8 { 9 return "Insrument"; 10 } 11 public abstract void adjust(); 12 } 13 14 class Wind extends Instrument 15 { 16 public void play() 17 { 18 System.out.println("Wind.play()"); 19 } 20 public String what() 21 { 22 return "Wind"; 23 } 24 public void adjust(){} 25 } 26 27 class Percussion extends Instrument 28 { 29 public void play() 30 { 31 System.out.println("Percussion.play()"); 32 } 33 public String what() 34 { 35 return "Percussion"; 36 } 37 public void adjust(){} 38 } 39 40 class Stringed extends Instrument 41 { 42 public void play() 43 { 44 System.out.println("Stringed.play()"); 45 } 46 public String what() 47 { 48 return "stringed"; 49 } 50 public void adjust(){} 51 } 52 53 class Brass extends Wind 54 { 55 public void play() 56 { 57 System.out.println("Brass.play()"); 58 } 59 public void adjust() 60 { 61 System.out.println("Brass.adjust()"); 62 } 63 } 64 65 class Woodwind extends Wind 66 { 67 public void play() 68 { 69 System.out.println("Woodwind.play()"); 70 } 71 public String what() 72 { 73 return "Woodwind"; 74 } 75 } 76 public class Music4 77 { 78 79 static void tune(Instrument i) //直接使用抽象基类,而不需要关注其类型 80 { 81 i.play(); 82 } 83 static void tuneAll(Instrument []e) 84 { 85 for(Instrument i : e) 86 { 87 tune(i); 88 } 89 } 90 public static void main(String []args) 91 { 92 Instrument []orchestra = 93 { 94 new Wind(), 95 new Percussion(), 96 new Stringed(), 97 new Brass(), 98 new Woodwind() 99 }; 100 tuneAll(orchestra); 101 } 102 }
输出如下:
Wind.play()
Percussion.play()
Stringed.play()
Brass.play()
Woodwind.play()
可以看到,基类作为一个抽象类,仅声明方法,而子类实现了各自所需的方法,并且在使用时(上面代码的tune和tuneAll),我们只需要使用其抽象基类,并不关注其具体类型。编译器会正确地调用相应的方法,编译器会将公共方法沿着继承层次向上移动,这样就完成了用一个通用类来操作更多的类的目标。
下图为上述代码的继承层次结构图
另外,如果在抽象基类的构造器中调用抽象方法,那么该抽象方法,将会被导出类的方法所覆盖。如以下列子所示:
1 package interfaces.test; 2 3 abstract class base 4 { 5 public base() 6 { 7 print(); 8 } 9 public abstract void print(); 10 11 } 12 13 public class Test extends base{ 14 15 public void print() 16 { 17 System.out.println(i); 18 } 19 20 public static void main(String []args) 21 { 22 Test a = new Test(); 23 a.print(); 24 } 25 private int i = 1; 26 } 27 28 //输出 0 和 1
interface关键字使得抽象的概念更进了一步,abstract类,允许人们在类中创建一个或多个非抽象方法。而interface则产生一个完全抽象的类,不提供任何一个方法的实现。接口通俗点来说,就是一组方法的集合,表示“所有实现了该特定接口的类看起来都像这样”。因此,任何使用某特定接口的代码都知道可以调用该接口的哪些方法,而且仅需要知道这些。因此,接口被用来建立类与类之间的协议,表明某个类实现了这个接口,那表示这个类一定会提供这个功能。在JAVA中,接口也被用来实现多重继承。
首先,接口的定义要用interface关键字,要让某一个类遵循某个特定接口,需要使用implements关键字,接口也可以包含域,但这些域都是隐式的static和final。在接口中的方法,即使不被声明为public,也默认是public。因此在实现接口的方法时,必须将方法声明为public,否则编译器将会提示出错。
1 package interfaces.music5; 2 3 interface Instrument 4 { 5 int VALUE = 5; 6 void play(); 7 void adjust(); 8 } 9 10 class Wind implements Instrument 11 { 12 public void play() 13 { 14 System.out.println(this+".play()"); 15 } 16 public void adjust(){}; 17 } 18 19 class Percussion implements Instrument 20 { 21 public void play() 22 { 23 System.out.println(this+".play()"); 24 } 25 public void adjust(){}; 26 public String toString() 27 { 28 return "Percussion"; 29 } 30 31 } 32 33 class Brass extends Percussion 34 { 35 public String toString() 36 { 37 return "Brass"; 38 } 39 } 40 41 public class Music5 42 { 43 public static void tune(Instrument i) 44 { 45 i.play(); 46 } 47 public static void tuneAll(Instrument []e) 48 { 49 for(Instrument i : e) 50 { 51 tune(i); 52 } 53 } 54 public static void main(String []args) 55 { 56 Instrument []orch = { 57 new Wind(), 58 new Percussion(), 59 new Brass() 60 }; 61 tuneAll(orch); 62 } 63 } 64 //输出如下: 65 /* 66 interfaces.music5.Wind@4e81d783.play() 67 Percussion.play() 68 Brass.play() 69 70 */
可以发现以上不同,输出对象的时候,toString被程序自动调用,toString方法如果没有重载,会导致输出的不同。其次,当实现了一个接口之后,其实现就变成一个普通的类,可以按照常规方式去使用它。下图为该程序的UML图
只要一个方法操作是类而非接口,那么你就只能使用这个类及其子类。接口在很大程度上放宽了这种限制,因此可以使得我们编写复用性更好的代码。下面给出一个例子。
例如,假设有一个Processor类,其中有一个name()方法,另外还有一个process方法,process方法中接受输入参数,修改它的值,然后产生输出。以这个类作为基类扩展,用以创建不同的Processor类。
1 package interfaces.classprocessor; 2 3 import java.util.*; 4 5 class Processor 6 { 7 public String name() 8 { 9 return getClass().getSimpleName(); 10 } 11 Object process(Object input) 12 { 13 return input; 14 } 15 } 16 17 class Upcase extends Processor 18 { 19 String process(Object input) 20 { 21 return ((String)input).toUpperCase(); 22 } 23 } 24 25 class Downcase extends Processor 26 { 27 String process(Object input) 28 { 29 return ((String)input).toLowerCase(); 30 } 31 } 32 33 class Splitter extends Processor 34 { 35 String process(Object input) 36 { 37 return Arrays.toString(((String)input).split(" ")); 38 } 39 } 40 41 public class Apply 42 { 43 public static void process(Processor p,Object s) 44 { 45 System.out.println("Using Processor "+p.name()); 46 System.out.println(p.process(s)); 47 } 48 49 public static String s = "Hello World!"; 50 51 public static void main(String []args) 52 { 53 process(new Upcase(),s); 54 process(new Downcase(),s); 55 process(new Splitter(),s); 56 } 57 58 } 59 /*输出如下 60 Using Processor Upcase 61 HELLO WORLD! 62 Using Processor Downcase 63 hello world! 64 Using Processor Splitter 65 [Hello, World!] 66 */
其中Apply.process()方法可以接受任何类型的Processor,并将其应用到一个Object对象上,然后打印结果。像本例这样,创建一个能够根据所传参数对象的不同而有不同行为的方法,被称为“策略设计模式”。其中策略就是传入的参数对象。现在我们再假设发现了一组电子滤波器,它们看起来好像适用于Apply.process()方法。具体代码如下:
1 package interfaces.filters; 2 3 public class Filter 4 { 5 public String name() 6 { 7 return getClass().getSimpleName(); 8 } 9 public Waveform process(Waveform input) 10 { 11 return input; 12 } 13 } 14 15 package interfaces.filters; 16 17 public class Waveform 18 { 19 private static long counter; 20 private final long id = counter++; 21 public String toString() 22 { 23 return "Waveform "+id; 24 } 25 } 26 27 28 package interfaces.filters; 29 30 public class HighPass extends Filter 31 { 32 double cutoff; 33 public HighPass(double cutoff) 34 { 35 this.cutoff = cutoff; 36 } 37 public Waveform process(Waveform input) 38 { 39 return input; 40 } 41 } 42 43 44 package interfaces.filters; 45 46 public class LowPass extends Filter 47 { 48 double cutoff; 49 public LowPass(double cutoff) 50 { 51 this.cutoff = cutoff; 52 } 53 public Waveform process(Waveform input) 54 { 55 return input; 56 } 57 } 58 59 60 package interfaces.filters; 61 62 public class BandPass extends Filter 63 { 64 double lowCutoff,highCutoff; 65 public BandPass(double lowCut,double highCut) 66 { 67 lowCutoff = lowCut; 68 highCutoff = highCut; 69 } 70 public Waveform process(Waveform input) 71 { 72 return input; 73 } 74 }
其中Filter跟Processor具有相同的接口元素,但是因为他们并非继承自Processor(因为Filter类并不知道你要将它作为一个Processor来用),所以你不能将Filter用于Apply.process()方法。这里主要是因为该方法和Processor之间的耦合过紧,故而导致了这种复用被禁止。但是如果Processor是一个接口,那么这种限制就会松动,使得你可以将Filter用于Apply.process()方法。下面给出修改方案:
1 public interface Processor 2 { 3 String name(); 4 Object process(Object input); 5 } 6 7 //另一个文件 8 public class Apply 9 { 10 public static void process(Processor p,Object s) 11 { 12 System.out.println("Using Processor "+p.name); 13 System.out.println(p.process(s)); 14 } 15 }
因而,复用代码的第一种方式是客户端程序员遵循该接口来编写他们的类。但是有时候,会经常碰到的情况是,你无法修改你想要使用的类。在这些情况下,就可以使用适配器模式。适配器中的代码将接受你所拥有的接口,并产生你所需要的接口。
1 import interfaces.classprocessor.Apply; 2 import interfaces.filters.*; 3 import interfaces.processor.Processor; 4 5 class FilterAdapter implements Processor 6 { 7 Filter filter; 8 public FilterAdapter(Filter filter) 9 { 10 this.filter = filter; 11 } 12 public String name() 13 { 14 return filter.name(); 15 } 16 public Waveform process(Object input) 17 { 18 return filter.process((Waveform)input); 19 } 20 } 21 public class FilterProcessor { 22 public static void main(String []args) 23 { 24 Waveform w = new Waveform(); 25 Apply.process(new FilterAdapter(new LowPass(1.0)), w); 26 } 27 //输出
Using Processor LowPass
Waveform 0
28 }
在这种使用适配器方式中,FilterAdapter的构造器将接受你所拥有的接口Filter,然后生成你所需要的Processor接口的对象,上面的代码用到了代理。将接口从具体实现中解耦使得接口可以应用于不同的具体实现,让代码更具可用性。
接口不仅仅是一种更纯粹的抽象类,Java中,通过组合接口实现多重继承。C++中通过组合多个类的接口实现多重继承,但是C++中每个类都有一个具体实现,而在Java中,相同的形式,但是每个类只有一个具体实现。因此JAVA的多重继承会被C++更简便。
如果我们要从一个非接口的类继承,那么只能从一个类去继承。其余的基元素都必须是接口。需要将所有接口名都置于implements关键字之后,用逗号将他们隔开。可以继承任意多个接口,并可以向上转型为每个接口,因为每个接口都是一个独立类型。
使用接口的核心原因:为了能够向上转型为多个基类(以及由此带来的灵活性),另一个原因是防止客户端程序员创建该类的对象。
接口可以继承接口来扩展接口。
原文:http://www.cnblogs.com/ak47-wz/p/4107893.html