一、接口

1. 接口的定义

用关键字interface来声明一个接口。接口中只能有两样东西:常量和抽象方法。接口定义的语法格式如下:

1
2
3
4
5
6
7
8
9
[public]   interface  接口名 {    
//常量声明
[public] [static] [final] 变量类型 变量名 = 值;
...

//抽象方法声明
[public] [abstract] 返回类型 方法名(参数列表);
...
}

接口中的抽象方法和常量不是必须都有,甚至两者都没有也可以。和抽象类一样,接口不能用来创建对象实例。此外,和public类一样,public接口必须定义在同名的java源文件中,一个java源文件文件最多只能有一个public接口或一个public类。接口和抽象类一样,不能用来创建实例。

1
2
3
4
5
6
7
8
9
10
interface Sleeper {
//抽象方法
void wakeUp();
void beep();

//常量
long ONE_SECOND = 1000;
long ONE_MINUTE = 60000;
String STR = new String(); //和类中的变量一样,也可以用非常量表达式初始化。
}

在接口中如果有变量声明,那么该变量一定由public、final、static修饰,没有的话系统在编译时也会默认加上。因此,接口中声明的变量一定是公有的静态常量,可以通过接口名访问。比如,Sleeper中的ONE_SECOND可以这么访问:Sleeper.ONE_SECOND。接口中final常量必须在定义时初始化,初始化时可以使用常量表达式或非常量表达式。
在接口中声明的方法一定由public和abstract修饰,没有的话系统在编译时也会默认加上,所以一定是公有的抽象方法。

2. 接口的实现

接口由类来实现,一个类通过关键字implements声明自己实现了一个或多个接口。如果实现了多个接口,用逗号分隔接口名。接口实现的定义格式如下:

1
2
3
4
5
6
7
[修饰符]  class  类名  implements 接口1, 接口2, … {
//接口方法的实现
...

//实现类新增的内容
...
}

一个类实现某个接口也就是要实现该接口的所有抽象方法。如果只实现了一部分,则接口未被实现的那些抽象方法会被实现类所继承,从而成为抽象类。
实现类继承接口的常量,成为自己的静态常量,既可以通过接口名访问,也可以通过类名访问,还可以通过实现类的对象访问。
在实现类中实现接口的抽象方法时,必须使用完全相同的方法原型,并且不能降低方法的访问权限,因而实现类所实现的方法权限必须是public。
一个类可以在继承某个父类的同时实现若干个接口,这种情况下的定义形式如下:

