?? chapter2.htm
字號:
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=gb2312">
<title>Thinking in Java | Chinese Version by Trans Bot</title>
<meta name="Microsoft Theme" content="inmotion 111, default"></head>
<body background="../_themes/inmotion/inmtextb.gif" tppabs="http://member.netease.com/%7etransbot/Thinking%20in%20Java/_themes/inmotion/inmtextb.gif" bgcolor="#FFFFCC" text="#000000" link="#800000" vlink="#996633" alink="#FF3399">
<p>第2章 一切都是對象<br>
<br>
“盡管以C++為基礎,但Java是一種更純粹的面向對象程序設計語言”。<br>
無論C++還是Java都屬于雜合語言。但在Java中,設計者覺得這種雜合并不象在C++里那么重要。雜合語言允許采用多種編程風格;之所以說C++是一種雜合語言,是因為它支持與C語言的向后兼容能力。由于C++是C的一個超集,所以包含的許多特性都是后者不具備的,這些特性使C++在某些地方顯得過于復雜。<br>
Java語言首先便假定了我們只希望進行面向對象的程序設計。也就是說,正式用它設計之前,必須先將自己的思想轉入一個面向對象的世界(除非早已習慣了這個世界的思維方式)。只有做好這個準備工作,與其他OOP語言相比,才能體會到Java的易學易用。在本章,我們將探討Java程序的基本組件,并體會為什么說Java乃至Java程序內的一切都是對象。<br>
<br>
2.1 用句柄操縱對象<br>
每種編程語言都有自己的數據處理方式。有些時候,程序員必須時刻留意準備處理的是什么類型。您曾利用一些特殊語法直接操作過對象,或處理過一些間接表示的對象嗎(C或C++里的指針)?<br>
所有這些在Java里都得到了簡化,任何東西都可看作對象。因此,我們可采用一種統一的語法,任何地方均可照搬不誤。但要注意,盡管將一切都“看作”對象,但操縱的標識符實際是指向一個對象的“句柄”(Handle)。在其他Java參考書里,還可看到有的人將其稱作一個“引用”,甚至一個“指針”。可將這一情形想象成用遙控板(句柄)操縱電視機(對象)。只要握住這個遙控板,就相當于掌握了與電視機連接的通道。但一旦需要“換頻道”或者“關小聲音”,我們實際操縱的是遙控板(句柄),再由遙控板自己操縱電視機(對象)。如果要在房間里四處走走,并想保持對電視機的控制,那么手上拿著的是遙控板,而非電視機。<br>
此外,即使沒有電視機,遙控板亦可獨立存在。也就是說,只是由于擁有一個句柄,并不表示必須有一個對象同它連接。所以如果想容納一個詞或句子,可創建一個String句柄:<br>
String s;<br>
但這里創建的只是句柄,并不是對象。若此時向s發送一條消息,就會獲得一個錯誤(運行期)。這是由于s實際并未與任何東西連接(即“沒有電視機”)。因此,一種更安全的做法是:創建一個句柄時,記住無論如何都進行初始化:<br>
String s = "asdf";<br>
然而,這里采用的是一種特殊類型:字串可用加引號的文字初始化。通常,必須為對象使用一種更通用的初始化類型。<br>
<br>
2.2 所有對象都必須創建<br>
創建句柄時,我們希望它同一個新對象連接。通常用new關鍵字達到這一目的。new的意思是:“把我變成這些對象的一種新類型”。所以在上面的例子中,可以說:<br>
String s = new String("asdf");<br>
它不僅指出“將我變成一個新字串”,也通過提供一個初始字串,指出了“如何生成這個新字串”。<br>
當然,字串(String)并非唯一的類型。Java配套提供了數量眾多的現成類型。對我們來講,最重要的就是記住能自行創建類型。事實上,這應是Java程序設計的一項基本操作,是繼續本書后余部分學習的基礎。<br>
<br>
2.2.1 保存到什么地方<br>
程序運行時,我們最好對數據保存到什么地方做到心中有數。特別要注意的是內存的分配。有六個地方都可以保存數據:<br>
(1)
寄存器。這是最快的保存區域,因為它位于和其他所有保存方式不同的地方:處理器內部。然而,寄存器的數量十分有限,所以寄存器是根據需要由編譯器分配。我們對此沒有直接的控制權,也不可能在自己的程序里找到寄存器存在的任何蹤跡。<br>
(2) 堆棧。駐留于常規RAM(隨機訪問存儲器)區域,但可通過它的“堆棧指針”獲得處理的直接支持。堆棧指針若向下移,會創建新的內存;若向上移,則會釋放那些內存。這是一種特別快、特別有效的數據保存方式,僅次于寄存器。創建程序時,Java編譯器必須準確地知道堆棧內保存的所有數據的“長度”以及“存在時間”。這是由于它必須生成相應的代碼,以便向上和向下移動指針。這一限制無疑影響了程序的靈活性,所以盡管有些Java數據要保存在堆棧里——特別是對象句柄,但Java對象并不放到其中。<br>
(3) 堆。一種常規用途的內存池(也在RAM區域),其中保存了Java對象。和堆棧不同,“內存堆”或“堆”(Heap)最吸引人的地方在于編譯器不必知道要從堆里分配多少存儲空間,也不必知道存儲的數據要在堆里停留多長的時間。因此,用堆保存數據時會得到更大的靈活性。要求創建一個對象時,只需用new命令編制相關的代碼即可。執行這些代碼時,會在堆里自動進行數據的保存。當然,為達到這種靈活性,必然會付出一定的代價:在堆里分配存儲空間時會花掉更長的時間!<br>
(4) 靜態存儲。這兒的“靜態”(Static)是指“位于固定位置”(盡管也在RAM里)。程序運行期間,靜態存儲的數據將隨時等候調用。可用static關鍵字指出一個對象的特定元素是靜態的。但Java對象本身永遠都不會置入靜態存儲空間。<br>
(5)
常數存儲。常數值通常直接置于程序代碼內部。這樣做是安全的,因為它們永遠都不會改變。有的常數需要嚴格地保護,所以可考慮將它們置入只讀存儲器(ROM)。<br>
(6) 非RAM存儲。若數據完全獨立于一個程序之外,則程序不運行時仍可存在,并在程序的控制范圍之外。其中兩個最主要的例子便是“流式對象”和“固定對象”。對于流式對象,對象會變成字節流,通常會發給另一臺機器。而對于固定對象,對象保存在磁盤中。即使程序中止運行,它們仍可保持自己的狀態不變。對于這些類型的數據存儲,一個特別有用的技巧就是它們能存在于其他媒體中。一旦需要,甚至能將它們恢復成普通的、基于RAM的對象。Java
1.1提供了對Lightweight persistence的支持。未來的版本甚至可能提供更完整的方案。<br>
<br>
2.2.2 特殊情況:主要類型<br>
有一系列類需特別對待;可將它們想象成“基本”、“主要”或者“主”(Primitive)類型,進行程序設計時要頻繁用到它們。之所以要特別對待,是由于用new創建對象(特別是小的、簡單的變量)并不是非常有效,因為new將對象置于“堆”里。對于這些類型,Java采納了與C和C++相同的方法。也就是說,不是用new創建變量,而是創建一個并非句柄的“自動”變量。這個變量容納了具體的值,并置于堆棧中,能夠更高效地存取。<br>
Java決定了每種主要類型的大小。就象在大多數語言里那樣,這些大小并不隨著機器結構的變化而變化。這種大小的不可更改正是Java程序具有很強移植能力的原因之一。<br>
<br>
主類型 大小 最小值 最大值 封裝器類型<br>
<br>
boolean 1位 - - Boolean<br>
char 16位 Unicode 0 Unicode 2的16次方-1 Character<br>
byte 8位 -128 +127 Byte(注釋①)<br>
short 16位 -2的15次方 +2的15次方-1 Short(注釋①)<br>
int 32位 -2的31次方 +2的31次方-1 Integer<br>
long 64位 -2的63次方 +2的63次方-1 Long<br>
float 32位 IEEE754 IEEE754 Float<br>
double 64位 IEEE754 IEEE754 Double<br>
Void - - - Void(注釋①)<br>
<br>
①:到Java 1.1才有,1.0版沒有。<br>
<br>
數值類型全都是有符號(正負號)的,所以不必費勁尋找沒有符號的類型。<br>
主數據類型也擁有自己的“封裝器”(wrapper)類。這意味著假如想讓堆內一個非主要對象表示那個主類型,就要使用對應的封裝器。例如:<br>
char c = 'x';<br>
Character C = new Character('c');<br>
也可以直接使用:<br>
Character C = new Character('x');<br>
這樣做的原因將在以后的章節里解釋。<br>
<br>
1. 高精度數字<br>
Java 1.1增加了兩個類,用于進行高精度的計算:BigInteger和BigDecimal。盡管它們大致可以劃分為“封裝器”類型,但兩者都沒有對應的“主類型”。<br>
這兩個類都有自己特殊的“方法”,對應于我們針對主類型執行的操作。也就是說,能對int或float做的事情,對BigInteger和BigDecimal一樣可以做。只是必須使用方法調用,不能使用運算符。此外,由于牽涉更多,所以運算速度會慢一些。我們犧牲了速度,但換來了精度。<br>
BigInteger支持任意精度的整數。也就是說,我們可精確表示任意大小的整數值,同時在運算過程中不會丟失任何信息。<br>
BigDecimal支持任意精度的定點數字。例如,可用它進行精確的幣值計算。<br>
至于調用這兩個類時可選用的構建器和方法,請自行參考聯機幫助文檔。<br>
<br>
2.2.3 Java的數組<br>
幾乎所有程序設計語言都支持數組。在C和C++里使用數組是非常危險的,因為那些數組只是內存塊。若程序訪問自己內存塊以外的數組,或者在初始化之前使用內存(屬于常規編程錯誤),會產生不可預測的后果(注釋②)。<br>
<br>
②:在C++里,應盡量不要使用數組,換用標準模板庫(Standard
TemplateLibrary)里更安全的容器。<br>
<br>
Java的一項主要設計目標就是安全性。所以在C和C++里困擾程序員的許多問題都未在Java里重復。一個Java可以保證被初始化,而且不可在它的范圍之外訪問。由于系統自動進行范圍檢查,所以必然要付出一些代價:針對每個數組,以及在運行期間對索引的校驗,都會造成少量的內存開銷。但由此換回的是更高的安全性,以及更高的工作效率。為此付出少許代價是值得的。<br>
創建對象數組時,實際創建的是一個句柄數組。而且每個句柄都會自動初始化成一個特殊值,并帶有自己的關鍵字:null(空)。一旦Java看到null,就知道該句柄并未指向一個對象。正式使用前,必須為每個句柄都分配一個對象。若試圖使用依然為null的一個句柄,就會在運行期報告問題。因此,典型的數組錯誤在Java里就得到了避免。<br>
也可以創建主類型數組。同樣地,編譯器能夠擔保對它的初始化,因為會將那個數組的內存劃分成零。<br>
數組問題將在以后的章節里詳細討論。<br>
<br>
2.3 絕對不要清除對象<br>
在大多數程序設計語言中,變量的“存在時間”(Lifetime)一直是程序員需要著重考慮的問題。變量應持續多長的時間?如果想清除它,那么何時進行?在變量存在時間上糾纏不清會造成大量的程序錯誤。在下面的小節里,將闡示Java如何幫助我們完成所有清除工作,從而極大了簡化了這個問題。<br>
<br>
2.3.1 作用域<br>
大多數程序設計語言都提供了“作用域”(Scope)的概念。對于在作用域里定義的名字,作用域同時決定了它的“可見性”以及“存在時間”。在C,C++和Java里,作用域是由花括號的位置決定的。參考下面這個例子:<br>
<br>
73頁程序<br>
<br>
作為在作用域里定義的一個變量,它只有在那個作用域結束之前才可使用。<br>
在上面的例子中,縮進排版使Java代碼更易辨讀。由于Java是一種形式自由的語言,所以額外的空格、制表位以及回車都不會對結果程序造成影響。<br>
注意盡管在C和C++里是合法的,但在Java里不能象下面這樣書寫代碼:<br>
<br>
74頁上程序<br>
<br>
編譯器會認為變量x已被定義。所以C和C++能將一個變量“隱藏”在一個更大的作用域里。但這種做法在Java里是不允許的,因為Java的設計者認為這樣做使程序產生了混淆。<br>
<br>
2.3.2 對象的作用域<br>
Java對象不具備與主類型一樣的存在時間。用new關鍵字創建一個Java對象的時候,它會超出作用域的范圍之外。所以假若使用下面這段代碼:<br>
<br>
{<br>
?? 快捷鍵說明
復制代碼
Ctrl + C
搜索代碼
Ctrl + F
全屏模式
F11
切換主題
Ctrl + Shift + D
顯示快捷鍵
?
增大字號
Ctrl + =
減小字號
Ctrl + -