首页 > 编程笔记

什么是接口,Java接口详解

接口是 Java 程序开发中很重要的一种思想,准确地讲不仅仅是 Java 编程,对于其他高级编程语言来说接口也是非常重要的,在实际开发中使用非常广泛。

接口是由抽象类衍生出来的一个概念,并由此产生了一种编程方式:面向接口编程。我们已经掌握了面向对象编程思想,那什么是面向接口编程呢?

面向接口编程不是一种思想,更准确地讲它应该是一种编程方式。

面向接口编程就是将程序的业务逻辑进行分离,以接口的形式去对接不同的业务模块。接口只串联不实现,真正的业务逻辑实现交给接口的实现类来完成。当用户需求变更的时候,只需要切换不同的实现类,而不需要修改串联模块的接口,减少对系统的影响。

上面这段解释不是很容易理解,我们通过一个现实生活中的例子来类比面向接口编程。

我们都知道计算机可以通过 USB 插口来连接外部设备,例如鼠标、U盘、散热架等。假如没有 USB 插口这种配置,那么外部设备就是固定在计算机上的,不能更换。例如一个鼠标固定在计算机上,但我现在希望换成 U 盘,怎么办呢?我们可能就需要把计算机拆了,重新组装计算机和 U 盘的内部连接。同理,每一次希望更换外置设备的时候,都需要把计算机拆了,移除之前的结构并重新组装,这种方式很显然是不可取的。维护成本太高,效率太低,用专业的语言来描述,叫作耦合性太高,模块和模块结合得太紧密,不灵活,要更换就必须重新构建内部组成来替换原有的结构。

但是有了 USB 插口之后,上述问题就迎刃而解了。通过 USB 插口连接到电脑上的外部设备是非常灵活的,即插即用,需要更换就拔掉旧的,把新的设备插入即可,这就是接口的思想。在设计制作计算机的时候,不需要考虑到底是跟鼠标、U 盘还是散热架连接,只需要把这个对接的部分提取出来,设计成一个接口。计算机内部只需要跟这个接口进行连接即可,你插入鼠标,计算机就识别鼠标,插入 U 盘就识别 U 盘。USB 插口就相当于接口,鼠标、U 盘、散热架就相当于接口的实现类,这样就很好理解了。

使用 USB 插口的方式可以很好地实现计算机与外部设备之间的各种切换,这是因为它们的连接是松散的、不紧密的,用专业的语言来讲就叫作低耦合,模块和模块之间连接松散,自然很容易切换。这里我们又引出来耦合性这个概念,很显然在实际开发中应该尽量降低程序的耦合性,以提高程序的扩展性,便于维护。

面向接口编程就具备以下优点:
了解完接口的概念,以及我们为什么要使用接口,接下来就是如何使用接口了。

如何使用接口

接口在 Java 中是独立存在的一种结构,和类相似,我们需要创建一个接口文件,Java 中用 class 关键字来标识类,用 interface 来标识接口,基本语法如下:
public interface 接口名{
    public 返回值 方法名(参数列表);
}
看到定义接口的基本语法,你有没有一种很熟悉的感觉?没错,接口与抽象类非常相似,同样是定义了没有实现的方法,只是一个抽象的概念,没有具体实现。

接口其实就是一个极度抽象的抽象类。为什么这么说呢?抽象类的概念我们知道,一个类中一旦存在没有具体实现的抽象方法,那么该类就必须定义为抽象类,同时抽象类中是允许存在非抽象方法的。但是接口完全不同,接口中不能存在非抽象方法,必须全部是抽象方法,所以我们说接口是极度抽象的抽象类。

因为接口中全部是抽象方法,所以修饰抽象方法的 abstract 可以省略,不需要添加在方法定义处。当然,在定义方法时添加 abstract,程序也不会报错。

我们知道了接口中的方法必须全部是抽象的,那么接口中可以存在成员变量吗?答案是肯定的,接口中可以定义成员变量,但是有如下要求:
关于接口的创建,参考如下的代码:
public interface MyInterface {
    public int ID = 0;
    String NAME = "";
    public void test();
}
接口定义完成,接下来如何使用呢?对于类的使用我们已经很熟悉了,首先需要实例化一个类的对象,然后通过对对象的操作来完成功能。但是接口的使用就大不一样了,因为接口是不能被实例化的,它描述的是一个抽象的信息,抽象的信息当然是没有实例的。

我们需要实例化的是接口的实现类。实现类就是对接口的抽象方法进行具体实现的,实现类本身就是一个普通的 Java 类,创建实现类的基本语法如下:
public class 实现类名 implements 接口名{
     public 返回值 方法名(参数列表){
     }
}
通过关键字 implements 来指定实现类具体要实现的接口,在实现类的内部需要对接口的所有抽象方法进行实现,同时要求访问权限修饰符、返回值类型、方法名和参数列表必须完全一致,例如:
public class MyImplements implements MyInterface{
    @Override
    public void test() {
        // TODO Auto-generated method stub
        System.out.println("实现了接口的抽象方法");
    }
}
在这里接口与继承也有一个可以对比的地方,继承只能实现单继承,即一个子类只能继承一个父类。接口可以多实现,即一个实现类可以同时实现多个接口。如何去理解这句话呢?