1
2
3
[修饰符]  class  类名  extends 父类名  implements 接口1, 接口2, … {
...
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
interface Runner {      //接口1
public void run();
}

interface Swimmer { //接口2
public void swim();
}

class Person implements Runner, Swimmer { //接口实现类

public void run() {
System.out.println("我是飞毛腿,跑步速度极快!");
}

public void swim(){
System.out.println("我游泳技术很好,会蛙泳、自由泳、仰泳、蝶泳...");
}

public void eat(){
System.out.println("我牙好胃好,吃啥都香!");
}

public static void main(String args[]) {
Person p = new Person();
p.swim();
p.run();
p.eat();
}
}

3. 接口的继承

Java允许一个接口继承其它的接口,声明格式如下:

1
2
3
4
[public]   interface   接口名   extends   父接口1, 父接口2, … {
// 新增的抽象方法或静态常量
...
}

一个接口可以继承一个或多个接口,从而继承其它接口的抽象方法和常量。

实现类除了实现当前接口自己定义的抽象方法,还要实现该接口继承而来的抽象方法。

如果在子接口中声明了和父接口同名的常量或原型相同的方法,则父接口中的常量被隐藏,方法被重写(覆盖)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
interface x{
void m1();
void m2();
}

interface y {
void m3();
}

interface z extends x, y{
void m4();
}

class xyz implements z{
public void m1() {
... // 实现代码
}
public void m2() {
... // 实现代码
}
public void m3() {
... // 实现代码
}
public void m4() {
... // 实现代码
}
}

注意:

  • 如果实现类是非abstract类,则该类必须实现接口的所有抽象方法,否则未被实现的抽象方法会被实现类所继承,从而只能是abstract类。
  • 如果实现类是abstract类,则该类可以只实现接口的一部分抽象方法。当然,即使实现类全部实现了接口的抽象方法,也可以是抽象类,为什么?
  • 如果父类实现了某个接口,则子类会继承父类的接口实现,相当于子类也实现了这个接口。

4. 接口类型

    接口也是一种引用数据类型,可以当作数据类型使用。可以用接口来声明变量,也可以把一个实现类的对象赋值给接口变量,这也是上转型。

5. 接口回调与多态

接口回调是指把实现类的对象赋值给接口变量,然后通过接口变量访问接口方法,则实际访问的是实现类所实现的方法:

1
2
接口类型 接口变量 = 用实现类创建的对象;        
接口变量.接口方法([参数列表]);

通过接口变量来访问接口方法时,由于不同的实现类对同一方法有不同的实现,从而表现出多态。如下例所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
interface  ShowMessage  {  
void showTradeMark();
}

class TV implements ShowMessage {
public void showTradeMark() {
System.out.println("电视机");
}
}

class PC implements ShowMessage {
public void showTradeMark() {
System.out.println("电脑");
}
}

public class Example {
public static void main(String args[]) {
ShowMessage sm; //声明接口变量。
sm = new TV(); //TV的对象赋值给接口变量。
sm.showTradeMark(); //通过接口回调来调用TV的方法showTradeMark()
sm = new PC(); //PC的对象赋值给接口变量。
sm.showTradeMark(); //通过接口回调来调用PC的方法showTradeMark()
}
}

6. 接口做参数

如果一个方法的形参是接口类型,就可以将实现类的对象传给这个参数,这也是上转型。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
interface Show{
void show();
}
class A implements Show{
public void show(){
System.out.println("I love This Game");
}
}
class B implements Show{
public void show(){
System.out.println("我喜欢看NBA");
}
}
class C{
public void f(Show s){ //接口作为参数
s.show();
}
public static void main(String args[]){
C c = new C();
c.f(new A()); //实现类的对象传给接口参数
c.f(new B()); //实现类的对象传给接口参数
}
}

二、instanceof运算符

功能:判断一个对象是否是某个类的实例,或者一个对象是否实现了某个接口。
格式:对象 instanceof 类或 接口
结果:truefalse
instanceof操作符的前值是一个对象,后值是一个类或接口。如果前值对象是后值类的对象实例,或者可以上转型(或下转型)为后值类的对象实例,或者实现了后值接口,则结果是true;否则是false。
子类对象可以当作父类对象,如果instanceof的前值是子类对象,后值是父类,则结果是true。一个类实现了子接口,那么也就实现了父接口,如果instanceof的前值是实现类对象,后值是当前接口的父接口,则结果也是true。

假定Student类是Person的子类

1
2
3
4
5
6
Person   p = new Person();
Student s = new Student();
System.out.println("s instanceof Student:" + (s instanceof Student));
System.out.println("s instanceof Person:" + (s instanceof Person));
System.out.println("p instanceof Person:" + (p instanceof Person));
System.out.println("p instanceof Student:" + (p instanceof Student));
    在不确定一个对象是否是某个类的实例或者实现了某个接口时,通过instanceof运算符进行检查,可以避免出现运行时异常,提高代码的健壮性。

三、接口实现多态

多态总结
多态是指在程序中相同的名称表现出不同的行为,包括静态多态和动态多态两种。静态多态一般指方法重载。
动态多态也称为运行时多态,实现条件:(1) 实现继承,或实现接口;(2) 子类重写父类方法,或实现类实现接口的方法);(2) 执行上转型,通过上转型对象访问父类被重写的方法,或接口被实现的方法。

继承实现多态的例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class Animal {
public void roar(){
System.out.println("动物:...");
}
}

class Cat extends Animal {
public void roar(){
System.out.println("猫:喵,喵,喵,...");
}
}

public class AnimalTest {
public static void main(String args[]){
Animal am;
am = new Animal();
am.roar();

am=new Dog();
am.roar();

am=new Cat();
am.roar();
}
}

接口实现多态的例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
public interface Geometry{
public abstract double getArea();
}

class Circle implements Geometry{
double r;

Circle(double r){ this.r = r; }
public double getArea(){ return(3.14*r*r); }
}

class Ladder implements Geometry{
double a,b,h;

Ladder(double a,double b,double h){
this.a = a; this.b = b; this.h = h;
}
public double getArea(){
return((1 /2.0) * (a + b) * h);
}
}

class Example{
public static void main(String args[]){
Geometry tuxing; //接口变量
tuxing = new Ladder(12,22,100);
System.out.println("梯形的面积" + tuxing.getArea());
tuxing = new Circle(10);
System.out.println("圆的面积" + tuxing.getArea());
}
}

上机作业:编写一个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)计算面积和周长。