Java的抽象类 & 接口

简介: 抽象类:在子类继承父类时,父类的一些方法实现是不明确的(父类对子类的实现一无所知)。这时需要使父类是抽象类,在子类中提供方法的实现。接口(interface)技术主要用来描述类具有什么功能,而并不给出每个功能的具体实现。

抽象类

如果自下而上在类的继承层次结构中上移,位于上层的类更具有通用性,甚至可能更加抽象。从某种角度看,祖先类更加通用,人们只将它作为派生其他类的基类,而不作为想使用的特定的实例类。例如,考虑一下对 Employee 类层次的扩展。一名雇员是一个人,一名学生也是一个人。下面将 Person 类和 Student 类添加到类的层次结构中。下图是这三个类
之间的关系层次图。

image-20230414175409470.png


为什么要花费精力进行这样高层次的抽象呢?每个人都有一些诸如姓名这样的属性。学生与雇员都有姓名属性,因此可以将 getName() 方法放置在位于继承关系较高层次的通用基类中。现在,再增加一个 getDescription() 方法,它可以返回对一个人的简短描述。例如:

an employee with a salary of $5000000
a student majoring in computer science

在 Employee 类和 Student 类中实现 getDescription() 这个方法很容易。但是在 Person 类中应该提供什么内容呢?除了姓名之外,Person 类一无所知。当然,可以让 Person::getDescription() 返回一个空字符串。然而,还有一个更好的方法, 就是使用 abstract 关键字,这样就完全不需要实现这个方法了。

// no implementation required
public abstract String getDescription();

为了提高程序的清晰度,包含一个或多个抽象方法的类本身必须被声明为抽象的。

public abstract class Person {
    public abstract String getDescription();
}

除了抽象方法之外,抽象类还可以包含具体数据和具体方法。例如,Person 类还保存着姓名和一个返回姓名的具体方法。

public abstract class Person {
    private String name;

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

    public abstract String getDescription();

    public String getName() {
        return name;
    }
}

提示:许多程序员认为,在抽象类中不能包含具体方法。建议尽量将通用的域和方法(不管是否是抽象的)放在基类(不管是否是抽象类)中。

抽象方法充当着占位的角色,它们的具体实现在子类中。扩展抽象类可以有两种选择。

  • 一种选择是:在子类中定义抽象类的部分方法或不定义抽象类的方法,这样就必须将子类也标记为抽象类;
  • 另一种选择是:在子类中定义抽象类全部的抽象方法,这样一来,子类就不是抽象类了。例如,通过扩展 Person 抽象类,并实现 getDescription() 方法来定义 Student 类。由于在 Student 类中不再含有抽象方法,所以不必将 Student 类声明为抽象的。

即使一个类不含抽象方法,也可以将该类声明为抽象类。

抽象类不能被实例化。也就是说,如果一个类被声明为 abstract,就不能创建这个类的对象。例如,表达式 new Person("Vince Vu") 是错误的,但可以创建一个具体子类的对象。

需要注意,可以定义一个抽象类的对象变量,但是它只能引用非抽象子类的对象。例如:Person p = new Student("Vince Vu", "Economics"); 这里的 p 是一个 Person 抽象类的对象变量,p 引用了一个 Student 非抽象子类的实例。


在 C++ 中,有一种在尾部用 =0 标记的抽象方法,被称为纯虚函数,例如:

// C++
class Person {
    public:
        virtual string getDescription() = 0;
};

在 C++ 中,一个类只要有一个纯虚函数,这个类就是抽象类。在 C++ 中,没有提供用于表示抽象类的特殊关键字。

接口

接口(interface)技术主要用来描述类具有什么功能,而并不给出每个功能的具体实现。

一个类可以实现(implement)—个或多个接口,并在需要接口的地方,随时使用实现了相应接口的对象。

在下面的小节中,你会了解 Java 接口是什么以及如何使用接口。

接口概念

在 Java 程序设计语言中,接口不是类,而是对类的一组需求描述,这些类要遵从接口描述的统一格式进行定义。实现接口的类必须定义接口中声明的所有方法。

在接口中还可以定义常量。然而,更为重要的是要知道接口不能提供哪些功能。接口绝不能含有实例域,在 Java8 之前, 也不能在接口中实现方法。(在 Java8 及之后,可以在接口中实现默认方法。)提供实例域和方法实现的任务应该由实现接口的那个类来完成。

接口中的方法都自动地被设置为 public ,接口中的域都自动地被设置为 public static final。因此,在接口中声明方法时,不必提供关键字 public。

为了让类实现一个接口,通常需要下面两个步骤:

  1. 将类声明为实现给定的接口。要将类声明为实现某个接口,需要使用 implements 关键字
  2. 对接口中的所有方法进行定义。

接口的特性

