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

为什么要花费精力进行这样高层次的抽象呢?每个人都有一些诸如姓名这样的属性。学生与雇员都有姓名属性,因此可以将 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。
为了让类实现一个接口,通常需要下面两个步骤:
- 将类声明为实现给定的接口。要将类声明为实现某个接口,需要使用 implements 关键字
- 对接口中的所有方法进行定义。
接口的特性
接口不是类,尤其不能使用 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 接口
Java的抽象类 & 接口的更多相关文章
- 10-01 Java 类,抽象类,接口的综合小练习--运动员和教练
运动员和教练的案例分析 运动运和教练的案例 代码实现 /* 教练和运动员案例 乒乓球运动员和篮球运动员. 乒乓球教练和篮球教练. 为了出国交流,跟乒乓球相关的人员都需要学习英语. 请用所学知识: 分析 ...
- java特殊抽象类-接口
- Java抽象类接口、内部类题库
一. 选择题 1. Person类和Test类的代码如下所示,则代码中的错误语句是( C ).(选择一项) public class Person { public String nam ...
- JAVA:抽象类VS接口
JAVA中抽象类和接口的区别比较,以及它们各自的用途. 1.JAVA抽象类: 抽象类除了不能实例化以外,跟普通类没有任何区别.在<JAVA编程思想>一书中,将抽象类定义为“包含抽象方法的类 ...
- 深入理解java的抽象类和接口(转载)
原文链接:http://www.cnblogs.com/dolphin0520/p/3811437.html 对于面向对象编程来说,抽象是它的一大特征之一.在Java中,可以通过两种形式来体现OOP的 ...
- 转:二十一、详细解析Java中抽象类和接口的区别
转:二十一.详细解析Java中抽象类和接口的区别 http://blog.csdn.net/liujun13579/article/details/7737670 在Java语言中, abstract ...
- 关于JAVA中抽象类和接口的区别辨析
今天主要整理一下新学习的有关于Java中抽象类和接口的相关知识和个人理解. 1 抽象类 用来描述事物的一般状态和行为,然后在其子类中去实现这些状态和行为.也就是说,抽象类中的方法,需要在子类中进行重写 ...
- [ Java学习基础 ] Java的抽象类与接口
一.抽象类 1. 抽象类 Java语言提供了两种类:一种是具体类:另一种是抽象子类. 2. 抽象类概念: 在面向对象的概念中,所有的对象都是通过类来描绘的,但是反过来,并不是所有的类都是用来描绘对象的 ...
- Java基础-抽象类和接口
抽象类与接口是Java语言中对抽象概念进行定义的两种机制,正是由于他们的存在才赋予java强大的面向对象的能力.他们两者之间对抽象概念的支持有很大的相似,甚至可以互换,但是也有区别. 抽象定义: 抽象 ...
- JAVA的抽象类和接口
抽象类 在面向对象的概念中,所有的对象都是通过类来描述的,但是反过来,并不是所有的类都是用来描述对象的,如果一个类中没有包含足够的信息来描绘一个具体的对象,这样的类就是抽象类. 抽象类除了不能实例化对 ...
随机推荐
- python-实现栈结构
# encoding=utf-8 class Stack(object): """栈""" def __init__(self): &quo ...
- 简单总结一下html中能见到的各种相对路径
试列举如下(在本文中,星号表示资源名): href="/*" href="//*" href="*" href="./*" ...
- [rk3568][buildroot] 移除RK3568 iodomain check
1. 问题背景 RK3568 基线代码默认会起一个服务监控RK3568 iodomain,该服务间隔性输出log信息: 由于该功能非必要,故选择移除该部分逻辑 2.解决方案 查看源码编译脚本,如下图所 ...
- FinOps首次超越安全成为企业头等大事丨云计算趋势报告
随着云计算在过去十年中的广泛应用,云计算用户所面临的一个持续不变的趋势是:安全一直是用户面临的首要挑战.然而,这种情况正在发生转变. 知名IT软件企业 Flexera 对云计算决策者进行年度调研已经持 ...
- Python学习笔记--从继承开始继续
继承的基础语法 单继承: 多继承:一个子类继承多个父类 pass关键字补全语法 注意事项: 复写和使用父类成员 复写父类成员 也就是相当于Java中的方法重写 调用父类成员 变量的类型注解 举例: 更 ...
- Markdown 利用HTML进行优雅排版
Markdown 利用HTML进行优雅排版 我在使用Markdown整理文档的时候发现,Markdown本身对文本格式的排版很单一,只有编号.字体加粗.固定标题格式等一些基础的排版,使用不够灵活,好在 ...
- 卡特兰路径和q,t-enumeration 学一半的笔记
目录 卡特兰 The1st q-analogue of \(C_n\) The 2nd q-analogue of \(C_n\) /定义\(C_n(q)\) The q-Vandermonde co ...
- SpringBoot工程入门case
SpringBoot的设计目的是用来简化Spring应用的初始搭建以及开发过程. SpringBoot入门案例: 1.创建一个新module 2.除pom和src文件剩余都删除. 3.在src.com ...
- 【转载】谈谈GIS三维渲染引擎
> 原文地址:https://zhuanlan.zhihu.com/p/419667971 三维引擎 minemap: 是我们公司的产品,主要以earth的形态展示,支持矢量切片+倾斜数据(这一 ...
- 第三部分:Spdlog 日志库的实现原理
! https://zhuanlan.zhihu.com/p/617432495 Spdlog 是一个快速.异步的 C++ 日志库,被广泛应用于 C++ 项目中.在这篇文章中,我们将探讨 Spdlog ...