本文接上一篇《编程那些事儿:为什么说抽象是面向对象编程的根基【1】?》,
在了解了面向对象编程抽象的过程抽象后,下面我们继续聊一下数据抽象。
数据抽象可以说面向对象编程最重要的基石。而数据抽象的结果就是数据类型(或简单的类型),它一般由以下三个部分定义:
- 一组值(或数据对象)。
- 一组操作,可应用于该集合中的所有值。
- 一种数据表示,它决定如何存储值。
通常编程语言会提供一些预定义的数据类型,称为内置数据类型。
他们也允许程序开发人员定义自己的数据类型,这被称为用户定义的数据类型。
由原子值和不可分割值组成的数据类型称为原始数据类型,它的定义不需要任何其他数据类型的帮助。例如,Java具有内置的基元数据类型,比如int, float, boolean, char等。
按照数据抽象的组成结构,Java中定义int基元数据类型的三个组成部分如下:
- 值:int数据类型由-2147483648和2147483647之间的所有整数组成。
- 操作:为int数据类型定义了加法、减法、乘法、除法、比较等操作。
- 数据表示:int数据类型的值以2的补码形式在32位内存中表示。
int数据类型的所有三个组件都是由Java语言预定义的。开发人员不能扩展或重新定义int数据类型的定义。
我们具体拿int 类型看一下它的抽象内容,比如:
int i;
该语句说 i 是一个名称(技术上称为标识符),可以与定义int数据类型值的一组值中的一个值关联。
例如,可以使用下面的赋值语句将整数100与名称i关联:
i = 100;
在此阶段,您可能会问,“与i关联的值100存储在内存中何处?” 根据int数据类型的定义,i将占用32位内存。
但是,您不知道,不能知道,也不需要知道在内存中为i分配32位空间的位置,这样做也是一种抽象。在像Java这种高级语言中,像这样的抽象的例子比比皆是。
在本例中,关于int数据类型的数据值的数据表示内存形式对数据类型的用户(程序员)来说是隐藏的。
换句话说,程序员会忽略i的内存位置,而专注于它的值和可以在其上执行的操作。
程序员并不关心i的内存是分配在寄存器、RAM还是硬盘中。
面向对象的编程语言(如Java)允许我们使用抽象机制创建新的数据类型称为数据抽象。
以此创建的新的数据类型被称为抽象数据类型(ADT)。
ADT的数据对象可能包括原始数据类型和其他ADT的组合,ADT定义了一组可以应用于其所有数据对象的操作,但其数据表示却总是隐藏在ADT中。
对于ADT的用户来说,他们只知道如何操作该数据,只能使用这些操作来处理它的数据元素。
使用数据抽象的好处是,它的数据表示可以在不影响使用ADT的任何代码的情况下进行更改。
总结一下,数据抽象允许程序员创建一个称为抽象数据类型的新数据类型,其中数据对象的存储表示形式对数据类型的用户是隐藏的。
换句话说,ADT仅仅是根据可以应用于其类型的数据对象的操作来定义的,而不需要知道数据的内部表示形式。
这种数据类型之所以称为抽象,是因为ADT的用户从来没有看到过数据值的表示。
用户以抽象的方式查看ADT的数据对象,方法是在不了解数据对象表示细节的情况下对它们应用操作。
当然,ADT并不意味着数据类型构成中没有数据表示。数据表示在ADT中是肯定存在的,只是对用户隐藏了数据表示而已。
Java有一些数据结构构造,比如类、接口、注释和枚举,这些构造都允许我们用它来定义新的ADT。
注意在我们使用一个类来定义一个新的ADT时,需要小心地隐藏数据表示,这样我们的新数据类型就真的是抽象的了。
如果我们Java类中的没有隐藏数据表示,该类将创建一个新的数据类型,而不再是ADT。
Java中的类提供了一些特性,我们可以使用这些特性公开或隐藏数据表示。
在Java语言中,我们拿类Class这个抽象数据类型来看它的三个组成部分:
其中数据类型是Object,其操作方法是methods,数据类型的表示就是我们的定义的私有字段。
我们可以通过Class中定义的方法来实现对字段(数据表示)的操作。
在Java语言中,有一个纯种的ADT,那就是接口(interface),它只提供了数据的操作部分,而没有具体的实现。
而它的数据值部分和数据表示部分都是实现它的具体类提供的。
public class Person{private String name;private String gender;public Person(String name, String gender){this.name = name;this.gender = gender;}public String getName(){return name;}public void setName(String name){this.name = name;}public String getGender(){return gender;}}
比如:使用Java语言语法定义Person类。通过定义一个名为Person的类,我们就创建了一个新的ADT。
我们定义内部属性name和gender就是其内部数据表示,它们使用String数据类型(String是Java类库提供的内置ADT)。
我们可以注意到,Person类的定义在name和gender声明中使用private关键字来隐藏它。所以Person类的用户不能访问name和gender数据元素。
它提供了四个操作:一个构造函数和三个方法(getName、setName和getGender)。
构造函数操作用于初始化新构造的Person类型的数据对象。getName和setName操作分别用于访问和修改name数据元素。
getGender操作用于访问性别数据元素的值。
Person类的用户只能使用这四个操作来处理Person类型的数据对象。
Person类型的用户不知道用于存储name和gender数据元素的数据存储类型。
我上面使用了三个词,“数据类型”、“类”和“接口”,其实在某种意义上说,它们可以互换使用,因为它们在数据类型上下文中的意思是相同的。
它让Person类型的开发人员可以自由地更改name和gender数据元素的数据表示形式,而不会影响任何Person类型的用户。
假设其中一个Person类型的用户有以下代码片段:
Person john = new Person("Si Li