9.2 接口

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

9.2.1 接口的概念

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

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

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

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

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

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

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

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

  1. [修饰符] interface 接口名 [extends] [接口列表]{
  2. 接口体
  3. }

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

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

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

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

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

  1. [类修饰符] class 类名 implements 接口列表{
  2. 类体
  3. }

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

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

  (1)定义蓝牙接口。

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

  1. //定义蓝牙接口
  2. public interface BlueTooth
  3. {
  4. //提供输入服务
  5. public void input();
  6. //提供输出服务
  7. public void output();
  8. }

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

  1. public class Earphone implements BlueTooth
  2. {
  3. String name = "蓝牙耳机";
  4. //实现蓝牙耳机输入功能
  5. public void input()
  6. {
  7. System.out.println(name + "正在输入音频数据...");
  8. }
  9. //实现蓝牙耳机输出功能
  10. public void output()
  11. {
  12. System.out.println(name + "正在输出反馈信息...");
  13. }
  14. }

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

  1. public class IPad implements BlueTooth
  2. {
  3. String name = "iPad";
  4. //实现iPad输入功能
  5. public void input()
  6. {
  7. System.out.println(name + "正在输入数据...");
  8. }
  9. //实现iPad输出功能
  10. public void output()
  11. {
  12. System.out.println(name + "正在输出数据...");
  13. }
  14. }

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

  1. public class TestInterface
  2. {
  3. public static void main(String[] args)
  4. {
  5. BlueTooth ep = new Earphone(); //创建并实例化一个实现了蓝牙接口的蓝牙耳机对象ep
  6. ep.input(); //调用ep的输入功能
  7. BlueTooth ip = new IPad(); //创建并实例化一个实现了蓝牙接口的iPad对象ip
  8. ip.input(); //调用ip的输入功能
  9. ip.output(); //调用ip的输出功能
  10. }
  11. }
9.2 接口 - 图1
图9.7 蓝牙接口使用

9.2.2 接口的使用

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

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

  1. public interface EmailInterface
  2. {
  3. //设置发信者邮箱
  4. public void setSendAdd(String add);
  5. //设置收信者邮箱
  6. public void setReceiveAdd(String add);
  7. //设置邮件主题
  8. public void setEmailTitle(String title);
  9. //设置邮件内容
  10. public void writeEmail(String email);
  11. }

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

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

  1. import java.util.Scanner;
  2. //定义Email,实现Email接口
  3. public class Email implements EmailInterface
  4. {
  5. String sendAdd = ""; //发信者邮箱
  6. String receiveAdd = ""; //收信者邮箱
  7. String emailTitle = ""; //邮件主题
  8. String email = ""; //邮件内容
  9. //实现设置发信者邮箱
  10. public void setSendAdd(String add)
  11. {
  12. this.sendAdd = add;
  13. }
  14. //实现设置收信者邮箱
  15. public void setReceiveAdd(String add)
  16. {
  17. this.receiveAdd = add;
  18. }
  19. //实现设置邮件主题
  20. public void setEmailTitle(String title)
  21. {
  22. this.emailTitle = title;
  23. }
  24. //实现设置邮件内容
  25. public void writeEmail(String email)
  26. {
  27. this.email = email;
  28. }
  29. //显示邮件全部信息
  30. public void showEmail()
  31. {
  32. System.out.println("***显示电子邮件内容***");
  33. System.out.println("发信者邮箱:" + sendAdd);
  34. System.out.println("收信者邮箱:" + receiveAdd);
  35. System.out.println("邮件主题:" + emailTitle);
  36. System.out.println("邮件内容:" + email);
  37. }
  38. }

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

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

  1. class EmailWriter
  2. {
  3. //定义写邮件的静态方法,形参是EmailInterface接口
  4. public static void writeEmail(EmailInterface email)
  5. {
  6. Scanner input = new Scanner(System.in);
  7. System.out.print("请输入发信者邮箱:");
  8. email.setSendAdd(input.next());
  9. System.out.print("请输入收信者邮箱:");
  10. email.setReceiveAdd(input.next());
  11. System.out.print("请输入邮件主题:");
  12. email.setEmailTitle(input.next());
  13. System.out.print("请输入邮件内容:");
  14. email.writeEmail(input.next());
  15. //email.showEmail();//编译无法通过,因为形参email是EmailInterface接口,没有此方法
  16. }
  17. }

  (4)编写测试类。

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

  1. public class TestInterface2
  2. {
  3. public static void main(String[] args)
  4. {
  5. //创建并实例化一个实现了电子邮件接口的对象email
  6. EmailInterface email = new Email();
  7. //调用EmailWriter类的静态方法writeEmail写邮件
  8. EmailWriter.writeEmail(email);
  9. //强制类型转换,调用Email类的showEmail()方法(不是接口方法)
  10. ((Email)email).showEmail();
  11. }
  12. }
9.2 接口 - 图2
图9.8 电子邮箱接口的使用

