Java接口:理解接口的作用及其用法

970 阅读10分钟

1. 什么是接口?

在编写Java程序的时候,我们需要定义很多类和方法,这些类和方法有时需要和其他程序进行交互,而接口就是用于定义这些交互方式的一种编程规范。

接口简单来说就是一组方法的声明,这些方法没有方法体,所有实现这个接口的类都必须要实现这些方法。接口不关心具体实现细节,只关心类的行为是否符合约定。因此,接口可以作为一个标准,定义了类应该遵循的规则和约定。

2. 为什么要使用接口?

Java中面向对象编程的一个重要概念就是“接口隔离原则”,即一个类只需依赖它需要的接口,而不需要依赖其它接口。这使得程序更具有可扩展性和可维护性。接口既可以提高系统的可维护性,也可以使系统的扩展性更加灵活。同时,接口还可以提高代码的可读性,降低代码的耦合度。

3. 接口的定义和实现

在Java中,我们通过interface关键字来声明一个接口,例如:

定义动物接口

首先,我们需要定义一个动物接口(Animal),规定动物应该具有的行为(方法):

public interface Animal {
    void eat(); // 吃
    void sleep(); // 睡觉
    void run(); // 跑
}

在上面的代码中,我们定义了一个Animal接口,规定了动物应该具有的方法:eat()、sleep()和run()。这些方法是抽象的,即没有具体实现,子类需要根据自己的特点来重新实现这些方法。

定义猫类和狗类,实现动物接口

接下来,我们需要定义猫类(Cat)和狗类(Dog),并实现Animal接口。这两个类都是动物,应该都具有eat()、sleep()和run()方法:

public class Cat implements Animal {
    @Override
    public void eat() {
        System.out.println("猫在吃饭");
    }

    @Override
    public void sleep() {
        System.out.println("猫在睡觉");
    }

    @Override
    public void run() {
        System.out.println("猫在奔跑");
    }
}
public class Dog implements Animal {
    @Override
    public void eat() {
        System.out.println("狗在吃饭");
    }

    @Override
    public void sleep() {
        System.out.println("狗在睡觉");
    }

    @Override
    public void run() {
        System.out.println("狗在奔跑");
    }
}

在这个例子中,我们分别定义了猫类和狗类,并且都实现了Animal接口。在实现了这些方法之后,我们就可以调用它们来获取猫和狗的行为了。

定义测试类,调用猫和狗的方法

最后,我们定义一个测试类(Test),来调用猫和狗的方法:

public class Test {
    public static void main(String[] args) {
        Cat cat = new Cat();
        cat.eat();
        cat.sleep();
        cat.run();

        Dog dog = new Dog();
        dog.eat();
        dog.sleep();
        dog.run();
    }
}

在这个例子中,我们分别创建了猫和狗的实例,并调用了它们的eat()、sleep()和run()方法。当我们运行这个测试类时,控制台会输出以下结果:

猫在吃饭
猫在睡觉
猫在奔跑
狗在吃饭
狗在睡觉
狗在奔跑

4. 一个类实现多个接口

以下是一个类实现多个接口的示例:

假设我们需要编写一个文本编辑器程序,它需要支持文本输入和保存功能。我们可以定义两个接口:Inputable和Savable,来规定这个程序应该具有的输入和保存功能。例如:

public interface Inputable {
    // 输入
    void readInput();
}

public interface Savable {
    // 保存
    void save();
}

现在,我们可以定义一个TextEditor类,来实现这两个接口,并提供相应的方法来实现输入和保存功能:

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.util.Scanner;

public class TextEditor implements Inputable, Savable {
    private String text;

    @Override
    public void readInput() {
        Scanner scanner = new Scanner(System.in);
        System.out.println("请输入要编辑的文本:");
        this.text = scanner.nextLine();
    }

