从前面关于抽象类的讨论中我们可以得出结论:抽象类是从多个类中抽象出来的模板。这些具体类中,共性的行为放到父类的抽象类中实现。而具体类中不同的行为,在抽象类中用抽象方法的形式定义,在具体的子类来实现。
如果将这种抽象进行得更彻底,则可以提炼出一种更为特殊的”抽象类”——接口(interface)。在接口中,所有方法都是抽象方法,所有方法都不提供具体的实现。
接口在日常生活中非常常见,当我们每天要为各种电子设备充电、传输数据、使用,都会使用到“数据线”,数据线上有什么?就是接口。像常见的 type C 接口、USB 接口、lightning 接口,其实就等于是一个规范,对于硬件上的接口只要满足要求,无论品牌,都可以插入到卡槽中。
当我们在购物网站、购物 APP 上购买商品发货之后,我们就可以在菜鸟裹裹、网站、APP 上同时实时查看当前的物流信息。那快递公司和购物网站作为两家独立的公司,为什么会在能看到快递信息?这就要用到接口。当查看自己的快递信息时,购物网站利用快递公司提供的接口实时调取信息呈现在自己的网站上。只要有合作,只要有允许,别的公司就都可以通过快递公司提供的接口调取到快递信息。既然有多方调用,那提供一个统一的调用规范就会方便很多。
java 中的接口通过关键字 interface
表示,它是一种新的引用数据类型,同样也是一种公共标准,公共规范。可以给 没有继承关系的类之间提供共享行为。提供多实现的效果来弥补 java 只有单继承的不足。
在接口之前还学习了抽象类,抽象类其实还不算彻底的抽象,因为它除了有方法外还有成员属性,属性通常是变化的,方法却是公共的,所以很多时候我们需要的是制定一个纯粹的标准让子类只去实现抽象方法。
接口与类存在相似性:
- 接口可以存放 n 个方法
- 接口保存为以”.java”为后缀名的文件,接口名与文件名同名
不同之处在于:
- 接口不能被实例化
- 接口没有构造器
- 接口中的所有方法都是抽象的
- 接口中不能包含实例成员变量。唯一可以出现在接口中的成员变量必须是同时声明为 static 和 final
- 接口不能被一个类继承,只能被一个类来实现
- 一个接口能够继承多个接口
接口的创建
语法: ```java 访问修饰符 interface 接口名称{
}
接口默认就是抽象类、抽象方法,所以不需要 abstract 关键字,访问修饰符只能是 public 和默认(同包)。
<a name="Xzh8A"></a>
## 接口的属性
属性只能是且默认是 **公共静态的常量属性 **`public static final` 的,但由于属性作为常量,并不能够修改,且接口更多的是关注“行为”而不是属性,所以导致接口属性并不常用。
<a name="vesW0"></a>
## 接口的方法
刚刚我们提到接口的方法只关注行为,不关注行为内部的具体实现,所以接口其实就是 **抽象方法的集合**,接口的方法都是且默认是公共抽象 `public abstract` 的,语法格式同抽象类一样:
```java
public void method();
整合:
public interface TestInterface {
public void methodA();
public void methodB();
}
接口的使用
对比看看抽象类和接口:
- 接口不能 new 对象,抽象类也不能
- 接口只能充当在设计结构的上层类型,抽象类也是
- 接口更多关注的是行为不是属性,抽象类也是
使用 extends 继承
这看起来好像跟抽象类重复了,那接口与抽象类的不同之处就在于:接口可以做多继承或多实现的。
使用 extends 可以实现继承。子接口自动拥有父接口还可以扩展自己的特有行为。
public interface interfaceBaby extends interfaceDaddy{
}
接口不但可以继承接口,还可以多继承,多个父接口之间使用 ,
分割:
public interface interfaceC extends interfaceA, interfaceB{
}
若两个父接口中有相同的方法在子接口中不会冲突,因为子接口只需要拥有一个即可。
若两个父接口中各自拥有一个同名属性那么子接口在使用属性时会报错,但通常不要去书写属性。
implements 来实现接口
使用关键字 implements
。 implements 译为实施、使…生效。所以这里的这个类就被称为类的 实现类。
抽象类与它的子类是 is-a 的关系,但接口与它的实现类是 like-a 的关系。
public class TestClass implements interfaceA{
}
使用实现类来使用接口中的抽象方法步骤:
- 创建接口,要不要接口继承接口看需求
- 实现类重写接口中所有抽象方法
- 创建实例调用方法 ```java // interMommy.java 接口 A public interface interMommy { public void eat(); public void walk(); } // interDaddy.java 接口 B public interface interDaddy { public void sleep(); public void sing(); } // babyInterface.java 继承了接口 A、B 的接口 public interface babyInterface extends interMommy, interDaddy {
} // Test.java 实现类 public class Test implements babyInterface { public void eat() { System.out.println(“吃饭”); } public void walk() { System.out.println(“散步”); } public void sleep() { System.out.println(“睡觉”); } public void sing() { System.out.println(“唱歌”); } } // TestMain.java public class TestMain { public static void main(String[] args) { new Test().eat(); // 吃饭 } }
- 一个类实现接口以后,应该重写这个接口中所有抽象方法,否则这个类就是抽象类
- 一个类可以同时实现多个接口,前提是必须实现这多个接口中的所有抽象方法,否则这个类依然是抽象类
- 一个类可以在实现接口的同时,继承父类。语法是先 extends 后 implements
<a name="oxUu7"></a>
# 接口的应用场景
接口的主要作用是让没有继承关系的类也能共享行为。由于接口和继承树解绑,加上多继承和多实现导致接口的应用范围可以更广泛和灵活。对于“接口和抽象类都是书写抽象方法的类型” 两者区别不仅仅在于**语法**区别,还有**场景**区别。
- 设计在**抽象类**上的抽象方法,秉承了类继承的 **is-a** 的关系,应该是**与生俱来**的行为才定义在抽象类中。即“没有这个方法就不是这个类”或者说“这个类型必须要有这个方法”
- 而对于**附属添加的行为**,就应该**设计在接口**中,根据实际需要给某些类型进行添加,增加丰富度。
<a name="FXopj"></a>
## 场景一
当前有一个门 Door 类,那么门的开 open 关 close 方法肯定是门与生俱来的,满足 “没有这个方法就不是这个类”,但对于门能不能锁 lock、按门铃 doorbell、可被外部查看 watch 等,就是属于附属添加的了,增加了一扇门的丰富度。<br />所以对于门而言:
- 与生俱来:开关方法 - 抽象类
- 附属添加:锁门 lock、按门铃 doorbell、通过猫眼或玻璃查看 watch - 接口
<a name="ovLSn"></a>
## 场景二
对于灯 Lamp 而言,与身俱来的就得有亮 light,不亮就根本算不上灯。商家在灯的基础上,还可以让它有音乐响(跑马灯)playMusic 的方法、颜色的变换 changeColor,包括亮的方式到底是常亮?闪烁?还是呼吸灯,都是在亮的基础之上扩展的。
- 与生俱来:亮 - 抽象类
- 附属添加:放音乐 playMusic、变色 changeColor、闪烁 flicker - 接口
<a name="sva42"></a>
## 设计原则
分析完与生俱来和附属添加以后,就可以分析一下需要定义几个接口,各接口中定义几个方法了。<br />在设计原则上遵循 “接口隔离”原则,即在接口的设计上,不应该让上层接口污染下层接口或实现类。也称为 “最小隔离”原则,尽量定义具有少量方法的小接口,而不是大而全的大接口。判断一个接口中是否应该有多个方法的标准是:这多个方法应该同时出现或同时消失。
<a name="XFdP1"></a>
# 多态
在抽象类中有一个说法: “父类的引用可以指向子类的对象”,那对于接口来说就是:接口的引用可以指向实现类的对象。<br />接口同样可以在多态应用中体现出动态多态效果。<br />同样可以通过 instanceof 来判断该接口的引用是否指向某个类的对象。
```java
System.out.println(new Inter() instanceof interParent); // true
System.out.println(new Student() instanceof interParent); // false
也可以通过强转,把该接口引用强转成某个实现类的引用,也有风险,如果该引用不是指向接口的实现类对象那么这个强转也会报错。
抽象类对比接口
语法层面区别:
抽象类 | 接口 | |
---|---|---|
声明 | abstract class | interface |
内容 | 变量常量属性、构造器、抽象方法、初始化块 | 公共静态常量属性、无构造器、抽象方法、默认公共抽象 |
使用 | 单继承 | 多继承+多实现 |
设计层面的区别:
抽象类 | 接口 | |
---|---|---|
关系 | is-a | like-a |
方法 | 与生俱来 | 附属添加 |