深入理解Java之接口

1. 为什么使用接口

Java中的接口是一组对需求的描述。例如,以下是Comparable<T>接口的定义:

public interface Comparable<T> {
    int compareTo(T o);
}

Comparable<T>接口中定义了一个compareTo方法,这个方法就是它所描述的需求。若我们想调用Arrays.sort方法对一个People对象数组进行比较,那么People对象必须是”可比较的”,即People类需要实现Comparable<T>接口。接口的实现类需要实现接口中定义的方法。也就是说,接口描述了一组需求,而实现一个接口的类就需要实现这个接口所描述的需求。比如我们想要People类对象是可比较的,我们可以这样:

public class People implements Comparable<People> {
    ...
    public int compareTo(People p) {
        //定义具体的比较标准
    }
    ...
}

实际上,Arrays.sort方法之所以要求它所比较的对象需实现了Comparable接口,是因为它在比较对象时调用的对象的compareTo方法(因为它不知道评价一个对象大小的标准,这个标准是由我们来定的)。那么,我们为什么不直接在People类中定义一个compareTo实例方法来定义People对象的比较标准,而是要去实现一个Comparable<T>接口呢?让我们举例来说明以下,假如我们调用了以下代码来对People对象数组peoples进行比较:

Arrays.sort(peoples);

在sort方法内部,实际上调用了类似下面这样的代码来比较People对象:

if (peoples[i].compareTo(peoples[j]) > 0) {
    //若为true说明peoples[i] 大于peoples[j]
}

也就是说Arrays.sort方法内部调用了People对象的compareTo方法,那么编译器如何知道People类中确实定义了一个方法呢?若People类没有实现Comparable接口,编译器就只有检查这个类是否实现了这个方法,这样做无疑会增大开销,若接口中的方法不只一个,开销就更大了。而且需要比较的对象可能不只一个,假如后面又有Date对象、Job对象需要我们比较呢?如果每次调用相应对象的compareTo方法都要去检查以下它究竟实现了这个方法没有,将无形中增加很多本不必要的开销。

反过来,我们看看引入接口的好处。People类头部的"implements Comparable<People>“就像是在告诉编译器”我是可比较的,可以直接调用我的compareTo方法而不用检查我有没有这个方法“。这样一来,所有可比较的对象只要实现Comparable<T>接口,编译器就能够知道它一定定义了compareTo方法。一个接口实际上是描述了一种规范,它只说明了这个接口需要实现什么需求,而没有强制规定这个需求如何实现。就像之前我们提到的Comparable<T>接口,需要比较大小的对象不只People对象一个,既然大家都需要比较大小,那索性就来个规定,所有想比较大小的对象的类都实现Compareble<T>接口,统一规定一下可比较的对象究竟该满足什么需求(这里的需求就是compareTo方法)。

2. 接口的特性

既然已经知道了为什么要使用接口,接下来让我们多了解下接口到底有什么特性。

首先,接口本身的访问修饰符可以为public或protected,接口中的所有方法都只能是public(所以可以不加这个修饰符)。其次接口中可以定义常量,但不能包含实例变量(提供实例变量这个活应该交给接口的实现类)。使用接口的步骤也很简单,正如我们上面提到的,只需要用关键子implements加上接口名表示这个类要实现这个接口,然后在类的定义中定义接口所要求实现的方法即可。

接口只是一种规范,所以我们不能用new实例化一个接口。但是我们可以声明接口类型的变量,接口变量只能引用实现了该接口的类对象,比如以下代码:

Comparable<People> p = new People(...);

虽然接口不是类,但我们也可以使用instanceof来判断一个对象是否实现了某个接口:

if (p instanceof Comparable) {
    ...
}

我们也可以”继承“一个接口:

public interface InA extends InB {
    ...
}

接口还有一个很有用的特性就是一个类可以实现多个接口。其实以上提到的接口特性只要”接口只是一种规范”这个本质来看就很好理解:比如接口中不能实例化、不能含有实例变量,就是因为接口不同于类那样是一个“蓝图”,接口更像一个“标签”。

3.接口与抽象类的比较

