接口
一、接口
接口是引用类型的一种,与类相似但也存在诸多不同;如果将类比作“电路的设计图”,那么接口就是“遥控器的设计图”
接口声明
这里以视频播放器、CD播放器、DVD播放器等播放器(播放设备)为例进行讲解,所有的播放器都可以执行“播放” 和“停止” 等操作;虽然播放器的实际运行各不相同, 但遥控器有"播放按钮” 和“停止按钮” 这一点是共通的。
共通部分的遥控器如图a,将"Player遥控器由play和stop两个按钮组成“ 这一遥控器的设计图表示为程序, 就是图b所示的接口声明,与类声明相似但开头的关键字并不是class,而是interface, 这一点与类有所不同
接口中的所有方法都为public且abstract,必须用 ';'来替换方法体{}进行声明, 这一点与类中的抽象方法相同
接口的实现
接口中声明的抽象方法的主体要在在实现该接口的类中定义
实现接口Player的类VideoPlayer的声明如图a所示,implements Player部分表示接口Player的实现。这个声明和派生类的声明相似,不过使用的关键字并不是extends, 而是implements
来理解类VideoPlayer的声明:该类会实现Player遥控器,为此需要实现各个按钮所调用的方法主体,它们之间的关系如图b所示
类VideoPlayer在实现接口Player的同时也会实现play和stop这两个方法(重写方法并定义主体);
重写的方法必须声明为public, 这是因为接口的方法为public, 已无法再强化其访问控制,这与类的派生中的重写是一样
【接口中的方法为public且abstract。在实现该接口的类中, 各方法在实现时需要加上public修饰符】
创建视频播放器VideoPlayer和CD播放器CDPlayer,以实现接口Player,程序如下:
// 播放器 接口
public interface Player {
void play(); // ○播放
void stop(); // ○停止
}
//===== 视频播放器 =====//
public class VideoPlayer implements Player {
private int id; // 制造编号
private static int count = 0; // 到目前为止已经赋的制造编号
public VideoPlayer() { // 构造函数
id = ++count;
}
public void play() { // ○播放
System.out.println("■视频播放开始!");
}
public void stop() { // ○停止
System.out.println("■视频播放结束!");
}
public void printInfo() { // 显示制造编号
System.out.println("该机器的制造编号为[" + id + "]。");
}
}
//===== CD播放器 =====//
public class CDPlayer implements Player {
public void play() { // ○播放
System.out.println("□CD播放开始!");
}
public void stop() { // ○停止
System.out.println("□CD播放结束!");
}
public void cleaning() { // 清洗
System.out.println("□已清洗磁头。");
}
}
这些接口和类如图所示,VideoPlayer 遥控器和CDPlayer遥控器都包含Player 遥控器上的play和stop 按钮。
理解一下”类VideoPlayer和CDPlayer 如何实现了Player"
类VideoPlayer
方法play显示"■视频播放开始!",方法stop显示"■视频播放结束I";
这个类的实例在创建时会被赋上制造编号1、2、3……赋给各个实例的制造编号为实例变员id. 表示已经赋到哪一号的是类变量count;方法printinfo负责显示制造编号
类CDPlayer
这个类中并未定义宇段;方法play显示“口CD播放开始!",方法stop显示“口CD播放结束!";方法cleaning显示“ 口已清洗磁头。”
这两个类并未继承接口Player中的字段和方法等资产,只继承了Player的方法规格(遥控器上的按钮规格)
二、接口语法规则和限制
1)无法创建接口类型的实例
接口相当于遥控器(而不是电路)的设计图,因此无法创建电路主体(实例),下面的声明会发生错误
Player c = new Player(); //错误
2)接口类型的变量可以引用实现该接口的类的实例
接口类型的变量可以引用实现该接口的类的实例,下面的操作可以正常执行:
Player pl = new CDPlayer();
Player p2 = new VideoPlayer();
Player遥控器的变量可以引用实现它的类CDPlayer和类VideoPlayer的实例【接口类型的变量可以引用实现类的实例】
// 接口Player的使用示例
class PlayerTester {
public static void main(String[] args) {
Player[] a = new Player[2];
a[0] = new VideoPlayer(); // 视频播放器
a[1] = new CDPlayer(); // CD播放器
for (Player p : a) {
p.play(); // 播放
p.stop(); // 停止
System.out.println();
}
}
}
输出:
数组a是一个元素类型为接口Player类型的数组,a[0]引用VideoPlayer的实例,a[1]引用CDPlayer的实例。扩展for语句中依次对各个元素调用方法play和方法stop如图,从中可以看出接口Player类型的遥控器只有play和stop按钮。因此, 通过a[0]和a[1] 无法调用printinfo或者cleaning方法。
3)接口实现时必须实现所有的方法
实现接口就是实现遥控器上各个按钮的功能,未实现接口中所有方法的类必须声明为抽象类
、
4)可以持有常量
接口可以持有下述成员:
1、类
2、接口
3、常量【Public、static、final 字段】,但不可以持非作常量字段【可以读写数值的变量】
4、抽样方法【public、abstract 方法】
持有常量的接口示例如下,接口Skinnable的名称表示“可换肤的“:
package com.example;// 换肤接口
public interface Skinnable {
int BLACK = 0; // 黑色
int RED = 1; // 红色
int GREEN = 2; // 绿色
int BLUE = 3; // 蓝色
int LEOPARD = 4; // 豹纹
void changeSkin(int skin); // ★换肤
}
换肤时指定的颜色和花纹都声明为了常量,接口中的字段都声明为public且static且final。它们并不是类中所说的“实例变量”而是"类变量” 。【接口中声明的字段为public且static且final, 即为不可以改写数值的类变量】
声明中带有static的类变量可以通过“类名 .字段名“ 进行访问,接口中的常量可以通过“接口名.字段名“ 进行访问;在本示例中,黑色可以通过Skinnable . BLACK进行访问,豹纹可以通过Skinnable.LEOPARD进行访问
5)命名方法与类相同
原则上,接口的名称为名词,但也可以使用表示动作的形容词。特别是表示“ 可.... 的"之意的接口名称可以使用-able
6)接口的访问属性与类相同
虽然接口中的所有成员都自动设为public, 但接口本身的访问属性与类相同, 可以任意指定;如果加上public, 则为公开访问,而如果未加上public, 则为包访问
三、类的派生和接口的实现
在新创建类时,可以同时执行类的派生和接口的实现。
package com.example;
//===== 二维接口 =====//
public interface Plane2D {
int getArea(); // ○计算面积
}
该接口中声明的getArea方法用来计算面积并返回结果,由于点和直线没有面积,因此这些类中无需实现该接口,下面在长方形类Rectangle中进行实现,再来新创建一个平行四边形类Parallelogram,实现接口Plane2D
package com.example.shape3;
/**
* 类Rectangle是表示长方形的类。
* 该类派生自表示图形的抽象类Shape。
* @see Shape
*/
public class Rectangle extends Shape implements Plane2D {
/**
* 表示长方形的长的int型字段。
*/
private int width;
/**
* 表示长方形的宽的int型字段。
*/
private int height;
/**
* 创建长方形的构造函数。
* 接收长和宽作为参数。
* @param width 长方形的长。
* @param height 长方形的宽。
*/
public Rectangle(int width, int height) {
this.width = width;
this.height = height;
}
/**
* 方法toString返回表示与长方形相关的图形信息的字符串。
* @return 返回字符串"Rectangle(width:4, height:3)"。
* 4和3这两部分分别对应长和宽的值。
*/
public String toString() {
return "Rectangle(width:" + width + ", height:" + height + ")";
}
/**
* 方法draw用于绘制长方形。
* 通过排列星号'*'进行绘图。
* 循环width次显示长度个数的'*'并换行。
*/
public void draw() {
for (int i = 1; i <= height; i++) {
for (int j = 1; j <= width; j++)
System.out.print('*');
System.out.println();
}
}
public int getArea() { return width * height; } // ○计算面积
}
Parallelogram
package com.example.shape3;
//===== 平行四边形 =====//
public class Parallelogram extends Shape implements Plane2D {
private int width; // 底边长
private int height; // 宽
public Parallelogram(int width, int height) {
this.width = width; this.height = height;
}
public String toString() { // 字符串表示
return "Parallelogram(width:" + width + ", height:" + height + ")";
}
public void draw() { // 绘图
for (int i = 1; i <= height; i++) {
for (int j = 1; j <= height - i; j++) System.out.print(' ');
for (int j = 1; j <= width; j++) System.out.print('#');
System.out.println();
}
}
public int getArea() { return width * height; } // ○计算面积
}
类和接口的关系如图,Rectangle和Parallelogram 派生自类Shape, 并实现了接口Plane2D
要点1:当类的声明中同时存在extends和implements时, 一定要先书写extends
把包含Shape的类群比作是根据血缘关系而结成的"shape家族”,而Plane2D和实现它的类群之间并不具有血缘关系, 而是属于同一个圈子, 故而可以比作朋友关系,无论是Shape家族,还是与其毫无关系的类,如果希望属于Plane2D圈子,只要实现Plane2D就可以了【接口就是将类划分为朋友关系之类的分组,朋友关系可以与具有血缘关系的派生毫无关系】
四、多个接口的实现
类的派生和接口的实现之间最大的不同在于是否可以同时派生/实现多个类,类的派生只允许单继承, 而一个类可以实现多个接口,一般的形式如下所示,当声明类时,implements后面是要实现的接口,接口之间使用逗号进行分隔。类A中会实现接口B和接口c中的所有方法:
class A implements B,C{
//实现接口B的方法
//实现接口C的方法
}
下面所示的程序中同时实现了接口Player和接口Skinnable:
package com.example.player;// 可换肤的随身播放器
class PortablePlayer implements Player, Skinnable {
private int skin = BLACK;
public PortablePlayer() { } // 构造函数
public void play() { // ○播放
System.out.println("◆播放开始!");
}
public void stop() { // ○停止
System.out.println("◆播放结束!");
}
public void changeSkin(int skin) { // ★换肤
System.out.print("皮肤换成了");
switch (skin) {
case BLACK: System.out.print("乌黑"); break;
case RED: System.out.print("深红"); break;
case GREEN: System.out.print("柳叶"); break;
case BLUE: System.out.print("露草"); break;
case LEOPARD: System.out.print("豹纹"); break;
default: System.out.print("素色"); break;
}
System.out.println("。");
}
}
类PortablePlayer为 “可换肤的随身播放器” ,接口和类之间的关系如图 :
类PortablePlayer中实现了接口Player的方法play和stop, 以及接口Skinnable的方法changeSkin 。在类中,可以使用简名来访问实现的接口中的字段,类PortablePlayer的使用示例如下:
package com.example.player;// 类PortablePlayer的使用示例
class PortablePlayerTester {
public static void main(String[] args) {
PortablePlayer a = new PortablePlayer();
a.play(); // 播放
a.stop(); // 停止
a.changeSkin(Skinnable.LEOPARD); // 将皮肤换成豹纹
}
}
输出:
由于接口Skinnable中定义的常量为类变量(静态字段),因此可以使用"接口名.字段名"进行访问,本程序中选择的Skinnable.LEOPARD为豹纹