接口

接口是抽象类的延伸,可以将它看作是纯粹的抽象类,接口中的所有方法都没有方法体。对于7.8节中遗留的问题,可以将 draw() 方法封装到一个接口中,使需要 draw() 方法的类实现这个接口,同时也继承图形类,这就是接口存在的必要性。图7.11描述了各个子类继承图形类后使用接口的关系。

image 2024 02 29 18 34 33 539
Figure 1. 图7.11 使用接口继承关系

定义接口

接口使用 interface 关键字进行定义,其语法如下:

public interface Paintable {
    void draw();        // 定义接口方法可省略 public abstract 关键字
}
java
  • public:接口可以像类一样被权限修饰符修饰,但 public 关键字仅限用于接口在与其同名的文件中被定义。

  • interface:定义接口关键字。

  • Paintable:接口名称。

实现接口

一个类继承一个父类的同时再实现一个接口,可以写成如下形式:

public class Parallelogram extends Quadrangle implements Paintable {
    ...
}
java
  1. 在接口中,方法必须被定义为 public 或 abstract 形式,其他修饰权限不被 Java 编译器认可。或者说,即使不将该方法声明为 public 形式,它也是 public 形式。

  2. 在接口中定义的任何字段都自动是 static 和 final 的。

【例7.13】将绘图方法设为接口方法(实例位置:资源包\TM\sl\7\13)

将图形对象的绘图方法剥离出来,作为 Paintable 可绘制接口中的抽象方法。创建四边形类作为平行四边形类和正方形类的父类,同时让这两个子类实现 Paintable 接口。创建圆形类实现 Paintable 接口,但不继承四边形类。

interface Paintable { // 可绘制接口
	public void draw(); // 绘制抽象方法
}

class Quadrangle { // 四边形类
	public void doAnything() {
		System.out.println("四边形提供的方法");
	}
}

//平行四边形类,继承四边形类,并实现了可绘制接口
class Parallelogram extends Quadrangle implements Paintable {
	public void draw() { // 由于该类实现了接口,所以需要覆盖draw()方法
		System.out.println("绘制平行四边形");
	}
}

//正方形类,继承四边形类,并实现了可绘制接口
class Square extends Quadrangle implements Paintable {
	public void draw() {
		System.out.println("绘制正方形");
	}
}


//圆形类,仅实现了可绘制接口
class Circular implements Paintable {
	public void draw() {
		System.out.println("绘制圆形");
	}
}

public class Demo7 {
	public static void main(String[] args) {
		Square s = new Square();
		s.draw();
		s.doAnything();

		Parallelogram p = new Parallelogram();
		p.draw();
		p.doAnything();

		Circular c = new Circular();
		c.draw();
	}
}
java

运行结果如下:

     绘制正方形
     四边形提供的方法
     绘制平行四边形
     四边形提供的方法
     绘制圆形
bash

从这个结果中可以看出,“绘制” 这个行为可不是四边形独有的,圆形也能被绘制,因此 draw() 方法被独立封装在了可绘制接口中。

正方形类与平行四边形类分别继承了四边形类并实现了可绘制接口,因此正方形类与平行四边形类既可以调用绘制方法,又可以调用四边形提供的 doAnything() 方法。但是,圆形不属于四边形,且可以被绘制,因此最后圆形对象只调用了 draw() 方法。

在 Java 中,类虽然不可以同时继承多个类,但可以同时实现多个接口。

当类同时实现多个接口时,要将所有需要实现的接口放在 implements 关键字后,并用英文格式下的逗号 “,” 隔开。类同时实现多个接口语法如下:

class 类名 implements 接口1,接口2,...,接口n {
  ...
}
bash

例如,通过类同时实现多个接口模拟如下场景:爸爸喜欢看电视和钓鱼,妈妈喜欢购物和画画,他们的孩子喜欢做的事和爸爸、妈妈喜欢做的事一样。

首先,创建一个 DadLike 接口(表示 “爸爸喜欢的”),在接口中声明两个抽象方法 watchTV() 和 fish()。代码如下:

interface DadLike {        // 爸爸喜欢的
    void watchTV();        // 看电视
    void fish();
}
java

然后,创建一个 MomLike 接口(表示 “妈妈喜欢的”),在接口中声明两个抽象方法 shop() 和 draw()。代码如下:

interface MonLike {       // 妈妈喜欢的
    void shop();          // 购物
    void draw();
}
java

最后,创建一个 ChildLikeThings 类,同时实现 DadLike 和 MomLike 两个接口,并实现这两个接口中所有的抽象方法。代码如下:

public class ChildLike Things implements MomLike,DadLike {
    // 继承了爸爸喜欢的
    @Override
    public void watchTV() {
        System.out.println("我喜欢看动画片");
    }

    @Override
    public void fish() {
        System.out.println("我爱掉鱼缸里的鱼");
    }

    // 继承了妈妈喜欢的
    @Override
    public void shop() {
        System.out.println("我爱去逛超市");
    }