接口与抽象类的一个本质区别是,抽象类只是一种含有抽象方法的类。也就是说,抽象类是对常规类的进一步”抽象“。常规类是可以直接实例化的,因为它所定义的方法都有了明确的实现,也就是说相应类对象的行为应经能够完全确定下来。而抽象类包含抽象方法,也就是说对象的一部分行为还没有完全确定下来。可以把抽象方法也理解成一种需求描述,因为抽象类存在着”待解决的需求“,所以它不能实例化,只能先派生出子类,解决了这个需求后(也就是实现了抽象方法),从而把实例的整个“蓝图”确定下来,才能够实例化。比如说抽象类People中定义一个write抽象方法,因为写这个动作不同人可以选择不同的方式完成(比如有的人是左撇子,拿笔姿势各不相同)。因为抽象类是一种类,所以一个类只能继承一个抽象类,而不能像接口那样可以“多继承”。

另一方面,抽象类由于本质上是一个类,所以可以包含实例变量,也可包含方法的实现。接口中只能含有public方法,而接口中的常量也都是public的(因为作为一种规范就要”公开透明“);而抽象类可以含有private变量及方法。抽象类中只有抽象方法需要派生类(假设派生来非抽象类)实现,而实现接口的类必须实现接口中规定的所有方法。这样一来,若我们向某个接口中新增一个方法,那么所有实现了这个接口的类都需要进行相应的改动;而对于抽象类,我们向其中添加非抽象方法,不需要它的派生类做任何改动。

4. 接口与回调(callback)

回调(callback)是一种常见的设计模式,利用这种模式,我们可以指出在某个事件发生时应该采取什么动作。比如,Java类库中有个Timer类,可以使通过它在过了指定的时间后发出通知,就像一个闹钟一样。初始化Time类时,需要指定一个时间和到达这个时间后要执行的动作。在这里,告诉Timer要执行的动作就是通过传递给它一个对象,然后到达指定时间后,Timer会调用这个对象的方法。Timer要求我们传递给它的对象需要实现ActionListener接口,这个接口的定义如下:

public interface ActionListener {
    public void actionPerformed(ActionEvent event);
}

我们传递给Time构造器的对象必然定义了actionPerformed方法,我们可以在这个方法中定义我们在定时间隔到达后需要执行的动作。请看以下代码:

 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 } 

在以上代码中,会每隔10秒调用一次TimerPrinter中的actionPerformed方法,获取并输出一次当前时间。actionPerformed方法就是回调方法,在回调模式中,通常都是一个类(TimePrinter)实现一个接口(ActionListener),然后另一个类(Timer)的对象通过持有实现该接口的类对象引用(listener)来调用相应的回调方法。

5.参考资料

JAVA核心技术(卷1) (豆瓣)

时间: 2024-10-12 10:09:23

深入理解Java之接口的相关文章

深入理解Java的接口和抽象类(转)

深入理解Java的接口和抽象类 对于面向对象编程来说,抽象是它的一大特征之一.在Java中,可以通过两种形式来体现OOP的抽象:接口和抽象类.这两者有太多相似的地方,又有太多不同的地方.很多人在初学的时候会以为它们可以随意互换使用,但是实际则不然.今天我们就一起来学习一下Java中的接口和抽象类.下面是本文的目录大纲: 一.抽象类 二.接口 三.抽象类和接口的区别 若有不正之处,请多多谅解并欢迎批评指正,不甚感激. 请尊重作者劳动成果,转载请标明原文链接: http://www.cnblogs.

(转载)深入理解java的接口和抽象类

本文转自地址:http://www.cnblogs.com/dolphin0520/p/3811437.html 作者:海子 出处:http://www.cnblogs.com/dolphin0520/ 深入理解Java的接口和抽象类 对于面向对象编程来说,抽象是它的一大特征之一.在Java中,可以通过两种形式来体现OOP的抽象:接口和抽象类.这两者有太多相似的地方,又有太多不同的地方.很多人在初学的时候会以为它们可以随意互换使用,但是实际则不然.今天我们就一起来学习一下Java中的接口和抽象类

【转】深入理解Java的接口和抽象类

