java是一门纯面向对象的编程语言,而接口就是面向对象的一个突出的关键特性:它可以在不具体实现时,指定特定的功能和特性。

接口的定义

接口是服务者与被服务对象之间约定的一种机制。(制定规则)

其实可以这样理解:

接口类是一个服务商公司,该公司可以定义它的可以对外提供的服务,但是这些服务本身该公司可能并不需要实现,可以交给外包公司来实现,当用户需要从该服务商获得相应的服务时,服务商便根据用户的需求,为用户提供服务,具体的服务由特定的外包公司负责。另外,同一个服务针对不同的服务对象,有不同的具体服务内容和方式,比如服务商提供一个保险的服务,但是该服务可以分为很多种:学生保险、养老保险、车辆保险、医疗保险等。当一个用户向服务商提出一个医疗保险的需求时,服务商便会通知实现了保险接口服务的医疗保险服务外包公司来为用户提供具体的服务;同样车辆保险就会通知一个车辆保险服务外包公司来提供具体的服务。

接口的声明

public interface Service {
	...
	void doService();//提供服务
	ServiceOrder creatServiceOrder(Client client);//创建服务订单
	...
}

这就是一个接口的具体声明方式,指出接口的名字,以及可以对外提供的方法(抽象方法),方法的具体实现交给接口的实现类(外包公司)完成,接口只负责制定规则即可。

注意

接口的所有方法默认为公有方法,因此在接口内部声明方法的时候,不需要显式地指明 public

接口的实现

接口的实现类,就可以理解成之前提到的外包公司,该公司必须实现服务商所规定的所有服务,以保证服务商的正常运营。

Java中可以使用 implements 关键字来实现一个接口,同时在 java中,一个类可以实现多个接口,就是外包公司可以接到其他服务商的服务订单。

public class CarService implements Service {
    @Override
    public void doService() {
        //具体的实现代码
        System.out.println("提供车辆服务");
    }

    @Override
    public ServiceOrder creatServiceOrder(Client client) {
        //具体的实现代码
        return new ServiceOrder("20200725",client);
    }
}

接口类型转换

上面提到的通知外包公司提供具体的服务时,就会让外包公司代替服务商为用户提供服务,这其实就是一个接口类型转换的过程。

目前有一个客户需要从服务商处得到一个车辆服务:

public class Client {
    public int id;
    public String name;
    public Client(int id, String name) {
        this.id = id;
        this.name = name;
    }
    public void doSomething(Service service) {
        service.doService();
    }
    //test
    public static void main(String[] args) {
        new Client(1,"test").doSomething(new CarService());//实际执行的是CarService中的doService()方法
    }
}

在上述的主方法中,在 doSomething()中理应传入一个 Service 对象,而实际上是一个实现了该接口的 CarService对象,这就是接口类型的转换,在方法的内部自动地进行了类型转换。

理解

当客户提出某种服务需求的时候:

客户来到 Service公司:

客户:请问贵公司可以提供 车辆xxx服务吗?

Service:我们可以为您提供高质量的服务,保证服务周到。

客户:那好。

Service安排工作:

Service:我们有一个客户,需要提供 车辆xxx服务,就安排给 CarService公司吧

CarService:莫得问题。

提供具体服务的时候:

CarService:你好,我是来为你提供 车辆xxx服务的。

客户:好的,开始工作吧。

这个特性可以使得代码解耦合和以及代码复用。

接口的继承

一个接口可以继承另外一个接口,在原有方法的基础上要求或提供额外的方法,用以扩展原来接口的功能。

可以理解为,之前的服务商因为发展迅速,现在要开辟海外市场,提供海外服务,需要扩展业务范围,则就可在海外设立一个子公司,他可以在基础的服务项目上,适当的扩展服务项目。

public interface OverseasService extends Service {
    void doOverseasService();//海外服务
}

这时,实现类(海外外包公司)就需要实现 ServiceOverseasService接口中的所有方法。

接口中的常量

在接口中声明的任何变量均会被自动声明为 public static final

比如,在 SwingConstants的接口中定义了用于罗盘定位的常量:

public interface SwingConstants {
	int NORTH = 1;
	int NORTH_EAST = 2;
	int EAST = 3;
	...
}

我们可以通过 SwingConstants.NORTH来引用它。但是这不是常用的习惯,使用 枚举来描述一组常量集合的方式远比这种方式好。

在接口中,你无法拥有实例变量,接口是指定行为的,不指定状态。

接口中的方法

Java的早期版本中,在接口中只能声明抽象方法,换句话说,就是一个方法外壳,内部没有实际的实现。但是,在现在,我们可以在接口中添加具有方法体的方法了:静态方法、默认方法和私有方法。

静态方法
public interface Service {
    void doService();//提供服务
    ServiceOrder creatServiceOrder(Client client);//创建服务订单
    static void finishService() {
        System.out.println("完成服务");
    }
}
默认方法

我们可以给接口中的任意方法提供默认实现,但是必须添加 default修饰符标签。

public interface Service {
    default void doService(){//提供服务
		System.out.println("提供 Service 的服务");
    }
    ServiceOrder creatServiceOrder(Client client);//创建服务订单
   
}

实现该接口的类可以选择覆盖该方法的实现或者选择继承默认实现。

在接口中使用默认方法的用途

这个在实际生活中可能比较难找到合适的例子,大体可以这样理解:服务商公司为应对发展的需要,需要增加新的服务项目,该服务项目可能并不需要之前所有的外包公司都必须实现,但是根据接口的定义,接口中的方法一旦被修改,实现类必须同步更新后,才能正常工作,那么这次新服务项目的增加会带来比较大的改动成本,采用默认方法,就可以避免这种现象,声明默认方法后,外包公司便得到了该方法的接口权限,他可以选择是否修改。

在开发中,就比较好理解了:

默认方法在 接口演化有很重要的作用。这里举一个 Java中的 Collection接口的例子,比如你作为 JDK的开发人员,在之前的 Java版本中 Collection不具有 stream方法,同时在之前有程序使用 一个 Collection实现类:

public class test implements Collection {
 	...
}

现在,新版本的 Java需要在 Collection接口中添加一个 stream方法,假设该方法不是默认方法,那么在更新到新版本的 Java后之前的 test类就会编译错误,进而导致之前的程序无法正常运行。而使用默认方法,便可以解决这个问题,并且使得 test再次编译后,具有了新增加的 stream方法,同时在程序后续的更新开发过程,还可以对 stream方法进行覆盖重写。这就保证了源代码的兼容性。

私有方法

Java9后,接口中的方法可以是私有的,但是该方法只是为接口中的其他方法提供辅助实现,并不对外界开放。

public interface TestInterface {
    default int max(int a ,int b) {
        return maxOfTwoNumber(a,b);
    }
	private static int maxOfTwoNumber(int a,int b) {
        return a > b ? a : b ;
    }
}