保存數據位置:
(1) 寄存器。這是最快的保存區域,由於它位於和其餘全部保存方式不一樣的地方:處理器內部。
(2) 堆棧。駐留於常規RAM(隨機訪問存儲器)區域,這是一種特別快、特別有效的數據保存方式,僅次於寄存器。建立程序時,Java編譯器必須準確地知道堆棧內保存的全部數據的「長度」以及「存在時間」。Java「對象句柄」以及「基本數據類型」也保存在堆棧,基本數據包括:boolean/char/byte /short/int/long/float/double,但Java對象並不放到其中。
(3) 堆。一種常規用途的內存池(也在RAM區域),其中保存了Java對象。和堆棧不一樣,「內存堆」或「堆」(Heap)最吸引人的地方在於編譯器沒必要知道要從堆裏分配多少存儲空間,也沒必要知道存儲的數據要在堆裏停留多長的時間。所以,用堆保存數據時會獲得更大的靈活性。要求建立一個對象時,只需用new命令編制相關的代碼便可。執行這些代碼時,會在堆裏自動進行數據的保存。固然,爲達到這種靈活性,必然會付出必定的代價:在堆裏分配存儲空間時會花掉更長的時間!
(4) 靜態存儲。這兒的「靜態」(Static)是指「位於固定位置」(儘管也在RAM裏)。程序運行期間,靜態存儲的數據將隨時等候調用。可用static關鍵字指出一個對象的特定元素是靜態的。但Java對象自己永遠都不會置入靜態存儲空間。
(5) 常數存儲。常數值一般直接置於程序代碼內部。這樣作是安全的,由於它們永遠都不會改變。有的常數須要嚴格地保護,因此可考慮將它們置入只讀存儲器(ROM)。
(6) 非RAM存儲。若數據徹底獨立於一個程序以外,則程序不運行時仍可存在,並在程序的控制範圍以外。其中兩個最主要的例子即是「流式對象」和「固定對象」。對於流式對象,對象會變成字節流,一般會發給另外一臺機器。而對於固定對象,對象保存在磁盤中。即便程序停止運行,它們仍可保持本身的狀態不變。程序員
抽象的進步
全部編程語言的最終目的都是提供一種「抽象」方法。一種較有爭議的說法是:解決問題的複雜程度直接取決於抽象的種類及質量。彙編語言是對基礎機器的少許抽象。後來的許多「命令式」語言(如FORTRAN,BASIC和C)是對彙編語言的一種抽象。與彙編語言相比,這些語言已有了長足的進步,但它們的抽象原理依然要求咱們着重考慮計算機的結構,而非考慮問題自己的結構。在機器模型與實際解決的問題模型之間,程序員必須創建起一種聯繫。
爲機器建模的另外一個方法是爲要解決的問題製做模型。面向對象的程序設計在此基礎上則跨出了一大步,程序員可利用一些工具表達問題空間內的元素。因爲這種表達很是廣泛,因此沒必要受限於特定類型的問題。咱們將問題空間中的元素以及它們在方案空間的表示物稱做「對象」(Object)。固然,還有一些在問題空間沒有對應體的其餘對象。經過添加新的對象類型,程序可進行靈活的調整,以便與特定的問題配合。因此在閱讀方案的描述代碼時,會讀到對問題進行表達的話語。與咱們之前見過的相比,這無疑是一種更加靈活、更增強大的語言抽象方法。總之,OOP容許咱們根據問題來描述問題,而不是根據方案。然而,仍有一個聯繫途徑回到計算機。每一個對象都相似一臺小計算機;它們有本身的狀態,並且可要求它們進行特定的操做。與現實世界的「對象」或者「物體」相比,編程「對象」與它們也存在共通的地方:它們都有本身的特徵和行爲。數據庫
(1) 全部東西都是對象。
(2) 程序是一大堆對象的組合;經過消息傳遞,各對象知道本身該作些什麼。爲了向對象發出請求,需向那個對象「發送一條消息」。
(3) 每一個對象都有本身的存儲空間,可容納其餘對象。
(4) 每一個對象都有一種類型。根據語法,每一個對象都是某個「類」的一個「實例」。
(5) 同一類全部對象都能接收相同的消息。編程
訪問控制:數組
一個緣由是防止程序員接觸他們不應接觸的東西——一般是內部數據類型的設計思想。若只是爲了解決特定的問題,用戶只需操做接口便可,毋需明白這些信息。瀏覽器
第二個緣由是容許庫設計人員修改內部結構,不用擔憂它會對客戶程序員形成什麼影響。安全
繼承:服務器
咱們費盡心思作出一種數據類型後,假如不得不又新建一種類型,令其實現大體相同的功能,那會是一件很是使人灰心的事情。但若能利用現成的數據類型,對其進行「克隆」,再根據狀況進行添加和修改,狀況就顯得理想多了。「繼承」正是針對這個目標而設計的。但繼承並不徹底等價於克隆。在繼承過程當中,若原始類(正式名稱叫做基礎類、超類或父類)發生了變化,修改過的「克隆」類(正式名稱叫做繼承類或者子類)也會反映出這種變化。在Java語言中,繼承是經過extends關鍵字實現的網絡
使用繼承時,至關於建立了一個新類。這個新類不只包含了現有類型的全部成員,但更重要的是,它複製了基礎類的接口。也就是說,可向基礎類的對象發送的全部消息亦可原樣發給衍生類的對象。根據能夠發送的消息,咱們能知道類的類型。這意味着衍生類具備與基礎類相同的類型!爲真正理解面向對象程序設計的含義,首先必須認識到這種類型的等價關係。
因爲基礎類和衍生類具備相同的接口,因此那個接口必須進行特殊的設計。也就是說,對象接收到一條特定的消息後,必須有一個「方法」可以執行。若只是簡單地繼承一個類,並不作其餘任何事情,來自基礎類接口的方法就會直接照搬到衍生類。這意味着衍生類的對象不只有相同的類型,也有一樣的行爲,這一後果一般是咱們不肯見到的。多線程
有兩種作法可將新得的衍生類與原來的基礎類區分開。第一種作法十分簡單:爲衍生類添加新函數(功能)。這是一種最簡單、最基本的繼承用法。第二種方法是:改善基礎類,即類的重寫和多態。在C++中,這個關鍵字是virtual。在Java中,咱們則徹底沒必要記住添加一個關鍵字,由於函數的動態綁定是自動進行的。編程語言
另外,上溯造型確定是安全的,由於咱們是從一個更特殊的類型到一個更常規的類型。換言之,衍生類是基礎類的一個超集。它能夠包含比基礎類更多的方法,但它至少包含了基礎類的方法。進行上溯造型的時候,類接口可能出現的惟一一個問題是它可能丟失方法,而不是贏得這些方法。這即是在沒有任何明確的造型或者其餘特殊標註的狀況下,編譯器爲何容許上溯造型的緣由所在。
代碼重用-合成與繼承:
第一個最簡單:在新類裏簡單地建立原有類的對象。咱們把這種方法叫做「合成」,由於新類由現有類的對象合併而成。咱們只是簡單地重複利用代碼的功能,而不是採用它的形式。
第二種方法就是繼承。
不管合成仍是繼承,都容許咱們將子對象置於本身的新類中。你們或許會奇怪二者間的差別,以及到底該如何選擇。
若是想利用新類內部一個現有類的特性,而不想使用它的接口,一般應選擇合成。也就是說,咱們可嵌入一個對象,使本身能用它實現新類的特性。但新類的用戶會看到咱們已定義的接口,而不是來自嵌入對象的接口。考慮到這種效果,咱們需在新類裏嵌入現有類的private對象。有些時候,咱們想讓類用戶直接訪問新類的合成。也就是說,須要將成員對象的屬性變爲public。成員對象會將自身隱藏起來,因此這是一種安全的作法。並且在用戶知道咱們準備合成一系列組件時,接口就更容易理解。不然選擇繼承。
在面向對象的程序設計中,建立和使用代碼最可能採起的一種作法是:將數據和方法統一封裝到一個類裏,而且使用那個類的對象。有些時候,需經過「合成」技術用現成的類來構造新類。而繼承是最少見的一種作法。所以,儘管繼承在學習OOP的過程當中獲得了大量的強調,但並不意味着應該儘量地處處使用它。相反,使用它時要特別慎重。只有在清楚知道繼承在全部方法中最有效的前提下,纔可考慮它。爲判斷本身到底應該選用合成仍是繼承,一個最簡單的辦法就是考慮是否須要重新類上溯造型回基礎類。若必須上溯,就須要繼承。但若是不須要上溯造型,就應提醒本身防止繼承的濫用。在下一章裏(多形性),會向你們介紹必須進行上溯造型的一種場合。但只要記住常常問本身「我真的須要上溯造型嗎」,對於合成仍是繼承的選擇就不該該是個太大的問題。
對象的建立和存在時間:
最重要的問題之一是對象的建立及破壞方式。對象須要的數據位於哪兒,如何控制對象的「存在時間」呢?針對這個問題,解決的方案是各異其趣的。C++認爲程序的執行效率是最重要的一個問題,因此它容許程序員做出選擇。爲得到最快的運行速度,存儲以及存在時間可在編寫程序時決定,只需將對象放置在堆棧(有時也叫做自動或定域變量)或者靜態存儲區域便可。這樣便爲存儲空間的分配和釋放提供了一個優先級。
第二個方法是在一個內存池中動態建立對象,該內存池亦叫「堆」或者「內存堆」。若採用這種方式,除非進入運行期,不然根本不知道到底須要多少個對象,也不知道它們的存在時間有多長,以及準確的類型是什麼。因爲存儲空間的管理是運行期間動態進行的,因此在內存堆裏分配存儲空間的時間比在堆棧裏建立的時間長得多。
C++容許咱們決定是在寫程序時建立對象,仍是在運行期間建立,這種控制方法更加靈活。程序員可用兩種方法來破壞一個對象:用程序化的方式決定什麼時候破壞對象,或者利用由運行環境提供的一種「垃圾收集器」特性,自動尋找那些再也不使用的對象,並將其清除。固然,垃圾收集器顯得方便得多,但出於效率未能包括到C++裏。但Java確實提供了一個垃圾收集器。
集合與繼承器:
針對一個特定問題的解決,若是事先不知道須要多少個對象,或者它們的持續時間有多長,那麼也不知道如何保存那些對象。既然如此,怎樣才能知道那些對象要求多少空間呢?事先上根本沒法提早知道,除非進入運行期。因此咱們事先沒必要知道要在一個集合裏容下多少東西。只需建立一個集合,之後的工做讓它本身負責好了。在C++中,它們是以「標準模板庫」(STL)的形式提供的。而Java也用本身的標準庫提供了集合。
全部集合都提供了相應的讀寫功能。
單根結構:
在Java中(與其餘幾乎全部OOP語言同樣),對這個問題的答案都是確定的,並且這個終級基礎類的名字很簡單,就是一個「Object」。這種「單根結構」具備許多方面的優勢。單根結構中的全部對象都有一個通用接口,因此它們最終都屬於相同的類型。
另外一種方案(就象C++那樣)是咱們不能保證全部東西都屬於相同的基本類型。從向後兼容的角度看,這一方案可與C模型更好地配合,並且能夠認爲它的限制更少一些。但假期咱們想進行純粹的面向對象編程,那麼必須構建本身的結構,以期得到與內建到其餘OOP語言裏的一樣的便利。需添加咱們要用到的各類新類庫,還要使用另外一些不兼容的接口。
JAVA單根結構中的全部對象均可以保證擁有一些特定的功能。在本身的系統中,咱們知道對每一個對象都能進行一些基本操做。一個單根結構,加上全部對象都在內存堆中建立,能夠極大簡化參數的傳遞(這在C++裏是一個複雜的概念)。
利用單根結構,咱們能夠更方便地實現一個垃圾收集器。與此有關的必要支持可安裝於基礎類中,而垃圾收集器可將適當的消息發給系統內的任何對象。若是沒有這種單根結構,並且系統經過一個句柄來操縱對象,那麼實現垃圾收集器的途徑會有很大的不一樣,並且會面臨許多障礙。
因爲運行期的類型信息確定存在於全部對象中,因此永遠不會遇到判斷不出一個對象的類型的狀況。這對系統級的操做來講顯得特別重要,好比違例控制;並且也能在程序設計時得到更大的靈活性。
既然你把好處說得這麼天花亂墜,爲何C++沒有采用單根結構呢?事實上,這是早期在效率與控制上權衡的一種結果。單根結構會帶來程序設計上的一些限制。並且更重要的是,它加大了新程序與原有C代碼兼容的難度。儘管這些限制僅在特定的場合會真的形成問題,但爲了得到最大的靈活程度,C++最終決定放棄採用單根結構這一作法。而Java不存在上述的問題,它是全新設計的一種語言,沒必要與現有的語言保持所謂的「向後兼容」。因此很天然地,與其餘大多數面向對象的程序設計語言同樣,單根結構在Java的設計方案中很快就落實下來。
集合庫與方便使用集合-單根結構的便利
因爲集合是咱們常常都要用到的一種工具,因此一個集合庫是十分必要的,它應該能夠方便地重複使用
下溯造型與模板/通用性
爲了使這些集合可以重複使用,或者「再生」,Java提供了一種通用類型,之前曾把它叫做「Object」。單根結構意味着、全部東西歸根結底都是一個對象」!因此容納了Object的一個集合實際能夠容納任何東西。這使咱們對它的重複使用變得很是簡便。爲使用這樣的一個集合,只需添加指向它的對象句柄便可,之後能夠經過句柄從新使用對象。
但因爲集合只能容納Object,因此在咱們向集合裏添加對象句柄時,它會上溯造型成Object,這樣便丟失了它的身份或者標識信息。再次使用它的時候,會獲得一個Object句柄,而非指向咱們早先置入的那個類型的句柄。因此怎樣才能歸還它的原本面貌,調用早先置入集合的那個對象的有用接口呢?在這裏,咱們再次用到了造型(Cast)。但這一次不是在分級結構中上溯造型成一種更「通用」的類型。而是下溯造型成一種更「特殊」的類型。這種造型方法叫做「下溯造型」(Downcasting)。舉個例子來講,咱們知道在上溯造型的時候,Circle(圓)屬於Shape(幾何形狀)的一種類型,因此上溯造型是安全的。但咱們不知道一個Object究竟是Circle仍是Shape,因此很難保證下溯造型的安全進行,除非確切地知道本身要操做的是什麼。但這也不是絕對危險的,由於假以下溯造型成錯誤的東西,會獲得咱們稱爲「違例」(Exception)的一種運行期錯誤。但在從一個集合提取對象句柄時,必須用某種方式準確地記住它們是什麼,以保證下溯造型的正確進行。下溯造型和運行期檢查都要求花額外的時間來運行程序,並且程序員必須付出額外的精力。既然如此,咱們能不能建立一個「智能」集合,令其知道本身容納的類型呢?這樣作可消除下溯造型的必要以及潛在的錯誤。答案是確定的,咱們能夠採用「參數化類型」,它們是編譯器能自動定製的類,可與特定的類型配合。例如,經過使用一個參數化集合,編譯器可對那個集合進行定製,使其只接受Shape,並且只提取Shape。
參數化類型是C++一個重要的組成部分,這部分是C++沒有單根結構的緣故。在C++中,用於實現參數化類型的關鍵字是template(模板)。Java目前還沒有提供參數化類型,由於因爲使用的是單根結構。
C++語言的設計者曾經向C程序員發出請求(並且作得很是成功),不要但願在可使用C的任何地方,向語言里加入可能對C++的速度或使用形成影響的任何特性。這個目的達到了,但代價就是C++的編程不可避免地複雜起來。Java比C++簡單,但付出的代價是效率以及必定程度的靈活性。但對大多數程序設計問題來講,Java無疑都應是咱們的首選。
垃圾收集器:
在Java中,垃圾收集器在設計時已考慮到了內存的釋放問題。垃圾收集器「知道」一個對象在何時再也不使用,而後會自動釋放那個對象佔據的內存空間。採用這種方式,另外加上全部對象都從單個根類Object繼承的事實,並且因爲咱們只能在內存堆中以一種方式建立對象,因此Java的編程要比C++的編程簡單得多。咱們只須要做出少許的抉擇,便可克服原先存在的大量障礙。
既然這是如此好的一種手段,爲何在C++裏沒有獲得充分的發揮呢?咱們固然要爲這種編程的方便性付出必定的代價,代價就是運行期的開銷。正如早先提到的那樣,在C++中,咱們可在堆棧中建立對象。在這種狀況下,對象會得以自動清除。在堆棧中建立對象是爲對象分配存儲空間最有效的一種方式,也是釋放那些空間最有效的一種方式。在內存堆(Heap)中建立對象可能要付出昂貴得多的代價。若是老是從同一個基礎類繼承,並使全部函數調用都具備「同質多形」特徵,那麼也不可避免地須要付出必定的代價。但垃圾收集器是一種特殊的問題,由於咱們永遠不能肯定它何時啓動或者要花多長的時間。這意味着在Java程序執行期間,存在着一種不連貫的因素。因此在某些特殊的場合,咱們必須避免用它——好比在一個程序的執行必須保持穩定、連貫的時候(一般把它們叫做「實時程序」,儘管並非全部實時編程問題都要這方面的要求。
C++語言的設計者曾經向C程序員發出請求(並且作得很是成功),不要但願在可使用C的任何地方,向語言里加入可能對C++的速度或使用形成影響的任何特性。這個目的達到了,但代價就是C++的編程不可避免地複雜起來。Java比C++簡單,但付出的代價是效率以及必定程度的靈活性。但對大多數程序設計問題來講,Java無疑都應是咱們的首選。
違例控制:解決錯誤
這裏的「違例」(Exception)屬於一個特殊的對象,它會從產生錯誤的地方「扔」或「擲」出來。隨後,這個違例會被設計用於控制特定類型錯誤的「違例控制器」捕獲。在狀況變得不對勁的時候,可能有幾個違例控制器並行捕獲對應的違例對象。Java的違例控制機制與大多數程序設計語言都有所不一樣。由於在Java中,違例控制模塊是從一開始就封裝好的,因此必須使用它!若是沒有本身寫一些代碼來正確地控制違例,就會獲得一條編譯期出錯提示。這樣可保證程序的連貫性,使錯誤控制變得更加容易。
多線程
許多程序設計問題都要求程序可以停下手頭的工做,改成處理其餘一些問題,再返回主進程。能夠經過多種途徑達到這個目的。最開始的時候,那些擁有機器低級知識的程序員編寫一些「中斷服務例程」,主進程的暫停是經過硬件級的中斷實現的。儘管這是一種有用的方法,但編出的程序很難移植,由此形成了另外一類的代價高昂問題。
最開始,線程只是用於分配單個處理器的處理時間的一種工具。但假如操做系統自己支持多個處理器,那麼每一個線程均可分配給一個不一樣的處理器,真正進入「並行運算」狀態。從程序設計語言的角度看,多線程操做最有價值的特性之一就是程序員沒必要關心到底使用了多少個處理器。程序在邏輯意義上被分割爲數個線程;假如機器自己安裝了多個處理器,那麼程序會運行得更快,毋需做出任何特殊的調校。
根據前面的論述,你們可能感受線程處理很是簡單。但必須注意一個問題:共享資源!若是有多個線程同時運行,並且它們試圖訪問相同的資源,就會遇到一個問題。舉個例子來講,兩個進程不能將信息同時發送給一臺打印機。爲解決這個問題,對那些可共享的資源來講(好比打印機),它們在使用期間必須進入鎖定狀態。因此一個線程可將資源鎖定,在完成了它的任務後,再解開(釋放)這個鎖,使其餘線程能夠接着使用一樣的資源。
Java的多線程機制已內建到語言中,這使一個可能較複雜的問題變得簡單起來。對多線程處理的支持是在對象這一級支持的,因此一個執行線程可表達爲一個對象。Java也提供了有限的資源鎖定方案。它能鎖定任何對象佔用的內存(內存實際是多種共享資源的一種),因此同一時間只能有一個線程使用特定的內存空間。爲達到這個目的,須要使用synchronized關鍵字。其餘類型的資源必須由程序員明確鎖定,這一般要求程序員建立一個對象,用它表明一把鎖,全部線程在訪問那個資源時都必須檢查這把鎖。
永久性:
建立一個對象後,只要咱們須要,它就會一直存在下去。但在程序結束運行時,對象的「生存期」也會宣告結束。儘管這一現象表面上很是合理,但深刻追究就會發現,假如在程序中止運行之後,對象也能繼續存在,並能保留它的所有信息,那麼在某些狀況下將是一件很是有價值的事情。
JAVA WEB
客戶機/服務器計算
客戶機/服務器系統的基本思想是咱們能在一個統一的地方集中存放信息資源。通常將數據集中保存在某個數據庫中,根據其餘人或者機器的請求將信息投遞給對方。將各類元素集中到一塊兒,信息倉庫、用於投遞信息的軟件以及信息及軟件所在的那臺機器(Server)。而後在遠程機器上顯示出來,這些就叫做「客戶」(Client)。
Web實際就是一套規模巨大的客戶機/服務器系統。但它的狀況要複雜一些,由於全部服務器和客戶都同時存在於單個網絡上面。但咱們不必瞭解更進一步的細節,由於惟一要關心的就是一次創建同一個服務器的鏈接,並同它打交道(即便可能要在全世界的範圍內搜索正確的服務器)。
最開始的時候,這是一個簡單的單向操做過程。咱們向一個服務器發出請求,它向咱們回傳一個文件,因爲本機的瀏覽器軟件(亦即「客戶」或「客戶程序」)負責解釋和格式化,並在咱們面前的屏幕上正確地顯示出來。但人們不久就不知足於只從一個服務器傳遞網頁。
Web最初採用的「服務器-瀏覽器」方案可提供交互式內容,但這種交互能力徹底由服務器提供,爲服務器和因特網帶來了不小的負擔。服務器通常爲客戶瀏覽器產生靜態網頁,由後者簡單地解釋並顯示出來。基本HTML語言提供了簡單的數據收集機制:文字輸入框、複選框、單選鈕、列表以及下拉列表等,另外還有一個按鈕,只能由程序規定從新設置表單中的數據,以便回傳給服務器。用戶提交的信息經過全部Web服務器均能支持的「通用網關接口」(CGI)回傳到服務器。包含在提交數據中的文字指示CGI該如何操做。
今天的許多Web站點都嚴格地創建在CGI的基礎上,事實上幾乎全部事情均可用CGI作到。惟一的問題就是響應時間。CGI程序的響應取決於須要傳送多少數據,以及服務器和因特網兩方面的負擔有多重(並且CGI程序的啓動比較慢)。Web的早期設計者並未預料到當初綽綽有餘的帶寬很快就變得不夠用,這正是大量應用充斥網上形成的結果。原來的方法是咱們按下網頁上的提交按鈕(Submit);數據回傳給服務器;服務器啓動一個CGI程序,檢查用戶輸入是否有錯;格式化一個HTML頁,通知可能遇到的錯誤,並將這個頁回傳給咱們;隨後必須回到原先那個表單頁,再輸入一遍。這種方法不只速度很是慢,也顯得很是繁瑣。
解決的辦法就是客戶端的程序設計。運行Web瀏覽器的大多數機器都擁有足夠強的能力,可進行其餘大量工做。與此同時,原始的靜態HTML方法仍然能夠採用,它會一直等到服務器送回下一個頁。客戶端編程意味着Web瀏覽器可得到更充分的利用,並可有效改善Web服務器的交互(互動)能力。
腳本編制語言
插件形成了腳本編制語言的爆炸性增加。經過這種腳本語言,可將用於本身客戶端程序的源碼直接插入HTML頁,而對那種語言進行解釋的插件會在HTML頁顯示的時候自動激活。腳本語言通常都傾向於儘可能簡化,易於理解。並且因爲它們是從屬於HTML頁的一些簡單正文,因此只需向服務器發出對那個頁的一次請求,便可很是快地載入。缺點是咱們的代碼所有暴露在人們面前。另外一方面,因爲一般不用腳本編制語言作過份複雜的事情,因此這個問題暫且能夠放在一邊。
腳本語言真正面向的是特定類型問題的解決,其中主要涉及如何建立更豐富、更具備互動能力的圖形用戶界面(GUI)。然而,腳本語言也許能解決客戶端編程中80%的問題。你碰到的問題可能徹底就在那80%裏面。
若是說一種腳本編制語言能解決80%的客戶端程序設計問題,那麼剩下的20%又該怎麼辦呢?它們屬於一些高難度的問題嗎?目前最流行的方案就是Java。它不只是一種功能強大、高度安全、能夠跨平臺使用以及國際通用的程序設計語言,也是一種具備旺盛生命力的語言。對Java的擴展是不斷進行的,提供的語言特性和庫可以很好地解決傳統語言不能解決的問題,好比多線程操做、數據庫訪問、連網程序設計以及分佈式計算等等。Java經過「程序片」(Applet)巧妙地解決了客戶端編程的問題。
程序片(或「小應用程序」)是一種很是小的程序,只能在Web瀏覽器中運行。做爲Web頁的一部分,程序片代碼會自動下載回來(這和網頁中的圖片差很少)。激活程序片後,它會執行一個程序。程序片的一個優勢體如今:經過程序片,一旦用戶須要客戶軟件,軟件就可從服務器自動下載回來。它們能自動取得客戶軟件的最新版本,不會出錯,也沒有從新安裝的麻煩。
安全
自動下載和經過因特網運行程序聽起來就象是一個病毒製造者的夢想。在客戶端的編程中,ActiveX帶來了最讓人頭痛的安全問題。點擊一個Web站點的時候,可能會隨同HTML網頁傳回任何數量的東西:GIF文件、腳本代碼、編譯好的Java代碼以及ActiveX組件。有些是無害的;GIF文件不會對咱們形成任何危害,而腳本編制語言一般在本身可作的事情上有着很大的限制。Java也設計成在一個安全「沙箱」裏在它的程序片中運行,這樣可防止操做位於沙箱之外的磁盤或者內存區域。
ActiveX是全部這些裏面最讓人擔憂的。用ActiveX編寫程序就象編制Windows應用程序——能夠作本身想作的任何事情。下載回一個ActiveX組件後,它徹底可能對咱們磁盤上的文件形成破壞。
Java經過「沙箱」來防止這些問題的發生。Java解釋器內嵌於咱們本地的Web瀏覽器中,在程序片裝載時會檢查全部有嫌疑的指令。特別地,程序片根本沒有權力將文件寫進磁盤,或者刪除文件(這是病毒最喜歡作的事情之一)。
Java/C++
Java特別象C++;由此很天然地會得出一個結論:C++彷佛會被Java取代。但我對這個邏輯存有一些疑問。不管如何,C++仍有一些特性是Java沒有的。並且儘管已有大量保證,
我感受Java強大之處反映在與C++稍有不一樣的領域。C++是一種絕對不會試圖迎合某個模子的語言。特別是它的形式能夠變化無窮,以解決不一樣類型的問題。人們對Java作了大量的工做,使它能方便程序員解決應用級問題(如連網和跨平臺UI等),因此它在本質上容許人們建立很是大型和靈活的代碼主體。同時,考慮到Java還擁有我迄今爲止還沒有在其餘任何一種語言裏見到的最「健壯」的類型檢查及錯誤控制系統,因此Java確實能大大提升咱們的編程效率。這一點是勿庸置疑的!
但對於本身某個特定的項目,真的能夠不假思索地將C++換成Java嗎?除了Web程序片,還有兩個問題須要考慮。首先,假如要使用大量現有的庫(這樣確定能夠提升很多的效率),或者已經有了一個堅實的C或C++代碼庫,那麼換成Java後,反映會阻礙開發進度,而不是加快它的速度。但若想從頭開始構建本身的全部代碼,那麼Java的簡單易用就能有效地縮短開發時間。
最大的問題是速度。在原始的Java解釋器中,解釋過的Java會比C慢上20到50倍。儘管通過長時間的發展,這個速度有必定程度的提升,但和C比起來仍然很懸殊。計算機最注重的就是速度;假如在一臺計算機上不能明顯較快地幹活,那麼還不如用手作(有人建議在開發期間使用Java,以縮短開發時間。而後用一個工具和支撐庫將代碼轉換成C++,這樣可得到更快的執行速度)。
一切都是對象
之因此說C++是一種雜合語言,是由於它支持與C語言的向後兼容能力。因爲C++是C的一個超集,因此包含的許多特性都是後者不具有的,這些特性使C++在某些地方顯得過於複雜。
Java語言首先便假定了咱們只但願進行面向對象的程序設計。
用句柄操縱對象
儘管將一切都「看做」對象,但操縱的標識符實際是指向一個對象的「句柄」(Handle)。只是因爲擁有一個句柄,並不表示必須有一個對象同它鏈接。因此若是想容納一個詞或句子,可建立一個String句柄:String s;但這裏建立的只是句柄,並非對象。若此時向s發送一條消息,就會得到一個錯誤(運行期)。這是因爲s實際並未與任何東西鏈接(即「沒有電視機」)。所以,一種更安全的作法是:建立一個句柄時,記住不管如何都進行初始化:
String s = new String("asdf");
咱們建立類時會指出那個類的對象的外觀與行爲。除非用new建立那個類的一個對象,不然實際上並未獲得任何東西。只有執行了new後,纔會正式生成數據存儲空間,並可以使用相應的方法。
在類內做爲字段使用的基本數據會初始化成零,但對象句柄會初始化成null。並且倘若試圖爲它們中的任何一個調用方法,就會產生一次「違例」。
編譯器並不僅是爲每一個句柄建立一個默認對象,由於那樣會在許多狀況下招致沒必要要的開銷。如但願句柄獲得初始化,可在下面這些地方進行:
(1) 在對象定義的時候。這意味着它們在構建器調用以前確定能獲得初始化。
(2) 在那個類的構建器中。
(3) 緊靠在要求實際使用那個對象以前。這樣作可減小沒必要要的開銷——假如對象並不須要建立的話。
保存到什麼地方:
程序運行時,咱們最好對數據保存到什麼地方作到心中有數。特別要注意的是內存的分配。有六個地方均可以保存數據:
(1) 寄存器。這是最快的保存區域,由於它位於和其餘全部保存方式不一樣的地方:處理器內部。然而,寄存器的數量十分有限,因此寄存器是根據須要由編譯器分配。咱們對此沒有直接的控制權,也不可能在本身的程序裏找到寄存器存在的任何蹤影。
(2) 堆棧。駐留於常規RAM(隨機訪問存儲器)區域,但可經過它的「堆棧指針」得到處理的直接支持。堆棧指針若向下移,會建立新的內存;若向上移,則會釋放那些內存。這是一種特別快、特別有效的數據保存方式,僅次於寄存器。建立程序時,Java編譯器必須準確地知道堆棧內保存的全部數據的「長度」以及「存在時間」。這是因爲它必須生成相應的代碼,以便向上和向下移動指針。這一限制無疑影響了程序的靈活性,因此儘管有些Java數據要保存在堆棧裏——特別是對象句柄,但Java對象並不放到其中。
(3) 堆。一種常規用途的內存池(也在RAM區域),其中保存了Java對象。和堆棧不一樣,「內存堆」或「堆」(Heap)最吸引人的地方在於編譯器沒必要知道要從堆裏分配多少存儲空間,也沒必要知道存儲的數據要在堆裏停留多長的時間。所以,用堆保存數據時會獲得更大的靈活性。要求建立一個對象時,只需用new命令編制相關的代碼便可。執行這些代碼時,會在堆裏自動進行數據的保存。固然,爲達到這種靈活性,必然會付出必定的代價:在堆裏分配存儲空間時會花掉更長的時間!
(4) 靜態存儲。這兒的「靜態」(Static)是指「位於固定位置」(儘管也在RAM裏)。程序運行期間,靜態存儲的數據將隨時等候調用。可用static關鍵字指出一個對象的特定元素是靜態的。但Java對象自己永遠都不會置入靜態存儲空間。
(5) 常數存儲。常數值一般直接置於程序代碼內部。這樣作是安全的,由於它們永遠都不會改變。有的常數須要嚴格地保護,因此可考慮將它們置入只讀存儲器(ROM)。
(6) 非RAM存儲。若數據徹底獨立於一個程序以外,則程序不運行時仍可存在,並在程序的控制範圍以外。其中兩個最主要的例子即是「流式對象」和「固定對象」。對於流式對象,對象會變成字節流,一般會發給另外一臺機器。而對於固定對象,對象保存在磁盤中。即便程序停止運行,它們仍可保持本身的狀態不變。對於這些類型的數據存儲,一個特別有用的技巧就是它們能存在於其餘媒體中。一旦須要,甚至能將它們恢復成普通的、基於RAM的對象。Java 1.1提供了對Lightweight persistence的支持。將來的版本甚至可能提供更完整的方案。
Java決定了每種主要類型的大小。就象在大多數語言裏那樣,這些大小並不隨着機器結構的變化而變化。這種大小的不可更改正是Java程序具備很強移植能力的緣由之一。
基本類型:
可將它們想象成「基本」、「主」(Primitive)類型或者「內置」,進行程序設計時要頻繁用到它們。之因此要特別對待,是因爲用new建立對象(特別是小的、簡單的變量)並非很是有效,由於new將對象置於「堆」裏。對於這些類型,Java採納了與C和C++相同的方法。也就是說,不是用new建立變量,而是建立一個並不是句柄的「自動」變量。這個變量容納了具體的值,並置於堆棧中,可以更高效地存取。Java不容許咱們建立本地(局部)對象——不管如何都要使用new。Java決定了每種主要類型的大小。就象在大多數語言裏那樣,這些大小並不隨着機器結構的變化而變化。這種大小的不可更改正是Java程序具備很強移植能力的緣由之一。
Java的數組:
在C和C++裏使用數組是很是危險的,由於那些數組只是內存塊。若程序訪問本身內存塊之外的數組,或者在初始化以前使用內存在C++裏,應儘可能不要使用數組,換用標準模板庫(Standard TemplateLibrary)裏更安全的容器。
Java的一項主要設計目標就是安全性。因此在C和C++裏困擾程序員的許多問題都未在Java裏重複。一個Java能夠保證被初始化,並且不可在它的範圍以外訪問。建立對象數組時,實際建立的是一個句柄數組。並且每一個句柄都會自動初始化成一個特殊值,並帶有本身的關鍵字:null(空)。一旦Java看到null,就知道該句柄並未指向一個對象。正式使用前,必須爲每一個句柄都分配一個對象。若試圖使用依然爲null的一個句柄,就會在運行期報告問題。所以,典型的數組錯誤在Java裏就獲得了避免。也能夠建立主類型數組。一樣地,編譯器可以擔保對它的初始化,由於會將那個數組的內存劃分紅零。
對象的做用域:
Java對象不具有與主類型同樣的存在時間。用new關鍵字建立一個Java對象的時候,它會超出做用域的範圍以外。因此倘若使用下面這段代碼:
{
String s = new String("a string");
} /* 做用域的終點 */
那麼句柄s會在做用域的終點處消失。然而,s指向的String對象依然佔據着內存空間。在上面這段代碼裏,咱們沒有辦法訪問對象,由於指向它的惟一一個句柄已超出了做用域的邊界。在後面的章節裏,你們還會繼續學習如何在程序運行期間傳遞和複製對象句柄。
這樣形成的結果即是:對於用new建立的對象,只要咱們願意,它們就會一直保留下去。這個編程問題在C和C++裏特別突出。看來在C++裏遇到的麻煩最大:因爲不能從語言得到任何幫助,因此在須要對象的時候,根本沒法肯定它們是否可用。並且更麻煩的是,在C++裏,一旦工做完成,必須保證將對象清除。
這樣便帶來了一個有趣的問題。假如Java讓對象依然故我,怎樣才能防止它們大量充斥內存,並最終形成程序的「凝固」呢。在C++裏,這個問題最令程序員頭痛。但Java之後,狀況卻發生了改觀。Java有一個特別的「垃圾收集器」,它會查找用new建立的全部對象,並辨別其中哪些再也不被引用。隨後,它會自動釋放由那些閒置對象佔據的內存,以便能由新對象使用。這意味着咱們根本沒必要操心內存的回收問題。只需簡單地建立對象,一旦再也不須要它們,它們就會自動離去。這樣作可防止在C++裏很常見的一個編程問題:因爲程序員忘記釋放內存形成的「內存溢出」。
static關鍵字:
建立類時會指出那個類的對象的外觀與行爲。除非用new建立那個類的一個對象,不然實際上並未獲得任何東西。只有執行了new後,纔會正式生成數據存儲空間,並可以使用相應的方法。
但在兩種特殊的情形下,上述方法並不堪用。一種情形是隻想用一個存儲區域來保存一個特定的數據——不管要建立多少個對象,甚至根本不建立對象。另外一種情形是咱們須要一個特殊的方法,它沒有與這個類的任何對象關聯。也就是說,即便沒有建立對象,也須要一個能調用的方法。爲知足這兩方面的要求,
finalize()用途何在:
此時,你們可能已相信了本身應該將finalize()做爲一種常規用途的清除方法使用。它有什麼好處呢?
要記住的第三個重點是:垃圾收集只跟內存有關!
也就是說,垃圾收集器存在的惟一緣由是爲了回收程序再也不使用的內存。因此對於與垃圾收集有關的任何活動來講,其中最值得注意的是finalize()方法,它們也必須同內存以及它的回收有關。
但這是否意味着假如對象包含了其餘對象,finalize()就應該明確釋放那些對象呢?答案是否認的——垃圾收集器會負責釋放全部對象佔據的內存,不管這些對象是如何建立的。它將對finalize()的需求限制到特殊的狀況。在這種狀況下,咱們的對象可採用與建立對象時不一樣的方法分配一些存儲空間。但你們或許會注意到,Java中的全部東西都是對象,因此這究竟是怎麼一回事呢?
之因此要使用finalize(),看起來彷佛是因爲有時須要採起與Java的普通方法不一樣的一種方法,經過分配內存來作一些具備C風格的事情。這主要能夠經過「固有方法」來進行,它是從Java裏調用非Java方法的一種方式(固有方法的問題在附錄A討論)。C和C++是目前惟一得到固有方法支持的語言。但因爲它們能調用經過其餘語言編寫的子程序,因此可以有效地調用任何東西。在非Java代碼內部,也許能調用C的malloc()系列函數,用它分配存儲空間。並且除非調用了free(),不然存儲空間不會獲得釋放,從而形成內存「漏洞」的出現。固然,free()是一個C和C++函數,因此咱們須要在finalize()內部的一個固有方法中調用它。
讀完上述文字後,你們或許已弄清楚了本身沒必要過多地使用finalize()。這個思想是正確的;它並非進行普通清除工做的理想場所。那麼,普通的清除工做應在何處進行呢?
多態-綁定:
方法調用的綁定
將一個方法調用同一個方法主體鏈接到一塊兒就稱爲「綁定」(Binding)。若在程序運行之前執行綁定(由編譯器和連接程序,若是有的話),就叫做「早期綁定」。C編譯器只有一種方法調用,那就是「早期綁定」。
「後期綁定」,它意味着綁定在運行期間進行,以對象的類型爲基礎。後期綁定也叫做「動態綁定」。若一種語言實現了後期綁定,同時必須提供一些機制,可在運行期間判斷對象的類型,並分別調用適當的方法。也就是說,編譯器此時依然不知道對象的類型,但方法調用機制能本身去調查,找到正確的方法主體。不一樣的語言對後期綁定的實現方法是有所區別的。但咱們至少能夠這樣認爲:它們都要在對象中安插某些特殊類型的信息。
Java中綁定的全部方法都採用後期綁定技術,除非一個方法已被聲明成final。這意味着咱們一般沒必要決定是否應進行後期綁定——它是自動發生的。
爲何要把一個方法聲明成final呢?正如上一章指出的那樣,它能防止其餘人覆蓋那個方法。但也許更重要的一點是,它可有效地「關閉」動態綁定,或者告訴編譯器不須要進行動態綁定。這樣一來,編譯器就可爲final方法調用生成效率更高的代碼。
抽象類-方法及接口:
有些方法的做用僅僅是表達接口,而不是表達一些具體的實施細節。Java專門提供了一種機制,名爲「抽象方法」。它屬於一種不完整的方法,只含有一個聲明,沒有方法主體。下面是抽象方法聲明時採用的語法:
abstract void X();
包含了抽象方法的一個類叫做「抽象類」。若是一個類裏包含了一個或多個抽象方法,類就必須指定成abstract(抽象)。不然,編譯器會向咱們報告一條出錯消息。
若一個抽象類是不完整的,那麼一旦有人試圖生成那個類的一個對象,編譯器又會採起什麼行動呢?因爲不能安全地爲一個抽象類建立屬於它的對象,因此會從編譯器那裏得到一條出錯提示。經過這種方法,編譯器可保證抽象類的「純潔性」,咱們沒必要擔憂會誤用它。若是從一個抽象類繼承,並且想生成新類型的一個對象,就必須爲基礎類中的全部抽象方法提供方法定義。若是不這樣作(徹底能夠選擇不作),則衍生類也會是抽象的,並且編譯器會強迫咱們用abstract關鍵字標誌那個類的「抽象」本質。即便不包括任何abstract方法,亦可將一個類聲明成「抽象類」。若是一個類不必擁有任何抽象方法,並且咱們想禁止那個類的全部實例,這種能力就會顯得很是有用。
因此在實現一個接口的時候,來自接口的方法必須定義成public。不然的話,它們會默認爲「友好的」,並且會限制咱們在繼承過程當中對一個方法的訪問——Java編譯器不容許咱們那樣作。
接口只是比抽象類「更純」的一種形式。它的用途並不止那些。因爲接口根本沒有具體的實施細節——也就是說,沒有與存儲空間與「接口」關聯在一塊兒——因此沒有任何辦法能夠防止多個接口合併到一塊兒。這一點是相當重要的,由於咱們常常都須要表達這樣一個意思:「x從屬於a,也從屬於b,也從屬於c」。在C++中,將多個類合併到一塊兒的行動稱做「多重繼承」,並且操做較爲不便,由於每一個類均可能有一套本身的實施細節。在Java中,咱們可採起一樣的行動,但只有其中一個類擁有具體的實施細節。因此在合併多個接口的時候,C++的問題不會在Java中重演。
在一個衍生類中,咱們並不必定要擁有一個抽象或具體(沒有抽象方法)的基礎類。若是確實想從一個非接口繼承,那麼只能從一個繼承。剩餘的全部基本元素都必須是「接口」。咱們將全部接口名置於implements關鍵字的後面,並用逗號分隔它們。可根據須要使用多個接口,並且每一個接口都會成爲一個獨立的類型,可對其進行上溯造型造成多態。
使用接口最重要的一個緣由:能上溯造型至多個基礎類。使用接口的第二個緣由與使用抽象基礎類的緣由是同樣的:防止客戶程序員製做這個類的一個對象,以及規定它僅僅是一個接口。這樣便帶來了一個問題:到底應該使用一個接口仍是一個抽象類呢?若使用接口,咱們能夠同時得到抽象類以及接口的好處。因此假如想建立的基礎類沒有任何方法定義或者成員變量,那麼不管如何都願意使用接口,而不要選擇抽象類。事實上,若是事先知道某種東西會成爲基礎類,那麼第一個選擇就是把它變成一個接口。只有在必須使用方法定義或者成員變量的時候,才應考慮採用抽象類。
抽象類與接口設計層面上的區別
1)抽象類是對一種事物的抽象,即對類抽象,而接口是對行爲的抽象。抽象類是對整個類總體進行抽象,包括屬性、行爲,可是接口倒是對類局部(行爲)進行抽象。舉個簡單的例子,飛機和鳥是不一樣類的事物,可是它們都有一個共性,就是都會飛。那麼在設計的時候,能夠將飛機設計爲一個類Airplane,將鳥設計爲一個類Bird,可是不能將 飛行 這個特性也設計爲類,所以它只是一個行爲特性,並非對一類事物的抽象描述。此時能夠將 飛行 設計爲一個接口Fly,包含方法fly( ),而後Airplane和Bird分別根據本身的須要實現Fly這個接口。而後至於有不一樣種類的飛機,好比戰鬥機、民用飛機等直接繼承Airplane便可,對於鳥也是相似的,不一樣種類的鳥直接繼承Bird類便可。從這裏能夠看出,繼承是一個 "是否是"的關係,而 接口 實現則是 "有沒有"的關係。若是一個類繼承了某個抽象類,則子類一定是抽象類的種類,而接口實現則是有沒有、具有不具有的關係,好比鳥是否能飛(或者是否具有飛行這個特色),能飛行則能夠實現這個接口,不能飛行就不實現這個接口。
2)設計層面不一樣,抽象類做爲不少子類的父類,它是一種模板式設計。而接口是一種行爲規範,它是一種輻射式設計。什麼是模板式設計?最簡單例子,你們都用過ppt裏面的模板,若是用模板A設計了ppt B和ppt C,ppt B和ppt C公共的部分就是模板A了,若是它們的公共部分須要改動,則只須要改動模板A就能夠了,不須要從新對ppt B和ppt C進行改動。而輻射式設計,好比某個電梯都裝了某種報警器,一旦要更新報警器,就必須所有更新。也就是說對於抽象類,若是須要添加新的方法,能夠直接在抽象類中添加具體的實現,子類能夠不進行變動;而對於接口則不行,若是接口進行了變動,則全部實現這個接口的類都必須進行相應的改動。