前言
在日常生活中,我们可以接触到很多接口。Java中的接口和现实中的接口很相似,都是把某些东西衔接起来。
Java中的接口是什么?
接口与类一样,也是一种“引用数据类型”,其中只能有常量(只能是常量,总是static和final修饰的)和方法的声明(只能有方法名称、参数及其类型,不能有方法体);接口没有构造方法,不能被实例化。
接口是完全抽象的,可以看作是特殊的抽象类。
接口和抽象类的比较:
抽象类 | 接口 |
---|---|
是半抽象的 | 是完全抽象的 |
有构造方法 | 没有构造方法,不能实例化 |
类与类之间只能单继承 | 接口与接口之间支持多继承 |
一个类可以实现多个接口,一个抽象类只能继承一个类(单继承) | - |
- | 接口中只允许出现常量和抽象方法 |
一、接口的基础语法
1、定义接口
语法是什么?
[修饰符列表] interface 接口名{ }
接口中只包含两部分内容,一部分是:常量。一部分是:抽象方法。接口中只有以上两部分,没有其它内容了。
接口中所有的元素都是public修饰的。(都是公开的。)
我们定义一个数学加减接口:
interface MyMath {
//常量
double PI = 3.14;
double add(double a,double b); //加法
double sub(double a,double b); //减法
}
在接口中,我们随便定义一个变量都是常量(即static和final修饰的,值不能发生改变的变量),在定义时可以把static和final省略掉。同理,接口中的方法都是抽象方法,它的public abstract也可以省略,但是注意其中不能有方法体,不然会报错。
可以选择在接口中将方法声明为public,但即使你不这么做,它们也是public的。因此,在实现一个接口时,在接口中定义的方法必须被定义成是public,否则,它们只能得到默认的包访问权限,这样在方法被继承的过程中,其访问权限就降低了,这是编译器不允许的。
public static void main(String[] args) {
//访问接口中常量
System.out.println(MyMath.PI); //输出3.14
}
2、接口的实现
类和类之间叫做继承,类和接口之间叫做实现。继承使用extends关键字完成,实现使用implements关键字完成。
当一个非抽象的类实现接口的话,必须将接口中所有的抽象方法全部实现(覆盖、重写)。
实现MyMath接口:
class MyMathImp implements MyMath {
@Override
public double add(double a, double b) {
return a + b;
}
@Override
public double sub(double a, double b) {
return a - b;
}
}
interface MyMath {
double PI = 3.14;
double add(double a,double b); //加法
double sub(double a,double b); //减法
}
3、接口支持多继承,一个接口可以继承多个接口
interface A{
}
// 接口支持继承
interface B extends A{
}
// 一个接口可以继承多个接口(支持多继承)
interface C extends A, B{
}
4、接口和接口之间支持多继承,那么一个类也可以同时实现多个接口
对于一个事物而言,比如一台计算机,它的上面有很多接口,有主机电源接口、键盘接口、鼠标接口,还有接显示器的…
这种机制弥补了java中的哪个缺陷? Java不像C++一样支持多继承,java中类和类只支持单继承。实际上单继承是为了简单而出现的,现实世界中存在多继承,java中的接口弥补了单继承带来的缺陷。
之前有一个结论:
无论向上转型还是向下转型,两种类型之间必须要有继承关系,没有继承关系编译器会报错。(这句话不适用在接口方面。)
在下面代码中,接口A和接口B虽然没有继承关系,但是写代码的时候,可以互转。编译器没意见。但是运行时可能出现:ClassCastException。
最好加:instanceof运算符进行判断。向下转型养成好习惯。转型之前先if+instanceof进行判断。
接口多继承示例:
public class interfaceTest03 {
public static void main(String[] args) {
A mma = new ABC();
B mmb = new ABC();
mma.a();
//调用其他接口中的方法,需要转型(接口转型)
B mmb2 = (B)mma;
mmb2.b();;
//向下转型
C mmc = (C)mmb;
mmc.b();
}
}
class ABC implements A,B,C {
@Override
public void a() {
System.out.println("This is A.");
}
@Override
public void b() {
System.out.println("This is B.");
}
@Override
public void c() {
System.out.println("This is C.");
}
}
interface A {
void a();
}
interface B {
void b();
}
interface C extends A,B {
void c();
}
输出:
This is A.
This is B.
This is B.
经过测试:接口和接口之间在进行强制类型转换的时候,没有继承关系,也可以强转。 但是要注意,运行时可能会出现ClassCastException异常。
5、当一个子类继承父类,还需要实现接口,该怎么写
extends和implements可以共存,extends在前,implements在后。
比如: class Cat extends Animal implements Flyable {}
不同的类实现接口,都需要按照需求重写方法。
下面的代码示例中,有动物父类,子类小白龙和猫咪。小龙和小猫都继承动物类,小龙实现了飞接口和游接口,小猫也实现了飞接口,但是它们飞的时候肯定是不一样的呀。即使它们 调用同一个fly()方法,最后的执行效果不同。
示例:
public class interfaceTest {
public static void main(String[] args) {
//多态
Flyable f1 = new Dragon();
f1.fly();
Flyable f2 = new Cat();
f2.fly();
// 调用同一个fly()方法,最后的执行效果不同。
Swimable s = new Dragon();
s.swim();
}
}
//父类:动物
class Animal {
}
//接口:可飞翔的
interface Flyable {
void fly();
}
//接口:可游泳的
interface Swimable {
void swim();
}
//子类:继承Animal父类和实现飞和游,一个上天入海的小白龙
class Dragon extends Animal implements Flyable,Swimable {
@Override
public void fly() {
System.out.println("飞龙在天~~");
}
@Override
public void swim() {
System.out.println("蛟龙入海~~");
}
}
//子类:继承Animal和实现飞
class Cat extends Animal implements Flyable {
public void fly() {
System.out.println("这是一只飞猫!!!");
}
}
输出:
飞龙在天~~
这是一只飞猫!!!
蛟龙入海~~
使用接口,写代码的时候,可以使用多态(父类型引用指向子类型对象)。
二、为什么需要接口
接口在开发中的作用,类似于多态在开发中的作用。
(多态的作用:面向抽象编程,不要面向具体编程。降低程序的耦合度。提高程序的扩展力。)
在日常的生活中,到处都有接口。用电脑举例,大家的鼠标、键盘和内存条等都是通过接口与主机连接,键盘坏了,可以重新买一个再插上去,很方便。而如果没有接口,键盘是和主机焊接死的,键盘坏了难道我们要把它整套换了吗?这就体现了接口的高扩展性(低耦合度)。
面向抽象编程这句话以后可以修改为:面向接口编程。
有了接口就有了可插拔。可插拔表示扩展力很强。不是焊接死的。
总结一句话:三个字“解耦合”
面向接口编程,可以降低程序的耦合度,提高程序的扩展力。符合OCP开发原则(对扩展开放,对修改关闭)。
接口的使用离不开多态机制。(接口+多态才可以达到降低耦合度。)
任何一个接口都有调用者和实现者。
接口可以将调用者和实现者解耦合。
调用者面向接口调用。
实现者面向接口编写实现。
以后进行大项目的开发,一般都是将项目分离成一个模块一个模块的,模块和模块之间采用接口衔接。降低耦合度。
三、“解耦合”示例
有一间饭馆,他们不仅有中国师傅,还有美国师傅。
这是他们的菜单,里面可以点的菜有番茄炒蛋和鱼香肉丝(接口):
//菜单
interface FoodMenu {
void fqcd(); //番茄炒蛋
void yxrs(); //鱼香肉丝
}
中国师傅可以按照菜单做菜(实现菜单接口):
//中餐师傅
class China_Cook implements FoodMenu{
@Override
public void fqcd() {
System.out.println("中餐-->番茄炒蛋!"); //中国师傅做的番茄炒蛋
}
@Override
public void yxrs() {
System.out.println("中餐-->鱼香肉丝!"); //中国师傅做的鱼香肉丝
}
就你中国人会做番茄炒蛋和鱼香肉丝?美国师傅也能做:
//美国师傅
class Americ_Cook implements FoodMenu{
@Override
public void fqcd() {
System.out.println("西餐-->番茄炒蛋!"); //美国师傅做的番茄炒蛋
}
@Override
public void yxrs() {
System.out.println("西餐-->鱼香肉丝!"); //美国师傅做的鱼香肉丝
}
}
只有菜单和厨师,没有顾客怎么行,想办法让顾客下单呗!
这是一个顾客类,每个顾客手里都有一份菜单,他们可以通过order()方法下单:
注意:传入的是一份菜单,不是厨师!!
*** private FoodMenu foodMenu; //每个顾客手里都有一份菜单 ***
//顾客
class Customer {
private FoodMenu foodMenu; //每个顾客手里都有一份菜单
public Customer() {
}
public Customer(FoodMenu foodMenu) {
this.foodMenu = foodMenu;
}
public void setFoodMenu(FoodMenu foodMenu) { //想改 用set方法
this.foodMenu = foodMenu;
}
public FoodMenu getFoodMenu() { //想读 用get方法
return foodMenu;
}
//点菜
public void order() {
FoodMenu fm = this.getFoodMenu(); //调用get方法获取菜单
fm.fqcd();
fm.yxrs();
}
}
有顾客类,也有厨师类了,接下来就是实例化他们:
测试:
public class Test01 {
public static void main(String[] args) {
//创建厨师对象(多态)
FoodMenu cook_China = new China_Cook();
FoodMenu cook_Americ = new Americ_Cook();
//创建顾客对象
Customer aa = new Customer(cook_China);
Customer bb = new Customer(cook_Americ);
//顾客下单
aa.order();
bb.order();
}
}
输出:
中餐-->番茄炒蛋!
中餐-->鱼香肉丝!
西餐-->番茄炒蛋!
西餐-->鱼香肉丝!
上面的bb顾客如果想点中国师傅做的,就设置Customer bb = new Customer(cook_China); 把cook_China传入里面。
“解耦合”体现在哪?
如果顾客需要换厨师,Customer类需要改程序吗?答案是不需要,因为传入Customer类的是一份菜单接口(抽象的),不是具体的美国厨师和中国厨师(具体的类),换厨师只需要把传入的参数改变一下。
如果Customer中的构造方法是把中国师傅传入,那西餐厨师怎么办?传进去就会类型不兼容。构造方法中的传参是接口的妙处就在于它既可以传中国师傅,也可以传美国师傅。
每个接口都有实现方和调用方。在这个程序中:
接口的实现者:中国厨师、美国师傅
接口的调用者:顾客
“解耦合” 解的是谁和谁的耦合?解的是顾客和厨师的耦合。顾客关心的是菜单,不关心你后厨;厨师关心的是我要做什么菜,才不管是谁点的。所以说实际上顾客和厨师实际上是“分离”的,靠的接口把他们衔接。
进行大项目的开发的时候,一般都是将项目分离成一个模块一个模块的(比如此例中的顾客模块、中国厨师模块、美国厨师模块),模块和模块之间采用接口衔接。降低耦合度。