《编程导论(Java)·4.1数据抽象的含义》

来源:互联网 发布:域名备案ps 编辑:程序博客网 时间:2024/05/16 05:49

You have no choice about the necessity to integrateyour observations,

your experiences, your knowledge into abstractideas, i.e., into principles.

——Ayn Rand, 《Philosophy: Who Needs It》 1974

数据抽象(Data abstraction)是将数据类型的抽象特征与其实现的具体细节清晰地分离。其中数据类型的“抽象特征”是指对用户代码而言可见的、如何使用该数据类型的接口。数据抽象是接口与实现分离原则在类型层面以及对象技术中的推广

本章将介绍作为数据抽象的Java基本数据类型、抽象类、Java接口;重点讨论抽象类、接口的作用。下一章继续介绍数据抽象——作为抽象数据类型的线性表及其实现。

4.1数据抽象的含义

内存中保存的数值被称为数据或操作数,对数据进行分类是为了方便程序员识别和操作它们。在[2.2.2 Java数据类型] 中,介绍了若干的概念:数据类型——定义一个值的集合以及处理这个值集的一组操作;Java语言的类型包括基本类型和程序员自定义的数据类型(引用类型)。

在[3.2.2 操作符]中,介绍了基本类型的各种操作;而在[2.4.1 引用的涵义]中列举了引用类型的7种操作。需要注意的是,上述所有的介绍均处于Java语言的层面[而非JVM层面]。或者说,一直讨论的是Java数据类型的抽象特征——接口。


4.1.1 基本类型的实现

基本类型的接口/抽象特征包括该类型的取值范围和适用操作符。这里以最常用的int和boolean为例,说明数据抽象的含义。

1. int类型的接口和实现
    【这里先介绍int使用的一些注意事项】整数类型int是对数学整数的模拟,int与数学整数的差别在于int不是无限的。int类型逻辑上拥有32位内存空间,按照[0.1.2 二进制补码],int类型的最大值(补码)为(231 -1)即2147483647或0x7FFF_FFFF,或二进制的0b01111111_11111111_11111111_11111111。当它加上1,就变成了0x8000_0000即最小值(-231即-2147483648)。通常把整型的计算形容为在时钟的钟面上运行。计算时超过最大值或最小值的情况,称为溢出(O verflow与underflow),当使用大数字时,要小心溢出,因为没有任何人或异常机制帮助程序员防止这种程序错误的发生,它是一个沉默的杀手。可用如下代码验证:
    void doSth(){
        int x=0x7FFFFFFF;
        System.out.println( x + 1 );
    }
此外,int中什么值可以使i == -i呢?除了零之外,还有最小值0x8000_0000。因此if(i == -i)的语义不同于if(i == 0)。
对于int的操作,包括了大多数操作符,如算术、比较、位操作、赋值和一元操作的正负号、++、--等等。


    上述内容均为int的接口/抽象特征。作为int的用户,说int类型(逻辑上)拥有32位内存空间时,意味着用户知道了int的值域范围。事实上,JVM规范中规定了int的值域范围,但是并没有定义int类型的内存空间。用于储存int数据的内存空间,由JVM的不同实现者自由设定。

    通常,在JVM中使用字(word)作为数据值的基本尺寸单位。要求字的尺寸足够大,以保存byte, short, int, char, float, returnValue(Java程序员不可用的一种JVM数据类型,用于实现Java程序中的finally子句)和reference。而两个字足以装下long或double。一般情况下以主机平台的本地指针的尺寸为字的尺寸标准。如果机器和系统平台是32位的,int值和所有引用(reference) 都被分配了32位内存;如果机器和系统平台是64位的,int值可能被分配了64位空间

通常int的用户并不关心int的实现,int的内存空间不管是32位还是64位,不得影响int的接口。即使系统用64位来保存int值,2147483647+1仍然会(也应该)表现为溢出(变成了最小值)。

练习4-1.:解释int类型的接口和实现的分离。

练习4-2.:编程typeSystem.primitive. IntDemo,验证溢出和i == -i。

 

