首页 > 编程笔记 > Java笔记 阅读:9

Java接口的用法(非常详细)

Java 是一门单继承的语言,一个子类只能继承一个父类。但是在编程实践中,对于某些类,只继承一个抽象类显然无法满足要求,需要实现多个抽象类的抽象方法才能解决问题,这就需要通过接口来实现。

在 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
显卡开始工作
显卡停止工作

从运行结果可以看到,程序中定义了一个 PCI 接口,该接口定义了一个全局常量 serialNumber 和 3 个抽象方法 start()、run()、stop(),显卡类 VideoCard 实现了 PCI 接口的这 3 个抽象方法,并在实现类的方法中调用接口的常量。最后,在 Demo 测试类中创建了显卡类 VideoCard 的实例,并输出运行结果。

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代通信技术,具有:
打电话功能
上网功能
发短信功能
看视频功能

从运行结果可以看到,I4G 接口继承了 I3G 接口,直接继承了 I3G 接口中的 3 个抽象方法 call()、onLine()、sendMsg(),并新增了一个抽象方法 watchVide(),在 main() 方法中 Nokia 类实现了 I3G 接口,从而实现父接口的 3 个抽象方法,而 Mi 类实现了 I4G 接口,实现了子接口的 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
水上飞机可以停靠

程序中,水上飞机类 SeaPlane 继承了交通工具类 Vehicle,并且实现了 IFly 接口和 ISail 接口。从程序运行结果中可以看到,它不仅具有了交通工具的功能,还增加了飞行功能和航行功能。

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();
    }
}
程序的运行结果如下:

黑猫在玩耍
猫咪在跑,猫步

程序中,ICat 接口定义了抽象方法 play() 和默认方法 run(),BlackCat 类实现了 ICat 接口,并重写了抽象方法 play(),通过测试类 Demo 中的 main() 方法创建了 BlackCat 类的实例,调用 play() 和 run() 方法后发现,ICat 接口的默认方法 run() 可以被它的实现类的对象直接调用。

注意,接口允许定义多个默认方法,其子类可以实现多个接口,因此接口默认方法可能会出现同名情况,此时子类在实现或者调用默认方法时通常遵循以下原则:

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 中对于抽象类定义进行支持的两种机制,它们都用于为对象定义共同的行为,二者比较如下:
在程序设计时,应该根据具体业务需求来确定是使用抽象类还是接口。如果子类需要从父类继承一些变量或继承一些抽象方法、非抽象方法,可以考虑使用抽象类;如果一个类不需要继承,只需要实现某些重要的抽象方法,可以考虑使用接口。

相关文章