9.2 接口

  9.1节详细介绍了抽象类,提到抽象类中可以有抽象方法,也可以有普通方法,但是有抽象方法的类必须是抽象类。如果抽象类中的方法都是抽象方法,那么由这些抽象方法组成的特殊的抽象类就是所说的接口。

9.2.1 接口的概念

  接口是一系列方法的声明,是一些抽象方法的集合。一个接口只有方法的声明,没有方法的实现,因此这些方法可以在不同的地方被不同的类实现,而这些实现类可以具有不同的行为。

  虽然我们常说,接口是一种特殊的抽象类,但是在面向对象编程的设计思想层面,两者还是有显著区别的。抽象类更侧重于对相似的类进行抽象,形成抽象的父类以供子类继承使用,而接口往往在程序设计的时候,定义模块与模块之间应满足的规约,使各模块之间能协调工作。接下来通过一个实际的例子来说明接口的作用。

  如今,蓝牙技术已经在社会生活中广泛应用。移动电话、蓝牙耳机、蓝牙鼠标、平板电脑等IT设备都支持蓝牙实现设备间短距离通信。那为什么这些不同的设备能通过蓝牙技术进行数据交换呢?其本质在于蓝牙提供了一组规范和标准,规定了频段、速率、传输方式等要求,各设备制造商按照蓝牙规范约定制造出来的设备,就可以按照约定的模式实现短距离通信。蓝牙提供的这组规范和标准,就是所谓的接口。

  蓝牙接口创建和使用步骤如下。

  (1)各相关组织、厂商约定蓝牙接口标准。

  (2)相关设备制造商按约定接口标准制作蓝牙设备。

  (3)符合蓝牙接口标准的设备可以实现短距离通信。

  Java接口定义的语法形式如下。

[修饰符]  interface 接口名 [extends] [接口列表]{

    接口体

}
copy

  interface前的修饰符是可选的,当没有修饰符的时候,表示此接口的访问只限于同包的类和接口。如果使用修饰符,则只能用public 修饰符,表示此接口是公有的,在任何地方都可以引用它,这一点和类是相同的。

  接口是和类同一层次的,所以接口名的命名规则参考类名命名规则即可。

  extends关键词和类语法中的extends类似,用来定义直接的父接口。和类不同,一个接口可以继承多个父接口,当extends 后面有多个父接口时,它们之间用逗号隔开。

  接口体就是用大括号括起来的那部分,接口体里定义接口的成员,包括常量和抽象方法。

  类实现接口的语法形式如下:

[类修饰符]  class  类名  implements 接口列表{

    类体

}
copy

  类实现接口用implements关键字,Java中的类只能是单继承的,但一个Java类可以实现多个接口,这也是Java解决多继承的方法。

  下面通过代码来模拟蓝牙接口规范的创建和使用步骤。

  (1)定义蓝牙接口。

  假设蓝牙接口通过input()和output()两个方法提供服务,这时就需要在蓝牙接口中定义这两个抽象方法,具体代码如下。

//定义蓝牙接口

public interface BlueTooth 

{

    //提供输入服务

    public void input();

    //提供输出服务

    public void output();

}
copy

  (2)定义蓝牙耳机类,实现蓝牙接口。

public class Earphone implements BlueTooth

{

    String name = "蓝牙耳机";

    //实现蓝牙耳机输入功能

    public void input()

    {

        System.out.println(name + "正在输入音频数据...");

    }

    //实现蓝牙耳机输出功能

    public void output()

    {

        System.out.println(name + "正在输出反馈信息...");

    }

}
copy

  (3)定义IPad类,实现蓝牙接口。

public class IPad implements BlueTooth

{

    String name = "iPad";

    //实现iPad输入功能

    public void input()

    {

        System.out.println(name + "正在输入数据...");

    }

    //实现iPad输出功能

    public void output()

    {

        System.out.println(name + "正在输出数据...");

    }

}
copy

  编写测试类,对蓝牙耳机类和IPad类进行测试,代码如下,运行结果如图9.7所示。

public class TestInterface 

{

    public static void main(String[] args) 

