9.2 接口
9.1节详细介绍了抽象类,提到抽象类中可以有抽象方法,也可以有普通方法,但是有抽象方法的类必须是抽象类。如果抽象类中的方法都是抽象方法,那么由这些抽象方法组成的特殊的抽象类就是所说的接口。
9.2.1 接口的概念
接口是一系列方法的声明,是一些抽象方法的集合。一个接口只有方法的声明,没有方法的实现,因此这些方法可以在不同的地方被不同的类实现,而这些实现类可以具有不同的行为。
虽然我们常说,接口是一种特殊的抽象类,但是在面向对象编程的设计思想层面,两者还是有显著区别的。抽象类更侧重于对相似的类进行抽象,形成抽象的父类以供子类继承使用,而接口往往在程序设计的时候,定义模块与模块之间应满足的规约,使各模块之间能协调工作。接下来通过一个实际的例子来说明接口的作用。
如今,蓝牙技术已经在社会生活中广泛应用。移动电话、蓝牙耳机、蓝牙鼠标、平板电脑等IT设备都支持蓝牙实现设备间短距离通信。那为什么这些不同的设备能通过蓝牙技术进行数据交换呢?其本质在于蓝牙提供了一组规范和标准,规定了频段、速率、传输方式等要求,各设备制造商按照蓝牙规范约定制造出来的设备,就可以按照约定的模式实现短距离通信。蓝牙提供的这组规范和标准,就是所谓的接口。
蓝牙接口创建和使用步骤如下。
(1)各相关组织、厂商约定蓝牙接口标准。
(2)相关设备制造商按约定接口标准制作蓝牙设备。
(3)符合蓝牙接口标准的设备可以实现短距离通信。
Java接口定义的语法形式如下。
[修饰符] interface 接口名 [extends] [接口列表]{
接口体
}
interface前的修饰符是可选的,当没有修饰符的时候,表示此接口的访问只限于同包的类和接口。如果使用修饰符,则只能用public 修饰符,表示此接口是公有的,在任何地方都可以引用它,这一点和类是相同的。
接口是和类同一层次的,所以接口名的命名规则参考类名命名规则即可。
extends关键词和类语法中的extends类似,用来定义直接的父接口。和类不同,一个接口可以继承多个父接口,当extends 后面有多个父接口时,它们之间用逗号隔开。
接口体就是用大括号括起来的那部分,接口体里定义接口的成员,包括常量和抽象方法。
类实现接口的语法形式如下:
[类修饰符] class 类名 implements 接口列表{
类体
}
类实现接口用implements关键字,Java中的类只能是单继承的,但一个Java类可以实现多个接口,这也是Java解决多继承的方法。
下面通过代码来模拟蓝牙接口规范的创建和使用步骤。
(1)定义蓝牙接口。
假设蓝牙接口通过input()和output()两个方法提供服务,这时就需要在蓝牙接口中定义这两个抽象方法,具体代码如下。
//定义蓝牙接口
public interface BlueTooth
{
//提供输入服务
public void input();
//提供输出服务
public void output();
}
(2)定义蓝牙耳机类,实现蓝牙接口。
public class Earphone implements BlueTooth
{
String name = "蓝牙耳机";
//实现蓝牙耳机输入功能
public void input()
{
System.out.println(name + "正在输入音频数据...");
}
//实现蓝牙耳机输出功能
public void output()
{
System.out.println(name + "正在输出反馈信息...");
}
}
(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 + "正在输出数据...");
}
}
编写测试类,对蓝牙耳机类和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的输出功能
}
}
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);
}
(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);
}
}
(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接口,没有此方法
}
}
(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();
}
}
9.2.3 接口的特征
接下来,逐个了解接口有哪些特征。
(1)接口中不允许有实体方法。
例如,在EmailInterface接口中增加下面的实体方法。
//显示邮件全部信息
public void showEmail()
{
}
编译时就会报错,提示接口中不能有实体方法,如图9.9所示。
(2)接口中可以有成员变量,默认修饰符是public static final,接口中的抽象方法必须用public修辞。
在EmailInterface接口中,增加邮件发送端口号的成员变量sendPort,代码如下。
int sendPort = 25;//必须赋静态最终值
在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);
}
EmailWriter类和TestInterface2类的代码不需要调整,运行TestInterface2类,程序运行结果如图9.10所示。
(3)一个类可以实现多个接口。
假设一个邮件,不仅需要符合EmailInterface接口对电子邮件规范的要求,而且需要符合对发送端和接收端端口号接口规范的要求,才允许成为一个合格的电子邮件。
发送端和接收端端口号接口的代码如下所示。
//定义发送端和接收端端口号接口
public interface PortInterface
{
//设置发送端端口号
public void setSendPort(int port);
//设置接收端端口号
public void setReceivePort(int port);
}
则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);
}
//省略了其他属性和方法的代码
}
修改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();
}
}
程序运行结果如图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);
}
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();
}
打印机类Printer的代码如下。
//打印机类
public class Printer{
//使用墨盒在纸张上打印
public void print(PrintBox box,PrintPaper paper){
System.out.println("正在使用" + box.getColor() + "墨盒在" + paper.getSize() + "纸张上打印!");
}
}
黑白墨盒类GrayPrintBox和彩色墨盒类ColorPrintBox的代码如下。
//黑白墨盒,实现了墨盒接口
public class GrayPrintBox implements PrintBox {
//实现getColor()方法,得到“黑白”
public String getColor() {
return "黑白";
}
}
//彩色墨盒,实现了墨盒接口
public class ColorPrintBox implements PrintBox {
//实现getColor()方法,得到“彩色”
public String getColor() {
return "彩色";
}
}
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";
}
}
编写打印系统,代码如下,程序运行结果如图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);
}
}