Java接口的用法(非常详细)
Java 是一门单继承的语言,一个子类只能继承一个父类。但是在编程实践中,对于某些类,只继承一个抽象类显然无法满足要求,需要实现多个抽象类的抽象方法才能解决问题,这就需要通过接口来实现。
在 Java 中,允许通过一个类实现多个接口来实现类似于多重继承的机制。
例如,计算机上提供了 USB 插槽,只要一个硬件遵守 USB 规范,就能插入 USB 插槽,可以是鼠标、键盘、数据线等,计算机无须关心是和哪个硬件对象建立了联系。同样,软件系统的开发也需要采用规范和实现分离的思想,即采用面向接口的编程思想,从而降低软件系统模块之间的耦合度,提高系统的可扩展性和可维护性。
在 Java 语言中,提供了 interface 关键字,用于声明接口,其语法格式如下:
接口中定义的变量和方法都包含默认的修饰符,其中变量默认声明为“public static final”,即全局静态常量,方法默认声明为“public abstract”,即抽象方法。
例如,定义一个 Charge 接口(收费接口),内有接口常量 PORT_STYLE 和成员方法 getCharge(),代码如下:
实现接口的目的主要是在新类中重写抽象的方法,当然也可以使用抽象类来实现抽象方法的重写。
接口的实现需要使用 implements 关键字,即在声明一个类的同时用关键字 implements 来实现接口,实现接口的类一般称为接口的实现类,具体语法如下:
一个类实现一个接口时,需要注意如下问题:
接下来,通过案例来演示接口的实现:
Java 程序中的接口与网络通信标准类似,定义之后,随着技术的不断发展以及应用需求的不断增加,接口往往也需要更新迭代,主要是功能扩展,增加新的方法,以适应新的需求。但是,使用直接在原接口中增加方法的途径来扩展接口可能会带来问题:所有实现原接口的实现类都将因为原来接口的改变而不能正常工作。
为了既能扩展接口,又不影响原接口的实现类,一种可行的方法是通过创建原接口的子接口来增加新的方法。
接口的继承与类的继承相似,都是使用 extends 关键字来实现,当一个接口继承父接口时,该接口会获得父接口中定义的所有抽象方法和常量。但是,接口的继承比类的继承要灵活,一个接口可以继承多个父接口,这样可以通过继承将多个接口合并为一个接口。
接口继承的语法格式如下:
接下来,通过案例来演示接口的继承:
如果一个类同时继承类并继承某个接口,需要先 extends 父类,再 implements 接口,格式如下:
然而,我们也不能将多个父类简单地合并成一个父类,因为每个父类都有自己的一套代码,合并到一起之后可能会出现同一方法的多种不同实现,由此会产生代码冲突,增加代码的不可靠性。
有了接口以后多重继承问题就迎刃而解了,由于一个类可以实现多个接口,所以在程序设计的过程中我们可以把一些“特殊类”设计成接口,进而通过接口间接地解决多重继承问题。
一个类实现多个接口时,在 implements 语句中分隔各个接口名,此时这些接口就可以被理解成特殊的类,而这种做法实际上就是使子类获得了多个父类的成员,并且由于接口成员没有实现细节,实现接口的类只能有一个具体的实现细节,从而避免了代码冲突,保证了 Java 代码的安全性和可靠性。
接下来,通过案例来演示利用接口实现多重继承:
但是,在接口中使用默认方法就不会有这个问题,所以从 JDK 8 开始新加了接口默认方法,便于接口的扩展。
接口默认方法是一个默认实现的方法,并且不强制实现类重写此方法,使用default关键字来修饰。接口新增的默认方法在实现类中可以直接使用。
接下来,通过案例来演示接口默认方法的使用:
注意,接口允许定义多个默认方法,其子类可以实现多个接口,因此接口默认方法可能会出现同名情况,此时子类在实现或者调用默认方法时通常遵循以下原则:
接下来,通过某汽车销售店案例演示如何通过接口实现多态:
在测试类 Demo 的 main() 方法中创建 CarShop 类的实例 shop、Haval 类的实例 haval、GreatWall 类的实例 greatWall,通过 shop 对象的 sellCar() 方法对汽车对象 havel 和 greatWall 销售,并调用 getMoney() 方法统计销售总收入。这样,我们便通过 ICar 接口实现了多态,
在程序设计时,应该根据具体业务需求来确定是使用抽象类还是接口。如果子类需要从父类继承一些变量或继承一些抽象方法、非抽象方法,可以考虑使用抽象类;如果一个类不需要继承,只需要实现某些重要的抽象方法,可以考虑使用接口。
在 Java 中,允许通过一个类实现多个接口来实现类似于多重继承的机制。
Java接口的定义
接口可以看作是从多个相似的类中抽象出来的规范,不提供任何实现,体现了规范和实现分离的思想。例如,计算机上提供了 USB 插槽,只要一个硬件遵守 USB 规范,就能插入 USB 插槽,可以是鼠标、键盘、数据线等,计算机无须关心是和哪个硬件对象建立了联系。同样,软件系统的开发也需要采用规范和实现分离的思想,即采用面向接口的编程思想,从而降低软件系统模块之间的耦合度,提高系统的可扩展性和可维护性。
在 Java 语言中,提供了 interface 关键字,用于声明接口,其语法格式如下:
[public]interface 接口名[extends 接口1,接口2…] { [public][static][final]数据类型 常量名 = 值; [public][abstract] 返回值的数据类型 方法名(参数列表); 默认方法… }在上述语法中,当 interface 关键字前加上 public 修饰符时,接口可以被任何类的成员访问。如果省略 public,则接口只能被与它处在同一包中的成员访问。extends 语句与类继承中的 extends 语句基本相同,不同点在于接口可以继承自多个父接口,父接口之间使用逗号分隔。
接口中定义的变量和方法都包含默认的修饰符,其中变量默认声明为“public static final”,即全局静态常量,方法默认声明为“public abstract”,即抽象方法。
例如,定义一个 Charge 接口(收费接口),内有接口常量 PORT_STYLE 和成员方法 getCharge(),代码如下:
interface Charge(){ int PORT_STYLE = 1; // 接口常量 void getCharge(); // 接口方法声明 }
Java接口实现
接口与抽象类相似,也包含抽象方法,因此不能直接实例化接口,即不能使用 new 创建接口的实例。但是,可以利用接口的特性来创造一个新的类,然后再用新类来创建对象,利用接口创建新类的过程称为接口的实现。实现接口的目的主要是在新类中重写抽象的方法,当然也可以使用抽象类来实现抽象方法的重写。
接口的实现需要使用 implements 关键字,即在声明一个类的同时用关键字 implements 来实现接口,实现接口的类一般称为接口的实现类,具体语法如下:
[修饰符]class 类名 implements 接口1,接口2, 接口3,… { // 如果实现多个接口,以逗号隔开 … }
一个类实现一个接口时,需要注意如下问题:
- 如果实现接口的类不是抽象类,则该类必须实现接口的所有抽象方法;
- 在类中实现接口的抽象方法时,必须使用与接口中完全一致的方法名及参数列表,否则只是定义一个新的方法,而不是实现已有的抽象方法。
接下来,通过案例来演示接口的实现:
interface PCI { // 定义PCI接口 String serialNumber = "9CC0AC186027"; void start(); void run(); void stop(); } public class VideoCard implements PCI { // 定义显卡类,实现PCI接口 @Override public void start() { System.out.println("显卡开始启动"); } @Override public void run() { System.out.println("显卡序列号是:" + serialNumber); System.out.println("显卡开始工作"); } @Override public void stop() { System.out.println("显卡停止工作"); } } public class Demo { public static void main(String[] args) { VideoCard videoCard = new VideoCard(); videoCard.start(); videoCard.run(); videoCard.stop(); } }程序的运行结果为:
显卡开始启动
显卡序列号是:9CC0AC186027
显卡开始工作
显卡停止工作
Java接口的继承
在现实世界中,网络通信具有一定的标准,手机只有遵守相应的标准规范才可以使用相应的网络。然而,随着移动互联网技术的发展,网络通信标准从之前的 2G、3G 到目前的 4G、5G,而且 6G 也已在研发之中。Java 程序中的接口与网络通信标准类似,定义之后,随着技术的不断发展以及应用需求的不断增加,接口往往也需要更新迭代,主要是功能扩展,增加新的方法,以适应新的需求。但是,使用直接在原接口中增加方法的途径来扩展接口可能会带来问题:所有实现原接口的实现类都将因为原来接口的改变而不能正常工作。
为了既能扩展接口,又不影响原接口的实现类,一种可行的方法是通过创建原接口的子接口来增加新的方法。
接口的继承与类的继承相似,都是使用 extends 关键字来实现,当一个接口继承父接口时,该接口会获得父接口中定义的所有抽象方法和常量。但是,接口的继承比类的继承要灵活,一个接口可以继承多个父接口,这样可以通过继承将多个接口合并为一个接口。
接口继承的语法格式如下:
interface 接口名 extends 接口1,接口2,接口3,… { … }
接下来,通过案例来演示接口的继承:
interface I3G { // 上网 void online(); // 上网 void call(); // 打电话 void sendMsg(); // 发短信 } interface I4G extends I3G { // 看视频 void watchVideo(); } class Nokia implements I3G { @Override public void call() { System.out.println("打电话功能"); } @Override public void sendMsg() { System.out.println("发短信功能"); } @Override public void online() { System.out.println("上网功能"); } } class Mi implements I4G { @Override public void call() { System.out.println("打电话功能"); } @Override public void sendMsg() { System.out.println("发短信功能"); } @Override public void online() { System.out.println("上网功能"); } @Override public void watchVideo() { System.out.println("看视频功能"); } } public class Demo { public static void main(String[] args) { System.out.println("Nokia手机使用第3代通信技术,具有:"); Nokia nokia = new Nokia(); nokia.call(); nokia.online(); nokia.sendMsg(); System.out.println("小米手机使用第4代通信技术,具有:"); Mi mi = new Mi(); mi.call(); mi.online(); mi.sendMsg(); mi.watchVideo(); } }程序的运行结果如下:
Nokia手机使用第3代通信技术,具有:
打电话功能
上网功能
发短信功能
小米手机使用第4代通信技术,具有:
打电话功能
上网功能
发短信功能
看视频功能
如果一个类同时继承类并继承某个接口,需要先 extends 父类,再 implements 接口,格式如下:
子类 extends 父类 implements [接口列表]{ … }
利用接口实现多重继承
Java 语言规定一个类只能继承一个父类,这给实际开发带来了许多困扰,因为许多类需要继承多个父类的成员才能满足需求,这种问题称为多重继承问题。然而,我们也不能将多个父类简单地合并成一个父类,因为每个父类都有自己的一套代码,合并到一起之后可能会出现同一方法的多种不同实现,由此会产生代码冲突,增加代码的不可靠性。
有了接口以后多重继承问题就迎刃而解了,由于一个类可以实现多个接口,所以在程序设计的过程中我们可以把一些“特殊类”设计成接口,进而通过接口间接地解决多重继承问题。
一个类实现多个接口时,在 implements 语句中分隔各个接口名,此时这些接口就可以被理解成特殊的类,而这种做法实际上就是使子类获得了多个父类的成员,并且由于接口成员没有实现细节,实现接口的类只能有一个具体的实现细节,从而避免了代码冲突,保证了 Java 代码的安全性和可靠性。
接下来,通过案例来演示利用接口实现多重继承:
// 定义IFly接口 interface IFly { void takeoff(); // 起飞方法 void land(); // 落地方法 void fly(); // 飞行方法 } // 定义ISail接口 interface ISail { void dock(); // 停靠方法 void cruise(); // 航行方法 } // 定义交通工具Vehicle类 class Vehicle { private double speed; // 设置速度方法 void setSpeed(int sd) { this.speed = sd; System.out.println("设置速度为" + speed); } void speedUp(int num) { // 加速方法 this.speed += num; System.out.println("加速" + num + ",速度变为" + speed); } void speedDown(int num) { // 减速方法 this.speed -= num; System.out.println("减速" + num + ",速度变为" + speed); } } // 定义水上飞机类 class SeaPlane extends Vehicle implements IFly, ISail { public void takeoff() { System.out.println("水上飞机开始起飞"); } public void land() { System.out.println("水上飞机开始落地"); } public void fly() { System.out.println("水上飞机可以飞行"); } public void dock() { System.out.println("水上飞机可以停靠"); } public void cruise() { System.out.println("水上飞机可以航行"); } } public class Demo { public static void main(String[] args) { SeaPlane sp = new SeaPlane(); sp.takeoff(); sp.setSpeed(1); sp.speedUp(2); sp.fly(); sp.speedDown(2); sp.land(); sp.cruise(); sp.speedDown(2); sp.dock(); } }程序的运行结果如下:
水上飞机开始起飞
设置速度为2
加速2,速度变为4
水上飞机可以飞行
减速2,速度变为2
水上飞机开始落地
水上飞机可以航行
减速2,速度变为0
水上飞机可以停靠
Java接口默认方法
在程序开发中,如果之前创建了一个接口,并且已经被大量的类实现,当需要再添加新的方法以扩充这个接口的功能的时候,就会导致所有已经实现的子类都要重写这个方法。但是,在接口中使用默认方法就不会有这个问题,所以从 JDK 8 开始新加了接口默认方法,便于接口的扩展。
接口默认方法是一个默认实现的方法,并且不强制实现类重写此方法,使用default关键字来修饰。接口新增的默认方法在实现类中可以直接使用。
接下来,通过案例来演示接口默认方法的使用:
public interface ICat { // 定义ICat接口 void play(); // 抽象方法 default void run() { // 默认方法 System.out.println("猫咪在跑,猫步"); } } class BlackCat implements ICat { // 黑猫类实现了ICat接口 @Override public void play() { // 重写ICat接口的抽象方法 System.out.println("黑猫在玩耍"); } } public class Demo { public static void main(String[] args) { BlackCat cat = new BlackCat(); cat.play(); cat.run(); } }程序的运行结果如下:
黑猫在玩耍
猫咪在跑,猫步
注意,接口允许定义多个默认方法,其子类可以实现多个接口,因此接口默认方法可能会出现同名情况,此时子类在实现或者调用默认方法时通常遵循以下原则:
- 子类中的同名方法优先级最高。
- 如果第一条无法进行判断,那么子接口的优先级更高;方法名相同时,优先选择拥有最具体实现的默认方法的接口,即如果接口 B 继承了接口 A,那么接口 B 就比接口 A 更加具体。
Java接口实现多态
前面我们讲解了使用继承机制来实现多态,事实上使用接口也同样可以实现多态。接下来,通过某汽车销售店案例演示如何通过接口实现多态:
// 定义ICar接口 interface ICar { String showName(); // 显示汽车名称 double getPrice(); // 获取汽车价格 } // 定义Haval汽车类 class Haval implements ICar { @Override public String showName() { return "哈佛SUV"; } @Override public double getPrice() { return 150000; } } // 定义GreatWall汽车类 class GreatWall implements ICar { @Override public String showName() { return "长城汽车"; } @Override public double getPrice() { return 68000; } } // 定义汽车销售店CarShop类 class CarShop { private double money = 0; public void sellCar(ICar car) { System.out.println("车型:" + car.showName() + ",价格:" + car.getPrice()); money += car.getPrice(); } public double getMoney() { return money; } } // 测试类 public class Demo { public static void main(String[] args) { CarShop shop = new CarShop(); Haval haval = new Haval(); shop.sellCar(haval); GreatWall greatWall = new GreatWall(); shop.sellCar(greatWall); System.out.println("销售总收入:" + shop.getMoney()); } }程序的运行结果如下:
车型:哈佛SUV,价格:150000.0 车型:长城汽车,价格:68000.0 销售总收入:218000.0程序中 ICar 接口定义了抽象方法 showName() 和 getPrice(),Haval 类和 GreatWall 类实现了 ICar 接口,汽车销售店类 CarShop 针对实现 ICar 接口的实现类进行销售并汇总金额。
在测试类 Demo 的 main() 方法中创建 CarShop 类的实例 shop、Haval 类的实例 haval、GreatWall 类的实例 greatWall,通过 shop 对象的 sellCar() 方法对汽车对象 havel 和 greatWall 销售,并调用 getMoney() 方法统计销售总收入。这样,我们便通过 ICar 接口实现了多态,
抽象类和接口的比较
抽象类与接口是 Java 中对于抽象类定义进行支持的两种机制,它们都用于为对象定义共同的行为,二者比较如下:- 抽象类和接口都包含抽象方法;
- 抽象类可以有非抽象方法,接口中如果要定义非抽象方法,需要标注为接口默认方法;
- 接口中只能有常量,不能有变量;抽象类中既可以有常量,也可以有变量;
- 一个类可以实现多个接口,但只能继承一个抽象类。
在程序设计时,应该根据具体业务需求来确定是使用抽象类还是接口。如果子类需要从父类继承一些变量或继承一些抽象方法、非抽象方法,可以考虑使用抽象类;如果一个类不需要继承,只需要实现某些重要的抽象方法,可以考虑使用接口。