天天看點

《程式設計導論(Java)·4.1資料抽象的含義》

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)的差異。
《程式設計導論(Java)·4.1資料抽象的含義》