首页 > 代码库 > 抽象类和接口

抽象类和接口

  • 简述

    接口是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关键字之后,用逗号将他们隔开。可以继承任意多个接口,并可以向上转型为每个接口,因为每个接口都是一个独立类型。

    使用接口的核心原因:为了能够向上转型为多个基类(以及由此带来的灵活性),另一个原因是防止客户端程序员创建该类的对象。

 

  • 通过继承来扩展接口  

    接口可以继承接口来扩展接口。

 

抽象类和接口