    {

        BlueTooth ep = new Earphone();   //创建并实例化一个实现了蓝牙接口的蓝牙耳机对象ep

        ep.input();                                       //调用ep的输入功能

        BlueTooth ip = new IPad();            //创建并实例化一个实现了蓝牙接口的iPad对象ip

        ip.input();                                        //调用ip的输入功能

        ip.output();                                      //调用ip的输出功能

    }

}
copy
图9.7 蓝牙接口使用

9.2.2 接口的使用

  电子邮件现在是人们广泛使用的一种信息沟通形式,要创建一封电子邮件,至少需要发信者邮箱、收信者邮箱、邮件主题和邮件内容4个部分。可以采用接口定义电子邮件的这些约定,让电子邮件类必须实现这个接口,从而达到让电子邮件必须满足这些约定的要求。

  (1)定义电子邮件接口。

public interface EmailInterface 

{

    //设置发信者邮箱

    public void setSendAdd(String add);

    //设置收信者邮箱

    public void setReceiveAdd(String add);

    //设置邮件主题

    public void setEmailTitle(String title);

    //设置邮件内容

    public void writeEmail(String email);

}
copy

  (2)定义邮箱类,实现EmailInterface接口。

  注意,在实现接口中抽象方法的同时,邮箱类本身还有一个showEmail()方法。

import java.util.Scanner;

//定义Email,实现Email接口

public class Email implements EmailInterface

{

    String sendAdd = "";                      //发信者邮箱

    String receiveAdd = "";                           //收信者邮箱

    String emailTitle = "";                    //邮件主题

    String email = "";                                     //邮件内容

    //实现设置发信者邮箱

    public void setSendAdd(String add)

    {

        this.sendAdd = add;

    }

    //实现设置收信者邮箱

    public void setReceiveAdd(String add)

    {

        this.receiveAdd = add;

    }

    //实现设置邮件主题

    public void setEmailTitle(String title)

    {

        this.emailTitle = title;

    }

    //实现设置邮件内容

    public void writeEmail(String email)

    {

        this.email = email;

    }

    //显示邮件全部信息

    public void showEmail()

    {

        System.out.println("***显示电子邮件内容***");

        System.out.println("发信者邮箱:" + sendAdd);

        System.out.println("收信者邮箱:" + receiveAdd);

        System.out.println("邮件主题:" + emailTitle);

        System.out.println("邮件内容:" + email);

    }

}
copy

  (3)定义一个邮件作者类。

  邮件作者类中含静态方法writeEmail(EmailInterface email),用于写邮件,具体代码如下。

class EmailWriter 

{

    //定义写邮件的静态方法,形参是EmailInterface接口

    public static void writeEmail(EmailInterface email) 

    {

        Scanner input = new Scanner(System.in);        

        System.out.print("请输入发信者邮箱:");

        email.setSendAdd(input.next());

        System.out.print("请输入收信者邮箱:");

        email.setReceiveAdd(input.next());

        System.out.print("请输入邮件主题:");

        email.setEmailTitle(input.next());

        System.out.print("请输入邮件内容:");

        email.writeEmail(input.next());

        //email.showEmail();//编译无法通过,因为形参email是EmailInterface接口,没有此方法

    }

}
copy

  (4)编写测试类。

  测试类代码首先创建并实例化出一个实现了电子邮件接口的对象email,然后调用EmailWriter类的静态方法writeEmail写邮件,最后将email对象强制类型转换成Email对象(不提倡此做法),调用Email类的showEmail()方法。具体代码如下,程序运行结果如图9.8所示。

public class TestInterface2 

{

    public static void main(String[] args) 

    {

        //创建并实例化一个实现了电子邮件接口的对象email

        EmailInterface email = new Email();

        //调用EmailWriter类的静态方法writeEmail写邮件

        EmailWriter.writeEmail(email);

        //强制类型转换,调用Email类的showEmail()方法(不是接口方法)

         ((Email)email).showEmail();

    }

}
copy
图9.8 电子邮箱接口的使用

9.2.3 接口的特征

  接下来,逐个了解接口有哪些特征。

  (1)接口中不允许有实体方法。

  例如,在EmailInterface接口中增加下面的实体方法。

//显示邮件全部信息

public void showEmail()

{

}
copy

  编译时就会报错,提示接口中不能有实体方法,如图9.9所示。

