Java从入门到放弃(二十八):接口

在Java中,抽象类和接口是实现抽象的两种重要机制。抽象类可以包含抽象方法和具体方法,而接口则是一种纯粹的抽象规范,它只定义了方法的签名,不包含方法的实现。

当一个抽象类中所有的方法都是抽象的,且没有字段时,我们可以将其改写为接口。这是因为接口本身就是一个完全抽象的类型,它不允许有任何具体的实现,只能定义方法签名。以下是如何将抽象类改写为接口的示例:

// 抽象类
abstract class Person {
    public abstract void run();
    public abstract String getName();
}

// 接口
interface PersonInterface {
    void run();
    String getName();
}

在Java中,接口定义的方法默认都是public abstract的,所以当我们在接口中定义方法时,不需要显式地指定这两个修饰符。接口中的方法都是抽象的,而且接口不能包含任何状态信息,即不能有字段。

当一个类实现了一个接口,它必须提供接口中所有方法的具体实现。以下是一个实现PersonInterface接口的Student类的示例:

class Student implements PersonInterface {
    private String name;

    public Student(String name) {
        this.name = name;
    }

    @Override
    public void run() {
        System.out.println(this.name + " is running.");
    }

    @Override
    public String getName() {
        return this.name;
    }
}

Java允许一个类实现多个接口,这提供了一种形式的多重继承。一个类可以实现多个接口,并且必须提供所有接口中定义的方法的具体实现。以下是一个实现了两个接口的Student类的示例:

interface Hello {
    void sayHello();
}

class Student implements PersonInterface, Hello {
    private String name;

    public Student(String name) {
        this.name = name;
    }

    @Override
    public void run() {
        System.out.println(this.name + " is running.");
    }

    @Override
    public String getName() {
        return this.name;
    }

    @Override
    public void sayHello() {
        System.out.println("Hello, my name is " + this.name);
    }
}

在这个例子中,Student类实现了PersonInterfaceHello两个接口。它提供了rungetName方法的实现,以及sayHello方法的实现。通过实现多个接口,Student类继承了多个类型的抽象规范,增强了它的功能和灵活性。

术语

在Java编程语言中,接口(interface)和抽象类(abstract class)都是定义抽象概念的工具,但它们有着本质的区别和不同的用途。以下是对它们之间区别的详细对比:

  1. 继承与实现

    • 抽象类:一个类(class)只能继承自一个抽象类。这意味着在类的继承层次结构中,每个类只有一个直接的父类(除了Object类)。
    • 接口:一个类可以实现多个接口。这允许类继承多个类型的抽象规范,提供了一种形式的多重继承。
  2. 字段

    • 抽象类:可以包含实例字段,这些字段可以是任何访问类型(public、protected、private),并且可以有初始值。
    • 接口:不能包含实例字段。接口只能包含静态常量,这些常量默认是public、final和static的。
  3. 方法

    • 抽象方法:抽象类可以包含抽象方法,这些方法只有声明没有实现。子类必须覆写这些抽象方法。
    • 接口:接口中的所有方法默认都是抽象的,直到Java 8之前。从Java 8开始,接口也可以包含具有默认实现的default方法。
  4. 非抽象方法

    • 抽象类:可以包含非抽象方法,即具有具体实现的方法。这使得抽象类可以定义一些通用的行为,这些行为对于所有子类都是相同的。
    • 接口:在Java 8及以后的版本中,接口可以包含default方法,这些方法提供了方法的默认实现,从而允许接口有更多的灵活性。但是,除了default方法之外,接口中的其他方法仍然是抽象的,必须由实现接口的类提供具体实现。

通过这些对比,我们可以看到,抽象类和接口在设计类的层次结构和定义对象的行为方面各有优势。选择使用哪一种取决于具体的应用场景和设计需求。抽象类更适合定义一组具有相似特征的对象,而接口则更适合定义系统的不同部分之间交互的契约。

接口继承

在Java中,接口可以继承自一个或多个其他接口,这允许我们构建接口的层次结构,并且可以创建包含多个其他接口方法的复合接口。当一个接口继承自另一个接口时,它会继承父接口的所有抽象方法,并且可以添加新的抽象方法。

使用extends关键字可以实现接口之间的继承,这与类之间的继承类似,但接口继承更加灵活,因为一个类只能继承一个类,而一个接口可以实现多个接口。下面是一个接口继承的例子:

// 定义一个基本的Hello接口
interface Hello {
    void hello();
}

