接口

Java中的接口

在软件工程中,存在许多场景需要不同团队的程序员达成一份明确软件交互方式的”契约”。每个团队都应能在完全不知晓其他团队代码实现细节的情况下编写自己的代码。通常而言,Interface 正是这样的契约。

例如,设想一个未来社会:计算机控制的无人驾驶汽车在城市街道上载客行驶,无需人类操作员。汽车制造商编写软件(当然是用Java语言)来操控车辆——停车、启动、加速、左转等等。另一类工业群体——电子导航仪器制造商——则生产计算机系统,这些系统接收全球定位系统(GPS)的位置数据以及无线传输的交通状况信息,并利用这些信息来驾驶汽车。

汽车制造商必须发布行业标准接口,详细说明可调用哪些方法来控制车辆行驶(任何制造商生产的任何车型)。导航设备制造商可据此编写软件,通过调用接口中定义的方法来控制车辆。双方无需了解对方软件的具体实现方式。事实上,各制造商均将其软件视为高度专有技术,保留随时修改的权利——只要持续遵循已发布的接口规范即可。

在Java编程语言中,接口是一种类似于类的引用类型,仅能包含常量、方法签名、默认方法、静态方法(私有或公有,非受保护)、实例非抽象方法(私有,非公有,非受保护)以及嵌套类型。方法体仅存在于默认方法、私有方法和静态方法中。接口不可实例化——它们只能由类实现或被其他接口扩展。扩展机制将在本节后续部分讨论。

定义一个接口类似于创建一个类:

public interface OperateCar {

   // constant declarations, if any

   // method signatures
   
   // An enum with values RIGHT, LEFT
   int turn(Direction direction,
            double radius,
            double startSpeed,
            double endSpeed);
   int changeLanes(Direction direction,
                   double startSpeed,
                   double endSpeed);
   int signalTurn(Direction direction,
                  boolean signalOn);
   int getRadarFront(double distanceToCar,
                     double speedOfCar);
   int getRadarRear(double distanceToCar,
                    double speedOfCar);
         ......
   // more method signatures
}

请注意,方法签名不包含大括号,且以分号结束。
要使用接口,你需要编写一个实现该接口的类。当可实例化的类实现接口时,它会为接口中声明的每个方法提供方法体。例如:

public class OperateBMW760i implements OperateCar {

    // the OperateCar method signatures, with implementation --
    // for example:
    public int signalTurn(Direction direction, boolean signalOn) {
       // code to turn BMW's LEFT turn indicator lights on
       // code to turn BMW's LEFT turn indicator lights off
       // code to turn BMW's RIGHT turn indicator lights on
       // code to turn BMW's RIGHT turn indicator lights off
    }

    // other members, as needed -- for example, helper classes not 
    // visible to clients of the interface
}

在上述自动驾驶汽车的例子中,汽车制造商将负责实现该接口。雪佛兰的实现方案与丰田的自然会存在显著差异,但两家制造商都将遵循相同的接口规范。作为接口客户端的导航设备制造商,将构建利用车辆GPS定位数据、数字街道地图及交通数据来操控汽车的系统。在此过程中,导航系统将调用接口方法:转向、变道、刹车、加速等操作。

接口作为API

自动驾驶汽车的例子展示了接口作为行业 Application Programming Interface(API)的使用方式。API在商业软件产品中也很常见。通常,一家公司出售包含复杂方法的软件包,而另一家公司希望将其应用于自身的软件产品。例如,某软件包包含数字图像处理方法,被出售给开发终端用户图形程序的公司:

  • 图像处理公司编写类来实现接口,并将该接口公开给客户使用。
  • 随后,图形公司调用图像处理方法,使用接口中定义的签名和返回类型。
    尽管图像处理公司的API向客户公开,但其API的具体实现方式却被严密保密——实际上,只要它继续实现客户所依赖的原始接口,便可在日后修改具体实现方式。接口作为多功能引用类型,既能定义默认方法,又可在不破坏实现类的前提下为特定类型扩展功能。此外,有时可将通用代码抽离至私有方法中,从而减少默认方法的代码冗余。欲深入了解接口内部的方法定义机制,请查阅本系列后续教程。

定义一个接口

接口声明由修饰符、interface关键字、接口名称、用逗号分隔的父接口列表(如有)以及接口主体组成。例如:

public interface GroupedInterface extends Interface1, Interface2, Interface3 {

    // constant declarations
    
    // base of natural logarithms
    double E = 2.718282;
 
    // method signatures
    void doSomething (int i, double x);
    int doSomethingElse(String s);
}

public 访问修饰符表示该接口可被任何包中的任何类使用。若未声明接口为public,则该接口仅对与接口定义在同一包中的类可见。

接口可以继承其他接口,就像类可以继承或扩展另一个类一样。然而,类只能继承一个其他类,而接口可以继承任意数量的接口。接口声明包含一个用逗号分隔的列表,列出其继承的所有接口。

接口主体可以包含抽象方法、默认方法和静态方法。

接口中的抽象方法后跟分号,但不跟大括号(抽象方法不包含实现)。

默认方法使用 default 修饰符定义,静态方法使用 static 关键字定义。接口中的所有抽象方法、默认方法和静态方法默认均为 public ,因此可以省略 public 修饰符。

此外,接口中可以包含常量声明。接口中定义的所有常量值默认具有 publicstaticfinal 修饰符。同样,这些修饰符可以省略。