首页 > 代码库 > 抽象类和接口
抽象类和接口
简述
接口是Java中比较重要的一个特性,为我们提供了一种将接口和实现分离的更加结构化的方法。此外,接口还可以用来实现多重继承。在了解接口之前,我们先学习一下抽象类。
抽象类和抽象方法
抽象类是指在某个类中存在抽象方法的类,不管是抽象方法一个还是多个。所谓抽象方法,就是指在类中,仅有方法声明,但没有方法体。包含抽象方法的类,必须被定义为抽象的,否则编译会报错。我们创建抽象类主要是希望创建一些通用的类,通过这个通用类去操纵一系列类。因此创建一个抽象类的对象没有意义,并且我们可能还想阻止使用者这么去做。当我们尝试去为一个抽象类创建对象时,编译器将会给出出错消息。此外,也可以创建一个不包含任何方法的抽象类。
1 //创建一个简单的抽象类 《Thinking in java》2 abstract class Instrument3 {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 = http://www.mamicode.com/5; 6 void play(); 7 void adjust(); 8 } 9 10 class Wind implements Instrument11 {12 public void play()13 {14 System.out.println(this+".play()");15 }16 public void adjust(){};17 }18 19 class Percussion implements Instrument20 {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 Percussion34 {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 Processor18 {19 String process(Object input)20 {21 return ((String)input).toUpperCase();22 }23 }24 25 class Downcase extends Processor26 {27 String process(Object input)28 {29 return ((String)input).toLowerCase();30 }31 }32 33 class Splitter extends Processor34 {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 Upcase61 HELLO WORLD!62 Using Processor Downcase63 hello world!64 Using Processor Splitter65 [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 Filter31 {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 Filter47 {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 Filter63 {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中的多重继承
接口不仅仅是一种更纯粹的抽象类,Java中,通过组合接口实现多重继承。C++中通过组合多个类的接口实现多重继承,但是C++中每个类都有一个具体实现,而在Java中,相同的形式,但是每个类只有一个具体实现。因此JAVA的多重继承会被C++更简便。
如果我们要从一个非接口的类继承,那么只能从一个类去继承。其余的基元素都必须是接口。需要将所有接口名都置于implements关键字之后,用逗号将他们隔开。可以继承任意多个接口,并可以向上转型为每个接口,因为每个接口都是一个独立类型。
使用接口的核心原因:为了能够向上转型为多个基类(以及由此带来的灵活性),另一个原因是防止客户端程序员创建该类的对象。
- 通过继承来扩展接口
接口可以继承接口来扩展接口。
抽象类和接口