9.2.3 接口的特征

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

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

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

  1. //显示邮件全部信息
  2. public void showEmail()
  3. {
  4. }

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

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

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

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

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

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

  1. //显示邮件全部信息
  2. public void showEmail()
  3. {
  4. System.out.println("***显示电子邮件内容***");
  5. System.out.println("发送端口号:" + sendPort);
  6. System.out.println("发信者邮箱:" + sendAdd);
  7. System.out.println("收信者邮箱:" + receiveAdd);
  8. System.out.println("邮件主题:" + emailTitle);
  9. System.out.println("邮件内容:" + email);
  10. }

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

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

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

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

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

  1. //定义发送端和接收端端口号接口
  2. public interface PortInterface
  3. {
  4. //设置发送端端口号
  5. public void setSendPort(int port);
  6. //设置接收端端口号
  7. public void setReceivePort(int port);
  8. }

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

  1. import java.util.Scanner;
  2. //定义Email,实现EmailInterface和PortInterface 接口
  3. public class Email implements EmailInterface,PortInterface
  4. {
  5. int sendPort = 25; //发送端端口号
  6. int receivePort = 110; //接收端端口号
  7. //实现设置发送端端口号
  8. public void setSendPort(int port)
  9. {
  10. this.sendPort = port;
  11. }
  12. //实现设置接收端端口号
  13. public void setReceivePort(int port)
  14. {
  15. this.receivePort = port;
  16. }
  17. //显示邮件全部信息
  18. public void showEmail()
  19. {
  20. System.out.println("***显示电子邮件内容***");
  21. System.out.println("发送端口号:" + sendPort);
  22. System.out.println("接收端口号:" + receivePort);
  23. System.out.println("发信者邮箱:" + sendAdd);
  24. System.out.println("收信者邮箱:" + receiveAdd);
  25. System.out.println("邮件主题:" + emailTitle);
  26. System.out.println("邮件内容:" + email);
  27. }
  28. //省略了其他属性和方法的代码
  29. }

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

  1. import java.util.Scanner;
  2. //定义邮件作者类
  3. class EmailWriter
  4. {
  5. //定义写邮件的静态方法,形参是Email类(非面向接口编程)
  6. //形参不能是EmailInterface接口,否则无法调用PortInterface接口的方法
  7. public static void writeEmail(Email email)
  8. {
  9. Scanner input = new Scanner(System.in);
  10. System.out.print("请输入发送端口号:");
  11. email.setSendPort(input.nextInt());
  12. System.out.print("请输入接收端口号:");
  13. email.setReceivePort(input.nextInt());
  14. System.out.print("请输入发信者邮箱:");
  15. email.setSendAdd(input.next());
  16. System.out.print("请输入收信者邮箱:");
  17. email.setReceiveAdd(input.next());
  18. System.out.print("请输入邮件主题:");
  19. email.setEmailTitle(input.next());
  20. System.out.print("请输入邮件内容:");
  21. email.writeEmail(input.next());
  22. }
  23. }
  24. public class TestInterface3
  25. {
  26. public static void main(String[] args)
  27. {
  28. //创建并实例化一个Email类的对象email
  29. Email email = new Email();
  30. //调用EmailWriter的静态方法writeEmail写邮件
  31. EmailWriter.writeEmail(email);
  32. //调用Email类的showEmail()方法(不是接口方法)
  33. email.showEmail();
  34. }
  35. }

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

9.2 接口 - 图5
图9.11 实现多个接口的类

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

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

  EmailInterface类的代码如下。

  1. //定义电子邮件接口,继承自PortInterface接口
  2. public interface EmailInterface extends PortInterface
  3. {
  4. //设置发信者邮箱
  5. public void setSendAdd(String add);
  6. //设置收信者邮箱
  7. public void setReceiveAdd(String add);
  8. //设置邮件主题
  9. public void setEmailTitle(String title);
  10. //设置邮件内容
  11. public void writeEmail(String email);
  12. }

  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接口的代码如下。

  1. //墨盒接口
  2. public interface PrintBox {
  3. //得到墨盒颜色,返回值为墨盒颜色
  4. public String getColor();
  5. }
  6. //纸张接口
  7. public interface PrintPaper {
  8. //得到纸张尺寸,返回值为纸张尺寸
  9. public String getSize();
  10. }

  打印机类Printer的代码如下。

  1. //打印机类
  2. public class Printer{
  3. //使用墨盒在纸张上打印
  4. public void print(PrintBox box,PrintPaper paper){
  5. System.out.println("正在使用" + box.getColor() + "墨盒在" + paper.getSize() + "纸张上打印!");
  6. }
  7. }

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

  1. //黑白墨盒,实现了墨盒接口
  2. public class GrayPrintBox implements PrintBox {
  3. //实现getColor()方法,得到“黑白”
  4. public String getColor() {
  5. return "黑白";
  6. }
  7. }
  8. //彩色墨盒,实现了墨盒接口
  9. public class ColorPrintBox implements PrintBox {
  10. //实现getColor()方法,得到“彩色”
  11. public String getColor() {
  12. return "彩色";
  13. }
  14. }

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

  1. //A4纸张,实现了纸张接口
  2. public class A4Paper implements PrintPaper {
  3. //实现getSize()方法,得到“A4”
  4. public String getSize() {
  5. return "A4";
  6. }
  7. }
  8. //B5纸张,实现了纸张接口
  9. public class B5Paper implements PrintPaper {
  10. //实现getSize()方法,得到“B5”
  11. public String getSize() {
  12. return "B5";
  13. }
  14. }

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

  1. public class TestPrinter {
  2. public static void main(String[] args) {
  3. PrintBox box = null; //墨盒
  4. PrintPaper paper = null; //纸张
  5. Printer printer = new Printer(); //打印机
  6. //使用彩色墨盒在B5纸上打印
  7. box = new ColorPrintBox();
  8. paper = new B5Paper();
  9. printer.print(box, paper);
  10. //使用黑白墨盒在A4纸上打印
  11. box = new GrayPrintBox();
  12. paper = new A4Paper();
  13. printer.print(box, paper);
  14. }
  15. }
9.2 接口 - 图6
图9.12 打印系统接口的实现