先问一个问题,为什么需要接口?
看例子:
需求:要求实现防盗门的功能。门有“开”和“关”的功能,所有“上锁”和“开锁”的功能。
分析:首先防盗门是一扇门,它有一把锁。按照面向对象的思想,可以将门分别定义成抽象类。但是,不能让防盗门在继承门的同时又继承锁。原因有两点:1、防盗门不是锁,不符合 is a的关系;2、Java只支持单继承。那么如何解决这个问题呢?这时就要用到接口,可以将锁定义为接口,让防盗门继承门,实现锁的接口。
那么什么是接口?它的特点又是什么?我们一步一步学习
在生活中,接口是一套规范,满足这个规范的设备,就可以将它们组合到一起,从而实现该设备的功能。现在以USB接口为例,来学习接口的含义。USB接口实际上是某些企业和组织等制定的一种约定或标准,规定了接口的大小、形状、各引脚信号的范围和含义、通信速度、通信流程等,并按照该约定或标准来设计各种设备,如U盘、USB风扇、USB键盘都可以插到USB口上正常工作。
在软件中,接口同样是一种规范和标准,它们可以约束类的行为,是一些方法特征的集合,但是没有方法的实现。从这个角度来讲,接口可以看作是一种特殊的“抽象类”,但是采用与抽象类完全不同的语法来表示,两者的设计理念也是不同的,抽象类利于代码复用,接口利于代码的扩展和维护。
接口语法:
//定义接口的语法:
[修饰符] interface 接口名 extends 父接口1、父接口2,……{
//常量定义
//方法定义
}
//接口同样可以继承接口
//类实现接口的语法:
class 类名 extends 父类名 inplements 接口1,接口2……{
//类成员
}
//一个类可以继承多个接口
接口的特点:
1、 接口的命名规则与类相同。如果修饰符是public,则该接口在整个项目中可见,如果省略修饰符,则该接口只在当前包可见。
2、接口中可以东一常量,不能定义变量。接口中的属性都会自动用public static final 修饰,即接口中的属性都是全局静态常量。接口中的常量必须在定义时指定初始值。
//示例
public static final int PI = 3.14;
int PI = 3.14;//在接口中,这两个定义语句效果完全相同
int PI;//错误!在接口中必须指定初始值,在类中会有默认值
3、接口中所有方法都是抽象类。接口中方法都会自动用public abstract修饰,即接口中只有全局抽象方法。
4、和抽象类一样,接口也不能实例化,接口中不能有构造函数。
5、接口之间可以通过extends实现继承关系,一个接口可以继承多个接口,但接口不能继承类。
6、接口的实现类必须实现接口的全部方法,否则必须定义为抽象类。
了解了什么是接口以及接口的特点,这里我们还要明白一个概念:接口是一种能力
通过之前所说的防盗门案例,我们来详细说明:
防盗门是一个门,符合is a 的关系,可以抽象出一个门类。防盗门有一把锁,代表它有“上锁”和“开锁”的能力,表达的是has a(组合关系)的关系,这里可以将锁定义成接口,让防盗门继承门类实现锁接口。
具体步骤:
- 定义抽象类Door,具备开和关的功能。
- 定义Lock接口,具备“上锁”和“开锁”的能力。
- 定义TheftproofDoor类,继承Door类的同时实现Lock接口。
- 编写测试类,实现防盗门的开、关和上锁、开锁的功能。
//定义抽象类、门类
public abstract class Dooe{
public abstract void open();//开门
public abstract void close();//关门
}
//定义锁接口
public interface Lock{
void lockUp();//上锁
void openLock();//开锁
}
//定义防盗门类、继承Door类,实现Lock接口
public TheftproofDoor extends Door implements Lock{
@ovreride
public void lockUp(){
System.out.println("向右转一圈,开锁");
}
@ovreride
public void openLock(){
System.out.println("向左转一圈,上锁");
}
@ovreride
public void open(){
System.out.println("轻轻一推,开门");
}
@ovreride
public void close(){
System.out.println("轻轻一拉,关门");
}
}
//测试类
public statci void main(String[] args){
//创建防盗门对象
TheftproofDoor tfd = new TheftproofDoor();
tfd.close();//关门
tfd.lockUp();//上锁
tfd.openDoor();//开锁
tfd.open();//开门
}
OutPut:
轻轻一拉,关门
向左转一圈,上锁
向右转一圈,开锁
轻轻一推,开门
通过示例,我们可以更加清晰的理解为什么接口是一种能力。一个类实现了某个接口,那么就表示这个类具备了某种能力,防盗门实现的锁的接口,就表示防盗门有了上锁开锁的能力。生活中还有许多这样的案例,如钳工、木匠并不是指某个人,而代表一种能力,招聘钳工、木匠就是招聘具备该能力的人。所以生活中一个人可以具备多项能力,程序的一个类也可以实现多个接口。
接口表示一种约定
问题:要求实现打印机打印功能。打印机的墨盒可能是彩色的,也可能是黑白的,所用的纸张可以有多种类型,如A4、B5等,并且墨盒和纸张都不是打印机厂商提供的,打印机产商如何避免自己的打印机与市场上的墨盒、纸张不符呢?
分析:有效解决该问题的途径是指定墨盒、纸张的约定或标准,然后打印机厂商按照约定对墨盒、纸张提供支持,无论最后使用的是哪个厂商提供的墨盒或纸张,只要符合统一的约定,打印机都可以打印。Java中的接口表示一种约定。
通过Java实现打印机打印的具体步骤:
- 定义墨盒接口InkBox,约定墨盒的标准。
- 定义纸张接口Paper,约定纸张的标准。
- 定义打印机类,引用墨盒接口、纸张接口实现打印功能。
- 墨盒厂商按照InkBox接口实现ColorInkBox类和GrayInkBox类。
- 纸张厂商按照Paper接口实现A4Paper类和B5Paper类。
- “组装”打印机,让打印机通过不同的墨盒和纸张实现打印功能。
//定义墨盒接口
public interface InkBox{
//return 墨盒颜色
public String getColor();
}
//定义纸张接口
public interface Paper{
//return 纸张大小
public String size();
}
//定义打印机类
public class Printer{
InkBox inkBox; //墨盒
Paper paper; //纸张
//设置打印机墨盒
public void setInkBox(InkBox inkBox){
this.InkBox = inkBox;
}
//设置打印机纸张
public void setPaper(Paper paper){
this.paper = paper;
}
//使用墨盒在纸上打印
public void print(){
System.out.println("使用"+inkBox.getColor()+"墨盒在"+paper.getSize()+"纸上打印。");
}
}
//墨盒厂商按照InkBox接口实现ColorInkBox类和GrayInkBox类
//彩色墨盒类
public class ColorInkBox implements InkBox{
public String getColor(){
return "彩色";
}
}
//黑白墨盒类
public class GrayInkBox implements InkBox{
public String getColor(){
return "黑白";
}
}
//纸张厂商按照Paper接口实现A4Paper类和B5Paper类
//A4纸类
public class A4Paper implements Paper{
public String getSize(){
return "A4";
}
}
//B5纸类
public class B5Paper implements Paper{
public String getSize(){
return "B5";
}
}
//“组装”打印机,让打印机通过不同的墨盒和纸张实现打印功能。(测试类)
public static void main(String[] args){
//1、定义打印机
InkBox inkBox = null;//定义墨盒引用
Paper paper = null; //定义纸张引用
Printer printer = new Printer();//定义打印机引用并指向打印机对象
//2、使用黑白墨盒在A4纸上打印
inkBox = new GrayInkBox();//指向黑白墨盒类对象
paper = new A4Paper();//指向A4纸类对象
printer.setColor(inkBox);//对墨盒进行赋值
printer.setSize(paper);//对纸张进行赋值
printer.print();//调用打印方法
//3、使用彩色墨盒在B5纸上打印
inkBox = new ColorInkBox();
paper = new B5Paper();
printer.setColor(inkBox);//对墨盒进行赋值
printer.setSize(paper);//对纸张进行赋值
printer.print();//调用打印方法
//4、使用彩色墨盒在A4纸上打印
paper = new A4Paper();
printer.setSize(paper);
printer.print();//调用打印方法
}
outPut:
使用黑白墨盒在A4纸上打印
使用彩色墨盒在B5纸上打印
使用彩色墨盒在A4纸上打印
在以上的例子中,我们首先定义了墨盒接口、纸张接口,然后定义打印机类,将两个接口作为类的成员属性,并且定义了两个方法,以接口作为形参,对当前对象的属性进行赋值。
然后根据接口,分类定义了相应的类,并且实现了接口,实现了接口当中的所有方法,通过方法返回相应的约定参数。
在测试类中,首先定义了接口的引用以及打印机的引用并且指向打印机的对象,根据实际需求,通过接口的引用指向相应的对象。
通过打印机当中的获取墨盒以及纸张的方法,传递实现了接口的子类的实参,对打印机中的成员属性进行赋值,然后输出结果。
在面向对象编程中提倡面向接口编程,而不是面向实现编程。什么是面向接口编程?简单来说,如果打印机厂商只面向某一家或几家厂商的墨盒产品规格生产打印机,而没有一个统一的约定,就无法使用更多厂商的墨盒。如果这家墨盒厂倒闭了,那么打印机就无用武之地了。而如果按照统一的约定生产打印机和墨盒,就不会存在这个问题。
针对本示例,在打印机类当中,Printer类的两个属性使用了InkBox接口和Paper接口,就可以接受所有实现了这两个接口的类的对象,即使是新推出的墨盒类型,只要遵循该接口的约定,就能够被接收。如果采用面向实现编程,则两个属性类型使用GrayInkBox和B5Paper,大大限制了打印机的适用范围,无法对新推出的ColorInkBox提供支持,这也体现了多态的可扩展、可维护的特性。
接口体现了约定和实现相分离的原则,通过面向接口编程,可以降低代码间的耦合性,提高代码的可扩展性和可维护性。面向接口编程就意味着:开发系统时,主体架构使用接口,接口构成系统的骨架,这样就可以通过更换实现接口的类来实现更换系统。