接口不是类,尤其不能使用 new 运算符实例化一个接口:

x = new Comparable(...); // ERROR

然而, 尽管不能构造接口的对象,却能声明接口的变量:

Comparable x; // OK

接口变量必须引用实现了接口的类对象:

x = new Employee(...); // OK provided Employee implements Comparable

接下来,如同使用 instanceof 检查一个对象是否属于某个特定类一样,也可以使用 instanceof 检查一个对象是否实现了某个特定的接口:

if (anObject instanceof Comparable) { ... }

与可以建立类的继承关系一样,接口也可以被扩展。这里允许存在多条从具有较高通用性的接口到较高专用性的接口的链。例如,假设有一个被称为 Moveable 的接口:

public interface Moveable {
    void move(double x, double y);
}

然后,可以以它为基础 扩展一个叫做 Powered 的接口:

public interface Powered extends Moveable {
    double milesPerCallon();
}

虽然在接口中不能包含实例域或静态方法,但却可以包含常量。例如:

public interface Powered extends Moveable {
    double milesPerCallonO;
    double SPEED_LIHIT = 95; // a public static final constant
}

与接口中的方法都自动地被设置为 public —样,接口中的域将被自动设为 public static final。

可以将接口方法标记为 public,将域标记为 public static final。有些程序员出于习惯或提高清晰度的考虑,愿意这样做。但 Java 语言规范却建议不要书写这些多余的关键字。

可以为接口方法提供一个默认实现。必须用 default 修饰符标记这样一个方法。

public interface Comparable<T> {
    default int compareTo(T other) {
        return 0;
    }
    // By default, all elements are the same
}

接口 & 抽象类

为什么 Java 程序设计语言还要不辞辛苦地引入接口概念?为什么不将 Comparable 直接设计成如下所示的抽象类。

// why not?
abstract class Comparable {
    public abstract int compareTo(Object other);
}

然后,Employee 类再直接扩展这个抽象类,并提供 compareTo() 方法的实现:

// why not?
class Employee extends Comparable {
    public int compareTo(Object other) { ... }
}

非常遗憾,使用抽象类表示通用属性存在这样一个问题:每个类只能扩展于一个类。假设 Employee 类已经扩展于一个类,例如 Person。它就不能再像下面这样扩展第二个类了:

class Employee extends Person, Comparable // Error

但每个类可以像下面这样实现多个接口:

class Employee extends Person implements Comparable // OK

有些程序设计语言允许一个类有多个父类,例如 C++。我们将此特性称为多重继承(multiple inheritance)。而 Java 的设计者选择了不支持多继承,其主要原因是多继承会让语言本身变得非常复杂(如同 C++),效率也会降低(如同 Eiffel)。

实际上,接口可以提供多重继承的大多数好处,同时还能避免多重继承的复杂性和低效性。

可以将接口看成是没有实例域的抽象类,但是这两个概念还是有一定区别的。接口 & 抽象类的区别:

  • 它们可以包含的内容不同:

    • 抽象类中可以包含数据域(实例域、static 域、final 域)、具体方法、抽象方法。
    • 接口中不能包含实例域,但可以包含常量(static final 域)、默认方法。接口中的方法都自动地被设置为 public ,接口中的域都自动地被设置为 public static final
  • 它们的用途不同:

    • 抽象类的用途是:在子类继承父类时,父类的一些方法实现是不明确的(父类对子类的实现一无所知)。这时需要使父类是抽象类,在子类中提供方法的实现(抽象类和普通的类是十分相似的:普通类中有的,抽象类中也都可以有,只是抽象类中可以有抽象方法)
    • 接口的用途是:接口主要用来描述类具有什么功能,而并不给出每个功能的具体实现。实现接口的类必须定义接口中声明的所有方法。确保一个类(实现接口的类)实现一个或一组特定的方法。
  • 在 Java 程序设计语言中,每个类只能够拥有一个父类,但却可以实现多个接口

参考资料

《Java核心技术卷一:基础知识》(第10版)第 5 章:继承 5.1.9 抽象类

《Java核心技术卷一:基础知识》(第10版)第 6 章:接口、lambda 表达式与内部类 6.1 接口