接口实际上描述的是功能,让某个类实现接口,就是让该类具备某些功能。类比现实生活中的例子,一个孩子只能有一个亲爹,所以是单继承,但是一个孩子可以同时具备多个技能,所以是多实现。例如:
public interface MyInterface {
    public void fly();
}

public interface MyInterface2 {
    public void run();
}

public class MyImplements implements MyInterface,MyInterface2{
    @Override
    public void fly() {
        // TODO Auto-generated method stub
        System.out.println("实现了fly的功能");
    }
    @Override
    public void run() {
        // TODO Auto-generated method stub
        System.out.println("实现了run的功能");
    }
}

public class Test {
    public static void main(String[] args) {
        MyImplements myImplements = new MyImplements();
        myImplements.fly();
        myImplements.run();
    }
}
运行结果为:

实现了fly的功能
实现了run的功能

面向接口编程的实际应用

面向接口编程是一种常用的编程方式,可以有效地提高代码的复用性,增强程序的扩展性和维护性,我们通过下面这个例子来学习什么是面向接口编程。

某工厂生产成品 A,主要由设备 A 来完成生产,用程序模拟这一过程。分别创建 Factory 类和 EquipmentA 类,并将 EquipmentA 设置为 Factory 的成员变量,在 Factory 的业务方法中调用 EquipmentA 的方法来完成生产,具体实现代码如下:

1) 定义 EquipmentA 类:
public class EquipmentA {
    public void work() {
        System.out.println("设备A运行,生产成品A");
    }
}

2) 定义 Factory 类:
public class Factory {
    private EquipmentA equipmentA;
    //getter、setter方法

    public void work() {
        System.out.println("开始生产...");
        this.equipmentA.work();
    }
}

3) Test 类中的工厂开始生产成品:
public class Test {
    public static void main(String[] args) {
        EquipmentA equipmentA = new EquipmentA();
        Factory factory = new Factory();
        factory.setEquipmentA(equipmentA);
        factory.work();
    }
}
运行结果为:

开始生产...
设备A运行,生产成品A


现在工厂接了一份新订单,要求生产成品 B,需要设备 B 来完成生产,用程序实现这一过程,首先需要创建 EquipmentB 类,同时修改 Factory 内部的属性,代码如下:

1) 定义 EquipmentB 类:
public class EquipmentB {
    public void work() {
        System.out.println("设备B运行,生产成品B");
    }
}

2) 修改 Factory 类,将成员变量的数据类型改为 EquipmentB:
public class Factory {
    private EquipmentB equipmentB;
    //getter、setter方法

    public void work() {
        System.out.println("开始生产...");
        this.equipmentB.work();
    }
}

3) 在 Test 类中完成生产:
public class Test {
    public static void main(String[] args) {
        EquipmentB equipmentB = new EquipmentB();
        Factory factory = new Factory();
        factory.setEquipmentB(equipmentB);
        factory.work();
    }
}
运行结果为:

开始生产...
设备B运行,生产成品B

这种方式需要修改 Factory 类的内部结构,如果此时需求又改回到生产成品 A 或者生产成品 C,就需要创建新的类,同时再次修改 Factory 类的属性信息,这种当需求发生变更就要频繁修改类结构的方式是我们应该避免的。这种结构的程序扩展性非常差,如何改进呢?使用面向接口编程即可。

将 Equipment 以接口的形式集成到 Factory 类中,具体实现如下:

1) 定义 Equipment 接口:
public interface Equipment {
    public void work();
}

2) 定义 Equipment 接口的实现类 EquipmentA 和 EquipmentB:
public class EquipmentA implements Equipment{
    @Override
    public void work() {
        // TODO Auto-generated method stub
        System.out.println("设备A运行,生产成品A");
    }
}

public class EquipmentB implements Equipment{
    @Override
    public void work() {
        // TODO Auto-generated method stub
        System.out.println("设备B运行,生产成品B");
    }
}

3) 修改 Factory 类,将 Equipment 接口设置为成员变量:
public class Factory {
    private Equipment equipment;
    //getter、setter方法

    public void work() {
        System.out.println("开始生产...");
        this.equipment.work();
    }
}

4) Test 类中的工厂生产成品 A:
public class Test {
    public static void main(String[] args) {
        Equipment equipment = new EquipmentA();
        Factory factory = new Factory();
        factory.setEquipment(equipment);
        factory.work();
    }
}
运行结果为:

开始生产...
设备A运行,生产成品A


如果此时需要生产成品 B,修改起来就方便多了。因为嵌入到 Factory 类中的是接口,所以 Factory 类不需要修改,只需要将 EquipmentB 组件以接口的形式赋给 Factory 实例对象即可,Test 类中修改一处代码,代码如下:
public class Test {
    public static void main(String[] args) {
        Equipment equipment = new EquipmentB();
        Factory factory = new Factory();
        factory.setEquipment(equipment);
        factory.work();
    }
}
运行结果为:

开始生产...
设备B运行,生产成品B

推荐阅读