    @Override
    public void save() {
        try {
            File file = new File("text.txt");
            FileWriter writer = new FileWriter(file);
            writer.write(text);
            writer.close();
            System.out.println("文本已保存至文件 text.txt");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

在这个例子中,我们定义了一个TextEditor类,。在TextEditor类中,我们分别并实现了Inputable接口和Savable接口实现readInput()和save()方法,在readInput()方法中读取用户输入的文本,在save()方法中将文本保存至本地文件。

现在我们可以在测试类中使用TextEditor类,来测试输入和保存功能:

public class Test {
    public static void main(String[] args) {
        TextEditor editor = new TextEditor();
        editor.readInput();
        editor.save();
        // 会在项目根目录生成一个text.txt文件
    }
}

当我们运行测试类时,程序会提示我们输入要编辑的文本,并将文本保存至本地文件中。

5. 接口的继承

和类一样,接口也可以使用继承关系。当一个接口继承了另一个接口时,它就可以使用从父接口中继承的方法。

下面是一个基于“顾客”接口和“会员”接口的继承示例:

public interface Customer {
    // 购物
    void shopping();
}

public interface Member extends Customer {
    // 获取折扣
    void getDiscount();
}

在上述代码中,“Member”接口继承了“Customer”接口,所以它可以使用从“Customer”接口中继承的“shopping()”方法。此外,“Member”接口还额外定义了一个“getDiscount()”方法。

如果要实现一个实现了接口继承的类,那么必须对接口中所有的方法进行实现,不管它们是来自哪个接口。

6. 接口的默认方法

在Java 8中,接口(interface)增加了一个新的特性:默认方法(default method)。默认方法是指因接口中的行为相似,而被设计成共享的方法。

在Java中,实现一个接口的类必须要实现该接口中的所有方法,但有时候,我们需要在接口定义后,在不破坏原有实现的情况下,向接口中添加新的方法。在Java 8之前,这是做不到的;在 Java 8 之后,我们可以在接口中定义默认方法,这样就可以向接口中添加新的方法,而不会破坏原有代码。

默认方法的声明

默认方法是使用关键字 default 来声明的。例如:

public interface Animal {
    void eat();
    void sleep();

    default void move() {
        System.out.println("Move by walking");
    }
}

public class Cat implements Animal {
    @Override
    public void eat() {
        System.out.println("Cat is eating");
    }

    @Override
    public void sleep() {
        System.out.println("Cat is sleeping");
    }
}

在上面的例子中,Animal 接口中定义了一个默认方法 move(),用来描述该动物的移动方式。Cat 类实现了 Animal 接口,并重写了接口中定义的 eat()sleep() 方法。由于 move() 方法是一个默认方法,因此 Cat 类不需要重写该方法。当我们调用 Cat 类对象的 move() 方法时,会默认使用接口中定义的默认方法。

默认方法的使用

默认方法主要有两个使用场景:

  1. 接口中添加新的方法

在 Java 8 之前,你需要重新设计你的接口和它的实现类,以添加新方法。在 Java 8 中,可以向接口中添加默认方法,避免了向你的 API 添加新方法时破坏现有实现的问题。

  1. 提供实现

默认方法给接口提供了一种自我的实现方式。默认方法会被继承,提供最基本的代码实现。

常用库中的默认方法

Java中有很多常用库已经使用了默认方法。以下是几个常用库中使用到默认方法的例子:

  1. Collection 接口

在Java 8之前,Collection 接口中只有 size()、isEmpty()、contains() 等方法,如果需要对集合进行遍历,需要使用 Iterator 或 for-each。Java 8 又增加了两个默认方法 stream() 和 forEach(),使得集合的遍历操作变得更加简洁和方便。

List<String> list = Arrays.asList("A", "B", "C");
list.stream().forEach(System.out::println);

该代码通过 stream() 方法把集合转换为一个流 stream,然后通过 forEach() 方法输出流中的每个项。

  1. Comparator 接口

在Java 8之前,使用Comparator 接口的时候需要实现compare方法,Java 8 中添加了一个名为 reversed() 的默认方法,它可以返回一个比较器对象的逆序排列。

List<Integer> list = Arrays.asList(1, 3, 2, 4);
Comparator<Integer> cmp = Integer::compare;
Collections.sort(list, cmp.reversed());

该代码使用 reversed() 方法将一个比较器对象反转以进行逆序排序。

7. 接口的静态方法

在Java 8中,接口允许声明一个静态方法。接口的静态方法也叫做接口方法,因为它们只能被接口及其实现类调用。

和默认方法一样,接口的静态方法可以提供一个默认实现,但静态方法不能被实现类继承或覆盖。这意味着,无需创建接口的实现类即可直接调用接口静态方法。

静态方法的声明

静态方法是通过定义为接口的一部分来声明的。这会使得一个方法与接口关联,并且可以使用接口来调用静态方法。

public interface Animal {
    void eat();
    void sleep();

    static void move() {
        System.out.println("Move by walking");
    }
}

在上述代码中,我们定义了一个静态方法 move(),该方法可以被 Animal 接口及其所有的实现类调用。

静态方法的使用

静态方法可使用接口直接调用,也可以被实现类继承。下面是两个常见的应用场景。

  1. 接口方法的实现

接口中的静态方法在默认情况下是公用的且相同的,因为它们不能被实现类重写。它们的使用场景通常是在接口级别上提供某些通用功能的方法。

public class Cat implements Animal {
    @Override
    public void eat() {
        System.out.println("Cat is eating");
    }

    @Override
    public void sleep() {
        System.out.println("Cat is sleeping");
    }

    public static void main(String[] args) {
        Animal.move(); // 直接使用接口调用静态方法
    }
}

在上述代码中,我们实现了 Animal 接口,并在 main() 方法中调用接口的静态方法 move()。由于静态方法是被接口调用的,因此我们可以直接使用接口名调用静态方法,无需实现类的实例化。

  1. 给实现类提供实用方法

我们可以使用 Java 8 中的接口来描述实现类的行为和特征。有时候我们还需要在接口中定义一些不是必需的工具方法。

public interface MathUtils {
    static double sine(double value) {
        return Math.sin(value);
    }
 
    static double cosine(double value) {
        return Math.cos(value);
    }
}

在上述代码中,我们定义了一个包含两个静态方法的 MathUtils 接口,用于提供常用的数学计算函数。我们可以使用该接口中定义的静态方法,而不用创建接口实现类的实例,从而方便地完成一些数学计算。

常用库中的静态方法

Java中的常用库也大量使用了静态方法。以下是一些常见的例子。

  1. Collections 类

Java集合框架中的 Collections 类提供了一组静态方法,用于对集合进行排序、搜索和修改,这些方法用于Java中最常见的集合类型:List 和 Set。静态方法和常规方法一样使用 Collections.方法名 的方式进行调用。

List<String> list = Arrays.asList("A", "C", "B");
Collections.sort(list);

该代码使用Collections类的静态方法sort()对List进行排序。

  1. Arrays 类

Java中的 Arrays 类提供了大量的静态方法,其中包括一些用于数组排序、转换和修改的方法。静态方法和常规方法一样使用 Arrays.方法名 的方式进行调用。

int[] numbers = {9,6,8,3,5,2,7,4,1};
Arrays.sort(numbers);

该代码使用Arrays类的静态方法sort()对数组进行排序。

8. 总结

Java接口是一组方法的抽象,它定义了一个协议或一种方式,用来交流和交换信息。接口可以帮助我们实现多态性、代码的一致性和可扩展性。在Java中,一个类可以实现多个接口,并且接口可以使用继承关系。Java 8增加了接口默认方法、静态方法,使得接口更加强大和灵活。

关注微信公众号:小虎哥的技术博客