2. boolean类型的接口和实现

    计算机会做的事情不过是算术操作和逻辑操作。因而布尔表达式在if-else语句、?:操作和循环语句中被广泛使用。关系操作符如5>2、x!=y等取值得到一个boolean值,布尔操作符如p && q进行逻辑运算。任何一个非0的整数值x,表达式x!=0的值为true;任何一个非null的引用ref,表达式ref!=null的值为true。

   当人们眉飞色舞地讨论Java的布尔表达式、boolean类型时,事实上讨论的是boolean接口。然而Java语言中如此重要和基础的boolean类型,并不实质性地存在。嗯,这个玩笑有点大,《JVM规范第2版》中有一节<3.3.4There Is No boolean Type>,事实上JVM定义了boolean类型,但是没有提供操作boolean的指令。《Java 虚拟机规范(Java SE 7 版)》的标题<2.3.4 boolean类型>更加严谨一些。

在JVM中,源代码的boolean类型的运算被实现为JVM的int类型运算。源代码的boolean[]类型,其元素的访问与修改使用JVM的byte类型数组的 baload 和 bastore 指令。简单地说,boolean类型的实现,JVM定义了boolean这种数据类型,如以"Z"或" [Z"分别表示boolean和boolean[],也通过newarray 指令直接支持创建boolean[]。但是对boolean操作转换为int操作,1代表true、0代表false;而boolean[]视为byte数组操作,Sun的JVM实现将boolean[]元素作为8bit的值,其他JVM实现可能采用压缩形式,如一位。

int和boolean两个例子,反应了数据类型的抽象特征与其实现的具体细节的差异,这就是将数据接口与实现加以清晰地分离的数据抽象的意义所在。

练习4-3:解释boolean类型在Java语言层面、JVM规范层面和JVM实现层面的不同,并由此解释抽象的价值。

4.1.2 类的接口

对于引用类型,不需要如同基本类型那样涉及到JVM。源代码中没有对象,只有引用变量和引用值,关于对象的实现参考[7.4.3堆上的对象]。类的接口与实现在[第6章封装]中详述,这里仅概要地说明。

1. 类的API

Parnas原则/接口与实现分离指在模块或方法层面,用户程序员有意识地忽略方法的实现。而每一个方法具有自己的接口。对于一个类A的用户而言,A所定义的(方法)接口只有能够被访问时才值得关注。许多方法——典型的如private方法,仅仅被A自己使用,对外界而言,这些方法是否存在无关紧要也不得而知。因而在面向对象技术中,Parnas原则被推广到类层面。

类的接口指外界对象能够访问的、类所定义的接口的集合。通常,类中声明的public、protected域,也作为类接口的一部分。

由上面的定义可知,随着客户程序与本类的关系的不同,如在或不在一个包中、是或不是本类的子类类的接口这一集合对客户类而言会有程度上的不同。使用访问修饰符限定类的接口,这一机制称为封装,在[第6章封装]中详细介绍。

类的接口常常称为该类的API,这是为了与Java中的接口类型相区别。一般而言private和package-private方法不是类的接口。

 

2. 类和类型

绝大多数情况下,可以视class为一种type。例如,在[2.2.2Java数据类型]中所给出的(数据)类型的概念,将类作为类类型。

在[2.1.1里氏替换原则]中介绍了子类(subclass)与子类型(subtype)的区别,那么,类(class)和类型(type) 又有什么差异呢?

差异表现在接口与实现的分离上。type是一个名称,它标识了类的接口。如果一个对象能够接受X类的接口的全部操作请求(方法调用),则称对象具有X类型。正是因为Java的子类能够满足父类的接口(尽管可以改写),所以子类的对象能够同时具有类层次中的多个类型

类(class)则是接口和实现的综合体。类不仅仅定义了一种类型,也定义了对象的内部状态和方法的实现,以及不是接口的方法(如private方法)。简言之,类型是类的接口,类是类型+实现。

练习4-4.:一个对象可以有多个类型。这些类型构成了一个____,如同生物学分类。

练习4-5:不同类的对象可以具有相同的类型,这个共同的类型被称为____ 。

练习4-6:子类继承父类的接口。请讨论这一命题。

练习4-7.:解释类(class)和类型(type)的差异。




0 0
原创粉丝点击