// 定义一个Person接口,它继承自Hello接口
interface Person extends Hello {
    void run();
    String getName();
}

在这个例子中,Person接口继承自Hello接口。这意味着Person接口的实现类必须提供hello()run()getName()这三个方法的具体实现。Person接口通过继承Hello接口,获得了Hello接口中定义的方法,从而扩展了自己的功能。

接口继承的主要用途是代码复用和规范的组合。通过继承一个或多个已有的接口,我们可以创建一个新接口,它不仅包含了已有接口的所有规范,还可以增加新的规范。这样做可以避免重复定义相同的方法签名,并且可以使我们的代码更加模块化和可维护。

此外,接口继承还支持多继承,即一个接口可以实现多个其他接口。这为Java语言提供了一种有限的多重继承机制。例如:

// 定义两个基础接口
interface Drivable {
    void drive();
}

interface Flyable {
    void fly();
}

// 定义一个复合接口,它继承自Drivable和Flyable接口
interface Vehicle extends Drivable, Flyable {
    void honk();
}

在这个例子中,Vehicle接口继承自DrivableFlyable两个接口。因此,Vehicle接口的实现类必须实现drive()fly()honk()这三个方法。这种多接口继承提供了一种强大的组合能力,允许我们将不同类型的功能组合到一个接口中。

继承关系

在Java中,合理地设计接口(interface)和抽象类(abstract class)的继承关系对于代码复用和系统架构的清晰性至关重要。

Java集合框架(Java Collections Framework)是一个很好的例子,它展示了如何通过接口和抽象类来组织代码。以下是Java集合框架中的一部分继承关系:

在这个继承结构中,Iterable是一个标记接口,它表示一个对象是可迭代的。Collection是所有集合类型的根接口,它继承自IterableList是一个特定的集合类型,它是有序的,可以包含重复的元素,它继承自CollectionAbstractListList的一个抽象实现,它提供了List接口的骨架实现。ArrayListLinkedListList的具体实现。

在使用这些集合类时,我们通常会通过接口类型来引用具体的对象实例,这是因为接口提供了一个更高层次的抽象。例如:

List list = new ArrayList(); // 使用List接口引用ArrayList的实例
Collection coll = list; // 向上转型为Collection接口
Iterable it = coll; // 向上转型为Iterable接口

通过向上转型,我们可以将具体的集合类型转换为它们的父接口类型。这样做的好处是,我们可以编写更加通用的代码,这些代码可以处理任何实现了相应接口的集合类型,而不需要关心具体的实现细节。

default方法

在Java 8及更高版本中,接口可以包含default方法,这些方法是具有默认实现的方法。default方法允许我们在不改变已有实现类的情况下,向接口中添加新的方法。这样,我们可以为接口提供部分实现,而实现类可以选择是否覆写这些方法。

以下是一个示例,展示了如何在Person接口中定义一个default方法,并演示了实现类如何与这个default方法交互:

// 定义一个Person接口,并包含一个default方法
interface Person {
    String getName();
    
    // default方法提供了run()的默认行为
    default void run() {
        System.out.println(getName() + " is running.");
    }
}

// Student类实现了Person接口
class Student implements Person {
    private String name;

    public Student(String name) {
        this.name = name;
    }

    @Override
    public String getName() {
        return this.name;
    }

    // Student类没有覆写run()方法,所以它会使用Person接口中的默认实现
}

// 主类用于测试
public class Main {
    public static void main(String[] args) {
        Person p = new Student("Xiao Ming");
        p.run(); // 调用Person接口的default方法
    }
}

在这个例子中,Person接口定义了一个run()方法,它有一个默认的行为,即打印出对象的名字和”is running.”的字符串。Student类实现了Person接口,但没有提供run()方法的实现,因此它继承了接口中的default实现。

当我们创建Student类的实例并通过Person类型的引用调用run()方法时,将会执行接口中的默认实现。这意味着,即使我们以后向Person接口添加了新的default方法,现有的实现类也不会受到影响,它们可以选择是否覆写新方法。

default方法与抽象类中的普通方法的主要区别在于,default方法提供了方法的默认实现,而普通方法是接口中定义的需要实现类去实现的方法。此外,由于接口不能包含状态(即字段),default方法无法直接访问实例字段,而抽象类中的普通方法可以访问类的实例字段和方法。

声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。
0 条回复 A文章作者 M管理员
    暂无讨论,说说你的看法吧