Java 核心技术点之接口
plua1130
8年前
<h2><strong>为什么使用接口</strong></h2> <p>Java中的接口是一组对需求的描述。接口通过声明接口方法来对外宣布:“要想具有XX功能,就得按我说的做(即实现接口方法)。” 而接口的实现类通过实现相应接口的方法来宣布:“我已经按你说的做了,请赐予我XX功能吧!” 例如,以下是Comparable<T>接口的定义:</p> <pre> <code class="language-java">public interface Comparable<T> { int compareTo(T o); }</code></pre> <p>Comparable<T>接口中定义了一个compareTo方法,这个方法就是它所描述的需求。也就是说Comparable接口对外宣布:“要想拥有进行比较的功能,得按我说的做(实现compareTo方法)。” 若我们想调用Arrays.sort方法对一个People对象数组进行比较,那么People对象必须是”可比较的”,即People类需要实现Comparable<T>接口。接口的实现类需要实现接口中定义的方法。People类通过实现Comparable<T>接口的方法来宣称:”我已经按你说的做啦,请让我变为可比较的吧!“ 也就是说,接口描述了一组需求,而实现一个接口的类就需要实现这个接口所描述的需求。比如我们想要People类对象是可比较的,我们可以这样:</p> <pre> <code class="language-java">public class People implements Comparable<People> { ... public int compareTo(People p) { //定义具体的比较标准 } ... }</code></pre> <p>实际上,Arrays.sort方法之所以要求它所比较的对象需实现了Comparable接口,是因为它在比较对象时调用的对象的compareTo方法(因为它不知道评价一个对象大小的标准,这个标准是由我们来定的)。那么,我们为什么不直接在People类中定义一个compareTo实例方法来定义People对象的比较标准,而是要去实现一个Comparable<T>接口呢?让我们举例来说明一下,假如我们调用了以下代码来对People对象数组peoples进行比较:</p> <pre> <code class="language-java">Arrays.sort(peoples);</code></pre> <p>在sort方法内部,实际上调用了类似下面这样的代码来比较People对象:</p> <pre> <code class="language-java">if (peoples[i].compareTo(peoples[j]) > 0) { //若为true说明peoples[i] 大于peoples[j] }</code></pre> <p>也就是说Arrays.sort方法内部调用了People对象的compareTo方法,那么编译器如何知道People类中确实定义了一个方法呢?若People类没有实现Comparable接口,编译器就只有检查这个类是否实现了这个方法,这样做无疑会增大开销,若接口中的方法不只一个,开销就更大了。而且需要比较的对象可能不只一个,假如后面又有Date对象、Job对象需要我们比较呢?如果每次调用相应对象的compareTo方法都要去检查一下它究竟实现了这个方法没有,将无形中增加很多本不必要的开销。</p> <p>反过来,我们看看引入接口的好处。People类头部的"implements Comparable<People>“就像是在告诉编译器”我是可比较的,可以直接调用我的compareTo方法而不用检查我有没有这个方法“。这样一来,所有可比较的对象只要实现Comparable<T>接口,编译器就能够知道它一定定义了compareTo方法。一个接口实际上是描述了一种规范,它只说明了这个接口需要实现什么需求,而没有强制规定这个需求如何实现。就像之前我们提到的Comparable<T>接口,需要比较大小的对象不只People对象一个,既然大家都需要比较大小,那索性就来个规定,所有想比较大小的对象的类都实现Compareble<T>接口,统一规定一下可比较的对象究竟该满足什么需求(这里的需求就是compareTo方法)。</p> <h2><strong>接口的特性</strong></h2> <p>既然已经知道了为什么要使用接口,接下来让我们多了解下接口到底有什么特性。</p> <p>首先,接口本身的访问修饰符可以为public或protected,接口中的所有方法都只能是public(所以可以不加这个修饰符)。其次接口中可以定义常量,但不能包含实例变量(提供实例变量这个活应该交给接口的实现类)。使用接口的步骤也很简单,正如我们上面提到的,只需要用关键子implements加上接口名表示这个类要实现这个接口,然后在类的定义中定义接口所要求实现的方法即可。</p> <p>接口只是一种规范,所以我们不能用new实例化一个接口。但是我们可以声明接口类型的变量,接口变量只能引用实现了该接口的类对象,比如以下代码:</p> <pre> <code class="language-java">Comparable<People> p = new People(...);</code></pre> <p>虽然接口不是类,但我们也可以使用instanceof来判断一个对象是否实现了某个接口:</p> <pre> <code class="language-java">if (p instanceof Comparable) { ... }</code></pre> <p>我们也可以”继承“一个接口:</p> <pre> <code class="language-java">public interface InA extends InB { ... }</code></pre> <pre> <code class="language-java">接口还有一个很有用的特性就是一个类可以实现多个接口。其实以上提到的接口特性只要”接口只是一种规范”这个本质来看就很好理解:比如接口中不能实例化、不能含有实例变量,就是因为接口不同于类那样是一个“蓝图”,接口更像一个“标签”。</code></pre> <h2><strong>接口与抽象类的比较</strong></h2> <p>接口与抽象类的一个本质区别是,抽象类只是一种含有抽象方法的类。也就是说,抽象类是对常规类的进一步”抽象“。常规类是可以直接实例化的,因为它所定义的方法都有了明确的实现,也就是说相应类对象的行为应经能够完全确定下来。而抽象类包含抽象方法,也就是说对象的一部分行为还没有完全确定下来。可以把抽象方法也理解成一种需求描述,因为抽象类存在着”待解决的需求“,所以它不能实例化,只能先派生出子类,解决了这个需求后(也就是实现了抽象方法),从而把实例的整个“蓝图”确定下来,才能够实例化。比如说抽象类People中定义一个write抽象方法,因为写这个动作不同人可以选择不同的方式完成(比如有的人是左撇子,拿笔姿势各不相同)。因为抽象类是一种类,所以一个类只能继承一个抽象类,而不能像接口那样可以“多继承”。</p> <p>另一方面,抽象类由于本质上是一个类,所以可以包含实例变量,也可包含方法的实现。接口中只能含有public方法,而接口中的常量也都是public的(因为作为一种规范就要”公开透明“);而抽象类可以含有private变量及方法。抽象类中只有抽象方法需要派生类(假设派生来非抽象类)实现,而实现接口的类必须实现接口中规定的所有方法。这样一来,若我们向某个接口中新增一个方法,那么所有实现了这个接口的类都需要进行相应的改动;而对于抽象类,我们向其中添加非抽象方法,不需要它的派生类做任何改动。</p> <h2><strong>接口与回调(callback)</strong></h2> <p>回调(callback)是一种常见的设计模式,利用这种模式,我们可以指出在某个事件发生时应该采取什么动作。比如,Java类库中有个Timer类,可以使通过它在过了指定的时间后发出通知,就像一个闹钟一样。初始化Timer类时,需要指定一个时间和到达这个时间后要执行的动作。在这里,告诉Timer要执行的动作就是通过传递给它一个对象,然后到达指定时间后,Timer会调用这个对象的方法。Timer要求我们传递给它的对象需要实现ActionListener接口,这个接口的定义如下:</p> <pre> <code class="language-java">public interface ActionListener { public void actionPerformed(ActionEvent event); }</code></pre> <p>我们传递给Timer构造器的对象必然定义了actionPerformed方法,我们可以在这个方法中定义我们在定时间隔到达后需要执行的动作。请看以下代码:</p> <pre> <code class="language-java">1 public class TimerTest { 2 public static void main(String[] args) { 3 ActionListener listener = new TimePrinter(); 4 Timer t = new Timer(10000, listener); 5 t.start(); 6 System.exit(0); 7 } 8 } 9 10 class TimePrinter implements ActionListener { 11 public void actionPerformed(ActionEvent event) { 12 Date now = new Date(); 13 System.out.println("The time is " + now); 14 } 15 }</code></pre> <p>在以上代码中,会每隔10秒调用一次TimerPrinter中的actionPerformed方法,获取并输出一次当前时间。actionPerformed方法就是回调方法,在回调模式中,通常都是一个类(TimePrinter)实现一个接口(ActionListener),然后另一个类(Timer)的对象通过持有实现该接口的类对象引用(listener)来调用相应的回调方法。</p> <p> </p> <p> </p> <p> </p> <p>来自:http://www.jianshu.com/p/e9e02bca7763</p> <p> </p>