    @Override
    public void draw() {
        System.out.println("我爱画大房子");
    }
}
java

接口继承接口

如果是接口继承接口,那么应该使用 extends 关键字,而不是 implements 关键字。接口继承接口的语法如下:

interface 接口1 extends 接口2 {
  ...
}
bash

需要注意的是,如果类实现了子接口,那么在类中就需要同时重写父接口和子接口中所有的抽象方法。示例代码如下:

interface FatherLike {        // 父接口
    void fatherMethod();      // 父接口方法
}

interface ChildLike extends FatherLike {  // 子接口,继承父接口
    void ChildLikeMethod();   // 子接口方法
}

class interfaceExtends implements ChildLike { // 实现子接口,但必须重写所有方法
    @Override
    public void fatherMethod() {
        System.out.println("实现父接口方法");
    }

    @Override
    public void ChildLikeMethod() {
        System.out.println("实现子接口方法");
    }
}
java

接口的多重继承

接口是一种比较特殊的结构,它可以使用 Java 明令禁止的 “多重继承” 语法,即子接口可以同时继承多个父接口。其语法如下:

interface 子接口 extends 父接口1,父接口2,父接口3,... {
  ...
}
bash

例如,先创建3个接口,分别为A、B、C。代码如下:

interface A {
    void a();
}

interface B {
    void b();
}

interface C {
    void c()
}
java

再创建一个接口 Letter,并继承了 A、B、C 3 个接口。代码如下:

interface Letter extends A,B,C {

}
java

接着创建一个类 LetterImp,并实现接口 Letter,此时必须同时实现 A、B、C 3 个接口中所有的抽象方法。代码如下:

class LetterImp implements Letter {
    @Override
    public void a() {
        System.out.println("A 接口中的抽象方法");
    }

    @Override
    public void b() {
        System.out.println("B 接口中的抽象方法");
    }

    @Override
    public void c() {
        System.out.println("C 接口中的抽象方法");
    }
}
java

接口的默认方法

Java 8 为接口新增了两个默认方法,即 default 方法和 static 方法,并且允许这两个方法可以有方法体。下面对 default 方法和 static 方法进行讲解。

default 方法既可以被子接口继承,也可以被其实现类调用。default 方法被继承时,可以被子接口重写方法。

在一个 Java 类实现了多个接口,且这些接口中无继承关系的情况下,但如果这些接口中包含相同的(同名,同参数)的 default 方法,则接口实现类会报错,接口实现类必须通过特殊语法指定该实现类要实现哪个接口的 default 方法。特殊语法的格式如下:

<接口>.super.<方法名>([参数])
bash

所谓 static 方法,指的是接口里的静态方法,即 static 修饰的有方法体的方法。它不会被继承或者实现,只能被自身调用。例如,定义一个 DefalutMethods 接口,在接口中定义 default 方法和 static 方法,代码如下:

public interface DefaultMethods {
    default void defaultMethod() {
        System.out.println("default方法");
    }
    static void staticMethod() {
        System.out.println("static方法");
    }
}
java

定义 Test 类,并且实现 DefalutMethods 接口。在 Test 类中重写 default方法,并且使用特殊语法调用 DefalutMethods 接口中的 default 方法。代码如下:

public class Test implements DefaultMethods {
    @Override
    public void defaultMethod() {
        DefaultMethods.super.defaultMethod();
    }
}
java

抽象类与接口的区别

因为接口在许多方面与抽象类很相似,所以很容易搞混抽象类和接口。下面将结合抽象类和接口的特点,总结二者的区别:

  • 抽象类和接口都可以有子类,其中把接口的子类称作实现类。

  • 抽象类通常作为子类的“模板”,接口通常用来描述子类的“行为”。

  • 子类虽然只能继承一个抽象类,但可以同时实现任意多个接口。

  • 创建抽象类需要使用abstract关键字,创建接口需要使用interface关键字。

  • 声明抽象类中的抽象方法需要使用abstract关键字,声明接口中的抽象方法可以省略abstract关键字。

  • 在接口中可以使用default关键字定义有方法体的非抽象方法,但是在抽象类中不能用。

  • 在接口中不能有构造方法,但是在抽象类中可以有。

  • 在抽象类中可以有代码块、静态代码块和静态方法,但是在接口中不能有。

  • 抽象类中的成员属性可以定义为任意权限、任意类型、静态或非静态的变量,但是接口中的成员属性只能是静态常量。

  • 子接口可以同时继承多个父接口,但是子抽象类只能继承一个父抽象类。

编程训练(答案位置:资源包\TM\sl\7\编程训练)

【训练15】汽车厂、鞋厂都是工厂 创建抽象的工厂类,工厂类中有一个抽象的生产方法。让汽车厂和鞋厂都继承工厂类,汽车厂生产的是汽车,鞋厂生产的是鞋。

【训练16】五颜六色的接口 创建一个表示五颜六色的接口Colorful,接口中有一个表示点亮的抽象方法shine()。编写一段代码,实现红灯发红光,黄灯发黄光,绿灯发绿光。