第五章 接口
一、接口
1. 接口的定义
用关键字interface来声明一个接口。接口中只能有两样东西:常量和抽象方法。接口定义的语法格式如下:
1 | [public] interface 接口名 { |
接口中的抽象方法和常量不是必须都有,甚至两者都没有也可以。和抽象类一样,接口不能用来创建对象实例。此外,和public类一样,public接口必须定义在同名的java源文件中,一个java源文件文件最多只能有一个public接口或一个public类。接口和抽象类一样,不能用来创建实例。
1 | interface Sleeper { |
在接口中如果有变量声明,那么该变量一定由public、final、static修饰,没有的话系统在编译时也会默认加上
。因此,接口中声明的变量一定是公有的静态常量,可以通过接口名访问。比如,Sleeper中的ONE_SECOND可以这么访问:Sleeper.ONE_SECOND。接口中final常量必须在定义时初始化
,初始化时可以使用常量表达式或非常量表达式。
在接口中声明的方法一定由public和abstract修饰,没有的话系统在编译时也会默认加上
,所以一定是公有的抽象方法。
2. 接口的实现
接口由类来实现,一个类通过关键字implements声明自己实现了一个或多个接口。如果实现了多个接口,用逗号分隔接口名。接口实现的定义格式如下:
1 | [修饰符] class 类名 implements 接口1, 接口2, … { |
一个类实现某个接口也就是要实现该接口的所有抽象方法。如果只实现了一部分,则接口未被实现的那些抽象方法会被实现类所继承,从而成为抽象类。
实现类继承接口的常量,成为自己的静态常量,既可以通过接口名访问,也可以通过类名访问,还可以通过实现类的对象访问。
在实现类中实现接口的抽象方法时,必须使用完全相同的方法原型,并且不能降低方法的访问权限,因而实现类所实现的方法权限必须是public。
一个类可以在继承某个父类的同时实现若干个接口,这种情况下的定义形式如下:
1 | [修饰符] class 类名 extends 父类名 implements 接口1, 接口2, … { |
1 | interface Runner { //接口1 |
3. 接口的继承
Java允许一个接口继承其它的接口,声明格式如下:
1 | [public] interface 接口名 extends 父接口1, 父接口2, … { |
一个接口可以继承一个或多个接口,从而继承其它接口的抽象方法和常量。
实现类除了实现当前接口自己定义的抽象方法,还要实现该接口继承而来的抽象方法。
如果在子接口中声明了和父接口同名的常量或原型相同的方法,则父接口中的常量被隐藏,方法被重写(覆盖)。
1 | interface x{ |
注意:
- 如果
实现类是非abstract类
,则该类必须实现接口的所有抽象方法,否则未被实现的抽象方法会被实现类所继承,从而只能是abstract类。 - 如果实现类是abstract类,则该类可以只实现接口的一部分抽象方法。当然,即使实现类全部实现了接口的抽象方法,也可以是抽象类,
为什么?
- 如果父类实现了某个接口,则子类会继承父类的接口实现,相当于子类也实现了这个接口。
4. 接口类型
接口也是一种引用数据类型,可以当作数据类型使用。可以用接口来声明变量,也可以把一个实现类的对象赋值给接口变量,这也是上转型。
5. 接口回调与多态
接口回调是指把实现类的对象赋值给接口变量,然后通过接口变量访问接口方法,则实际访问的是实现类所实现的方法:
1 | 接口类型 接口变量 = 用实现类创建的对象; |
通过接口变量来访问接口方法时,由于不同的实现类对同一方法有不同的实现,从而表现出多态。如下例所示:
1 | interface ShowMessage { |
6. 接口做参数
如果一个方法的形参是接口类型,就可以将实现类的对象传给这个参数,这也是上转型。
1 | interface Show{ |
二、instanceof运算符
功能:判断一个对象是否是某个类的实例,或者一个对象是否实现了某个接口。
格式:对象 instanceof 类或 接口
结果:true
或 false
instanceof操作符的前值是一个对象,后值是一个类或接口。如果前值对象是后值类的对象实例,或者可以上转型(或下转型)为后值类的对象实例,或者实现了后值接口,则结果是true;否则是false。
子类对象可以当作父类对象,如果instanceof的前值是子类对象,后值是父类,则结果是true。一个类实现了子接口,那么也就实现了父接口,如果instanceof的前值是实现类对象,后值是当前接口的父接口,则结果也是true。
假定Student类是Person的子类
1 | Person p = new Person(); |
在不确定一个对象是否是某个类的实例或者实现了某个接口时,通过instanceof运算符进行检查,可以避免出现运行时异常,提高代码的健壮性。
三、接口实现多态
多态总结
多态是指在程序中相同的名称表现出不同的行为,包括静态多态和动态多态两种。静态多态一般指方法重载。
动态多态也称为运行时多态,实现条件:(1) 实现继承,或实现接口;(2) 子类重写父类方法,或实现类实现接口的方法);(2) 执行上转型,通过上转型对象访问父类被重写的方法,或接口被实现的方法。
继承实现多态的例子
1 | class Animal { |
接口实现多态的例子
1 | public interface Geometry{ |
上机作业:编写一个Java程序,除了主类TestGeometry外,该程序还有一个Shape接口、一个Rectangle类和一个Circle类。
要求:
- Shape接口有两个抽象方法:public abstract double computeArea(),用于计算面积;public abstract double computeLength(),用于计算周长。
- Rectangle类实现Shape接口,实现Shape接口计算面积和周长的方法,并打印输出面积和周长,格式为:
矩形的面积 = xxx; 矩形的周长 = xxx。 - 有两个私有成员变量分别保存矩形的长和宽,一个构造方法用于初始化这两个成员变量。
- Circle类实现Shape接口,实现计算圆的面积和周长的方法,并打印输出面积和周长,格式为:园的面积 = xxx; 园的周长 = xxx。
- 有一个私有成员变量用于保存圆的半径,一个构造方法用于初始化这个成员变量。
- 圆周率PI = 3.142定义为常量。
- 在TestGeometry类中定义实例方法double computeArea(Shape s)和double computeLength(Shape s),在这两个方法内部通过接口回调分别计算面积和周长。
- 在TestGeometry类的main方法中从键盘输入矩形的长和宽以及圆的半径,分别创建Rectangle类和Circle类的对象,调用TestGeometry类的实例方法computeArea(Shape s)和 computeLength(Shape s)计算面积和周长。