深入理解Java的接口和抽象类 对于面向对象编程来说,抽象是它的一大特征之一.在Java中,可以通过两种形式来体现OOP的抽象:接口和抽象类.这两者有太多相似的地方,又有太多不同的地方.很多人在初学的时候会以为它们可以随意互换使用,但是实际则不然.今天我们就一起来学习一下Java中的接口和抽象类.下面是本文的目录大纲: 一.抽象类 二.接口 三.抽象类和接口的区别 一.抽象类 在了解抽象类之前,先来了解一下抽象方法.抽象方法是一种特殊的方法:它只有声明,而没有具体的实现.抽象方法的声明格式为:

深入理解Java的接口和抽象类 _摘抄

http://www.cnblogs.com/dolphin0520/p/3811437.html 原文 深入理解Java的接口和抽象类 对于面向对象编程来说,抽象是它的一大特征之一.在Java中,可以通过两种形式来体现OOP的抽象:接口和抽象类.这两者有太多相似的地方,又有太多不同的地方.很多人在初学的时候会以为它们可以随意互换使用,但是实际则不然.今天我们就一起来学习一下Java中的接口和抽象类.下面是本文的目录大纲: 一.抽象类 二.接口 三.抽象类和接口的区别 若有不正之处,请多多谅解并

深入理解Java的接口和抽象类

对于面向对象编程来说,抽象是它的一大特征之一.在Java中,可以通过两种形式来体现OOP的抽象:接口和抽象类.这两者有太多相似的地方,又有太多不同的地方.很多人在初学的时候会以为它们可以随意互换使用,但是实际则不然.今天我们就一起来学习一下Java中的接口和抽象类.下面是本文的目录大纲: 一.抽象类 二.接口 三.抽象类和接口的区别 若有不正之处,请多多谅解并欢迎批评指正,不甚感激. 请尊重作者劳动成果,转载请标明原文链接: http://www.cnblogs.com/dolphin0520/

[转]深入理解Java的接口和抽象类

对于面向对象编程来说,抽象是它的一大特征之一.在Java中,可以通过两种形式来体现OOP的抽象:接口和抽象类.这两者有太多相似的地方,又有太多不同的地方.很多人在初学的时候会以为它们可以随意互换使用,但是实际则不然.今天我们就一起来学习一下Java中的接口和抽象类.下面是本文的目录大纲: 一.抽象类 二.接口 三.抽象类和接口的区别 若有不正之处,请多多谅解并欢迎批评指正,不甚感激. 请尊重作者劳动成果,转载请标明原文链接: http://www.cnblogs.com/dolphin0520/

[转载] 深入理解Java的接口和抽象类

转载自: http://www.cnblogs.com/dolphin0520/p/3811437.html 对于面向对象编程来说,抽象是它的一大特征之一.在Java中,可以通过两种形式来体现OOP的抽象:接口和抽象类.这两者有太多相似的地方,又有太多不同的地方.很多人在初学的时候会以为它们可以随意互换使用,但是实际则不然.今天我们就一起来学习一下Java中的接口和抽象类.下面是本文的目录大纲: 一.抽象类 二.接口 三.抽象类和接口的区别 一.抽象类 在了解抽象类之前,先来了解一下抽象方法.抽

2018.4.22 深入理解Java的接口和抽象类

前言 对于面向对象编程来说,抽象是他的一大特征之一.在Java中,可以通过两种形式来体现oop 的抽象:接口和抽象类.这两者有太多相似的地方,又有太多不同的地方.很多人在初雪的时候会以为他们可以随意互换但是实际则不可以.下面就来学习一下 1.抽象类 在了解抽象类之前先来了解抽象方法.抽象方法是一种特殊的方法:他只有申明,而没有具体的实现.抽象方法的声明格式为 public abstract class AbstractClassName{ public abstract void method(

深入理解Java Callable接口

概述Callable和Runnbale一样代表着任务,区别在于Callable有返回值并且可以抛出异常.其使用如下: public class CallableDemo { static class SumTask implements Callable<Long> { @Override public Long call() throws Exception { long sum = 0; for (int i = 0; i < 9000; i++) { sum += i; } ret