相关文章
|
10天前
|
Java
java基础(12)抽象类以及抽象方法abstract以及ArrayList对象使用
本文介绍了Java中抽象类和抽象方法的使用,以及ArrayList的基本操作,包括添加、获取、删除元素和判断列表是否为空。
13 2
java基础(12)抽象类以及抽象方法abstract以及ArrayList对象使用
|
21天前
|
Java
Java——抽象类和接口
抽象类是一种不能被实例化的类,至少包含一个抽象方法(无实现体的方法),常用于定义一组相关类的共同特征,并强制子类实现特定方法。抽象方法不能被 `static` 或 `final` 修饰,且必须被重写。 接口则是一个完全抽象的类,用于规范类的行为。接口使用 `interface` 关键字定义,不能实例化,并且类与接口之间是实现关系。 内部类是在一个类内定义的类,分为成员内部类、静态内部类、局部内部类和匿名内部类。成员内部类可被修饰符修饰,静态内部类只能访问外部类的静态成员,局部内部类定义在方法内,匿名内部类则隐藏了名字,直接通过 `new` 关键字定义并实现接口或继承类。
14 5
Java——抽象类和接口
|
21天前
|
Java
Java——接口的使用实例
Comparable接口用于自定义类的对象比较。通过实现此接口并重写`compareTo`方法,可以定义自定义类型的比较规则。 接下来介绍了Comparator接口,它提供了一种更灵活的比较方式。通过实现Comparator接口并重写`compare`方法,可以根据不同属性定义不同的比较规则。例如,定义一个`BrandComparator`类来比较汽车的品牌。 最后,介绍了Cloneable接口,用于实现对象的克隆。实现该接口并重写`clone`方法后,可以创建对象的浅拷贝或深拷贝。浅拷贝仅复制对象本身,深拷贝则会递归复制所有成员变量。
15 4
Java——接口的使用实例
|
9天前
|
Java
Java 抽象类详解
在 Java 中,抽象类是一种特殊类,用于提供基础定义并派生具体子类,支持代码复用。它包含抽象方法(无实现)和具体方法(有实现),不能被实例化,需用 `abstract` 关键字定义。子类必须实现所有抽象方法,除非子类也是抽象类。抽象类可用于定义模板或框架,确保子类实现特定方法。通过示例展示了如何定义抽象类 `Animal` 及其子类 `Dog` 和 `Cat`,并在主类中调用相关方法。使用抽象类可以提高代码复用性和设计质量,但也可能增加维护难度和性能开销。
|
6天前
|
Java 测试技术
Java接口的生产环境应用注意点
在Java生产环境中,合理使用接口对提升代码质量至关重要。设计接口时应遵循单一职责原则,采用清晰命名,并控制方法数量。默认方法应谨慎使用,避免与实现类产生冲突。通过版本化管理接口更新,确保向后兼容。实现接口时需明确行为,保持实现与接口分离,利用多态增强灵活性。关注性能影响,适当文档注释及充分测试确保接口稳定可靠。综合运用这些策略,可以显著提高系统的可扩展性和维护性。
|
6天前
|
Java
Java 接口的简化理解
Java 接口是一种强大的概念,用于定义方法签名而非具体实现,作为行为规范,强调功能而非实现细节。接口是特殊的引用类型,包含常量和方法签名。其特点包括:无实现方法体、支持多重继承、内置常量定义。通过示例展示了如何定义和实现接口,以及如何通过接口引用调用实现类的方法。接口的应用场景包括抽象化、插件架构和松耦合设计。从 Java 8 起,接口还支持默认方法和静态方法,进一步增强了其灵活性和扩展性。理解接口是 Java 编程的基础之一。
|
7天前
|
Java
接口和抽象类【Java面向对象知识回顾②】
本文讨论了Java中抽象类和接口的概念与区别。抽象类是不能被实例化的类,可以包含抽象和非抽象方法,常用作其他类的基类。接口是一种纯抽象类型,只包含抽象方法和常量,不能被实例化,且实现接口的类必须实现接口中定义的所有方法。文章还比较了抽象类和接口在实现方式、方法类型、成员变量、构造方法和访问修饰符等方面的不同,并探讨了它们的使用场景。
接口和抽象类【Java面向对象知识回顾②】
|
15天前
|
Java
领略Lock接口的风采,通过实战演练,让你迅速掌握这门高深武艺,成为Java多线程领域的武林盟主
领略Lock接口的风采,通过实战演练,让你迅速掌握这门高深武艺,成为Java多线程领域的武林盟主
22 7
|
11天前
|
JSON 前端开发 JavaScript
java中post请求调用下载文件接口浏览器未弹窗而是返回一堆json,为啥
客户端调接口需要返回另存为弹窗,下载文件,但是遇到的问题是接口调用成功且不报错,浏览器F12查看居然返回一堆json,而没有另存为弹窗; > 正确的效果应该是:接口调用成功且浏览器F12不返回任何json,而是弹窗另存为窗口,直接保存文件即可。
43 2
|
27天前
|
Java 数据库连接 数据库
Java服务提供接口(SPI)的设计与应用剖析
Java SPI提供了一种优雅的服务扩展和动态加载机制,使得Java应用程序可以轻松地扩展功能和替换组件。通过合理的设计与应用,SPI可以大大增强Java应用的灵活性和可扩展性。
49 18
下一篇
无影云桌面