图9.9 接口中不能有实体方法

  (2)接口中可以有成员变量,默认修饰符是public static final,接口中的抽象方法必须用public修辞。

  在EmailInterface接口中,增加邮件发送端口号的成员变量sendPort,代码如下。

int sendPort = 25;//必须赋静态最终值
copy

  在Email类的showEmail()方法中增加语句System.out.println("发送端口号:" + sendPort);,含义为访问EmailInterface接口中的sendPort并显示出来,具体代码如下。

//显示邮件全部信息

public void showEmail()

{

    System.out.println("***显示电子邮件内容***");

    System.out.println("发送端口号:" + sendPort);

    System.out.println("发信者邮箱:" + sendAdd);

    System.out.println("收信者邮箱:" + receiveAdd);

    System.out.println("邮件主题:" + emailTitle);

    System.out.println("邮件内容:" + email);

}
copy

  EmailWriter类和TestInterface2类的代码不需要调整,运行TestInterface2类,程序运行结果如图9.10所示。

图9.10 接口中成员变量的使用

  (3)一个类可以实现多个接口。

  假设一个邮件,不仅需要符合EmailInterface接口对电子邮件规范的要求,而且需要符合对发送端和接收端端口号接口规范的要求,才允许成为一个合格的电子邮件。

  发送端和接收端端口号接口的代码如下所示。

//定义发送端和接收端端口号接口

public interface PortInterface 

{

//设置发送端端口号

public void setSendPort(int port);

//设置接收端端口号

public void setReceivePort(int port);

}
copy

  则Email类不仅要实现EmailInterface接口,还要实现PortInterface接口,同时类方法中必须实现PortInterface接口的抽象方法。Email类的代码如下。

import java.util.Scanner;

//定义Email,实现EmailInterface和PortInterface 接口

public class Email implements EmailInterface,PortInterface

{

    int sendPort = 25;                  //发送端端口号

    int receivePort = 110;           //接收端端口号

    //实现设置发送端端口号

    public void setSendPort(int port)

    {

        this.sendPort = port;

    }

    //实现设置接收端端口号

    public void setReceivePort(int port)

    {

        this.receivePort = port;

    }

    //显示邮件全部信息

    public void showEmail()

    {

        System.out.println("***显示电子邮件内容***");

        System.out.println("发送端口号:" + sendPort);

        System.out.println("接收端口号:" + receivePort);

        System.out.println("发信者邮箱:" + sendAdd);

        System.out.println("收信者邮箱:" + receiveAdd);

        System.out.println("邮件主题:" + emailTitle);

        System.out.println("邮件内容:" + email);

    }

    //省略了其他属性和方法的代码

}
copy

  修改EmailWriter类和TestInterface2(形成TestInterface3)类时,尤其需要注意的是EmailWriter类的静态方法writeEmail(Email email)中的形参不再是EmailInterface接口,而是Email类,否则无法在writeEmail方法中调用PortInterface接口的方法,不过这样做属于非面向接口编程,不提倡。类似地,TestInterface3代码中声明email对象时,也从EmailInterface接口调整成Email类。具体代码如下。

import java.util.Scanner;

//定义邮件作者类

class EmailWriter 

{

    //定义写邮件的静态方法,形参是Email类(非面向接口编程)

    //形参不能是EmailInterface接口,否则无法调用PortInterface接口的方法

    public static void writeEmail(Email email) 

    {

        Scanner input = new Scanner(System.in);        

        System.out.print("请输入发送端口号:");

        email.setSendPort(input.nextInt());

        System.out.print("请输入接收端口号:");

        email.setReceivePort(input.nextInt());

        System.out.print("请输入发信者邮箱:");

        email.setSendAdd(input.next());

        System.out.print("请输入收信者邮箱:");

        email.setReceiveAdd(input.next());

        System.out.print("请输入邮件主题:");

        email.setEmailTitle(input.next());

        System.out.print("请输入邮件内容:");

        email.writeEmail(input.next());

    }

}

public class TestInterface3 

{

    public static void main(String[] args) 

    {

        //创建并实例化一个Email类的对象email

        Email email = new Email();

        //调用EmailWriter的静态方法writeEmail写邮件

        EmailWriter.writeEmail(email);

        //调用Email类的showEmail()方法(不是接口方法)

        email.showEmail();

    }

}
copy

  程序运行结果如图9.11所示。

图9.11 实现多个接口的类

  (4)接口可以继承其他接口,实现接口合并的功能。

  在刚才的代码中,让一个类实现了多个接口,但是再调用这个类的时候,形参就必须是这个类,而不能是该类实现的某个接口,这样做就不是面向接口编程,程序的多态性得不到充分的体现。接下来在刚才例子的基础上,用接口继承的方式解决这个问题。

  EmailInterface类的代码如下。

//定义电子邮件接口,继承自PortInterface接口

public interface EmailInterface extends PortInterface 

{

    //设置发信者邮箱

    public void setSendAdd(String add);

    //设置收信者邮箱

    public void setReceiveAdd(String add);

    //设置邮件主题

    public void setEmailTitle(String title);

    //设置邮件内容

    public void writeEmail(String email);

}
copy

  PortInterface接口、Email类的代码不用调整,EmailWriter类和测试类TestInterface3中的声明为Email的类,改回为EmailInterface接口的声明,这样的程序又恢复了面向接口编程的特性,可以实现多态性。

9.2.4 接口的应用

  在接口的应用中,有一个非常典型的案例,就是实现打印机系统的功能。在打印机系统中,有打印机对象,有墨盒对象(可以是黑白墨盒,也可以是彩色墨盒),有纸张对象(可以是A4纸,也可以是B5纸)。怎么能让打印机、墨盒和纸张这些生产厂商生产的各自不同的设备,组装在一起成为打印机,却能正常打印呢?解决的办法就是接口。

  打印机系统开发的主要步骤如下。

  (1)打印机和墨盒之间需要接口,定义为墨盒接口PrintBox,打印机和纸张之间需要接口,定义为纸张接口PrintPaper。

  (2)定义打印机类,引用墨盒接口PrintBox和纸张接口PrintPaper,实现打印功能。

  (3)定义黑白墨盒和彩色墨盒实现墨盒接口PrintBox,定义A4纸和B5纸实现纸张接口PrintPaper。

  (4)编写打印系统,调用打印机实施打印功能。

  PrintBox和PrintPaper接口的代码如下。

//墨盒接口

public interface PrintBox {

    //得到墨盒颜色,返回值为墨盒颜色

    public String getColor();

    }

    //纸张接口
public interface PrintPaper {

    //得到纸张尺寸,返回值为纸张尺寸

    public String getSize();

}
copy

  打印机类Printer的代码如下。

//打印机类

public class Printer{     

    //使用墨盒在纸张上打印

    public void print(PrintBox box,PrintPaper paper){

        System.out.println("正在使用" + box.getColor() + "墨盒在" + paper.getSize() + "纸张上打印!");

    }

}
copy

  黑白墨盒类GrayPrintBox和彩色墨盒类ColorPrintBox的代码如下。

//黑白墨盒,实现了墨盒接口

public class GrayPrintBox implements PrintBox {

    //实现getColor()方法,得到“黑白”

    public String getColor() {

        return "黑白";

    }

}

//彩色墨盒,实现了墨盒接口

public class ColorPrintBox implements PrintBox {

    //实现getColor()方法,得到“彩色”

    public String getColor() {

        return "彩色";

    }

}
copy

  A4纸类A4Paper和B5纸类B5Paper的代码如下。

//A4纸张,实现了纸张接口

public class A4Paper implements PrintPaper {

    //实现getSize()方法,得到“A4”

    public String getSize() {

        return "A4";

    }

}

//B5纸张,实现了纸张接口

public class B5Paper implements PrintPaper {

    //实现getSize()方法,得到“B5”

    public String getSize() {

        return "B5";

    }

}
copy

  编写打印系统,代码如下,程序运行结果如图9.12所示。

public class TestPrinter {

    public static void main(String[] args) {

        PrintBox box = null;                       //墨盒

        PrintPaper paper = null;                  //纸张

        Printer printer = new Printer();       //打印机

        //使用彩色墨盒在B5纸上打印

        box = new ColorPrintBox();

        paper = new B5Paper();                  

        printer.print(box, paper);

        //使用黑白墨盒在A4纸上打印

        box = new GrayPrintBox();

        paper = new A4Paper();        

        printer.print(box, paper);                

    }

}
copy
图9.12 打印系统接口的实现